From c8eb402ca3f6e61eab319b86195c8d87af1fe36b Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Fri, 1 Dec 2017 11:59:50 +0000 Subject: [PATCH 1/6] Convert to ES6 --- .babelrc | 13 + .gitignore | 1 + .jshintrc | 15 + bimsurfer/lib/StringView.js | 1081 +-- bimsurfer/lib/text.js | 391 - bimsurfer/src/BimServerGeometryLoader.js | 933 ++- bimsurfer/src/BimServerModel.js | 412 +- bimsurfer/src/BimServerModelLoader.js | 186 +- bimsurfer/src/BimSurfer.js | 824 ++- bimsurfer/src/DataInputStreamReader.js | 200 +- bimsurfer/src/DefaultMaterials.js | 82 +- bimsurfer/src/EventHandler.js | 73 +- bimsurfer/src/MetaDataRenderer.js | 455 +- bimsurfer/src/Notifier.js | 54 +- bimsurfer/src/PreloadQuery.js | 88 +- bimsurfer/src/Request.js | 42 +- bimsurfer/src/StaticTreeRenderer.js | 316 +- bimsurfer/src/Utils.js | 185 +- bimsurfer/src/index.js | 4 + .../xeoViewer/controls/bimCameraControl.js | 7 +- .../src/xeoViewer/effects/highlightEffect.js | 6 +- bimsurfer/src/xeoViewer/entities/bimModel.js | 5 +- bimsurfer/src/xeoViewer/entities/bimObject.js | 5 +- .../xeoViewer/helpers/bimBoundaryHelper.js | 6 +- bimsurfer/src/xeoViewer/utils/collection.js | 6 +- bimsurfer/src/xeoViewer/xeoViewer.js | 2780 ++++---- build/bimsurfer.js | 6284 +++++++++++++++++ docs/example_BIMServer.html | 48 +- docs/example_glTF.html | 188 +- docs/example_testModel.html | 95 +- package.json | 26 + rollup.config.js | 32 + 32 files changed, 10350 insertions(+), 4493 deletions(-) create mode 100644 .babelrc create mode 100644 .jshintrc delete mode 100644 bimsurfer/lib/text.js create mode 100644 bimsurfer/src/index.js create mode 100644 build/bimsurfer.js create mode 100644 package.json create mode 100644 rollup.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..27d6c65 --- /dev/null +++ b/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + [ + "env", + { + "modules": false + } + ] + ], + "plugins": [ + "external-helpers" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b83d222..e777dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target/ +/node_modules/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..d6768b1 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,15 @@ +{ + "esversion": 6, + "undef": true, + "unused": true, + "curly": true, + "varstmt": true, + "maxdepth": 5, + "devel": true, + "browser": true, + "maxerr": 500, + "-W041": false, + "globals": { + "xeogl": true + } +} \ No newline at end of file diff --git a/bimsurfer/lib/StringView.js b/bimsurfer/lib/StringView.js index 31c5cdb..4fe34ce 100644 --- a/bimsurfer/lib/StringView.js +++ b/bimsurfer/lib/StringView.js @@ -1,675 +1,704 @@ -define( - -function() { - /*\ |*| -|*| :: Number.isInteger() polyfill :: +|*| :: Number.isInteger() polyfill :: |*| -|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger +|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger |*| \*/ if (!Number.isInteger) { - Number.isInteger = function isInteger (nVal) { - return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal; - }; + Number.isInteger = function isInteger(nVal) { + return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal; + }; } /*\ |*| -|*| StringView - Mozilla Developer Network - revision #6 +|*| StringView - Mozilla Developer Network +|*| +|*| Revision #12, March 21st, 2017 |*| -|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView -|*| https://developer.mozilla.org/User:fusionchess +|*| https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView +|*| https://developer.mozilla.org/en-US/docs/User:fusionchess +|*| https://github.com/madmurphy/stringview.js |*| -|*| This framework is released under the GNU Public License, version 3 or later. -|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html +|*| This framework is released under the GNU Lesser General Public License, version 3 or later. +|*| http://www.gnu.org/licenses/lgpl-3.0.html |*| \*/ -function StringView (vInput, sEncoding /* optional (default: UTF-8) */, nOffset /* optional */, nLength /* optional */) { - - var fTAView, aWhole, aRaw, fPutOutptCode, fGetOutptChrSize, nInptLen, nStartIdx = isFinite(nOffset) ? nOffset : 0, nTranscrType = 15; - - if (sEncoding) { this.encoding = sEncoding.toString(); } - - encSwitch: switch (this.encoding) { - case "UTF-8": - fPutOutptCode = StringView.putUTF8CharCode; - fGetOutptChrSize = StringView.getUTF8CharLength; - fTAView = Uint8Array; - break encSwitch; - case "UTF-16": - fPutOutptCode = StringView.putUTF16CharCode; - fGetOutptChrSize = StringView.getUTF16CharLength; - fTAView = Uint16Array; - break encSwitch; - case "UTF-32": - fTAView = Uint32Array; - nTranscrType &= 14; - break encSwitch; - default: - /* case "ASCII", or case "BinaryString" or unknown cases */ - fTAView = Uint8Array; - nTranscrType &= 14; - } - - typeSwitch: switch (typeof vInput) { - case "string": - /* the input argument is a primitive string: a new buffer will be created. */ - nTranscrType &= 7; - break typeSwitch; - case "object": - classSwitch: switch (vInput.constructor) { - case StringView: - /* the input argument is a stringView: a new buffer will be created. */ - nTranscrType &= 3; - break typeSwitch; - case String: - /* the input argument is an objectified string: a new buffer will be created. */ - nTranscrType &= 7; - break typeSwitch; - case ArrayBuffer: - /* the input argument is an arrayBuffer: the buffer will be shared. */ - aWhole = new fTAView(vInput); - nInptLen = this.encoding === "UTF-32" ? - vInput.byteLength >>> 2 - : this.encoding === "UTF-16" ? - vInput.byteLength >>> 1 - : - vInput.byteLength; - aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? - aWhole - : new fTAView(vInput, nStartIdx, !isFinite(nLength) ? nInptLen - nStartIdx : nLength); - - break typeSwitch; - case Uint32Array: - case Uint16Array: - case Uint8Array: - /* the input argument is a typedArray: the buffer, and possibly the array itself, will be shared. */ - fTAView = vInput.constructor; - nInptLen = vInput.length; - aWhole = vInput.byteOffset === 0 && vInput.length === ( - fTAView === Uint32Array ? - vInput.buffer.byteLength >>> 2 - : fTAView === Uint16Array ? - vInput.buffer.byteLength >>> 1 - : - vInput.buffer.byteLength - ) ? vInput : new fTAView(vInput.buffer); - aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? - vInput - : vInput.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); - - break typeSwitch; - default: - /* the input argument is an array or another serializable object: a new typedArray will be created. */ - aWhole = new fTAView(vInput); - nInptLen = aWhole.length; - aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? - aWhole - : aWhole.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); - } - break typeSwitch; - default: - /* the input argument is a number, a boolean or a function: a new typedArray will be created. */ - aWhole = aRaw = new fTAView(Number(vInput) || 0); - - } - - if (nTranscrType < 8) { - - var vSource, nOutptLen, nCharStart, nCharEnd, nEndIdx, fGetInptChrSize, fGetInptChrCode; - - if (nTranscrType & 4) { /* input is string */ - - vSource = vInput; - nOutptLen = nInptLen = vSource.length; - nTranscrType ^= this.encoding === "UTF-32" ? 0 : 2; - /* ...or...: nTranscrType ^= Number(this.encoding !== "UTF-32") << 1; */ - nStartIdx = nCharStart = nOffset ? Math.max((nOutptLen + nOffset) % nOutptLen, 0) : 0; - nEndIdx = nCharEnd = (Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0) + nStartIdx, nOutptLen) : nOutptLen) - 1; - - } else { /* input is stringView */ - - vSource = vInput.rawData; - nInptLen = vInput.makeIndex(); - nStartIdx = nCharStart = nOffset ? Math.max((nInptLen + nOffset) % nInptLen, 0) : 0; - nOutptLen = Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0), nInptLen - nCharStart) : nInptLen; - nEndIdx = nCharEnd = nOutptLen + nCharStart; - - if (vInput.encoding === "UTF-8") { - fGetInptChrSize = StringView.getUTF8CharLength; - fGetInptChrCode = StringView.loadUTF8CharCode; - } else if (vInput.encoding === "UTF-16") { - fGetInptChrSize = StringView.getUTF16CharLength; - fGetInptChrCode = StringView.loadUTF16CharCode; - } else { - nTranscrType &= 1; - } - - } - - if (nOutptLen === 0 || nTranscrType < 4 && vSource.encoding === this.encoding && nCharStart === 0 && nOutptLen === nInptLen) { - - /* the encoding is the same, the length too and the offset is 0... or the input is empty! */ - - nTranscrType = 7; - - } - - conversionSwitch: switch (nTranscrType) { - - case 0: +export default class StringView { + constructor(vInput, sEncoding /* optional (default: UTF-8) */ , nOffset /* optional */ , nLength /* optional */ ) { + let fTAView, aWhole, aRaw, fPutOutptCode, fGetOutptChrSize, nInptLen, nTranscrType = 15, + nStartIdx = isFinite(nOffset) ? nOffset : 0; + + if (sEncoding) { + this.encoding = sEncoding.toString(); + } else { + this.encoding = "UTF-8"; + } + + encSwitch: switch (this.encoding) { + case "UTF-8": + fPutOutptCode = this.putUTF8CharCode; + fGetOutptChrSize = this.getUTF8CharLength; + fTAView = Uint8Array; + break encSwitch; + case "UTF-16": + fPutOutptCode = this.putUTF16CharCode; + fGetOutptChrSize = this.getUTF16CharLength; + fTAView = Uint16Array; + break encSwitch; + case "UTF-32": + fTAView = Uint32Array; + nTranscrType &= 14; + break encSwitch; + default: + /* case "ASCII", or case "BinaryString" or unknown cases */ + fTAView = Uint8Array; + nTranscrType &= 14; + } + + typeSwitch: switch (typeof vInput) { + case "string": + /* the input argument is a primitive string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case "object": + classSwitch: switch (vInput.constructor) { + case StringView: + /* the input argument is a stringView: a new buffer will be created. */ + nTranscrType &= 3; + break typeSwitch; + case String: + /* the input argument is an objectified string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case ArrayBuffer: + /* the input argument is an arrayBuffer: the buffer will be shared. */ + aWhole = new fTAView(vInput); + nInptLen = this.encoding === "UTF-32" ? + vInput.byteLength >>> 2 : + this.encoding === "UTF-16" ? + vInput.byteLength >>> 1 : + vInput.byteLength; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + aWhole : + new fTAView(vInput, nStartIdx, !isFinite(nLength) ? nInptLen - nStartIdx : nLength); + + break typeSwitch; + case Uint32Array: + case Uint16Array: + case Uint8Array: + /* the input argument is a typedArray: the buffer, and possibly the array itself, will be shared. */ + fTAView = vInput.constructor; + nInptLen = vInput.length; + aWhole = vInput.byteOffset === 0 && vInput.length === ( + fTAView === Uint32Array ? + vInput.buffer.byteLength >>> 2 : + fTAView === Uint16Array ? + vInput.buffer.byteLength >>> 1 : + vInput.buffer.byteLength + ) ? vInput : new fTAView(vInput.buffer); + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + vInput : + vInput.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + + break typeSwitch; + default: + /* the input argument is an array or another serializable object: a new typedArray will be created. */ + aWhole = new fTAView(vInput); + nInptLen = aWhole.length; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + aWhole : + aWhole.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + } + break typeSwitch; + default: + /* the input argument is a number, a boolean or a function: a new typedArray will be created. */ + aWhole = aRaw = new fTAView(Number(vInput) || 0); + + } + + if (nTranscrType < 8) { + + let vSource, nOutptLen, nCharStart, nCharEnd, nEndIdx, fGetInptChrSize, fGetInptChrCode; + + if (nTranscrType & 4) { /* input is string */ + + vSource = vInput; + nOutptLen = nInptLen = vSource.length; + nTranscrType ^= this.encoding === "UTF-32" ? 0 : 2; + /* ...or...: nTranscrType ^= Number(this.encoding !== "UTF-32") << 1; */ + nStartIdx = nCharStart = nOffset ? Math.max((nOutptLen + nOffset) % nOutptLen, 0) : 0; + nEndIdx = nCharEnd = (Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0) + nStartIdx, nOutptLen) : nOutptLen) - 1; + + } else { /* input is stringView */ + + vSource = vInput.rawData; + nInptLen = vInput.makeIndex(); + nStartIdx = nCharStart = nOffset ? Math.max((nInptLen + nOffset) % nInptLen, 0) : 0; + nOutptLen = Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0), nInptLen - nCharStart) : nInptLen; + nEndIdx = nCharEnd = nOutptLen + nCharStart; + + if (vInput.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (vInput.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } else { + nTranscrType &= 1; + } - /* both the source and the new StringView have a fixed-length encoding... */ + } - aWhole = new fTAView(nOutptLen); - for (var nOutptIdx = 0; nOutptIdx < nOutptLen; aWhole[nOutptIdx] = vSource[nStartIdx + nOutptIdx++]); - break conversionSwitch; + if (nOutptLen === 0 || nTranscrType < 4 && vSource.encoding === this.encoding && nCharStart === 0 && nOutptLen === nInptLen) { - case 1: + /* the encoding is the same, the length too and the offset is 0... or the input is empty! */ - /* the source has a fixed-length encoding but the new StringView has a variable-length encoding... */ + nTranscrType = 7; - /* mapping... */ + } - nOutptLen = 0; + conversionSwitch: switch (nTranscrType) { - for (var nInptIdx = nStartIdx; nInptIdx < nEndIdx; nInptIdx++) { - nOutptLen += fGetOutptChrSize(vSource[nInptIdx]); - } + case 0: - aWhole = new fTAView(nOutptLen); + /* both the source and the new StringView have a fixed-length encoding... */ - /* transcription of the source... */ + aWhole = new fTAView(nOutptLen); + for (let nOutptIdx = 0; nOutptIdx < nOutptLen; aWhole[nOutptIdx] = vSource[nStartIdx + nOutptIdx++]) {} + break conversionSwitch; - for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx++) { - nOutptIdx = fPutOutptCode(aWhole, vSource[nInptIdx], nOutptIdx); - } + case 1: - break conversionSwitch; + /* the source has a fixed-length encoding but the new StringView has a variable-length encoding... */ - case 2: + /* mapping... */ - /* the source has a variable-length encoding but the new StringView has a fixed-length encoding... */ + nOutptLen = 0; - /* mapping... */ + for (let nInptIdx = nStartIdx; nInptIdx < nEndIdx; nInptIdx++) { + nOutptLen += fGetOutptChrSize(vSource[nInptIdx]); + } - nStartIdx = 0; + aWhole = new fTAView(nOutptLen); - var nChrCode; + /* transcription of the source... */ - for (nChrIdx = 0; nChrIdx < nCharStart; nChrIdx++) { - nChrCode = fGetInptChrCode(vSource, nStartIdx); - nStartIdx += fGetInptChrSize(nChrCode); - } + for (let nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx++) { + nOutptIdx = fPutOutptCode(aWhole, vSource[nInptIdx], nOutptIdx); + } - aWhole = new fTAView(nOutptLen); + break conversionSwitch; - /* transcription of the source... */ + case 2: - for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode), nOutptIdx++) { - nChrCode = fGetInptChrCode(vSource, nInptIdx); - aWhole[nOutptIdx] = nChrCode; - } + /* the source has a variable-length encoding but the new StringView has a fixed-length encoding... */ - break conversionSwitch; + /* mapping... */ - case 3: + nStartIdx = 0; - /* both the source and the new StringView have a variable-length encoding... */ + let nChrCode; - /* mapping... */ + for (let nChrIdx = 0; nChrIdx < nCharStart; nChrIdx++) { + nChrCode = fGetInptChrCode(vSource, nStartIdx); + nStartIdx += fGetInptChrSize(nChrCode); + } - nOutptLen = 0; + aWhole = new fTAView(nOutptLen); - var nChrCode; + /* transcription of the source... */ - for (var nChrIdx = 0, nInptIdx = 0; nChrIdx < nCharEnd; nInptIdx += fGetInptChrSize(nChrCode)) { - nChrCode = fGetInptChrCode(vSource, nInptIdx); - if (nChrIdx === nCharStart) { nStartIdx = nInptIdx; } - if (++nChrIdx > nCharStart) { nOutptLen += fGetOutptChrSize(nChrCode); } - } + for (let nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode), nOutptIdx++) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + aWhole[nOutptIdx] = nChrCode; + } - aWhole = new fTAView(nOutptLen); + break conversionSwitch; - /* transcription... */ + case 3: - for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode)) { - nChrCode = fGetInptChrCode(vSource, nInptIdx); - nOutptIdx = fPutOutptCode(aWhole, nChrCode, nOutptIdx); - } + /* both the source and the new StringView have a variable-length encoding... */ - break conversionSwitch; + /* mapping... */ - case 4: + nOutptLen = 0; - /* DOMString to ASCII or BinaryString or other unknown encodings */ + for (let nChrIdx = 0, nInptIdx = 0; nChrIdx < nCharEnd; nInptIdx += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + if (nChrIdx === nCharStart) { + nStartIdx = nInptIdx; + } + if (++nChrIdx > nCharStart) { + nOutptLen += fGetOutptChrSize(nChrCode); + } + } - aWhole = new fTAView(nOutptLen); + aWhole = new fTAView(nOutptLen); - /* transcription... */ + /* transcription... */ - for (var nIdx = 0; nIdx < nOutptLen; nIdx++) { - aWhole[nIdx] = vSource.charCodeAt(nIdx) & 0xff; - } + for (let nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + nOutptIdx = fPutOutptCode(aWhole, nChrCode, nOutptIdx); + } - break conversionSwitch; + break conversionSwitch; - case 5: + case 4: - /* DOMString to UTF-8 or to UTF-16 */ + /* DOMString to ASCII or BinaryString or other unknown encodings */ - /* mapping... */ + aWhole = new fTAView(nOutptLen); - nOutptLen = 0; + /* transcription... */ - for (var nMapIdx = 0; nMapIdx < nInptLen; nMapIdx++) { - if (nMapIdx === nCharStart) { nStartIdx = nOutptLen; } - nOutptLen += fGetOutptChrSize(vSource.charCodeAt(nMapIdx)); - if (nMapIdx === nCharEnd) { nEndIdx = nOutptLen; } - } + for (let nIdx = 0; nIdx < nOutptLen; nIdx++) { + aWhole[nIdx] = vSource.charCodeAt(nIdx) & 0xff; + } - aWhole = new fTAView(nOutptLen); + break conversionSwitch; - /* transcription... */ + case 5: - for (var nOutptIdx = 0, nChrIdx = 0; nOutptIdx < nOutptLen; nChrIdx++) { - nOutptIdx = fPutOutptCode(aWhole, vSource.charCodeAt(nChrIdx), nOutptIdx); - } + /* DOMString to UTF-8 or to UTF-16 */ - break conversionSwitch; + /* mapping... */ - case 6: + nOutptLen = 0; - /* DOMString to UTF-32 */ + for (let nMapIdx = 0; nMapIdx < nInptLen; nMapIdx++) { + if (nMapIdx === nCharStart) { + nStartIdx = nOutptLen; + } + nOutptLen += fGetOutptChrSize(vSource.charCodeAt(nMapIdx)); + if (nMapIdx === nCharEnd) { + nEndIdx = nOutptLen; + } + } - aWhole = new fTAView(nOutptLen); + aWhole = new fTAView(nOutptLen); - /* transcription... */ + /* transcription... */ - for (var nIdx = 0; nIdx < nOutptLen; nIdx++) { - aWhole[nIdx] = vSource.charCodeAt(nIdx); - } + for (let nOutptIdx = 0, nChrIdx = 0; nOutptIdx < nOutptLen; nChrIdx++) { + nOutptIdx = fPutOutptCode(aWhole, vSource.charCodeAt(nChrIdx), nOutptIdx); + } - break conversionSwitch; + break conversionSwitch; - case 7: + case 6: - aWhole = new fTAView(nOutptLen ? vSource : 0); - break conversionSwitch; + /* DOMString to UTF-32 */ - } + aWhole = new fTAView(nOutptLen); - aRaw = nTranscrType > 3 && (nStartIdx > 0 || nEndIdx < aWhole.length - 1) ? aWhole.subarray(nStartIdx, nEndIdx) : aWhole; + /* transcription... */ - } + for (let nIdx = 0; nIdx < nOutptLen; nIdx++) { + aWhole[nIdx] = vSource.charCodeAt(nIdx); + } - this.buffer = aWhole.buffer; - this.bufferView = aWhole; - this.rawData = aRaw; + break conversionSwitch; - Object.freeze(this); + case 7: -} - -/* CONSTRUCTOR'S METHODS */ - -StringView.loadUTF8CharCode = function (aChars, nIdx) { - - var nLen = aChars.length, nPart = aChars[nIdx]; - - return nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? - /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ - /* six bytes */ (nPart - 252) * 1073741824 + (aChars[nIdx + 1] - 128 << 24) + (aChars[nIdx + 2] - 128 << 18) + (aChars[nIdx + 3] - 128 << 12) + (aChars[nIdx + 4] - 128 << 6) + aChars[nIdx + 5] - 128 - : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? - /* five bytes */ (nPart - 248 << 24) + (aChars[nIdx + 1] - 128 << 18) + (aChars[nIdx + 2] - 128 << 12) + (aChars[nIdx + 3] - 128 << 6) + aChars[nIdx + 4] - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? - /* four bytes */(nPart - 240 << 18) + (aChars[nIdx + 1] - 128 << 12) + (aChars[nIdx + 2] - 128 << 6) + aChars[nIdx + 3] - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? - /* three bytes */ (nPart - 224 << 12) + (aChars[nIdx + 1] - 128 << 6) + aChars[nIdx + 2] - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? - /* two bytes */ (nPart - 192 << 6) + aChars[nIdx + 1] - 128 - : - /* one byte */ nPart; - -}; - -StringView.putUTF8CharCode = function (aTarget, nChar, nPutAt) { - - var nIdx = nPutAt; - - if (nChar < 0x80 /* 128 */) { - /* one byte */ - aTarget[nIdx++] = nChar; - } else if (nChar < 0x800 /* 2048 */) { - /* two bytes */ - aTarget[nIdx++] = 0xc0 /* 192 */ + (nChar >>> 6); - aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); - } else if (nChar < 0x10000 /* 65536 */) { - /* three bytes */ - aTarget[nIdx++] = 0xe0 /* 224 */ + (nChar >>> 12); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); - } else if (nChar < 0x200000 /* 2097152 */) { - /* four bytes */ - aTarget[nIdx++] = 0xf0 /* 240 */ + (nChar >>> 18); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); - } else if (nChar < 0x4000000 /* 67108864 */) { - /* five bytes */ - aTarget[nIdx++] = 0xf8 /* 248 */ + (nChar >>> 24); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); - } else /* if (nChar <= 0x7fffffff) */ { /* 2147483647 */ - /* six bytes */ - aTarget[nIdx++] = 0xfc /* 252 */ + /* (nChar >>> 32) is not possible in ECMAScript! So...: */ (nChar / 1073741824); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 24) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); - aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); - } - - return nIdx; - -}; - -StringView.getUTF8CharLength = function (nChar) { - return nChar < 0x80 ? 1 : nChar < 0x800 ? 2 : nChar < 0x10000 ? 3 : nChar < 0x200000 ? 4 : nChar < 0x4000000 ? 5 : 6; -}; - -StringView.loadUTF16CharCode = function (aChars, nIdx) { - - /* UTF-16 to DOMString decoding algorithm */ - var nFrstChr = aChars[nIdx]; - - return nFrstChr > 0xD7BF /* 55231 */ && nIdx + 1 < aChars.length ? - (nFrstChr - 0xD800 /* 55296 */ << 10) + aChars[nIdx + 1] + 0x2400 /* 9216 */ - : nFrstChr; - -}; - -StringView.putUTF16CharCode = function (aTarget, nChar, nPutAt) { - - var nIdx = nPutAt; - - if (nChar < 0x10000 /* 65536 */) { - /* one element */ - aTarget[nIdx++] = nChar; - } else { - /* two elements */ - aTarget[nIdx++] = 0xD7C0 /* 55232 */ + (nChar >>> 10); - aTarget[nIdx++] = 0xDC00 /* 56320 */ + (nChar & 0x3FF /* 1023 */); - } - - return nIdx; - -}; - -StringView.getUTF16CharLength = function (nChar) { - return nChar < 0x10000 ? 1 : 2; -}; + aWhole = new fTAView(nOutptLen ? vSource : 0); + break conversionSwitch; -/* Array of bytes to base64 string decoding */ + } -StringView.b64ToUint6 = function (nChr) { + aRaw = nTranscrType > 3 && (nStartIdx > 0 || nEndIdx < aWhole.length - 1) ? aWhole.subarray(nStartIdx, nEndIdx) : aWhole; - return nChr > 64 && nChr < 91 ? - nChr - 65 - : nChr > 96 && nChr < 123 ? - nChr - 71 - : nChr > 47 && nChr < 58 ? - nChr + 4 - : nChr === 43 ? - 62 - : nChr === 47 ? - 63 - : - 0; + } -}; + this.buffer = aWhole.buffer; + this.bufferView = aWhole; + this.rawData = aRaw; -StringView.uint6ToB64 = function (nUint6) { + Object.freeze(this); - return nUint6 < 26 ? - nUint6 + 65 - : nUint6 < 52 ? - nUint6 + 71 - : nUint6 < 62 ? - nUint6 - 4 - : nUint6 === 62 ? - 43 - : nUint6 === 63 ? - 47 - : - 65; + } -}; + static loadUTF8CharCode(aChars, nIdx) { + /* The ISO 10646 view of UTF-8 considers valid codepoints encoded by 1-6 bytes, + * while the Unicode view of UTF-8 in 2003 has limited them to 1-4 bytes in order to + * match UTF-16's codepoints. In front of a 5/6-byte sequence StringView tries to + * encode it in any case. + */ + const nLen = aChars.length, + nPart = aChars[nIdx]; + return nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? + /* (nPart - 252 << 30) may be not safe in ECMAScript! So...: */ + /* six bytes */ + (nPart - 252) * 1073741824 + (aChars[nIdx + 1] - 128 << 24) + (aChars[nIdx + 2] - 128 << 18) + (aChars[nIdx + 3] - 128 << 12) + (aChars[nIdx + 4] - 128 << 6) + aChars[nIdx + 5] - 128 : + nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? + /* five bytes */ + (nPart - 248 << 24) + (aChars[nIdx + 1] - 128 << 18) + (aChars[nIdx + 2] - 128 << 12) + (aChars[nIdx + 3] - 128 << 6) + aChars[nIdx + 4] - 128 : + nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? + /* four bytes */ + (nPart - 240 << 18) + (aChars[nIdx + 1] - 128 << 12) + (aChars[nIdx + 2] - 128 << 6) + aChars[nIdx + 3] - 128 : + nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? + /* three bytes */ + (nPart - 224 << 12) + (aChars[nIdx + 1] - 128 << 6) + aChars[nIdx + 2] - 128 : + nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? + /* two bytes */ + (nPart - 192 << 6) + aChars[nIdx + 1] - 128 : + /* one byte */ + nPart; -/* Base64 string to array encoding */ + } -StringView.bytesToBase64 = function (aBytes) { + static putUTF8CharCode(aTarget, nChar, nPutAt) { - var sB64Enc = ""; + let nIdx = nPutAt; + + if (nChar < 0x80 /* 128 */ ) { + /* one byte */ + aTarget[nIdx++] = nChar; + } else if (nChar < 0x800 /* 2048 */ ) { + /* two bytes */ + aTarget[nIdx++] = 0xc0 /* 192 */ + (nChar >>> 6); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */ ); + } else if (nChar < 0x10000 /* 65536 */ ) { + /* three bytes */ + aTarget[nIdx++] = 0xe0 /* 224 */ + (nChar >>> 12); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */ ); + } else if (nChar < 0x200000 /* 2097152 */ ) { + /* four bytes */ + aTarget[nIdx++] = 0xf0 /* 240 */ + (nChar >>> 18); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */ ); + } else if (nChar < 0x4000000 /* 67108864 */ ) { + /* five bytes */ + aTarget[nIdx++] = 0xf8 /* 248 */ + (nChar >>> 24); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */ ); + } else /* if (nChar <= 0x7fffffff) */ { /* 2147483647 */ + /* six bytes */ + aTarget[nIdx++] = 0xfc /* 252 */ + /* (nChar >>> 30) may be not safe in ECMAScript! So...: */ (nChar / 1073741824); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 24) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */ ); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */ ); + } + + return nIdx; + + } + + static getUTF8CharLength(nChar) { + return nChar < 0x80 ? 1 : nChar < 0x800 ? 2 : nChar < 0x10000 ? 3 : nChar < 0x200000 ? 4 : nChar < 0x4000000 ? 5 : 6; + } + + static loadUTF16CharCode(aChars, nIdx) { + + /* UTF-16 to DOMString decoding algorithm */ + const nFrstChr = aChars[nIdx]; + + return nFrstChr > 0xD7BF /* 55231 */ && nIdx + 1 < aChars.length ? + (nFrstChr - 0xD800 /* 55296 */ << 10) + aChars[nIdx + 1] + 0x2400 /* 9216 */ : + nFrstChr; + + } + + static putUTF16CharCode(aTarget, nChar, nPutAt) { + + let nIdx = nPutAt; + + if (nChar < 0x10000 /* 65536 */ ) { + /* one element */ + aTarget[nIdx++] = nChar; + } else { + /* two elements */ + aTarget[nIdx++] = 0xD7C0 /* 55232 */ + (nChar >>> 10); + aTarget[nIdx++] = 0xDC00 /* 56320 */ + (nChar & 0x3FF /* 1023 */ ); + } + + return nIdx; + + } + + static getUTF16CharLength(nChar) { + return nChar < 0x10000 ? 1 : 2; + } + + /* Array of bytes to base64 string decoding */ + + static b64ToUint6(nChr) { + + return nChr > 64 && nChr < 91 ? + nChr - 65 : + nChr > 96 && nChr < 123 ? + nChr - 71 : + nChr > 47 && nChr < 58 ? + nChr + 4 : + nChr === 43 ? + 62 : + nChr === 47 ? + 63 : + 0; + + } + + static uint6ToB64(nUint6) { + + return nUint6 < 26 ? + nUint6 + 65 : + nUint6 < 52 ? + nUint6 + 71 : + nUint6 < 62 ? + nUint6 - 4 : + nUint6 === 62 ? + 43 : + nUint6 === 63 ? + 47 : + 65; - for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { - nMod3 = nIdx % 3; - if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } - nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); - if (nMod3 === 2 || aBytes.length - nIdx === 1) { - sB64Enc += String.fromCharCode(StringView.uint6ToB64(nUint24 >>> 18 & 63), StringView.uint6ToB64(nUint24 >>> 12 & 63), StringView.uint6ToB64(nUint24 >>> 6 & 63), StringView.uint6ToB64(nUint24 & 63)); - nUint24 = 0; - } - } + } - return sB64Enc.replace(/A(?=A$|$)/g, "="); + /* Base64 string to array encoding */ -}; + static bytesToBase64(aBytes) { + const eqLen = (3 - (aBytes.length % 3)) % 3; + let sB64Enc = ""; -StringView.base64ToBytes = function (sBase64, nBlockBytes) { + for (let nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + /* Uncomment the following line in order to split the output in lines 76-character long: */ + /* + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } + */ + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(this.uint6ToB64(nUint24 >>> 18 & 63), this.uint6ToB64(nUint24 >>> 12 & 63), this.uint6ToB64(nUint24 >>> 6 & 63), this.uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } - var - sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, - nOutLen = nBlockBytes ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockBytes) * nBlockBytes : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen); + return eqLen === 0 ? + sB64Enc : + sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "=="); - for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { - nMod4 = nInIdx & 3; - nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; - if (nMod4 === 3 || nInLen - nInIdx === 1) { - for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { - aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; - } - nUint24 = 0; - } - } - return aBytes; + } -}; -StringView.makeFromBase64 = function (sB64Inpt, sEncoding, nByteOffset, nLength) { + static base64ToBytes(sBase64, nBlockBytes) { - return new StringView(sEncoding === "UTF-16" || sEncoding === "UTF-32" ? StringView.base64ToBytes(sB64Inpt, sEncoding === "UTF-16" ? 2 : 4).buffer : StringView.base64ToBytes(sB64Inpt), sEncoding, nByteOffset, nLength); + const sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), + nInLen = sB64Enc.length, + nOutLen = nBlockBytes ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockBytes) * nBlockBytes : nInLen * 3 + 1 >>> 2, + aBytes = new Uint8Array(nOutLen); -}; + for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + } + nUint24 = 0; + } + } -/* DEFAULT VALUES */ + return aBytes; -StringView.prototype.encoding = "UTF-8"; /* Default encoding... */ + } -/* INSTANCES' METHODS */ + static makeFromBase64(sB64Inpt, sEncoding, nByteOffset, nLength) { -StringView.prototype.makeIndex = function (nChrLength, nStartFrom) { + return new StringView(sEncoding === "UTF-16" || sEncoding === "UTF-32" ? this.base64ToBytes(sB64Inpt, sEncoding === "UTF-16" ? 2 : 4).buffer : this.base64ToBytes(sB64Inpt), sEncoding, nByteOffset, nLength); - var + } - aTarget = this.rawData, nChrEnd, nRawLength = aTarget.length, - nStartIdx = nStartFrom || 0, nIdxEnd = nStartIdx, nStopAtChr = isNaN(nChrLength) ? Infinity : nChrLength; + /* INSTANCES' METHODS */ - if (nChrLength + 1 > aTarget.length) { throw new RangeError("StringView.prototype.makeIndex - The offset can\'t be major than the length of the array - 1."); } + makeIndex(nChrLength, nStartFrom) { - switch (this.encoding) { + let aTarget = this.rawData, + nChrEnd, nRawLength = aTarget.length, + nStartIdx = nStartFrom || 0, + nIdxEnd = nStartIdx, + nStopAtChr = isNaN(nChrLength) ? Infinity : nChrLength; - case "UTF-8": + if (nChrLength + 1 > aTarget.length) { + throw new RangeError("prototype.makeIndex - The offset can\'t be major than the length of the array - 1."); + } - var nPart; + switch (this.encoding) { - for (nChrEnd = 0; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { - nPart = aTarget[nIdxEnd]; - nIdxEnd += nPart > 251 && nPart < 254 && nIdxEnd + 5 < nRawLength ? 6 - : nPart > 247 && nPart < 252 && nIdxEnd + 4 < nRawLength ? 5 - : nPart > 239 && nPart < 248 && nIdxEnd + 3 < nRawLength ? 4 - : nPart > 223 && nPart < 240 && nIdxEnd + 2 < nRawLength ? 3 - : nPart > 191 && nPart < 224 && nIdxEnd + 1 < nRawLength ? 2 - : 1; - } + case "UTF-8": - break; + let nPart; - case "UTF-16": + for (nChrEnd = 0; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nPart = aTarget[nIdxEnd]; + nIdxEnd += nPart > 251 && nPart < 254 && nIdxEnd + 5 < nRawLength ? 6 : + nPart > 247 && nPart < 252 && nIdxEnd + 4 < nRawLength ? 5 : + nPart > 239 && nPart < 248 && nIdxEnd + 3 < nRawLength ? 4 : + nPart > 223 && nPart < 240 && nIdxEnd + 2 < nRawLength ? 3 : + nPart > 191 && nPart < 224 && nIdxEnd + 1 < nRawLength ? 2 : + 1; + } - for (nChrEnd = nStartIdx; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { - nIdxEnd += aTarget[nIdxEnd] > 0xD7BF /* 55231 */ && nIdxEnd + 1 < aTarget.length ? 2 : 1; - } + break; - break; + case "UTF-16": - default: + for (nChrEnd = nStartIdx; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nIdxEnd += aTarget[nIdxEnd] > 0xD7BF /* 55231 */ && nIdxEnd + 1 < aTarget.length ? 2 : 1; + } - nIdxEnd = nChrEnd = isFinite(nChrLength) ? nChrLength : nRawLength - 1; + break; - } + default: - if (nChrLength) { return nIdxEnd; } + nIdxEnd = nChrEnd = isFinite(nChrLength) ? nChrLength : nRawLength - 1; - return nChrEnd; + } -}; + if (nChrLength) { + return nIdxEnd; + } -StringView.prototype.toBase64 = function (bWholeBuffer) { + return nChrEnd; - return StringView.bytesToBase64( - bWholeBuffer ? - ( - this.bufferView.constructor === Uint8Array ? - this.bufferView - : - new Uint8Array(this.buffer) - ) - : this.rawData.constructor === Uint8Array ? - this.rawData - : - new Uint8Array(this.buffer, this.rawData.byteOffset, this.rawData.length << (this.rawData.constructor === Uint16Array ? 1 : 2)) - ); + } -}; + toBase64(bWholeBuffer) { -StringView.prototype.subview = function (nCharOffset /* optional */, nCharLength /* optional */) { + return this.bytesToBase64( + bWholeBuffer ? + ( + this.bufferView.constructor === Uint8Array ? + this.bufferView : + new Uint8Array(this.buffer) + ) : + this.rawData.constructor === Uint8Array ? + this.rawData : + new Uint8Array(this.buffer, this.rawData.byteOffset, this.rawData.length << (this.rawData.constructor === Uint16Array ? 1 : 2)) + ); - var + } - nChrLen, nCharStart, nStrLen, bVariableLen = this.encoding === "UTF-8" || this.encoding === "UTF-16", - nStartOffset = nCharOffset, nStringLength, nRawLen = this.rawData.length; + subview(nCharOffset /* optional */ , nCharLength /* optional */ ) { - if (nRawLen === 0) { - return new StringView(this.buffer, this.encoding); - } + let nRawSubLen, nRawSubOffset, nSubOffset, nSubLen, bVariableLen = this.encoding === "UTF-8" || this.encoding === "UTF-16", + nThisLen, nRawLen = this.rawData.length; - nStringLength = bVariableLen ? this.makeIndex() : nRawLen; - nCharStart = nCharOffset ? Math.max((nStringLength + nCharOffset) % nStringLength, 0) : 0; - nStrLen = Number.isInteger(nCharLength) ? Math.max(nCharLength, 0) + nCharStart > nStringLength ? nStringLength - nCharStart : nCharLength : nStringLength; + if (nRawLen === 0) { + return new StringView(this.buffer, this.encoding); + } - if (nCharStart === 0 && nStrLen === nStringLength) { return this; } + nThisLen = bVariableLen ? this.makeIndex() : nRawLen; + nSubOffset = nCharOffset ? nCharOffset + 1 > nThisLen ? nThisLen : Math.max((nThisLen + nCharOffset) % nThisLen, 0) : 0; + nSubLen = Number.isInteger(nCharLength) ? Math.max(nCharLength, 0) + nSubOffset > nThisLen ? nThisLen - nSubOffset : nCharLength : nThisLen - nSubOffset; - if (bVariableLen) { - nStartOffset = this.makeIndex(nCharStart); - nChrLen = this.makeIndex(nStrLen, nStartOffset) - nStartOffset; - } else { - nStartOffset = nCharStart; - nChrLen = nStrLen - nCharStart; - } + if (nSubOffset === 0 && nSubLen === nThisLen) { + return this; + } - if (this.encoding === "UTF-16") { - nStartOffset <<= 1; - } else if (this.encoding === "UTF-32") { - nStartOffset <<= 2; - } + if (bVariableLen) { + nRawSubOffset = nSubOffset < nThisLen ? this.makeIndex(nSubOffset) : nThisLen; + nRawSubLen = nSubLen ? this.makeIndex(nSubLen, nRawSubOffset) - nRawSubOffset : 0; + } else { + nRawSubOffset = nSubOffset; + nRawSubLen = nSubLen; + } - return new StringView(this.buffer, this.encoding, nStartOffset, nChrLen); + if (this.encoding === "UTF-16") { + nRawSubOffset <<= 1; + } else if (this.encoding === "UTF-32") { + nRawSubOffset <<= 2; + } -}; + return new StringView(this.buffer, this.encoding, this.rawData.byteOffset + nRawSubOffset, nRawSubLen); -StringView.prototype.forEachChar = function (fCallback, oThat, nChrOffset, nChrLen) { + } - var aSource = this.rawData, nRawEnd, nRawIdx; + forEachChar(fCallback, oThat, nChrOffset, nChrLen) { - if (this.encoding === "UTF-8" || this.encoding === "UTF-16") { + let aSource = this.rawData, + nRawEnd, nRawIdx; - var fGetInptChrSize, fGetInptChrCode; + if (this.encoding === "UTF-8" || this.encoding === "UTF-16") { - if (this.encoding === "UTF-8") { - fGetInptChrSize = StringView.getUTF8CharLength; - fGetInptChrCode = StringView.loadUTF8CharCode; - } else if (this.encoding === "UTF-16") { - fGetInptChrSize = StringView.getUTF16CharLength; - fGetInptChrCode = StringView.loadUTF16CharCode; - } + let fGetInptChrSize, fGetInptChrCode; - nRawIdx = isFinite(nChrOffset) ? this.makeIndex(nChrOffset) : 0; - nRawEnd = isFinite(nChrLen) ? this.makeIndex(nChrLen, nRawIdx) : aSource.length; + if (this.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } - for (var nChrCode, nChrIdx = 0; nRawIdx < nRawEnd; nChrIdx++) { - nChrCode = fGetInptChrCode(aSource, nRawIdx); - fCallback.call(oThat || null, nChrCode, nChrIdx, nRawIdx, aSource); - nRawIdx += fGetInptChrSize(nChrCode); - } + nRawIdx = isFinite(nChrOffset) ? this.makeIndex(nChrOffset) : 0; + nRawEnd = isFinite(nChrLen) ? this.makeIndex(nChrLen, nRawIdx) : aSource.length; - } else { + for (let nChrCode, nChrIdx = 0; nRawIdx < nRawEnd; nChrIdx++) { + nChrCode = fGetInptChrCode(aSource, nRawIdx); + if (!oThat) { + fCallback(nChrCode, nChrIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, nChrCode, nChrIdx, nRawIdx, aSource); + } + nRawIdx += fGetInptChrSize(nChrCode); + } - nRawIdx = isFinite(nChrOffset) ? nChrOffset : 0; - nRawEnd = isFinite(nChrLen) ? nChrLen + nRawIdx : aSource.length; + } else { - for (nRawIdx; nRawIdx < nRawEnd; nRawIdx++) { - fCallback.call(oThat || null, aSource[nRawIdx], nRawIdx, nRawIdx, aSource); - } + nRawIdx = isFinite(nChrOffset) ? nChrOffset : 0; + nRawEnd = isFinite(nChrLen) ? nChrLen + nRawIdx : aSource.length; - } + for (nRawIdx; nRawIdx < nRawEnd; nRawIdx++) { + if (!oThat) { + fCallback(aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } + } -}; + } -StringView.prototype.valueOf = StringView.prototype.toString = function () { + } - if (this.encoding !== "UTF-8" && this.encoding !== "UTF-16") { - /* ASCII, UTF-32 or BinaryString to DOMString */ - return String.fromCharCode.apply(null, this.rawData); - } + valueOf() { - var fGetCode, fGetIncr, sView = ""; + if (this.encoding !== "UTF-8" && this.encoding !== "UTF-16") { + /* ASCII, UTF-32 or BinaryString to DOMString */ + return String.fromCharCode.apply(null, this.rawData); + } + + let fGetCode, fGetIncr, sView = ""; + + if (this.encoding === "UTF-8") { + fGetIncr = StringView.getUTF8CharLength; + fGetCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetIncr = StringView.getUTF16CharLength; + fGetCode = StringView.loadUTF16CharCode; + } - if (this.encoding === "UTF-8") { - fGetIncr = StringView.getUTF8CharLength; - fGetCode = StringView.loadUTF8CharCode; - } else if (this.encoding === "UTF-16") { - fGetIncr = StringView.getUTF16CharLength; - fGetCode = StringView.loadUTF16CharCode; - } + for (let nChr, nLen = this.rawData.length, nIdx = 0; nIdx < nLen; nIdx += fGetIncr(nChr)) { + nChr = fGetCode(this.rawData, nIdx); + sView += String.fromCharCode(nChr); + } - for (var nChr, nLen = this.rawData.length, nIdx = 0; nIdx < nLen; nIdx += fGetIncr(nChr)) { - nChr = fGetCode(this.rawData, nIdx); - sView += String.fromCharCode(nChr); - } - - return sView; - -}; - -return StringView; - -}); + return sView; + } + + toString() { + return this.valueOf(); + } +} diff --git a/bimsurfer/lib/text.js b/bimsurfer/lib/text.js deleted file mode 100644 index 4c311ed..0000000 --- a/bimsurfer/lib/text.js +++ /dev/null @@ -1,391 +0,0 @@ -/** - * @license RequireJS text 2.0.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/text for details - */ -/*jslint regexp: true */ -/*global require, XMLHttpRequest, ActiveXObject, - define, window, process, Packages, - java, location, Components, FileUtils */ - -define(['module'], function (module) { - 'use strict'; - - var text, fs, Cc, Ci, xpcIsWindows, - progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], - xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, - bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, - hasLocation = typeof location !== 'undefined' && location.href, - defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), - defaultHostName = hasLocation && location.hostname, - defaultPort = hasLocation && (location.port || undefined), - buildMap = {}, - masterConfig = (module.config && module.config()) || {}; - - text = { - version: '2.0.14', - - strip: function (content) { - //Strips declarations so that external SVG and XML - //documents can be added to a document without worry. Also, if the string - //is an HTML document, only the part inside the body tag is returned. - if (content) { - content = content.replace(xmlRegExp, ""); - var matches = content.match(bodyRegExp); - if (matches) { - content = matches[1]; - } - } else { - content = ""; - } - return content; - }, - - jsEscape: function (content) { - return content.replace(/(['\\])/g, '\\$1') - .replace(/[\f]/g, "\\f") - .replace(/[\b]/g, "\\b") - .replace(/[\n]/g, "\\n") - .replace(/[\t]/g, "\\t") - .replace(/[\r]/g, "\\r") - .replace(/[\u2028]/g, "\\u2028") - .replace(/[\u2029]/g, "\\u2029"); - }, - - createXhr: masterConfig.createXhr || function () { - //Would love to dump the ActiveX crap in here. Need IE 6 to die first. - var xhr, i, progId; - if (typeof XMLHttpRequest !== "undefined") { - return new XMLHttpRequest(); - } else if (typeof ActiveXObject !== "undefined") { - for (i = 0; i < 3; i += 1) { - progId = progIds[i]; - try { - xhr = new ActiveXObject(progId); - } catch (e) {} - - if (xhr) { - progIds = [progId]; // so faster next time - break; - } - } - } - - return xhr; - }, - - /** - * Parses a resource name into its component parts. Resource names - * look like: module/name.ext!strip, where the !strip part is - * optional. - * @param {String} name the resource name - * @returns {Object} with properties "moduleName", "ext" and "strip" - * where strip is a boolean. - */ - parseName: function (name) { - var modName, ext, temp, - strip = false, - index = name.lastIndexOf("."), - isRelative = name.indexOf('./') === 0 || - name.indexOf('../') === 0; - - if (index !== -1 && (!isRelative || index > 1)) { - modName = name.substring(0, index); - ext = name.substring(index + 1); - } else { - modName = name; - } - - temp = ext || modName; - index = temp.indexOf("!"); - if (index !== -1) { - //Pull off the strip arg. - strip = temp.substring(index + 1) === "strip"; - temp = temp.substring(0, index); - if (ext) { - ext = temp; - } else { - modName = temp; - } - } - - return { - moduleName: modName, - ext: ext, - strip: strip - }; - }, - - xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, - - /** - * Is an URL on another domain. Only works for browser use, returns - * false in non-browser environments. Only used to know if an - * optimized .js version of a text resource should be loaded - * instead. - * @param {String} url - * @returns Boolean - */ - useXhr: function (url, protocol, hostname, port) { - var uProtocol, uHostName, uPort, - match = text.xdRegExp.exec(url); - if (!match) { - return true; - } - uProtocol = match[2]; - uHostName = match[3]; - - uHostName = uHostName.split(':'); - uPort = uHostName[1]; - uHostName = uHostName[0]; - - return (!uProtocol || uProtocol === protocol) && - (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && - ((!uPort && !uHostName) || uPort === port); - }, - - finishLoad: function (name, strip, content, onLoad) { - content = strip ? text.strip(content) : content; - if (masterConfig.isBuild) { - buildMap[name] = content; - } - onLoad(content); - }, - - load: function (name, req, onLoad, config) { - //Name has format: some.module.filext!strip - //The strip part is optional. - //if strip is present, then that means only get the string contents - //inside a body tag in an HTML string. For XML/SVG content it means - //removing the declarations so the content can be inserted - //into the current doc without problems. - - // Do not bother with the work if a build and text will - // not be inlined. - if (config && config.isBuild && !config.inlineText) { - onLoad(); - return; - } - - masterConfig.isBuild = config && config.isBuild; - - var parsed = text.parseName(name), - nonStripName = parsed.moduleName + - (parsed.ext ? '.' + parsed.ext : ''), - url = req.toUrl(nonStripName), - useXhr = (masterConfig.useXhr) || - text.useXhr; - - // Do not load if it is an empty: url - if (url.indexOf('empty:') === 0) { - onLoad(); - return; - } - - //Load the text. Use XHR if possible and in a browser. - if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { - text.get(url, function (content) { - text.finishLoad(name, parsed.strip, content, onLoad); - }, function (err) { - if (onLoad.error) { - onLoad.error(err); - } - }); - } else { - //Need to fetch the resource across domains. Assume - //the resource has been optimized into a JS module. Fetch - //by the module name + extension, but do not include the - //!strip part to avoid file system issues. - req([nonStripName], function (content) { - text.finishLoad(parsed.moduleName + '.' + parsed.ext, - parsed.strip, content, onLoad); - }); - } - }, - - write: function (pluginName, moduleName, write, config) { - if (buildMap.hasOwnProperty(moduleName)) { - var content = text.jsEscape(buildMap[moduleName]); - write.asModule(pluginName + "!" + moduleName, - "define(function () { return '" + - content + - "';});\n"); - } - }, - - writeFile: function (pluginName, moduleName, req, write, config) { - var parsed = text.parseName(moduleName), - extPart = parsed.ext ? '.' + parsed.ext : '', - nonStripName = parsed.moduleName + extPart, - //Use a '.js' file name so that it indicates it is a - //script that can be loaded across domains. - fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; - - //Leverage own load() method to load plugin value, but only - //write out values that do not have the strip argument, - //to avoid any potential issues with ! in file names. - text.load(nonStripName, req, function (value) { - //Use own write() method to construct full module value. - //But need to create shell that translates writeFile's - //write() to the right interface. - var textWrite = function (contents) { - return write(fileName, contents); - }; - textWrite.asModule = function (moduleName, contents) { - return write.asModule(moduleName, fileName, contents); - }; - - text.write(pluginName, nonStripName, textWrite, config); - }, config); - } - }; - - if (masterConfig.env === 'node' || (!masterConfig.env && - typeof process !== "undefined" && - process.versions && - !!process.versions.node && - !process.versions['node-webkit'] && - !process.versions['atom-shell'])) { - //Using special require.nodeRequire, something added by r.js. - fs = require.nodeRequire('fs'); - - text.get = function (url, callback, errback) { - try { - var file = fs.readFileSync(url, 'utf8'); - //Remove BOM (Byte Mark Order) from utf8 files if it is there. - if (file[0] === '\uFEFF') { - file = file.substring(1); - } - callback(file); - } catch (e) { - if (errback) { - errback(e); - } - } - }; - } else if (masterConfig.env === 'xhr' || (!masterConfig.env && - text.createXhr())) { - text.get = function (url, callback, errback, headers) { - var xhr = text.createXhr(), header; - xhr.open('GET', url, true); - - //Allow plugins direct access to xhr headers - if (headers) { - for (header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header.toLowerCase(), headers[header]); - } - } - } - - //Allow overrides specified in config - if (masterConfig.onXhr) { - masterConfig.onXhr(xhr, url); - } - - xhr.onreadystatechange = function (evt) { - var status, err; - //Do not explicitly handle errors, those should be - //visible via console output in the browser. - if (xhr.readyState === 4) { - status = xhr.status || 0; - if (status > 399 && status < 600) { - //An http 4xx or 5xx error. Signal an error. - err = new Error(url + ' HTTP status: ' + status); - err.xhr = xhr; - if (errback) { - errback(err); - } - } else { - callback(xhr.responseText); - } - - if (masterConfig.onXhrComplete) { - masterConfig.onXhrComplete(xhr, url); - } - } - }; - xhr.send(null); - }; - } else if (masterConfig.env === 'rhino' || (!masterConfig.env && - typeof Packages !== 'undefined' && typeof java !== 'undefined')) { - //Why Java, why is this so awkward? - text.get = function (url, callback) { - var stringBuffer, line, - encoding = "utf-8", - file = new java.io.File(url), - lineSeparator = java.lang.System.getProperty("line.separator"), - input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), - content = ''; - try { - stringBuffer = new java.lang.StringBuffer(); - line = input.readLine(); - - // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 - // http://www.unicode.org/faq/utf_bom.html - - // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 - if (line && line.length() && line.charAt(0) === 0xfeff) { - // Eat the BOM, since we've already found the encoding on this file, - // and we plan to concatenating this buffer with others; the BOM should - // only appear at the top of a file. - line = line.substring(1); - } - - if (line !== null) { - stringBuffer.append(line); - } - - while ((line = input.readLine()) !== null) { - stringBuffer.append(lineSeparator); - stringBuffer.append(line); - } - //Make sure we return a JavaScript string and not a Java string. - content = String(stringBuffer.toString()); //String - } finally { - input.close(); - } - callback(content); - }; - } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && - typeof Components !== 'undefined' && Components.classes && - Components.interfaces)) { - //Avert your gaze! - Cc = Components.classes; - Ci = Components.interfaces; - Components.utils['import']('resource://gre/modules/FileUtils.jsm'); - xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); - - text.get = function (url, callback) { - var inStream, convertStream, fileObj, - readData = {}; - - if (xpcIsWindows) { - url = url.replace(/\//g, '\\'); - } - - fileObj = new FileUtils.File(url); - - //XPCOM, you so crazy - try { - inStream = Cc['@mozilla.org/network/file-input-stream;1'] - .createInstance(Ci.nsIFileInputStream); - inStream.init(fileObj, 1, 0, false); - - convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] - .createInstance(Ci.nsIConverterInputStream); - convertStream.init(inStream, "utf-8", inStream.available(), - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - convertStream.readString(inStream.available(), readData); - convertStream.close(); - inStream.close(); - callback(readData.value); - } catch (e) { - throw new Error((fileObj && fileObj.path || '') + ': ' + e); - } - }; - } - return text; -}); diff --git a/bimsurfer/src/BimServerGeometryLoader.js b/bimsurfer/src/BimServerGeometryLoader.js index 4dad6d4..06b5129 100644 --- a/bimsurfer/src/BimServerGeometryLoader.js +++ b/bimsurfer/src/BimServerGeometryLoader.js @@ -1,489 +1,468 @@ -define(["./DataInputStreamReader"], function (DataInputStreamReader) { - - function BimServerGeometryLoader(bimServerApi, viewer, model, roid, globalTransformationMatrix) { - - var o = this; - - o.bimServerApi = bimServerApi; - o.viewer = viewer; - o.state = {}; - o.progressListeners = []; - o.objectAddedListeners = []; - o.prepareReceived = false; - o.todo = []; - o.geometryIds = {}; - o.dataToInfo = {}; - - o.model = model; - o.roid = roid; - - console.log(globalTransformationMatrix); +import DataInputStreamReader from "./DataInputStreamReader.js"; +import "xeogl"; + +export default class BimServerGeometryLoader { + + constructor(bimServerApi, viewer, model, roid, globalTransformationMatrix) { + this.bimServerApi = bimServerApi; + this.viewer = viewer; + this.state = {}; + this.progressListeners = []; + this.objectAddedListeners = []; + this.prepareReceived = false; + this.todo = []; + this.geometryIds = {}; + this.dataToInfo = {}; + this.globalTransformationMatrix = globalTransformationMatrix; + this.model = model; + this.roid = roid; - this.addProgressListener = function (progressListener) { - o.progressListeners.push(progressListener); - }; - - this.process = function () { - - var data = o.todo.shift(); - var stream; - - while (data != null) { - - stream = new DataInputStreamReader(data); - - var channel = stream.readLong(); - var messageType = stream.readByte(); - - if (messageType == 0) { - o._readStart(stream); - } else if (messageType == 6) { - o._readEnd(stream); - } else { - o._readObject(stream, messageType); - } - - data = o.todo.shift(); - } - }; - - this.setLoadOids = function (oids) { - o.options = {type: "oids", oids: oids}; - }; - - /** - * Starts this loader. - */ - this.start = function () { - if (!o.options || o.options.type !== "oids") { - throw new Error("Invalid loader configuration"); - } - - if (BIMSERVER_VERSION == "1.4") { - - o.groupId = o.roid; - o.oids = o.options.oids; - o.bimServerApi.getMessagingSerializerByPluginClassName("org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingSerializerPlugin", function (serializer) { - o.bimServerApi.call("Bimsie1ServiceInterface", "downloadByOids", { - roids: [o.roid], - oids: o.options.oids, - serializerOid: serializer.oid, - sync: false, - deep: false - }, function (topicId) { - o.topicId = topicId; - o.bimServerApi.registerProgressHandler(o.topicId, o._progressHandler, o._afterRegistration); - }); - }); - - } else { - var obj = []; - - o.groupId = o.roid; - o.infoToOid = o.options.oids; - - for (var k in o.infoToOid) { - var oid = parseInt(o.infoToOid[k]); - o.model.apiModel.get(oid, function(object){ - if (object.object._rgeometry != null) { - if (object.model.objects[object.object._rgeometry] != null) { - // Only if this data is preloaded, otherwise just don't include any gi - object.getGeometry(function(geometryInfo){ - obj.push({gid: object.object._rgeometry, oid: object.oid, object: object, info: geometryInfo.object}); - }); - } else { - obj.push({gid: object.object._rgeometry, oid: object.oid, object: object}); - } - } - }); - } - obj.sort(function(a, b){ - if (a.info != null && b.info != null) { - var topa = (a.info._emaxBounds.z + a.info._eminBounds.z) / 2; - var topb = (b.info._emaxBounds.z + b.info._eminBounds.z) / 2; - return topa - topb; - } else { - // Resort back to type - // TODO this is dodgy when some objects do have info, and others don't - return a.object.getType().localeCompare(b.object.getType()); - } - }); - - var oids = []; - obj.forEach(function(wrapper){ - oids.push(wrapper.object.object._rgeometry._i); - }); - - var query = { - type: "GeometryInfo", - oids: oids, - include: { - type: "GeometryInfo", - field: "data" - } - }; - o.bimServerApi.getSerializerByPluginClassName("org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingStreamingSerializerPlugin3", function (serializer) { - o.bimServerApi.call("ServiceInterface", "download", { - roids: [o.roid], - query: JSON.stringify(query), - serializerOid : serializer.oid, - sync : false - }, function(topicId){ - o.topicId = topicId; - o.bimServerApi.registerProgressHandler(o.topicId, o._progressHandler); - }); - }); - } - }; - - this._progressHandler = function (topicId, state) { - if (topicId == o.topicId) { - if (state.title == "Done preparing") { - if (!o.prepareReceived) { - o.prepareReceived = true; - o._downloadInitiated(); - } - } - if (state.state == "FINISHED") { - o.bimServerApi.unregisterProgressHandler(o.topicId, o._progressHandler); - } - } - }; - - this._downloadInitiated = function () { - o.state = { - mode: 0, - nrObjectsRead: 0, - nrObjects: 0 - }; - // o.viewer.SYSTEM.events.trigger('progressStarted', ['Loading Geometry']); - // o.viewer.SYSTEM.events.trigger('progressBarStyleChanged', BIMSURFER.Constants.ProgressBarStyle.Continuous); - var msg = { - longActionId: o.topicId, - topicId: o.topicId - }; - o.bimServerApi.setBinaryDataListener(o.topicId, o._binaryDataListener); - o.bimServerApi.downloadViaWebsocket(msg); - }; - - this._binaryDataListener = function (data) { - o.todo.push(data); - }; - - this._afterRegistration = function (topicId) { - o.bimServerApi.call("Bimsie1NotificationRegistryInterface", "getProgress", { - topicId: o.topicId - }, function (state) { - o._progressHandler(o.topicId, state); - }); - }; - - this._readEnd = function (data) { - o.progressListeners.forEach(function(progressListener){ - progressListener("done", o.state.nrObjectsRead, o.state.nrObjectsRead); + console.log(globalTransformationMatrix); + } + + addProgressListener(progressListener) { + this.progressListeners.push(progressListener); + } + + process() { + let data = this.todo.shift(); + let stream; + + while (data != null) { + stream = new DataInputStreamReader(data); + + const channel = stream.readLong(); + const messageType = stream.readByte(); + + if (messageType == 0) { + this._readStart(stream); + } else if (messageType == 6) { + this._readEnd(stream); + } else { + this._readObject(stream, messageType); + } + + data = this.todo.shift(); + } + } + + setLoadOids(oids) { + this.options = { + type: "oids", + oids: oids + }; + } + + /** + * Starts this loader. + */ + start() { + if (!this.options || this.options.type !== "oids") { + throw new Error("Invalid loader configuration"); + } + + let obj = []; + + this.groupId = this.roid; + this.infoToOid = this.options.oids; + + for (let k in this.infoToOid) { + const oid = parseInt(this.infoToOid[k]); + this.model.apiModel.get(oid, (object) => { + if (object.object._rgeometry != null) { + if (object.model.objects[object.object._rgeometry] != null) { + // Only if this data is preloaded, otherwise just don't include any gi + object.getGeometry((geometryInfo) => { + obj.push({ + gid: object.object._rgeometry, + oid: object.oid, + object: object, + info: geometryInfo.object + }); + }); + } else { + obj.push({ + gid: object.object._rgeometry, + oid: object.oid, + object: object + }); + } + } + }); + } + + obj.sort((a, b) => { + if (a.info != null && b.info != null) { + const topa = (a.info._emaxBounds.z + a.info._eminBounds.z) / 2; + const topb = (b.info._emaxBounds.z + b.info._eminBounds.z) / 2; + return topa - topb; + } else { + // Resort back to type + // TODO this is dodgy when some objects do have info, and others don't + return a.object.getType().localeCompare(b.object.getType()); + } + }); + + let oids = []; + obj.forEach((wrapper) => { + oids.push(wrapper.object.object._rgeometry/*._i*/); + }); + + const query = { + type: "GeometryInfo", + oids: oids, + include: { + type: "GeometryInfo", + field: "data" + } + }; + + this.bimServerApi.getSerializerByPluginClassName("org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingStreamingSerializerPlugin3", (serializer) => { + this.bimServerApi.call("ServiceInterface", "download", { + roids: [this.roid], + query: JSON.stringify(query), + serializerOid: serializer.oid, + sync: false + }, (topicId) => { + this.topicId = topicId; + this.bimServerApi.registerProgressHandler(this.topicId, this._progressHandler.bind(this)); }); - o.bimServerApi.call("ServiceInterface", "cleanupLongAction", {topicId: o.topicId}, function(){}); - }; - - this._readStart = function (data) { - var start = data.readUTF8(); - - if (start != "BGS") { - console.error("data does not start with BGS (" + start + ")"); - return false; - } - - o.protocolVersion = data.readByte(); - - if (BIMSERVER_VERSION == "1.4") { - if (version != 4 && version != 5 && version != 6) { - console.error("Unimplemented version"); - return false; - } - } else { - if (o.protocolVersion != 10 && o.protocolVersion != 11) { - console.error("Unimplemented version"); - return false; - } - } - data.align8(); - - if (BIMSERVER_VERSION == "1.4") { - var boundary = data.readFloatArray(6); - } else { - var boundary = data.readDoubleArray(6); - } - - this._initCamera(boundary); - - o.state.mode = 1; - - if (BIMSERVER_VERSION == "1.4") { - o.state.nrObjects = data.readInt(); - } - - o.progressListeners.forEach(function(progressListener){ - progressListener("start", o.state.nrObjectsRead, o.state.nrObjectsRead); + }); + } + + _progressHandler(topicId, state) { + if (topicId == this.topicId) { + if (state.title == "Done preparing") { + if (!this.prepareReceived) { + this.prepareReceived = true; + this._downloadInitiated(); + } + } + if (state.state == "FINISHED") { + this.bimServerApi.unregisterProgressHandler(this.topicId, this._progressHandler.bind(this)); + } + } + } + + _downloadInitiated() { + this.state = { + mode: 0, + nrObjectsRead: 0, + nrObjects: 0 + }; + // this.viewer.SYSTEM.events.trigger('progressStarted', ['Loading Geometry']); + // this.viewer.SYSTEM.events.trigger('progressBarStyleChanged', BIMSURFER.Constants.ProgressBarStyle.Continuous); + const msg = { + longActionId: this.topicId, + topicId: this.topicId + }; + this.bimServerApi.setBinaryDataListener(this.topicId, this._binaryDataListener.bind(this)); + this.bimServerApi.downloadViaWebsocket(msg); + } + + _binaryDataListener(data) { + this.todo.push(data); + } + + _afterRegistration(topicId) { + this.bimServerApi.call("Bimsie1NotificationRegistryInterface", "getProgress", { + topicId: this.topicId + }, (state) => { + this._progressHandler(this.topicId, state); + }); + } + + _readEnd(data) { + this.progressListeners.forEach((progressListener) => { + progressListener("done", this.state.nrObjectsRead, this.state.nrObjectsRead); + }); + this.bimServerApi.call("ServiceInterface", "cleanupLongAction", { + topicId: this.topicId + }, () => { }); + } + + _readStart(data) { + const start = data.readUTF8(); + + if (start != "BGS") { + console.error("data does not start with BGS (" + start + ")"); + return false; + } + + this.protocolVersion = data.readByte(); + + if (this.protocolVersion != 10 && this.protocolVersion != 11) { + console.error("Unimplemented version"); + return false; + } + + data.align8(); + + const boundary = data.readDoubleArray(6); + + this._initCamera(boundary); + + this.state.mode = 1; + + this.progressListeners.forEach((progressListener) => { + progressListener("start", this.state.nrObjectsRead, this.state.nrObjectsRead); + }); + //this._updateProgress(); + } + + _initCamera(boundary) { + + if (!this._gotCamera) { + + this._gotCamera = true; + + // Bump scene origin to center the model + + const xmin = boundary[0]; + const ymin = boundary[1]; + const zmin = boundary[2]; + const xmax = boundary[3]; + const ymax = boundary[4]; + const zmax = boundary[5]; + + const diagonal = Math.sqrt( + Math.pow(xmax - xmin, 2) + + Math.pow(ymax - ymin, 2) + + Math.pow(zmax - zmin, 2)); + + const scale = 100 / diagonal; + + this.viewer.setScale(scale); // Temporary until we find a better scaling system. + + const far = diagonal * 5; // 5 being a guessed constant that should somehow coincide with the max zoom-out-factor + + const center = [ + scale * ((xmax + xmin) / 2), + scale * ((ymax + ymin) / 2), + scale * ((zmax + zmin) / 2) + ]; + + this.viewer.setCamera({ + type: "persp", + target: center, + up: [0, 0, 1], + eye: [center[0] - scale * diagonal, center[1] - scale * diagonal, center[2] + scale * diagonal], + far: 5000, + near: 0.1, + fovy: 35.8493 }); - //o._updateProgress(); - }; - - this._initCamera = function (boundary) { - - if (!this._gotCamera) { - - this._gotCamera = true; - - // Bump scene origin to center the model - - var xmin = boundary[0]; - var ymin = boundary[1]; - var zmin = boundary[2]; - var xmax = boundary[3]; - var ymax = boundary[4]; - var zmax = boundary[5]; - - var diagonal = Math.sqrt( - Math.pow(xmax - xmin, 2) + - Math.pow(ymax - ymin, 2) + - Math.pow(zmax - zmin, 2)); - - var scale = 100 / diagonal; - - this.viewer.setScale(scale); // Temporary until we find a better scaling system. - - var far = diagonal * 5; // 5 being a guessed constant that should somehow coincide with the max zoom-out-factor - - var center = [ - scale * ((xmax + xmin) / 2), - scale * ((ymax + ymin) / 2), - scale * ((zmax + zmin) / 2) - ]; - - this.viewer.setCamera({ - type: "persp", - target: center, - up: [0, 0, 1], - eye: [center[0] - scale * diagonal, center[1] - scale * diagonal, center[2] + scale * diagonal], - far: 5000, - near: 0.1, - fovy: 35.8493 - }); - } - }; - - this._updateProgress = function () { -// if (o.state.nrObjectsRead < o.state.nrObjects) { -// var progress = Math.ceil(100 * o.state.nrObjectsRead / o.state.nrObjects); -// if (progress != o.state.lastProgress) { -// o.progressListeners.forEach(function (progressListener) { -// progressListener(progress, o.state.nrObjectsRead, o.state.nrObjects); -// }); -// // TODO: Add events -// // o.viewer.SYSTEM.events.trigger('progressChanged', [progress]); -// o.state.lastProgress = progress; -// } -// } else { -// // o.viewer.SYSTEM.events.trigger('progressDone'); -// o.progressListeners.forEach(function (progressListener) { -// progressListener("done", o.state.nrObjectsRead, o.state.nrObjects); -// }); -// // o.viewer.events.trigger('sceneLoaded', [o.viewer.scene.scene]); -// -// var d = {}; -// d[BIMSERVER_VERSION == "1.4" ? "actionId" : "topicId"] = o.topicId; -// o.bimServerApi.call("ServiceInterface", "cleanupLongAction", d, function () {}); -// } - }; - - this._readObject = function (stream, geometryType) { - if (BIMSERVER_VERSION != "1.4") { - stream.align8(); - } - -// var type = stream.readUTF8(); -// var roid = stream.readLong(); // TODO: Needed? -// var objectId = stream.readLong(); -// var oid = objectId; - - var geometryId; - var numGeometries; - var numParts; - var objectBounds; - var numIndices; - var indices; - var numPositions; - var positions; - var numNormals; - var normals; - var numColors; - var colors = null; - - var i; - - if (geometryType == 1) { - geometryId = stream.readLong(); - numIndices = stream.readInt(); - if (BIMSERVER_VERSION == "1.4") { - indices = stream.readIntArray(numIndices); - } else { - indices = stream.readShortArray(numIndices); - } - if (o.protocolVersion == 11) { - var b = stream.readInt(); - if (b == 1) { - var color = {r: stream.readFloat(), g: stream.readFloat(), b: stream.readFloat(), a: stream.readFloat()}; - } - } - stream.align4(); - numPositions = stream.readInt(); - positions = stream.readFloatArray(numPositions); - numNormals = stream.readInt(); - normals = stream.readFloatArray(numNormals); - numColors = stream.readInt(); - if (numColors > 0) { - colors = stream.readFloatArray(numColors); - } else if (color != null) { + } + } + + _updateProgress() { + // if (this.state.nrObjectsRead < this.state.nrObjects) { + // const progress = Math.ceil(100 * this.state.nrObjectsRead / this.state.nrObjects); + // if (progress != this.state.lastProgress) { + // this.progressListeners.forEach(function (progressListener) { + // progressListener(progress, this.state.nrObjectsRead, this.state.nrObjects); + // }); + // // TODO: Add events + // // this.viewer.SYSTEM.events.trigger('progressChanged', [progress]); + // this.state.lastProgress = progress; + // } + // } else { + // // this.viewer.SYSTEM.events.trigger('progressDone'); + // this.progressListeners.forEach(function (progressListener) { + // progressListener("done", this.state.nrObjectsRead, this.state.nrObjects); + // }); + // // this.viewer.events.trigger('sceneLoaded', [this.viewer.scene.scene]); + // + // const d = {}; + // d[BIMSERVER_VERSION == "1.4" ? "actionId" : "topicId"] = this.topicId; + // this.bimServerApi.call("ServiceInterface", "cleanupLongAction", d, function () {}); + // } + } + + _readObject(stream, geometryType) { + stream.align8(); + + // const type = stream.readUTF8(); + // const roid = stream.readLong(); // TODO: Needed? + // const objectId = stream.readLong(); + // const oid = objectId; + + let geometryId; + let numGeometries; + let numParts; + let objectBounds; + let numIndices; + let indices; + let numPositions; + let positions; + let numNormals; + let normals; + let numColors; + let colors = null; + let color; + + let i; + + if (geometryType == 1) { + geometryId = stream.readLong(); + numIndices = stream.readInt(); + indices = stream.readShortArray(numIndices); + + if (this.protocolVersion == 11) { + const b = stream.readInt(); + if (b == 1) { + color = { + r: stream.readFloat(), + g: stream.readFloat(), + b: stream.readFloat(), + a: stream.readFloat() + }; + } + } + stream.align4(); + numPositions = stream.readInt(); + positions = stream.readFloatArray(numPositions); + numNormals = stream.readInt(); + normals = stream.readFloatArray(numNormals); + numColors = stream.readInt(); + if (numColors > 0) { + colors = stream.readFloatArray(numColors); + } else if (color != null) { + // Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors + colors = new Array(numPositions * 4); + for (let i = 0; i < numPositions; i++) { + colors[i * 4 + 0] = color.r; + colors[i * 4 + 1] = color.g; + colors[i * 4 + 2] = color.b; + colors[i * 4 + 3] = color.a; + } + } + + this.geometryIds[geometryId] = [geometryId]; + this.viewer.createGeometry(geometryId, positions, normals, colors, indices); + + if (this.dataToInfo[geometryId] != null) { + this.dataToInfo[geometryId].forEach((oid) => { + const ob = this.viewer.getObject(this.roid + ":" + oid); + ob.add(geometryId); + }); + delete this.dataToInfo[geometryId]; + } + } else if (geometryType == 2) { + console.log("Unimplemented", 2); + } else if (geometryType == 3) { + const geometryDataOid = stream.readLong(); + numParts = stream.readInt(); + this.geometryIds[geometryDataOid] = []; + + let geometryIds = []; + for (i = 0; i < numParts; i++) { + const partId = stream.readLong(); + geometryId = geometryDataOid + "_" + i; + numIndices = stream.readInt(); + indices = stream.readShortArray(numIndices); + + if (this.protocolVersion == 11) { + const b = stream.readInt(); + if (b == 1) { + const color = { + r: stream.readFloat(), + g: stream.readFloat(), + b: stream.readFloat(), + a: stream.readFloat() + }; + } + } + stream.align4(); + + numPositions = stream.readInt(); + positions = stream.readFloatArray(numPositions); + numNormals = stream.readInt(); + normals = stream.readFloatArray(numNormals); + numColors = stream.readInt(); + if (numColors > 0) { + colors = stream.readFloatArray(numColors); + } else if (color != null) { // Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors colors = new Array(numPositions * 4); - for (var i=0; i 0) { - colors = stream.readFloatArray(numColors); - } else if (color != null) { - // Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors - colors = new Array(numPositions * 4); - for (var i=0; i { + const ob = this.viewer.getObject(this.roid + ":" + oid); + geometryIds.forEach((geometryId) => { + ob.add(geometryId); + }); + }); + delete this.dataToInfo[geometryDataOid]; + } + } else if (geometryType == 4) { + console.log("Unimplemented", 4); + } else if (geometryType == 5) { + const roid = stream.readLong(); + const geometryInfoOid = stream.readLong(); + const objectBounds = stream.readDoubleArray(6); + const matrix = stream.readDoubleArray(16); + if (this.globalTransformationMatrix != null) { + xeogl.math.mulMat4(matrix, matrix, this.globalTransformationMatrix); + } + const geometryDataOid = stream.readLong(); + let geometryDataOids = this.geometryIds[geometryDataOid]; + const oid = this.infoToOid[geometryInfoOid]; + if (geometryDataOids == null) { + geometryDataOids = []; + let list = this.dataToInfo[geometryDataOid]; + if (list == null) { + list = []; + this.dataToInfo[geometryDataOid] = list; + } + list.push(oid); + } + if (oid == null) { + console.error("Not found", this.infoToOid, geometryInfoOid); + } else { + this.model.apiModel.get(oid, (object) => { + object.gid = geometryInfoOid; + const modelId = this.roid; // TODO: set to the model ID + this._createObject(modelId, roid, oid, oid, geometryDataOids, object.getType(), matrix); + }); + } + } else { + this.warn("Unsupported geometry type: " + geometryType); + return; + } + + this.state.nrObjectsRead++; + + this._updateProgress(); + } + + _createObject(modelId, roid, oid, objectId, geometryIds, type, matrix) { + + if (this.state.mode == 0) { + console.log("Mode is still 0, should be 1"); + return; + } + + + // this.models[roid].get(oid, + // function () { + if (this.viewer.createObject(modelId, roid, oid, objectId, geometryIds, type, matrix)) { + + // this.objectAddedListeners.forEach(function (listener) { + // listener(objectId); + // }); + } + + // }); + } +} \ No newline at end of file diff --git a/bimsurfer/src/BimServerModel.js b/bimsurfer/src/BimServerModel.js index 0d71877..00910bf 100644 --- a/bimsurfer/src/BimServerModel.js +++ b/bimsurfer/src/BimServerModel.js @@ -1,223 +1,205 @@ -define(["../lib/text"], function(text) { - - /* - var getElementName = function(elem) { - var name = null; - // TODO: This is synchronous right? - ["LongName", "Name"].forEach(function(attr) { - if (name === null && elem["get" + attr] && elem["get" + attr]()) { - name = elem["get" + attr](); - } - }); - return name; - }; - */ - - function BimServerModel(apiModel) { - - this.api = apiModel.bimServerApi; - this.apiModel = apiModel; - this.tree = null; - this.treePromise = null; - - this.getTree = function(args) { - - /* - // TODO: This is rather tricky. Never know when the list of Projects is exhausted. - // Luckily a valid IFC contains one and only one. Let's assume there is just one. - var projectEncountered = false; - - this.model.getAllOfType("IfcProject", false, function(project) { - if (projectEncountered) { - throw new Error("More than a single project encountered, bleh!"); - } - console.log('project', project); - }); - - */ - - var self = this; - - return self.treePromise || (self.treePromise = new Promise(function(resolve, reject) { - - if (self.tree) { - resolve(self.tree); - } - - var query = - { - defines:{ - Representation:{ - type: "IfcProduct", - field: "Representation" - }, - ContainsElementsDefine: { - type: "IfcSpatialStructureElement", - field:"ContainsElements", - include:{ - type:"IfcRelContainedInSpatialStructure", - field:"RelatedElements", - includes:[ - "IsDecomposedByDefine", - "ContainsElementsDefine", - "Representation" - ] - } - }, - IsDecomposedByDefine: { - type: "IfcObjectDefinition", - field:"IsDecomposedBy", - include: { - type:"IfcRelDecomposes", - field:"RelatedObjects", - includes:[ - "IsDecomposedByDefine", - "ContainsElementsDefine", - "Representation" - ] - } - }, +export default class BimServerModel { + constructor(apiModel) { + this.apiModel = apiModel; + this.tree = null; + this.treePromise = null; + } + + getTree(args) { + + /* + // TODO: This is rather tricky. Never know when the list of Projects is exhausted. + // Luckily a valid IFC contains one and only one. Let's assume there is just one. + const projectEncountered = false; + + this.model.getAllOfType("IfcProject", false, function(project) { + if (projectEncountered) { + throw new Error("More than a single project encountered, bleh!"); + } + console.log('project', project); + }); + + */ + + const self = this; + + return self.treePromise || (self.treePromise = new Promise(function (resolve, reject) { + + if (self.tree) { + resolve(self.tree); + } + + const query = + { + defines: { + Representation: { + type: "IfcProduct", + field: "Representation" + }, + ContainsElementsDefine: { + type: "IfcSpatialStructureElement", + field: "ContainsElements", + include: { + type: "IfcRelContainedInSpatialStructure", + field: "RelatedElements", + includes: [ + "IsDecomposedByDefine", + "ContainsElementsDefine", + "Representation" + ] + } + }, + IsDecomposedByDefine: { + type: "IfcObjectDefinition", + field: "IsDecomposedBy", + include: { + type: "IfcRelDecomposes", + field: "RelatedObjects", + includes: [ + "IsDecomposedByDefine", + "ContainsElementsDefine", + "Representation" + ] + } }, - queries:[{ - type:"IfcProject", - includes:[ - "IsDecomposedByDefine", - "ContainsElementsDefine" - ] + }, + queries: [{ + type: "IfcProject", + includes: [ + "IsDecomposedByDefine", + "ContainsElementsDefine" + ] + }, { + type: "IfcRepresentation", + includeAllSubtypes: true + }, { + type: "IfcProductRepresentation" + }, { + type: "IfcPresentationLayerWithStyle" + }, { + type: "IfcProduct", + includeAllSubtypes: true + }, { + type: "IfcProductDefinitionShape" + }, { + type: "IfcPresentationLayerAssignment" + }, { + type: "IfcRelAssociatesClassification", + includes: [{ + type: "IfcRelAssociatesClassification", + field: "RelatedObjects" }, { - type:"IfcRepresentation", - includeAllSubtypes: true - },{ - type:"IfcProductRepresentation" - },{ - type:"IfcPresentationLayerWithStyle" - },{ - type:"IfcProduct", - includeAllSubtypes: true - }, { - type:"IfcProductDefinitionShape" - }, { - type:"IfcPresentationLayerAssignment" - }, { - type:"IfcRelAssociatesClassification", - includes:[{ - type:"IfcRelAssociatesClassification", - field:"RelatedObjects" - }, { - type:"IfcRelAssociatesClassification", - field:"RelatingClassification" - }] - }, { - type:"IfcSIUnit" - }, { - type:"IfcPresentationLayerAssignment" - }] - }; - - // Perform the download -// apiModel.query(query, function(o) {}).done(function(){ - - // A list of entities that define parent-child relationships - var entities = { - 'IfcRelDecomposes': 1, - 'IfcRelAggregates': 1, - 'IfcRelContainedInSpatialStructure': 1, - 'IfcRelFillsElement': 1, - 'IfcRelVoidsElement': 1 - } - - // Create a mapping from id->instance - var instance_by_id = {}; - var objects = []; - - for (var e in apiModel.objects) { - // The root node in a dojo store should have its parent - // set to null, not just something that evaluates to false - var o = apiModel.objects[e].object; - o.parent = null; - instance_by_id[o._i] = o; - objects.push(o); - }; - - // Filter all instances based on relationship entities - var relationships = objects.filter(function (o) { - return entities[o._t]; - }); + type: "IfcRelAssociatesClassification", + field: "RelatingClassification" + }] + }, { + type: "IfcSIUnit" + }, { + type: "IfcPresentationLayerAssignment" + }] + }; - // Construct a tuple of {parent, child} ids - var parents = relationships.map(function (o) { - var ks = Object.keys(o); - var related = ks.filter(function (k) { - return k.indexOf('Related') !== -1; - }); - var relating = ks.filter(function (k) { - return k.indexOf('Relating') !== -1; - }); - return [o[relating[0]], o[related[0]]]; - }); + // Perform the download + // apiModel.query(query, function(o) {}).done(function(){ + + // A list of entities that define parent-child relationships + const entities = { + 'IfcRelDecomposes': 1, + 'IfcRelAggregates': 1, + 'IfcRelContainedInSpatialStructure': 1, + 'IfcRelFillsElement': 1, + 'IfcRelVoidsElement': 1 + }; - var is_array = function (o) { - return Object.prototype.toString.call(o) === '[object Array]'; + // Create a mapping from id->instance + const instance_by_id = {}; + const objects = []; + + for (const e in self.apiModel.objects) { + // The root node in a dojo store should have its parent + // set to null, not just something that evaluates to false + const o = self.apiModel.objects[e].object; + o.parent = null; + instance_by_id[o._i] = o; + objects.push(o); + } + + // Filter all instances based on relationship entities + const relationships = objects.filter(function (o) { + return entities[o._t]; + }); + + // Construct a tuple of {parent, child} ids + const parents = relationships.map(function (o) { + const ks = Object.keys(o); + const related = ks.filter(function (k) { + return k.indexOf('Related') !== -1; + }); + const relating = ks.filter(function (k) { + return k.indexOf('Relating') !== -1; + }); + return [o[relating[0]], o[related[0]]]; + }); + + const is_array = function (o) { + return Object.prototype.toString.call(o) === '[object Array]'; + }; + + const data = []; + const visited = {}; + parents.forEach(function (a) { + // Relationships in IFC can be one to one/many + const ps = is_array(a[0]) ? a[0] : [a[0]]; + const cs = is_array(a[1]) ? a[1] : [a[1]]; + for (let i = 0; i < ps.length; ++i) { + for (let j = 0; j < cs.length; ++j) { + // Lookup the instance ids in the mapping + const p = instance_by_id[ps[i]._i]; + const c = instance_by_id[cs[j]._i]; + + // parent, id, hasChildren are significant attributes in a dojo store + c.parent = p.id = p._i; + c.id = c._i; + p.hasChildren = true; + + // Make sure to only add instances once + if (!visited[c.id]) { + data.push(c); + } + if (!visited[p.id]) { + data.push(p); + } + visited[p.id] = visited[c.id] = true; } + } + }); - var data = []; - var visited = {}; - parents.forEach(function (a) { - // Relationships in IFC can be one to one/many - var ps = is_array(a[0]) ? a[0] : [a[0]]; - var cs = is_array(a[1]) ? a[1] : [a[1]]; - for (var i = 0; i < ps.length; ++i) { - for (var j = 0; j < cs.length; ++j) { - // Lookup the instance ids in the mapping - var p = instance_by_id[ps[i]._i]; - var c = instance_by_id[cs[j]._i]; - - // parent, id, hasChildren are significant attributes in a dojo store - c.parent = p.id = p._i; - c.id = c._i; - p.hasChildren = true; - - // Make sure to only add instances once - if (!visited[c.id]) { - data.push(c); - } - if (!visited[p.id]) { - data.push(p); - } - visited[p.id] = visited[c.id] = true; - } + const make_element = function (o) { + return { name: o.Name, id: o.id, guid: o.GlobalId, parent: o.parent, gid: (o._rgeometry == null ? null : o._rgeometry/*._i*/) }; + }; + + const fold = (function () { + let root = null; + return function (li) { + const by_oid = {}; + li.forEach(function (elem) { + by_oid[elem.id] = elem; + }); + li.forEach(function (elem) { + if (elem.parent === null) { + root = elem; + } else { + const p = by_oid[elem.parent]; + (p.children || (p.children = [])).push(elem); } }); - - var make_element = function (o) { - return {name: o.Name, id: o.id, guid: o.GlobalId, parent: o.parent, gid: (o._rgeometry == null ? null : o._rgeometry._i)}; - }; - - var fold = (function() { - var root = null; - return function(li) { - var by_oid = {}; - li.forEach(function(elem) { - by_oid[elem.id] = elem; - }); - li.forEach(function(elem) { - if (elem.parent === null) { - root = elem; - } else { - var p = by_oid[elem.parent]; - (p.children || (p.children = [])).push(elem); - } - }); - return root; - }})(); - - resolve(self.tree = fold(data.map(make_element))); -// }); - })); - }; - - } - - return BimServerModel; - -}); \ No newline at end of file + return root; + }; + })(); + + resolve(self.tree = fold(data.map(make_element))); + // }); + })); + } + +} diff --git a/bimsurfer/src/BimServerModelLoader.js b/bimsurfer/src/BimServerModelLoader.js index 65ba53b..2598212 100644 --- a/bimsurfer/src/BimServerModelLoader.js +++ b/bimsurfer/src/BimServerModelLoader.js @@ -1,93 +1,101 @@ -define(["./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./BimSurfer"], function(BimServerModel, PreloadQuery, BimServerGeometryLoader, BimSufer) { - - function BimServerModelLoader(bimServerClient, bimSurfer) { - - var o = this; - - this.loadFullModel = function(apiModel){ - return new Promise(function(resolve, reject) { - var model = new BimServerModel(apiModel); - - apiModel.query(PreloadQuery, function () {}).done(function(){ - var oids = []; - apiModel.getAllOfType("IfcProduct", true, function(object){ - oids.push(object.oid); - }); - o.loadOids(model, oids); - resolve(model); - }); - }); - }; - - this.loadObjects = function(apiModel, objects){ - return new Promise(function(resolve, reject) { - var model = new BimServerModel(apiModel); - - var oids = []; - objects.forEach(function(object){ +import BimServerModel from './BimServerModel.js'; +import PreloadQuery from './PreloadQuery.js'; +import BimServerGeometryLoader from './BimServerGeometryLoader.js'; + +export default class BimServerModelLoader { + + //define(["./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./BimSurfer"], function(BimServerModel, PreloadQuery, BimServerGeometryLoader, BimSufer) { + + constructor(bimServerClient, bimSurfer) { + this.bimServerClient = bimServerClient; + this.bimSurfer = bimSurfer; + this.globalTransformationMatrix = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; + } + + loadFullModel(apiModel) { + return new Promise((resolve, reject) => { + const model = new BimServerModel(apiModel); + + apiModel.query(PreloadQuery, () => { }).done(() => { + const oids = []; + apiModel.getAllOfType("IfcProduct", true, (object) => { oids.push(object.oid); }); - o.loadOids(model, oids); + this.loadOids(model, oids); resolve(model); - }); - }; - - this.loadOids = function(model, oids){ - var oidToGuid = {}; - var guidToOid = {}; - - var oidGid = {}; - - oids.forEach(function(oid){ - model.apiModel.get(oid, function(object){ - if (object.object._rgeometry != null) { - var gid = object.object._rgeometry._i; - var guid = object.object.GlobalId; - oidToGuid[oid] = guid; - guidToOid[guid] = oid; - oidGid[gid] = oid; - } - }); - }); - - bimSurfer._idMapping.toGuid.push(oidToGuid); - bimSurfer._idMapping.toId.push(guidToOid); - - var viewer = bimSurfer.viewer; - viewer.taskStarted(); - - viewer.createModel(model.apiModel.roid); - - var loader = new BimServerGeometryLoader(model.api, viewer, model, model.apiModel.roid, o.globalTransformationMatrix); - - loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { - if (progress == "start") { - console.log("Started loading geometries"); -// self.fire("loading-started"); - } else if (progress == "done") { - console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); -// self.fire("loading-finished"); - viewer.taskFinished(); + }); + }); + } + + loadObjects(apiModel, objects) { + return new Promise((resolve, reject) => { + const model = new BimServerModel(apiModel); + + const oids = []; + objects.forEach((object) => { + oids.push(object.oid); + }); + this.loadOids(model, oids); + resolve(model); + }); + } + + loadOids(model, oids) { + const oidToGuid = {}; + const guidToOid = {}; + + const oidGid = {}; + + oids.forEach((oid) => { + model.apiModel.get(oid, (object) => { + if (object.object._rgeometry != null) { + const gid = object.object._rgeometry;//._i; + const guid = object.object.GlobalId; + oidToGuid[oid] = guid; + guidToOid[guid] = oid; + oidGid[gid] = oid; } - }); - - loader.setLoadOids(oidGid); - - // viewer.clear(); // For now, until we support multiple models through the API - - viewer.on("tick", function () { // TODO: Fire "tick" event from xeoViewer - loader.process(); - }); - - loader.start(); - } - - this.setGlobalTransformationMatrix = function(globalTransformationMatrix) { - o.globalTransformationMatrix = globalTransformationMatrix; - } - } - - BimServerModelLoader.prototype = Object.create(BimServerModelLoader.prototype); - - return BimServerModelLoader; -}); \ No newline at end of file + }); + }); + + this.bimSurfer._idMapping.toGuid.push(oidToGuid); + this.bimSurfer._idMapping.toId.push(guidToOid); + + const viewer = this.bimSurfer.viewer; + viewer.taskStarted(); + + viewer.createModel(model.apiModel.roid); + + const loader = new BimServerGeometryLoader(model.apiModel.bimServerApi, viewer, model, model.apiModel.roid, this.globalTransformationMatrix); + + loader.addProgressListener((progress, nrObjectsRead, totalNrObjects) => { + if (progress == "start") { + console.log("Started loading geometries"); + this.bimSurfer.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + this.bimSurfer.fire("loading-finished"); + viewer.taskFinished(); + } + }); + + loader.setLoadOids(oidGid); + + // viewer.clear(); // For now, until we support multiple models through the API + + viewer.on("tick", () => { // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); + + loader.start(); + } + + setGlobalTransformationMatrix(globalTransformationMatrix) { + this.globalTransformationMatrix = globalTransformationMatrix; + } +} \ No newline at end of file diff --git a/bimsurfer/src/BimSurfer.js b/bimsurfer/src/BimSurfer.js index 7900094..37d3b84 100644 --- a/bimsurfer/src/BimSurfer.js +++ b/bimsurfer/src/BimSurfer.js @@ -1,435 +1,409 @@ -// Backwards compatibility -var deps = ["./Notifier", "./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./xeoViewer/xeoViewer", "./EventHandler"]; - -/* -if (typeof(BimServerClient) == 'undefined') { - window.BIMSERVER_VERSION = "1.4"; - deps.push("bimserverapi_BimServerApi"); -} else { - window.BIMSERVER_VERSION = "1.5"; -} -*/ - -window.BIMSERVER_VERSION = "1.5"; - -define(deps, function (Notifier, Model, PreloadQuery, GeometryLoader, xeoViewer, EventHandler, _BimServerApi) { - - // Backwards compatibility - var BimServerApi; - if (_BimServerApi) { - BimServerApi = _BimServerApi; - } else { - BimServerApi = window.BimServerClient; +import Notifier from './Notifier.js'; +import Model from './BimServerModel.js'; +import PreloadQuery from './PreloadQuery.js'; +import GeometryLoader from './BimServerGeometryLoader.js'; +import EventHandler from './EventHandler.js'; +import xeoViewer from './xeoViewer/xeoViewer.js'; + +import BimServerClient from "bimserverapi"; + +export default class BimSurfer extends EventHandler { + constructor(cfg) { + super(); + + this.BimServerApi = BimServerClient; + + cfg = cfg || {}; + + this.viewer = new xeoViewer(cfg); + + /** + * Fired whenever this BIMSurfer's camera changes. + * @event camera-changed + */ + this.viewer.on("camera-changed", (args) => { + this.fire("camera-changed", args); + }); + + /** + * Fired whenever this BIMSurfer's selection changes. + * @event selection-changed + */ + this.viewer.on("selection-changed", (args) => { + this.fire("selection-changed", args); + }); + + // This are arrays as multiple models might be loaded or unloaded. + this._idMapping = { + 'toGuid': [], + 'toId': [] + }; } - - function BimSurfer(cfg) { - - var self = this; - - EventHandler.call(this); - - cfg = cfg || {}; - - var viewer = this.viewer = new xeoViewer(cfg); - - /** - * Fired whenever this BIMSurfer's camera changes. - * @event camera-changed - */ - viewer.on("camera-changed", function() { - self.fire("camera-changed", arguments); - }); - - /** - * Fired whenever this BIMSurfer's selection changes. - * @event selection-changed - */ - viewer.on("selection-changed", function() { - self.fire("selection-changed", arguments); - }); - - // This are arrays as multiple models might be loaded or unloaded. - this._idMapping = { - 'toGuid': [], - 'toId' : [] - }; - - /** - * Loads a model into this BIMSurfer. - * @param params - */ - this.load = function (params) { - - if (params.test) { - viewer.loadRandom(params); - return null; - - } else if (params.bimserver) { - return this._loadFromServer(params); - - } else if (params.api) { - return this._loadFromAPI(params); - - } else if (params.src) { - return this._loadFrom_glTF(params); - } - }; - - this._loadFromServer = function (params) { - - var notifier = new Notifier(); - var bimServerApi = new BimServerApi(params.bimserver, notifier); - - params.api = bimServerApi; // TODO: Make copy of params - - return self._initApi(params) - .then(self._loginToServer) - .then(self._getRevisionFromServer) - .then(self._loadFromAPI); - }; - - this._initApi = function(params) { - return new Promise(function(resolve, reject) { - params.api.init(function () { + /** + * Loads a model into this BIMSurfer. + * @param params + */ + load(params) { + + if (params.test) { + this.viewer.loadRandom(params); + return null; + + } else if (params.bimserver) { + return this._loadFromServer(params); + + } else if (params.api) { + return this._loadFromAPI(params); + + } else if (params.src) { + return this._loadFrom_glTF(params); + } + } + + _loadFromServer(params) { + + const notifier = new Notifier(); + const bimServerApi = new this.BimServerApi(params.bimserver, notifier); + + params.api = bimServerApi; // TODO: Make copy of params + + return this._initApi(params) + .then(this._loginToServer) + .then(this._getRevisionFromServer.bind(this)) + .then(this._loadFromAPI.bind(this)); + } + + _initApi(params) { + return new Promise((resolve, reject) => { + params.api.init(() => { + resolve(params); + }); + }); + } + + _loginToServer(params) { + return new Promise((resolve, reject) => { + if (params.token) { + params.api.setToken(params.token, () => { + resolve(params); + }, reject); + } else { + params.api.login(params.username, params.password, () => { resolve(params); + }, reject); + } + }); + } + + _getRevisionFromServer(params) { + return new Promise((resolve, reject) => { + if (params.roid) { + resolve(params); + } else { + params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, (data) => { + let resolved = false; + + data.forEach((projectData) => { + if (projectData.oid == params.poid) { + params.roid = projectData.lastRevisionId; + params.schema = projectData.schema; + if (!this.models) { + this.models = []; + } + this.models.push(projectData); + resolved = true; + resolve(params); + } + }); + + if (!resolved) { + reject(); + } + }, reject); + } + }); + } + + _loadFrom_glTF(params) { + if (params.src) { + return new Promise((resolve, reject) => { + const m = this.viewer.loadglTF(params.src); + m.on("loaded", () => { + + let numComponents = 0, componentsLoaded = 0; + + m.iterate((component) => { + if (component.isType("xeogl.Entity")) { + ++numComponents; + ((c) => { + let timesUpdated = 0; + c.worldBoundary.on("updated", () => { + if (++timesUpdated == 2) { + ++componentsLoaded; + if (componentsLoaded == numComponents) { + this.viewer.viewFit({}); + + resolve(m); + } + } + }); + })(component); + } + }); }); }); - }; - - this._loginToServer = function (params) { - return new Promise(function(resolve, reject) { - if (params.token) { - params.api.setToken(params.token, function() { - resolve(params) - }, reject); - } else { - params.api.login(params.username, params.password, function() { - resolve(params) - }, reject); + } + } + + _loadFromAPI(params) { + + return new Promise((resolve, reject) => { + + params.api.getModel(params.poid, params.roid, params.schema, false, + (model) => { + + // TODO: Preload not necessary combined with the bruteforce tree + let fired = false; + + model.query(PreloadQuery, + () => { + if (!fired) { + fired = true; + const vmodel = new Model(params.api, model); + + this._loadModel(vmodel); + + resolve(vmodel); + } + }); + }); + }); + } + + _loadModel(model) { + + model.getTree().then((tree) => { + + const oids = []; + const oidToGuid = {}; + const guidToOid = {}; + + const visit = (n) => { + oids[n.gid] = n.id; + oidToGuid[n.id] = n.guid; + guidToOid[n.guid] = n.id; + + for (let i = 0; i < (n.children || []).length; ++i) { + visit(n.children[i]); } - }); - }; - - this._getRevisionFromServer = function (params) { - return new Promise(function(resolve, reject) { - if (params.roid) { - resolve(params); - } else { - params.api.call("ServiceInterface", "getAllRelatedProjects", {poid: params.poid}, function(data) { - var resolved = false; - - data.forEach(function(projectData) { - if (projectData.oid == params.poid) { - params.roid = projectData.lastRevisionId; - params.schema = projectData.schema; - if (!self.models) { - self.models = []; - } - self.models.push(projectData); - resolved = true; - resolve(params); - } - }); - - if (!resolved) { - reject(); - } - }, reject); + }; + + visit(tree); + + this._idMapping.toGuid.push(oidToGuid); + this._idMapping.toId.push(guidToOid); + + const models = {}; + + // TODO: Ugh. Undecorate some of the newly created classes + models[model.model.roid] = model.model; + + // Notify viewer that things are loading, so viewer can + // reduce rendering speed and show a spinner. + this.viewer.taskStarted(); + + this.viewer.createModel(model.model.roid); + + const loader = new GeometryLoader(model.api, models, this.viewer); + + loader.addProgressListener((progress, nrObjectsRead, totalNrObjects) => { + if (progress == "start") { + console.log("Started loading geometries"); + this.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + this.fire("loading-finished"); + this.viewer.taskFinished(); } }); - }; - this._loadFrom_glTF = function (params) { - if (params.src) { - return new Promise(function (resolve, reject) { - var m = viewer.loadglTF(params.src); - m.on("loaded", function() { - - var numComponents = 0, componentsLoaded = 0; - - m.iterate(function (component) { - if (component.isType("xeogl.Entity")) { - ++ numComponents; - (function(c) { - var timesUpdated = 0; - c.worldBoundary.on("updated", function() { - if (++timesUpdated == 2) { - ++ componentsLoaded; - if (componentsLoaded == numComponents) { - viewer.viewFit({}); - - resolve(m); - } - } - }); - })(component); - } - }); - }); - }); - } - }; - - this._loadFromAPI = function (params) { - - return new Promise(function (resolve, reject) { - - params.api.getModel(params.poid, params.roid, params.schema, false, - function (model) { - - // TODO: Preload not necessary combined with the bruteforce tree - var fired = false; - - model.query(PreloadQuery, - function () { - if (!fired) { - fired = true; - var vmodel = new Model(params.api, model); - - self._loadModel(vmodel); - - resolve(vmodel); - } - }); - }); - }); - }; - - this._loadModel = function (model) { - - model.getTree().then(function (tree) { - - var oids = []; - var oidToGuid = {}; - var guidToOid = {}; - - var visit = function (n) { - if (BIMSERVER_VERSION == "1.4") { - oids.push(n.id); - } else { - oids[n.gid] = n.id; - } - oidToGuid[n.id] = n.guid; - guidToOid[n.guid] = n.id; - - for (var i = 0; i < (n.children || []).length; ++i) { - visit(n.children[i]); - } - }; - - visit(tree); - - self._idMapping.toGuid.push(oidToGuid); - self._idMapping.toId.push(guidToOid); - - var models = {}; - - // TODO: Ugh. Undecorate some of the newly created classes - models[model.model.roid] = model.model; - - // Notify viewer that things are loading, so viewer can - // reduce rendering speed and show a spinner. - viewer.taskStarted(); - - viewer.createModel(model.model.roid); - - var loader = new GeometryLoader(model.api, models, viewer); - - loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { - if (progress == "start") { - console.log("Started loading geometries"); - self.fire("loading-started"); - } else if (progress == "done") { - console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); - self.fire("loading-finished"); - viewer.taskFinished(); - } - }); - - loader.setLoadOids([model.model.roid], oids); - - // viewer.clear(); // For now, until we support multiple models through the API - - viewer.on("tick", function () { // TODO: Fire "tick" event from xeoViewer - loader.process(); - }); - - loader.start(); - }); - }; - - // Helper function to traverse over the mappings for individually loaded models - var _traverseMappings = function(mappings) { - return function(k) { - for (var i = 0; i < mappings.length; ++i) { - var v = mappings[i][k]; - if (v) return v; - } - return null; - } - }; - - /** - * Returns a list of object ids (oid) for the list of guids (GlobalId) - * - * @param guids List of globally unique identifiers from the IFC model - */ - this.toId = function(guids) { - return guids.map(_traverseMappings(self._idMapping.toId)); - }; - - /** - * Returns a list of guids (GlobalId) for the list of object ids (oid) - * - * @param ids List of internal object ids from the BIMserver / glTF file - */ - this.toGuid = function(ids) { - return ids.map(_traverseMappings(self._idMapping.toGuid)); - }; - - /** - * Shows/hides objects specified by id or entity type, e.g IfcWall. - * - * When recursive is set to true, hides children (aggregates, spatial structures etc) or - * subtypes (IfcWallStandardCase ⊆ IfcWall). - * - * @param params - */ - this.setVisibility = function (params) { - viewer.setVisibility(params); - }; - - /** - * Selects/deselects objects specified by id. - ** - * @param params - */ - this.setSelection = function (params) { - return viewer.setSelection(params); - }; - - /** - * Gets a list of selected elements. - */ - this.getSelection = function () { - return viewer.getSelection(); - }; - - /** - * Sets color of objects specified by ids or entity type, e.g IfcWall. - ** - * @param params - */ - this.setColor = function (params) { - viewer.setColor(params); - }; - - /** - * Sets opacity of objects specified by ids or entity type, e.g IfcWall. - ** - * @param params - */ - this.setOpacity = function (params) { - viewer.setOpacity(params); - }; - - /** - * Fits the elements into view. - * - * Fits the entire model into view if ids is an empty array, null or undefined. - * Animate allows to specify a transition period in milliseconds in which the view is altered. - * - * @param params - */ - this.viewFit = function (params) { - viewer.viewFit(params); - }; - - /** - * - */ - this.getCamera = function () { - return viewer.getCamera(); - }; - - /** - * - * @param params - */ - this.setCamera = function (params) { - viewer.setCamera(params); - }; - - /** - * Redefines light sources. - * - * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type - */ - this.setLights = function (params) { - viewer.setLights(params); - }; - - - /** - * Returns light sources. - * - * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - */ - this.getLights = function () { - return viewer.getLights; + loader.setLoadOids([model.model.roid], oids); + + // viewer.clear(); // For now, until we support multiple models through the API + + this.viewer.on("tick", () => { // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); + + loader.start(); + }); + } + + // Helper function to traverse over the mappings for individually loaded models + static _traverseMappings(mappings) { + return (k) => { + for (let i = 0; i < mappings.length; ++i) { + const v = mappings[i][k]; + if (v) { return v; } + } + return null; }; + } + + /** + * Returns a list of object ids (oid) for the list of guids (GlobalId) + * + * @param guids List of globally unique identifiers from the IFC model + */ + toId(guids) { + return guids.map(this._traverseMappings(this._idMapping.toId)); + } + + /** + * Returns a list of guids (GlobalId) for the list of object ids (oid) + * + * @param ids List of internal object ids from the BIMserver / glTF file + */ + toGuid(ids) { + return ids.map(this._traverseMappings(this._idMapping.toGuid)); + } + + /** + * Shows/hides objects specified by id or entity type, e.g IfcWall. + * + * When recursive is set to true, hides children (aggregates, spatial structures etc) or + * subtypes (IfcWallStandardCase ⊆ IfcWall). + * + * @param params + */ + setVisibility(params) { + this.viewer.setVisibility(params); + } + + /** + * Selects/deselects objects specified by id. + ** + * @param params + */ + setSelection(params) { + return this.viewer.setSelection(params); + } + + /** + * Gets a list of selected elements. + */ + getSelection() { + return this.viewer.getSelection(); + } + + /** + * Sets color of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ + setColor(params) { + this.viewer.setColor(params); + } + + /** + * Sets opacity of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ + setOpacity(params) { + this.viewer.setOpacity(params); + } - /** - * - * @param params - */ - this.reset = function (params) { - viewer.reset(params); - } - - /** - * Returns a list of loaded IFC entity types in the model. - * - * @method getTypes - * @returns {Array} List of loaded IFC entity types, with visibility flag - */ - this.getTypes = function() { - return viewer.getTypes(); - }; - - /** - * Sets the default behaviour of mouse and touch drag input - * - * @method setDefaultDragAction - * @param {String} action ("pan" | "orbit") - */ - this.setDefaultDragAction = function (action) { - viewer.setDefaultDragAction(action); - }; - - /** - * Returns the world boundary of an object - * - * @method getWorldBoundary - * @param {String} objectId id of object - * @param {Object} result Existing boundary object - * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D - */ - this.getWorldBoundary = function(objectId, result) { - return viewer.getWorldBoundary(objectId, result); - }; - - /** - * Destroys the BIMSurfer - */ - this.destroy = function() { - viewer.destroy(); - } - } - - BimSurfer.prototype = Object.create(EventHandler.prototype); - - return BimSurfer; - -}); + /** + * Fits the elements into view. + * + * Fits the entire model into view if ids is an empty array, null or undefined. + * Animate allows to specify a transition period in milliseconds in which the view is altered. + * + * @param params + */ + viewFit(params) { + this.viewer.viewFit(params); + } + + /** + * + */ + getCamera() { + return this.viewer.getCamera(); + } + + /** + * + * @param params + */ + setCamera(params) { + this.viewer.setCamera(params); + } + + /** + * Redefines light sources. + * + * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type + */ + setLights(params) { + this.viewer.setLights(params); + } + + /** + * Returns light sources. + * + * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + */ + getLights() { + return this.viewer.getLights; + } + + /** + * + * @param params + */ + reset(params) { + this.viewer.reset(params); + } + + /** + * Returns a list of loaded IFC entity types in the model. + * + * @method getTypes + * @returns {Array} List of loaded IFC entity types, with visibility flag + */ + getTypes() { + return this.viewer.getTypes(); + } + + /** + * Sets the default behaviour of mouse and touch drag input + * + * @method setDefaultDragAction + * @param {String} action ("pan" | "orbit") + */ + setDefaultDragAction(action) { + this.viewer.setDefaultDragAction(action); + } + + /** + * Returns the world boundary of an object + * + * @method getWorldBoundary + * @param {String} objectId id of object + * @param {Object} result Existing boundary object + * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D + */ + getWorldBoundary(objectId, result) { + return this.viewer.getWorldBoundary(objectId, result); + } + + /** + * Destroys the BIMSurfer + */ + destroy() { + this.viewer.destroy(); + } +} diff --git a/bimsurfer/src/DataInputStreamReader.js b/bimsurfer/src/DataInputStreamReader.js index 68c47a3..a542d1d 100644 --- a/bimsurfer/src/DataInputStreamReader.js +++ b/bimsurfer/src/DataInputStreamReader.js @@ -1,111 +1,109 @@ -define(["../lib/StringView"], function(StringView) { - - function DataInputStreamReader(arrayBuffer) { - - this.arrayBuffer = arrayBuffer; - this.dataView = new DataView(this.arrayBuffer); - this.pos = 0; - - this.readUTF8 = function() { - var length = this.dataView.getInt16(this.pos); - this.pos += 2; - var view = this.arrayBuffer.slice(this.pos, this.pos + length); - var result = new StringView(view).toString(); - this.pos += length; - return result; - }; +import StringView from '../lib/StringView.js'; - this.align4 = function() { - // Skips to the next alignment of 4 (source should have done the same!) - var skip = 4 - (this.pos % 4); - if(skip > 0 && skip != 4) { - // console.log("Skip", skip); - this.pos += skip; - } - }; +export default class DataInputStreamReader { + constructor(arrayBuffer) { - this.align8 = function() { - // Skips to the next alignment of 4 (source should have done the same!) - var skip = 8 - (this.pos % 8); - if(skip > 0 && skip != 8) { - // console.log("Skip", skip); - this.pos += skip; - } - }; + this.arrayBuffer = arrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + this.pos = 0; + } - this.readDoubleArray = function(length) { - var result = new Float64Array(this.arrayBuffer, this.pos, length); - this.pos += length * 8; - return result; - }, + readUTF8() { + const length = this.dataView.getInt16(this.pos); + this.pos += 2; + const view = this.arrayBuffer.slice(this.pos, this.pos + length); + const result = new StringView(view).toString(); + this.pos += length; + return result; + } - this.readFloat = function() { - var value = this.dataView.getFloat32(this.pos, true); - this.pos += 4; - return value; - }; + align4() { + // Skips to the next alignment of 4 (source should have done the same!) + const skip = 4 - (this.pos % 4); + if (skip > 0 && skip != 4) { + // console.log("Skip", skip); + this.pos += skip; + } + } - this.readInt = function() { - var value = this.dataView.getInt32(this.pos, true); - this.pos += 4; - return value; - }; + align8() { + // Skips to the next alignment of 4 (source should have done the same!) + const skip = 8 - (this.pos % 8); + if (skip > 0 && skip != 8) { + // console.log("Skip", skip); + this.pos += skip; + } + } - this.readByte = function() { - var value = this.dataView.getInt8(this.pos); - this.pos += 1; - return value; - }; + readDoubleArray(length) { + const result = new Float64Array(this.arrayBuffer, this.pos, length); + this.pos += length * 8; + return result; + } - this.readLong = function() { - var value = this.dataView.getUint32(this.pos, true) + 0x100000000 * this.dataView.getUint32(this.pos + 4, true); - this.pos += 8; - return value; - }; + readFloat() { + const value = this.dataView.getFloat32(this.pos, true); + this.pos += 4; + return value; + } - this.readFloatArray2 = function(length) { - var results = []; - for (var i=0; i= -1) { - h.splice(i, 1); - found = true; - } - } - if (!found) { - throw new Error("Handler not found"); - } - }; - - EventHandler.prototype.fire = function(evt, args) { - var h = this.handlers[evt]; - if (!h) { - return; - } - for (var i = 0; i < h.length; ++i) { - h[i].apply(this, args); - } - }; - - return EventHandler; - -}); \ No newline at end of file +export default class EventHandler { + + constructor() { + this.handlers = {}; + } + + on(evt, handler) { + (this.handlers[evt] || (this.handlers[evt] = [])).push(handler); + } + + off(evt, handler) { + const h = this.handlers[evt]; + let found = false; + if (typeof (h) !== 'undefined') { + const i = h.indexOf(handler); + if (i >= -1) { + h.splice(i, 1); + found = true; + } + } + if (!found) { + throw new Error("Handler not found"); + } + } + + fire(evt, args) { + const h = this.handlers[evt]; + if (!h) { + return; + } + for (let i = 0; i < h.length; ++i) { + h[i].apply(this, args); + } + } +} \ No newline at end of file diff --git a/bimsurfer/src/MetaDataRenderer.js b/bimsurfer/src/MetaDataRenderer.js index 86a775e..3e6d158 100644 --- a/bimsurfer/src/MetaDataRenderer.js +++ b/bimsurfer/src/MetaDataRenderer.js @@ -1,225 +1,230 @@ -define(["./EventHandler", "./Request", "./Utils"], function(EventHandler, Request, Utils) { - - function Row(args) { - var self = this; - var num_names = 0; - var num_values = 0; - - this.setName = function(name) { - if (num_names++ > 0) { - args.name.appendChild(document.createTextNode(" ")); - } - args.name.appendChild(document.createTextNode(name)); - } - - this.setValue = function(value) { - if (num_values++ > 0) { - args.value.appendChild(document.createTextNode(", ")); - } - args.value.appendChild(document.createTextNode(value)); - } - } - - function Section(args) { - var self = this; - - var div = self.div = document.createElement("div"); - var nameh = document.createElement("h3"); - var table = document.createElement("table"); - - var tr = document.createElement("tr"); - table.appendChild(tr); - var nameth = document.createElement("th"); - var valueth = document.createElement("th"); - nameth.appendChild(document.createTextNode("Name")); - valueth.appendChild(document.createTextNode("Value")); - tr.appendChild(nameth); - tr.appendChild(valueth); - - div.appendChild(nameh); - div.appendChild(table); - - args.domNode.appendChild(div); - - this.setName = function(name) { - nameh.appendChild(document.createTextNode(name)); - } - - this.addRow = function() { - var tr = document.createElement("tr"); - table.appendChild(tr); - var nametd = document.createElement("td"); - var valuetd = document.createElement("td"); - tr.appendChild(nametd); - tr.appendChild(valuetd); - return new Row({name:nametd, value:valuetd}); - } - }; - - function loadModelFromSource(src) { - return new Promise(function (resolve, reject) { - Request.Make({url: src}).then(function(xml) { - var json = Utils.XmlToJson(xml, {'Name': 'name', 'id': 'guid'}); - - var psets = Utils.FindNodeOfType(json, "properties")[0]; - var project = Utils.FindNodeOfType(json, "decomposition")[0].children[0]; - var types = Utils.FindNodeOfType(json, "types")[0]; - - var objects = {}; - var typeObjects = {}; - var properties = {}; - psets.children.forEach(function(pset) { - properties[pset.guid] = pset; - }); - - var visitObject = function(parent, node) { - var props = []; - var o = (parent && parent.ObjectPlacement) ? objects : typeObjects; - - if (node["xlink:href"]) { - if (!o[parent.guid]) { - var p = Utils.Clone(parent); - p.GlobalId = p.guid; - o[p.guid] = p; - o[p.guid].properties = [] - } - var g = node["xlink:href"].substr(1); - var p = properties[g]; - if (p) { - o[parent.guid].properties.push(p); - } else if (typeObjects[g]) { - // If not a pset, it is a type, so concatenate type props - o[parent.guid].properties = o[parent.guid].properties.concat(typeObjects[g].properties); - } - } - node.children.forEach(function(n) { - visitObject(node, n); - }); - }; - - visitObject(null, types); - visitObject(null, project); - - resolve({model: {objects: objects, source: 'XML'}}); - }); - }); - } - - function MetaDataRenderer(args) { - - var self = this; - EventHandler.call(this); - - var models = {}; - var domNode = document.getElementById(args['domNode']); - - this.addModel = function(args) { - return new Promise(function (resolve, reject) { - if (args.model) { - models[args.id] = args.model; - resolve(args.model); - } else { - loadModelFromSource(args.src).then(function(m) { - models[args.id] = m; - resolve(m); - }); - } - }); - }; - - var renderAttributes = function(elem) { - var s = new Section({domNode:domNode}); - s.setName(elem.type || elem.getType()); - ["GlobalId", "Name", "OverallWidth", "OverallHeight", "Tag"].forEach(function(k) { - var v = elem[k]; - if (typeof(v) === 'undefined') { - var fn = elem["get"+k]; - if (fn) { - v = fn.apply(elem); - } - } - if (typeof(v) !== 'undefined') { - r = s.addRow(); - r.setName(k); - r.setValue(v); - } - }); - return s; - }; - - var renderPSet = function(pset) { - var s = new Section({domNode:domNode}); - if (pset.name && pset.children) { - s.setName(pset.name); - pset.children.forEach(function(v) { - var r = s.addRow(); - r.setName(v.name); - r.setValue(v.NominalValue); - }); - } else { - pset.getName(function(name) { - s.setName(name); - }); - var render = function(prop, index, row) { - var r = row || s.addRow(); - prop.getName(function(name) { - r.setName(name); - }); - if (prop.getNominalValue) { - prop.getNominalValue(function(value) { - r.setValue(value._v); - }); - } - if (prop.getHasProperties) { - prop.getHasProperties(function(prop, index) { - render(prop, index, r); - }); - } - }; - pset.getHasProperties(render); - } - return s; - }; - - this.setSelected = function(oid) { - if (oid.length !== 1) { - domNode.innerHTML = " 
Select a single element in order to see object properties." - return; - }; - - domNode.innerHTML = ""; - - oid = oid[0]; - - if (oid.indexOf(':') !== -1) { - oid = oid.split(':'); - var o = models[oid[0]].model.objects[oid[1]]; - - renderAttributes(o); - - o.getIsDefinedBy(function(isDefinedBy){ - if (isDefinedBy.getType() == "IfcRelDefinesByProperties") { - isDefinedBy.getRelatingPropertyDefinition(function(pset){ - if (pset.getType() == "IfcPropertySet") { - renderPSet(pset); - } - }); - } - }); - } else { - var o = models["1"].model.objects[oid]; - renderAttributes(o); - o.properties.forEach(function(pset) { - renderPSet(pset); - }); - } - }; - - self.setSelected([]); - }; - - MetaDataRenderer.prototype = Object.create(EventHandler.prototype); - - return MetaDataRenderer; - -}); \ No newline at end of file +import EventHandler from './EventHandler'; +import * as Request from './Request'; +import Utils from './Utils'; + +class Row { + constructor(args) { + this.args = args; + this.num_names = 0; + this.num_values = 0; + } + setName(name) { + if (this.num_names++ > 0) { + this.args.name.appendChild(document.createTextNode(" ")); + } + this.args.name.appendChild(document.createTextNode(name)); + } + + setValue(value) { + if (this.num_values++ > 0) { + this.args.value.appendChild(document.createTextNode(", ")); + } + this.args.value.appendChild(document.createTextNode(value)); + } +} + +class Section { + constructor(args) { + this.args = args; + + this.div = document.createElement("div"); + this.nameh = document.createElement("h3"); + this.table = document.createElement("table"); + + this.tr = document.createElement("tr"); + this.table.appendChild(this.tr); + + this.nameth = document.createElement("th"); + this.valueth = document.createElement("th"); + + this.nameth.appendChild(document.createTextNode("Name")); + this.valueth.appendChild(document.createTextNode("Value")); + this.tr.appendChild(this.nameth); + this.tr.appendChild(this.valueth); + + this.div.appendChild(this.nameh); + this.div.appendChild(this.table); + + args.domNode.appendChild(this.div); + + this.setSelected([]); + } + + setName(name) { + this.nameh.appendChild(document.createTextNode(name)); + } + + addRow() { + const tr = document.createElement("tr"); + this.table.appendChild(tr); + const nametd = document.createElement("td"); + const valuetd = document.createElement("td"); + tr.appendChild(nametd); + tr.appendChild(valuetd); + return new Row({ name: nametd, value: valuetd }); + } +} + + +function loadModelFromSource(src) { + return new Promise((resolve, reject) => { + Request.make({ url: src }).then((xml) => { + const json = Utils.XmlToJson(xml, { 'Name': 'name', 'id': 'guid' }); + + const psets = Utils.FindNodeOfType(json, "properties")[0]; + const project = Utils.FindNodeOfType(json, "decomposition")[0].children[0]; + const types = Utils.FindNodeOfType(json, "types")[0]; + + const objects = {}; + const typeObjects = {}; + const properties = {}; + psets.children.forEach((pset) => { + properties[pset.guid] = pset; + }); + + const visitObject = (parent, node) => { + const props = []; + const o = (parent && parent.ObjectPlacement) ? objects : typeObjects; + + if (node["xlink:href"]) { + if (!o[parent.guid]) { + const p = Utils.Clone(parent); + p.GlobalId = p.guid; + o[p.guid] = p; + o[p.guid].properties = []; + } + const g = node["xlink:href"].substr(1); + const p = properties[g]; + if (p) { + o[parent.guid].properties.push(p); + } else if (typeObjects[g]) { + // If not a pset, it is a type, so concatenate type props + o[parent.guid].properties = o[parent.guid].properties.concat(typeObjects[g].properties); + } + } + node.children.forEach((n) => { + visitObject(node, n); + }); + }; + + visitObject(null, types); + visitObject(null, project); + + resolve({ model: { objects: objects, source: 'XML' } }); + }); + }); +} + +export default class MetaDataRenderer extends EventHandler { + constructor(args) { + super(); + this.args = args; + + this.models = {}; + this.domNode = document.getElementById(this.args.domNode); + } + + addModel(args) { + return new Promise((resolve, reject) => { + if (args.model) { + this.models[args.id] = args.model; + resolve(args.model); + } else { + loadModelFromSource(args.src).then((m) => { + this.models[args.id] = m; + resolve(m); + }); + } + }); + } + + renderAttributes(elem) { + const s = new Section({ domNode: this.domNode }); + s.setName(elem.type || elem.getType()); + + ["GlobalId", "Name", "OverallWidth", "OverallHeight", "Tag"].forEach((k) => { + let v = elem[k]; + if (typeof (v) === 'undefined') { + const fn = elem["get" + k]; + if (fn) { + v = fn.apply(elem); + } + } + if (typeof (v) !== 'undefined') { + const r = s.addRow(); + r.setName(k); + r.setValue(v); + } + }); + return s; + } + + renderPSet(pset) { + const s = new Section({ domNode: this.domNode }); + if (pset.name && pset.children) { + s.setName(pset.name); + pset.children.forEach((v) => { + const r = s.addRow(); + r.setName(v.name); + r.setValue(v.NominalValue); + }); + } else { + pset.getName((name) => { + s.setName(name); + }); + const render = (prop, index, row) => { + const r = row || s.addRow(); + prop.getName((name) => { + r.setName(name); + }); + if (prop.getNominalValue) { + prop.getNominalValue((value) => { + r.setValue(value._v); + }); + } + if (prop.getHasProperties) { + prop.getHasProperties((prop, index) => { + render(prop, index, r); + }); + } + }; + pset.getHasProperties(render); + } + return s; + } + + setSelected(oid) { + if (oid.length !== 1) { + this.domNode.innerHTML = " 
Select a single element in order to see object properties."; + return; + } + + this.domNode.innerHTML = ""; + + oid = oid[0]; + + if (oid.indexOf(':') !== -1) { + oid = oid.split(':'); + const o = this.models[oid[0]].model.objects[oid[1]]; + + this.renderAttributes(o); + + o.getIsDefinedBy((isDefinedBy) => { + if (isDefinedBy.getType() == "IfcRelDefinesByProperties") { + isDefinedBy.getRelatingPropertyDefinition((pset) => { + if (pset.getType() == "IfcPropertySet") { + this.renderPSet(pset); + } + }); + } + }); + } else { + const o = this.models["1"].model.objects[oid]; + this.renderAttributes(o); + o.properties.forEach((pset) => { + this.renderPSet(pset); + }); + } + } + +} diff --git a/bimsurfer/src/Notifier.js b/bimsurfer/src/Notifier.js index beb8f97..938e79d 100644 --- a/bimsurfer/src/Notifier.js +++ b/bimsurfer/src/Notifier.js @@ -1,35 +1,29 @@ -define(function() { - - function Notifier() { - this.setSelector = function(selector) { - console.log('setSelector', arguments); - }; +export default class Notifier { + setSelector(selector) { + console.log('setSelector', arguments); + } - this.clear = function() { - console.log('clear', arguments); - }; + clear() { + console.log('clear', arguments); + } - this.resetStatus = function(){ - console.log('status', arguments); - }; + resetStatus() { + console.log('status', arguments); + } - this.resetStatusQuick = function(){ - console.log('status', arguments); - }; + resetStatusQuick() { + console.log('status', arguments); + } - this.setSuccess = function(status, timeToShow) { - console.log('success', arguments); - }; - - this.setInfo = function(status, timeToShow) { - console.log('info', arguments); - }; + setSuccess(status, timeToShow) { + console.log('success', arguments); + } - this.setError = function(error) { - console.log('error', arguments); - }; - }; - - return Notifier; - -}); \ No newline at end of file + setInfo(status, timeToShow) { + console.log('info', arguments); + } + + setError(error) { + console.log('error', arguments); + } +} diff --git a/bimsurfer/src/PreloadQuery.js b/bimsurfer/src/PreloadQuery.js index 858129e..9631559 100644 --- a/bimsurfer/src/PreloadQuery.js +++ b/bimsurfer/src/PreloadQuery.js @@ -1,4 +1,4 @@ -define({ +const PreloadQuery = { defines: { Representation: { type: "IfcProduct", @@ -32,51 +32,53 @@ define({ } }, queries: [ - { + { type: "IfcProject", includes: [ "IsDecomposedByDefine", "ContainsElementsDefine" ] - }, - { - type: "IfcRepresentation", - includeAllSubtypes: true - }, - { - type: "IfcProductRepresentation" - }, - { - type: "IfcPresentationLayerWithStyle" - }, - { - type: "IfcProduct", - includeAllSubtypes: true - }, - { - type: "IfcProductDefinitionShape" - }, - { - type: "IfcPresentationLayerAssignment" - }, - { - type: "IfcRelAssociatesClassification", - includes: [ - { - type: "IfcRelAssociatesClassification", - field: "RelatedObjects" - }, - { - type: "IfcRelAssociatesClassification", - field: "RelatingClassification" - } - ] - }, - { - type: "IfcSIUnit" - }, - { - type: "IfcPresentationLayerAssignment" - } + }, + { + type: "IfcRepresentation", + includeAllSubtypes: true + }, + { + type: "IfcProductRepresentation" + }, + { + type: "IfcPresentationLayerWithStyle" + }, + { + type: "IfcProduct", + includeAllSubtypes: true + }, + { + type: "IfcProductDefinitionShape" + }, + { + type: "IfcPresentationLayerAssignment" + }, + { + type: "IfcRelAssociatesClassification", + includes: [ + { + type: "IfcRelAssociatesClassification", + field: "RelatedObjects" + }, + { + type: "IfcRelAssociatesClassification", + field: "RelatingClassification" + } + ] + }, + { + type: "IfcSIUnit" + }, + { + type: "IfcPresentationLayerAssignment" + } ] -}); +}; + +export default PreloadQuery; \ No newline at end of file diff --git a/bimsurfer/src/Request.js b/bimsurfer/src/Request.js index e361234..28e43a6 100644 --- a/bimsurfer/src/Request.js +++ b/bimsurfer/src/Request.js @@ -1,25 +1,17 @@ -define(function() { - - function make(args) { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.open(args.method || "GET", args.url, true); - xhr.onload = function (e) { - console.log(args.url, xhr.readyState, xhr.status) - if (xhr.readyState === 4) { - if (xhr.status === 200) { - resolve(xhr.responseXML); - } else { - reject(xhr.statusText); - } - } - }; - xhr.send(null); - }); - } - - return { - 'Make': make - }; - -}); \ No newline at end of file +export function make(args) { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + xhr.open(args.method || "GET", args.url, true); + xhr.onload = (e) => { + console.log(args.url, xhr.readyState, xhr.status); + if (xhr.readyState === 4) { + if (xhr.status === 200) { + resolve(xhr.responseXML); + } else { + reject(xhr.statusText); + } + } + }; + xhr.send(null); + }); +} diff --git a/bimsurfer/src/StaticTreeRenderer.js b/bimsurfer/src/StaticTreeRenderer.js index e82ab00..ab21dca 100644 --- a/bimsurfer/src/StaticTreeRenderer.js +++ b/bimsurfer/src/StaticTreeRenderer.js @@ -1,158 +1,158 @@ -define(["./EventHandler", "./Request", "./Utils"], function(EventHandler, Request, Utils) { - - function StaticTreeRenderer(args) { - - var self = this; - EventHandler.call(this); - - var TOGGLE = self.TOGGLE = 0; - var SELECT = self.SELECT = 1; - var SELECT_EXCLUSIVE = self.SELECT_EXCLUSIVE = 2; - var DESELECT = self.DESELECT = 3; - - var fromXml = false; - - var domNodes = {}; - var selectionState = {}; - - this.getOffset = function(elem) { - var reference = document.getElementById(args['domNode']); - var y = 0; - while (true) { - y += elem.offsetTop; - if (elem == reference) { - break; - } - elem = elem.offsetParent; - } - return y; - }; - - this.setSelected = function(ids, mode) { - if (mode == SELECT_EXCLUSIVE) { - self.setSelected(self.getSelected(true), DESELECT); - } - - ids.forEach(function(id) { - var s = null; - if (mode == TOGGLE) { - s = selectionState[id] = !selectionState[id]; - } else if (mode == SELECT || mode == SELECT_EXCLUSIVE) { - s = selectionState[id] = true; - } else if (mode == DESELECT) { - s = selectionState[id] = false; - } - - domNodes[id].className = s ? "label selected" : "label"; - }); - - var desiredViewRange = self.getSelected().map(function(id) { - return self.getOffset(domNodes[id]); - }); - - if (desiredViewRange.length) { - desiredViewRange.sort() - desiredViewRange = [desiredViewRange[0], desiredViewRange[desiredViewRange.length-1]]; - - var domNode = document.getElementById(args['domNode']); - var currentViewRange = [domNode.scrollTop, domNode.scrollTop + domNode.offsetHeight]; - - if (!(desiredViewRange[0] >= currentViewRange[0] && desiredViewRange[1] <= currentViewRange[1])) { - if ( (desiredViewRange[1] - desiredViewRange[0]) > (currentViewRange[1] - currentViewRange[0]) ) { - domNode.scrollTop = desiredViewRange[0]; - } else { - var l = parseInt((desiredViewRange[1] + desiredViewRange[0]) / 2. - (currentViewRange[1] - currentViewRange[0]) / 2., 10); - l = Math.max(l, 0); - l = Math.min(l, domNode.scrollHeight - domNode.offsetHeight); - domNode.scrollTop = l; - } - } - } - - this.fire("selection-changed", [self.getSelected(true)]) - }; - - this.getSelected = function(b) { - b = typeof(b) === 'undefined' ? true: !!b; - var l = []; - Object.keys(selectionState).forEach(function (k) { - if (!!selectionState[k] === b) { - l.push(k); - } - }); - return l; - }; - - var models = []; - - this.addModel = function(args) { - models.push(args); - if (args.src) { - fromXml = true; - } - }; - - this.qualifyInstance = function(modelId, id) { - if (fromXml) { - return id; - } else { - return modelId + ":" + id; - } - }; - - this.build = function() { - var build = function(modelId, d, n) { - var qid = self.qualifyInstance(modelId, fromXml ? n.guid : n.id); - var label = document.createElement("div"); - var children = document.createElement("div"); - - label.className = "label"; - label.appendChild(document.createTextNode(n.name || n.guid)); - d.appendChild(label); - children.className = "children"; - d.appendChild(children); - domNodes[qid] = label; - - label.onclick = function(evt) { - evt.stopPropagation(); - evt.preventDefault(); - self.setSelected([qid], evt.shiftKey ? TOGGLE : SELECT_EXCLUSIVE); - self.fire("click", [qid, self.getSelected(true)]); - return false; - }; - - for (var i = 0; i < (n.children || []).length; ++i) { - var child = n.children[i]; - if (fromXml) { - if (child["xlink:href"]) continue; - if (child.type === "IfcOpeningElement") continue; - } - var d2 = document.createElement("div"); - d2.className = "item"; - children.appendChild(d2); - build(modelId, d2, child); - } - } - models.forEach(function(m) { - var d = document.createElement("div"); - d.className = "item"; - if (m.tree) { - build(m.id, d, m.tree); - } else if (m.src) { - Request.Make({url: m.src}).then(function(xml) { - var json = Utils.XmlToJson(xml, {'Name': 'name', 'id': 'guid'}); - var project = Utils.FindNodeOfType(json.children[0], "decomposition")[0].children[0]; - build(m.id || i, d, project); - }); - } - document.getElementById(args['domNode']).appendChild(d); - }); - } - - }; - - StaticTreeRenderer.prototype = Object.create(EventHandler.prototype); - - return StaticTreeRenderer; - -}); \ No newline at end of file +import EventHandler from './EventHandler'; +import * as Request from './Request'; +import Utils from './Utils'; + +export default class StaticTreeRenderer extends EventHandler { + constructor(args) { + super(); + + this.args = args; + this.TOGGLE = 0; + this.SELECT = 1; + this.SELECT_EXCLUSIVE = 2; + this.DESELECT = 3; + + this.fromXml = false; + + this.domNodes = {}; + this.selectionState = {}; + this.models = []; + } + + getOffset(elem) { + const reference = document.getElementById(this.args.domNode); + let y = 0; + + while (true) { + y += elem.offsetTop; + if (elem == reference) { + break; + } + elem = elem.offsetParent; + } + return y; + } + + setSelected(ids, mode) { + if (mode == this.SELECT_EXCLUSIVE) { + this.setSelected(this.getSelected(true), this.DESELECT); + } + + ids.forEach((id) => { + let s = null; + + if (mode == this.TOGGLE) { + s = this.selectionState[id] = !this.selectionState[id]; + } else if (mode == this.SELECT || mode == this.SELECT_EXCLUSIVE) { + s = this.selectionState[id] = true; + } else if (mode == this.DESELECT) { + s = this.selectionState[id] = false; + } + + this.domNodes[id].className = s ? "label selected" : "label"; + }); + + let desiredViewRange = this.getSelected().map((id) => { + return this.getOffset(this.domNodes[id]); + }); + + if (desiredViewRange.length) { + desiredViewRange.sort(); + desiredViewRange = [desiredViewRange[0], desiredViewRange[desiredViewRange.length - 1]]; + + const domNode = document.getElementById(this.args.domNode); + let currentViewRange = [domNode.scrollTop, domNode.scrollTop + domNode.offsetHeight]; + + if (!(desiredViewRange[0] >= currentViewRange[0] && desiredViewRange[1] <= currentViewRange[1])) { + if ((desiredViewRange[1] - desiredViewRange[0]) > (currentViewRange[1] - currentViewRange[0])) { + domNode.scrollTop = desiredViewRange[0]; + } else { + let l = parseInt((desiredViewRange[1] + desiredViewRange[0]) / 2.0 - (currentViewRange[1] - currentViewRange[0]) / 2.0, 10); + + l = Math.max(l, 0); + l = Math.min(l, domNode.scrollHeight - domNode.offsetHeight); + domNode.scrollTop = l; + } + } + } + + this.fire("selection-changed", [this.getSelected(true)]); + } + + getSelected(b) { + b = typeof (b) === 'undefined' ? true : !!b; + const l = []; + Object.keys(this.selectionState).forEach((k) => { + if (!!this.selectionState[k] === b) { + l.push(k); + } + }); + return l; + } + + addModel(args) { + this.models.push(args); + if (args.src) { + this.fromXml = true; + } + } + + qualifyInstance(modelId, id) { + if (this.fromXml) { + return id; + } else { + return modelId + ":" + id; + } + } + + build() { + const build = (modelId, d, n) => { + const qid = this.qualifyInstance(modelId, this.fromXml ? n.guid : n.id); + const label = document.createElement("div"); + const children = document.createElement("div"); + + label.className = "label"; + label.appendChild(document.createTextNode(n.name || n.guid)); + d.appendChild(label); + children.className = "children"; + d.appendChild(children); + this.domNodes[qid] = label; + + label.onclick = (evt) => { + evt.stopPropagation(); + evt.preventDefault(); + this.setSelected([qid], evt.shiftKey ? this.TOGGLE : this.SELECT_EXCLUSIVE); + this.fire("click", [qid, this.getSelected(true)]); + return false; + }; + + for (let i = 0; i < (n.children || []).length; ++i) { + const child = n.children[i]; + if (this.fromXml) { + if (child["xlink:href"]) { continue; } + if (child.type === "IfcOpeningElement") { continue; } + } + const d2 = document.createElement("div"); + d2.className = "item"; + children.appendChild(d2); + build(modelId, d2, child); + } + }; + + this.models.forEach((m) => { + const d = document.createElement("div"); + d.className = "item"; + if (m.tree) { + build(m.id, d, m.tree); + } else if (m.src) { + Request.make({ url: m.src }).then((xml) => { + const json = Utils.XmlToJson(xml, { 'Name': 'name', 'id': 'guid' }); + const project = Utils.FindNodeOfType(json.children[0], "decomposition")[0].children[0]; + build(m.id || i, d, project); + }); + } + document.getElementById(this.args.domNode).appendChild(d); + }); + } + +} diff --git a/bimsurfer/src/Utils.js b/bimsurfer/src/Utils.js index 9cb2f6f..65633bd 100644 --- a/bimsurfer/src/Utils.js +++ b/bimsurfer/src/Utils.js @@ -1,86 +1,99 @@ -define(function() { - - var xmlToJson = function(node, attributeRenamer) { - if (node.nodeType === node.TEXT_NODE) { - var v = node.nodeValue; - if (v.match(/^\s+$/) === null) { - return v; - } - } else if (node.nodeType === node.ELEMENT_NODE || - node.nodeType === node.DOCUMENT_NODE) - { - var json = {type: node.nodeName, children: []}; - - if (node.nodeType === node.ELEMENT_NODE) { - for (var j = 0; j < node.attributes.length; j++) { - var attribute = node.attributes[j]; - var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName; - json[nm] = attribute.nodeValue; - } - } - - for (var i = 0; i < node.childNodes.length; i++) { - var item = node.childNodes[i]; - var j = xmlToJson(item, attributeRenamer); - if (j) json.children.push(j); - } - - return json; - } - }; - - var clone = function(ob) { - return JSON.parse(JSON.stringify(ob)); - }; - - var guidChars = [["0",10],["A",26],["a",26],["_",1],["$",1]].map(function(a) { - var li = []; - var st = a[0].charCodeAt(0); - var en = st + a[1]; - for (var i = st; i < en; ++i) { - li.push(i); - } - return String.fromCharCode.apply(null, li); - }).join(""); - - var b64 = function(v, len) { - var r = (!len || len == 4) ? [0,6,12,18] : [0,6]; - return r.map(function(i) { - return guidChars.substr(parseInt(v / (1 << i)) % 64, 1) - }).reverse().join(""); - }; - - var compressGuid = function(g) { - var bs = [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30].map(function(i) { - return parseInt(g.substr(i, 2), 16); - }); - return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function(i) { - return b64((bs[i] << 16) + (bs[i+1] << 8) + bs[i+2]); - }).join(""); - }; - - var findNodeOfType = function(m, t) { - var li = []; - var _ = function(n) { - if (n.type === t) li.push(n); - (n.children || []).forEach(function(c) {_(c);}); - } - _(m); - return li; - }; - - var timeout = function(dt) { - return new Promise(function (resolve, reject) { - setTimeout(resolve, dt); - }); - }; - - return { - 'XmlToJson': xmlToJson, - 'Clone': clone, - 'CompressGuid': compressGuid, - 'FindNodeOfType': findNodeOfType, - 'Delay': timeout - }; - -}); \ No newline at end of file +const xmlToJson = function (node, attributeRenamer) { + if (node.nodeType === node.TEXT_NODE) { + const v = node.nodeValue; + if (v.match(/^\s+$/) === null) { + return v; + } + } else if (node.nodeType === node.ELEMENT_NODE || + node.nodeType === node.DOCUMENT_NODE) { + const json = { + type: node.nodeName, + children: [] + }; + + if (node.nodeType === node.ELEMENT_NODE) { + for (let j = 0; j < node.attributes.length; j++) { + const attribute = node.attributes[j]; + const nm = attributeRenamer[attribute.nodeName] || attribute.nodeName; + json[nm] = attribute.nodeValue; + } + } + + for (let i = 0; i < node.childNodes.length; i++) { + const item = node.childNodes[i]; + const j = xmlToJson(item, attributeRenamer); + if (j) { + json.children.push(j); + } + } + + return json; + } +}; + +const clone = function (ob) { + return JSON.parse(JSON.stringify(ob)); +}; + +const guidChars = [ + ["0", 10], + ["A", 26], + ["a", 26], + ["_", 1], + ["$", 1] +].map(function (a) { + let li = []; + const st = a[0].charCodeAt(0); + const en = st + a[1]; + for (let i = st; i < en; ++i) { + li.push(i); + } + return String.fromCharCode.apply(null, li); +}).join(""); + +const b64 = function (v, len) { + const r = (!len || len == 4) ? [0, 6, 12, 18] : [0, 6]; + return r.map(function (i) { + return guidChars.substr(parseInt(v / (1 << i)) % 64, 1); + }).reverse().join(""); +}; + +const compressGuid = function (g) { + const bs = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30].map(function (i) { + return parseInt(g.substr(i, 2), 16); + }); + return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function (i) { + return b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]); + }).join(""); +}; + +const findNodeOfType = function (m, t) { + let li = []; + const _ = function (n) { + if (n.type === t) { + li.push(n); + } + (n.children || []).forEach(function (c) { + _(c); + }); + }; + + _(m); + return li; +}; + +const timeout = function (dt) { + return new Promise(function (resolve, reject) { + setTimeout(resolve, dt); + }); +}; + +const Utils = { + 'XmlToJson': xmlToJson, + 'Clone': clone, + 'CompressGuid': compressGuid, + 'FindNodeOfType': findNodeOfType, + 'Delay': timeout +}; + +export default Utils; \ No newline at end of file diff --git a/bimsurfer/src/index.js b/bimsurfer/src/index.js new file mode 100644 index 0000000..72bc3a2 --- /dev/null +++ b/bimsurfer/src/index.js @@ -0,0 +1,4 @@ +export { default as BimSurfer } from "./BimSurfer.js"; +export { default as BimServerModelLoader } from "./BimServerModelLoader.js"; +export { default as StaticTreeRenderer } from "./StaticTreeRenderer.js"; +export { default as MetaDataRenderer } from "./MetaDataRenderer.js"; diff --git a/bimsurfer/src/xeoViewer/controls/bimCameraControl.js b/bimsurfer/src/xeoViewer/controls/bimCameraControl.js index 0d9410f..53ae7f9 100644 --- a/bimsurfer/src/xeoViewer/controls/bimCameraControl.js +++ b/bimsurfer/src/xeoViewer/controls/bimCameraControl.js @@ -1,6 +1,4 @@ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; /** @@ -974,7 +972,6 @@ define(["../../../lib/xeogl"], function () { this._defaultDragAction = value; } } - } + } } }); -}); diff --git a/bimsurfer/src/xeoViewer/effects/highlightEffect.js b/bimsurfer/src/xeoViewer/effects/highlightEffect.js index bd81d39..3467ea0 100644 --- a/bimsurfer/src/xeoViewer/effects/highlightEffect.js +++ b/bimsurfer/src/xeoViewer/effects/highlightEffect.js @@ -1,6 +1,4 @@ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; xeogl.HighlightEffect = xeogl.Component.extend({ @@ -110,4 +108,4 @@ define(["../../../lib/xeogl"], function () { } }); -}); + diff --git a/bimsurfer/src/xeoViewer/entities/bimModel.js b/bimsurfer/src/xeoViewer/entities/bimModel.js index e2305bb..3377ec0 100644 --- a/bimsurfer/src/xeoViewer/entities/bimModel.js +++ b/bimsurfer/src/xeoViewer/entities/bimModel.js @@ -1,6 +1,4 @@ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; /** Custom xeoEngine component that represents a BIMSurfer model within a xeoEngine scene. @@ -31,4 +29,3 @@ define(["../../../lib/xeogl"], function () { this.collection.add(object); } }); -}); diff --git a/bimsurfer/src/xeoViewer/entities/bimObject.js b/bimsurfer/src/xeoViewer/entities/bimObject.js index 13ac7fe..0a71f63 100644 --- a/bimsurfer/src/xeoViewer/entities/bimObject.js +++ b/bimsurfer/src/xeoViewer/entities/bimObject.js @@ -1,6 +1,4 @@ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; /** Custom xeoEngine component that represents a BIMSurfer object within a xeoEngine scene. @@ -160,4 +158,3 @@ define(["../../../lib/xeogl"], function () { } } }); -}); diff --git a/bimsurfer/src/xeoViewer/helpers/bimBoundaryHelper.js b/bimsurfer/src/xeoViewer/helpers/bimBoundaryHelper.js index 5ba4e9b..8535843 100644 --- a/bimsurfer/src/xeoViewer/helpers/bimBoundaryHelper.js +++ b/bimsurfer/src/xeoViewer/helpers/bimBoundaryHelper.js @@ -1,6 +1,4 @@ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; /** Custom xeoEngine component that shows a wireframe box representing an non axis-aligned 3D boundary. @@ -96,5 +94,3 @@ define(["../../../lib/xeogl"], function () { }; }; - -}); diff --git a/bimsurfer/src/xeoViewer/utils/collection.js b/bimsurfer/src/xeoViewer/utils/collection.js index b2f9555..4177e6d 100644 --- a/bimsurfer/src/xeoViewer/utils/collection.js +++ b/bimsurfer/src/xeoViewer/utils/collection.js @@ -141,9 +141,7 @@ @param [cfg.components] {{Array of String|Component}} Array of {{#crossLink "Component"}}{{/crossLink}} IDs or instances. @extends Component */ -define(["../../../lib/xeogl"], function () { - - "use strict"; +import 'xeogl'; xeogl.Collection = xeogl.Component.extend({ @@ -476,5 +474,3 @@ define(["../../../lib/xeogl"], function () { this.clear(); } }); - -}); diff --git a/bimsurfer/src/xeoViewer/xeoViewer.js b/bimsurfer/src/xeoViewer/xeoViewer.js index 65084a5..58337b2 100644 --- a/bimsurfer/src/xeoViewer/xeoViewer.js +++ b/bimsurfer/src/xeoViewer/xeoViewer.js @@ -1,42 +1,36 @@ -define([ - "../DefaultMaterials", - "../EventHandler", - "../Utils", - "../../lib/xeogl", - "./controls/bimCameraControl", - "./entities/bimModel", - "./entities/bimObject", - "./helpers/bimBoundaryHelper", - "./effects/highlightEffect", - "./utils/collection" -], function (DefaultMaterials, EventHandler, Utils) { - - "use strict"; - - function xeoViewer(cfg) { - - // Distance to WebGL's far clipping plane. - const FAR_CLIP = 5000; - - // Create xeoViewer - - EventHandler.call(this); - - var self = this; - - var domNode = document.getElementById(cfg.domNode); - var canvas = document.createElement("canvas"); - - domNode.appendChild(canvas); - - // Create a Scene - var scene = self.scene = new xeogl.Scene({ // http://xeoengine.org/docs/classes/Scene.html - canvas: canvas, +import 'xeogl'; + +import DefaultMaterials from '../DefaultMaterials'; +import EventHandler from '../EventHandler'; +import Utils from '../Utils'; +import './controls/bimCameraControl'; +import './entities/bimModel'; +import './entities/bimObject'; +import './helpers/bimBoundaryHelper'; +import './effects/highlightEffect'; +import './utils/collection'; + +export default class xeoViewer extends EventHandler { + constructor(cfg) { + // Create xeoViewer + super(); + + // Distance to WebGL's far clipping plane. + const FAR_CLIP = 5000; + + const domNode = document.getElementById(cfg.domNode); + const canvas = document.createElement("canvas"); + + domNode.appendChild(canvas); + + // Create a Scene + this.scene = new xeogl.Scene({ // http://xeoengine.org/docs/classes/Scene.html + canvas: canvas, transparent: true - }); + }); - // Redefine default light sources; - var lights = [ + // Redefine default light sources; + this.lights = [ { type: "ambient", params: { @@ -54,1431 +48,1419 @@ define([ } } ]; - scene.lights.lights = buildLights(lights); + this.scene.lights.lights = this.buildLights(this.lights); + + // Attached to all objects to fit the model inside the view volume + this.scale = new xeogl.Scale(this.scene, { + xyz: [1, 1, 1] + }); + + // Provides user input + this.input = this.scene.input; + + // Using the scene's default camera + this.camera = this.scene.camera; + this.camera.project.far = FAR_CLIP; + + // Flies cameras to objects + this.cameraFlight = new xeogl.CameraFlightAnimation(this.scene, { // http://xeoengine.org/docs/classes/CameraFlightAnimation.html + fitFOV: 25, + duration: 1 + }); + + // Registers loaded xeoEngine components for easy destruction + this.collection = new xeogl.Collection(this.scene); // http://xeoengine.org/docs/classes/Collection.html + + // Shows a wireframe box at the given boundary + this.boundaryHelper = new xeogl.BIMBoundaryHelper(this.scene, this, { color: cfg.selectionBorderColor }); + + this.highlightEffect = new xeogl.HighlightEffect(this.scene, { color: cfg.selectionColor }); + + // Models mapped to their IDs + this.models = {}; + + // Objects mapped to IDs + this.objects = {}; + + this.objects_by_guid = {}; - // Attached to all objects to fit the model inside the view volume - var scale = new xeogl.Scale(scene, { - xyz: [1, 1, 1] - }); + // For each RFC type, a map of objects mapped to their IDs + this.rfcTypes = {}; - // Provides user input - var input = scene.input; + // Objects that are currently visible, mapped to IDs + this.visibleObjects = {}; - // Using the scene's default camera - var camera = scene.camera; - camera.project.far = FAR_CLIP; + // Lazy-generated array of visible object IDs, for return by #getVisibility() + this.visibleObjectList = null; - // Flies cameras to objects - var cameraFlight = new xeogl.CameraFlightAnimation(scene, { // http://xeoengine.org/docs/classes/CameraFlightAnimation.html - fitFOV: 25, - duration: 1 - }); + // Array of objects RFC types hidden by default + this.hiddenTypes = ["IfcOpeningElement", "IfcSpace"]; - // Registers loaded xeoEngine components for easy destruction - var collection = new xeogl.Collection(scene); // http://xeoengine.org/docs/classes/Collection.html + // Objects that are currently selected, mapped to IDs + this.selectedObjects = {}; - // Shows a wireframe box at the given boundary - var boundaryHelper = new xeogl.BIMBoundaryHelper(scene, self, {color: cfg.selectionBorderColor}); + // Lazy-generated array of selected object IDs, for return by #getSelection() + this.selectedObjectList = null; - var highlightEffect = new xeogl.HighlightEffect(scene, {color: cfg.selectionColor}); + // Bookmark of initial state to reset to - captured with #saveReset(), applied with #reset(). + this.resetBookmark = null; - // Models mapped to their IDs - var models = {}; + // Component for each projection type, + // to swap on the camera when we switch projection types + this.projections = { - // Objects mapped to IDs - var objects = {}; - - var objects_by_guid = {}; + persp: this.camera.project, // Camera has a xeogl.Perspective by default - // For each RFC type, a map of objects mapped to their IDs - var rfcTypes = {}; + ortho: new xeogl.Ortho(this.scene, { + scale: 1.0, + near: 0.1, + far: FAR_CLIP + }) + }; + + // The current projection type + this.projectionType = "persp"; + + //----------------------------------------------------------------------------------------------------------- + // Camera notifications + //----------------------------------------------------------------------------------------------------------- + + + // Fold xeoEngine's separate events for view and projection updates + // into a single "camera-changed" event, deferred to fire on next scene tick. - // Objects that are currently visible, mapped to IDs - var visibleObjects = {}; + let cameraUpdated = false; - // Lazy-generated array of visible object IDs, for return by #getVisibility() - var visibleObjectList = null; + this.camera.on("projectMatrix", () => { + cameraUpdated = true; + }); - // Array of objects RFC types hidden by default - var hiddenTypes = ["IfcOpeningElement", "IfcSpace"]; + this.camera.on("viewMatrix", () => { + cameraUpdated = true; + }); - // Objects that are currently selected, mapped to IDs - var selectedObjects = {}; + this.scene.on("tick", () => { - // Lazy-generated array of selected object IDs, for return by #getSelection() - var selectedObjectList = null; + /** + * Fired on the iteration of each "game loop" for this xeoViewer. + * @event tick + * @param {String} sceneID The ID of this Scene. + * @param {Number} startTime The time in seconds since 1970 that this xeoViewer was instantiated. + * @param {Number} time The time in seconds since 1970 of this "tick" event. + * @param {Number} prevTime The time of the previous "tick" event from this xeoViewer. + * @param {Number} deltaTime The time in seconds since the previous "tick" event from this xeoViewer. + */ + this.fire("tick"); - // Bookmark of initial state to reset to - captured with #saveReset(), applied with #reset(). - var resetBookmark = null; + if (cameraUpdated) { - // Component for each projection type, - // to swap on the camera when we switch projection types - var projections = { + /** + * Fired whenever this xeoViewer's camera changes. + * @event camera-changed + * @params New camera state, same as that got with #getCamera. + */ + this.fire("camera-changed", [this.getCamera()]); + cameraUpdated = false; + } + }); + + //----------------------------------------------------------------------------------------------------------- + // Camera control + //----------------------------------------------------------------------------------------------------------- + + this.cameraControl = new xeogl.BIMCameraControl(this.scene, { + camera: this.camera + }); - persp: camera.project, // Camera has a xeogl.Perspective by default + this.cameraControl.on("pick", (hit) => { + // Get BIM object ID from entity metadata + const entity = hit.entity; + + if (!entity.meta) { + return; + } - ortho: new xeogl.Ortho(scene, { - scale: 1.0, - near: 0.1, - far: FAR_CLIP - }) - }; + const objectId = entity.meta.objectId || entity.id; - // The current projection type - var projectionType = "persp"; + if (objectId === undefined) { + return; + } + + const selected = !!this.selectedObjects[objectId]; // Object currently selected? + const shiftDown = this.scene.input.keyDown[this.input.KEY_SHIFT]; // Shift key down? + + this.setSelection({ + ids: [objectId], + selected: !selected, // Picking an object toggles its selection status + clear: !shiftDown // Clear selection first if shift not down + }); + }); + + this.cameraControl.on("nopick", (hit) => { + this.setSelection({ + clear: true + }); + }); + } + + /** + * Sets the default behaviour of mouse and touch drag input + * + * @method setDefaultDragAction + * @param {String} action ("pan" | "orbit") + */ + setDefaultDragAction(action) { + this.cameraControl.defaultDragAction = action; + } + + /** + * Sets the global scale for models loaded into the viewer. + * + * @method setScale + * @param {Number} s Scale factor. + */ + setScale(s) { + this.scale.xyz = [s, s, s]; + } + + /** + * Notifies the viewer that a task (such as loading a model) has started. Use #taskFinished + * to signal that the task has finished. + * + * Whenever the number of tasks is greater than zero, the viewer will display a spinner, + * and reduce rendering speed so as to allow scene updates to happen faster. + */ + taskStarted() { + this.scene.canvas.spinner.processes++; + this.scene.ticksPerRender = 15; // Tweak this if necessary + } + + /** + * Signals that a task has finished (see #taskStarted). + */ + taskFinished() { + const spinner = this.scene.canvas.spinner; + if (spinner.processes === 0) { + return; + } + spinner.processes--; + if (spinner.processes === 0) { + this.scene.ticksPerRender = 1; // Back to max speed, one render per tick + } + } + + /** + * Loads random objects into the viewer for testing. + * + * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. + * + * @method loadRandom + * @param {*} params Parameters + * @param {Number} [params.numEntities=200] Number of entities to create. + * @param {Number} [params.size=200] Size of model on every axis. + * @param {Float32Array} [params.center] Center point of model. + */ + loadRandom(params) { + + params = params || {}; + + this.clear(); + + const geometry = new xeogl.BoxGeometry(this.scene, { + id: "geometry.test" + }); + + this.collection.add(geometry); + + const modelId = "test"; + const roid = "test"; + let oid; + let type; + let objectId; + let translate; + let scale; + let matrix; + let types = Object.keys(DefaultMaterials); + + const numEntities = params.numEntities || 200; + const size = params.size || 200; + const halfSize = size / 2; + const centerX = params.center ? params.center[0] : 0; + const centerY = params.center ? params.center[1] : 0; + const centerZ = params.center ? params.center[2] : 0; + + this.createModel(modelId); + + for (let i = 0; i < numEntities; i++) { + objectId = "object" + i; + oid = objectId; + translate = xeogl.math.translationMat4c( + (Math.random() * size - halfSize) + centerX, + (Math.random() * size - halfSize) + centerY, + (Math.random() * size - halfSize) + centerZ); + scale = xeogl.math.scalingMat4c(Math.random() * 32 + 0.2, Math.random() * 32 + 0.2, Math.random() * 10 + 0.2); + matrix = xeogl.math.mulMat4(translate, scale, xeogl.math.mat4()); + type = types[Math.round(Math.random() * types.length)]; + this.createObject(modelId, roid, oid, objectId, ["test"], type, matrix); + } - //----------------------------------------------------------------------------------------------------------- - // Camera notifications - //----------------------------------------------------------------------------------------------------------- - - (function () { - - // Fold xeoEngine's separate events for view and projection updates - // into a single "camera-changed" event, deferred to fire on next scene tick. - - var cameraUpdated = false; - - camera.on("projectMatrix", - function () { - cameraUpdated = true; - }); - - camera.on("viewMatrix", - function () { - cameraUpdated = true; - }); - - scene.on("tick", - function () { - - /** - * Fired on the iteration of each "game loop" for this xeoViewer. - * @event tick - * @param {String} sceneID The ID of this Scene. - * @param {Number} startTime The time in seconds since 1970 that this xeoViewer was instantiated. - * @param {Number} time The time in seconds since 1970 of this "tick" event. - * @param {Number} prevTime The time of the previous "tick" event from this xeoViewer. - * @param {Number} deltaTime The time in seconds since the previous "tick" event from this xeoViewer. - */ - self.fire("tick"); - - if (cameraUpdated) { - - /** - * Fired whenever this xeoViewer's camera changes. - * @event camera-changed - * @params New camera state, same as that got with #getCamera. - */ - self.fire("camera-changed", [self.getCamera()]); - cameraUpdated = false; - } - }); - })(); - - //----------------------------------------------------------------------------------------------------------- - // Camera control - //----------------------------------------------------------------------------------------------------------- - - var cameraControl = new xeogl.BIMCameraControl(scene, { - camera: camera - }); - - cameraControl.on("pick", - function (hit) { - - // Get BIM object ID from entity metadata - - var entity = hit.entity; - - if (!entity.meta) { - return; - } - - var objectId = entity.meta.objectId || entity.id; - - if (objectId === undefined) { - return; - } - - var selected = !!selectedObjects[objectId]; // Object currently selected? - var shiftDown = scene.input.keyDown[input.KEY_SHIFT]; // Shift key down? - - self.setSelection({ - ids: [objectId], - selected: !selected, // Picking an object toggles its selection status - clear: !shiftDown // Clear selection first if shift not down - }); - }); - - cameraControl.on("nopick", - function (hit) { - self.setSelection({ - clear: true - }); - }); - - /** - * Sets the default behaviour of mouse and touch drag input - * - * @method setDefaultDragAction - * @param {String} action ("pan" | "orbit") - */ - this.setDefaultDragAction = function (action) { - cameraControl.defaultDragAction = action; - }; - - /** - * Sets the global scale for models loaded into the viewer. - * - * @method setScale - * @param {Number} s Scale factor. - */ - this.setScale = function (s) { - scale.xyz = [s, s, s]; - }; - - /** - * Notifies the viewer that a task (such as loading a model) has started. Use #taskFinished - * to signal that the task has finished. - * - * Whenever the number of tasks is greater than zero, the viewer will display a spinner, - * and reduce rendering speed so as to allow scene updates to happen faster. - */ - this.taskStarted = function() { - scene.canvas.spinner.processes++; - scene.ticksPerRender = 15; // Tweak this if necessary - }; - - /** - * Signals that a task has finished (see #taskStarted). - */ - this.taskFinished = function() { - var spinner = scene.canvas.spinner; - if (spinner.processes === 0) { - return; - } - spinner.processes--; - if (spinner.processes === 0) { - scene.ticksPerRender = 1; // Back to max speed, one render per tick - } - }; - - /** - * Loads random objects into the viewer for testing. - * - * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. - * - * @method loadRandom - * @param {*} params Parameters - * @param {Number} [params.numEntities=200] Number of entities to create. - * @param {Number} [params.size=200] Size of model on every axis. - * @param {Float32Array} [params.center] Center point of model. - */ - this.loadRandom = function (params) { - - params = params || {}; - - this.clear(); - - var geometry = new xeogl.BoxGeometry(scene, { - id: "geometry.test" - }); - - collection.add(geometry); - - var modelId = "test"; - var roid = "test"; - var oid; - var type; - var objectId; - var translate; - var scale; - var matrix; - var types = Object.keys(DefaultMaterials); - - var numEntities = params.numEntities || 200; - var size = params.size || 200; - var halfSize = size / 2; - var centerX = params.center ? params.center[0] : 0; - var centerY = params.center ? params.center[1] : 0; - var centerZ = params.center ? params.center[2] : 0; - - this.createModel(modelId); - - for (var i = 0; i < numEntities; i++) { - objectId = "object" + i; - oid = objectId; - translate = xeogl.math.translationMat4c( - (Math.random() * size - halfSize) + centerX, - (Math.random() * size - halfSize) + centerY, - (Math.random() * size - halfSize) + centerZ); - scale = xeogl.math.scalingMat4c(Math.random() * 32 + 0.2, Math.random() * 32 + 0.2, Math.random() * 10 + 0.2); - matrix = xeogl.math.mulMat4(translate, scale, xeogl.math.mat4()); - type = types[Math.round(Math.random() * types.length)]; - this.createObject(modelId, roid, oid, objectId, ["test"], type, matrix); - } - - // Set camera just to establish the up vector as +Z; the following - // call to viewFit() will arrange the eye and target positions properly. - this.setCamera({ - eye: [0,0,0], - target: [centerX, centerY, centerZ], - up: [0,0,1] - }); - - this.viewFit(); - - this.saveReset(); - }; - - /** - * Creates a geometry. - * - * @method createGeometry - * @param geometryId - * @param positions - * @param normals - * @param colors - * @param indices - * @returns {xeogl.Geometry} The new geometry - * @private - */ - this.createGeometry = function (geometryId, positions, normals, colors, indices) { - var geometry = new xeogl.Geometry(scene, { // http://xeoengine.org/docs/classes/Geometry.html - id: "geometry." + geometryId, - primitive: "triangles", - positions: positions, - normals: normals, - colors: colors, - indices: indices - }); - - collection.add(geometry); - - return geometry; - }; - - - /** - * Creates a model. - * - * @param modelId - * @returns {xeogl.BIMModel} The new model - * @private - */ - this.createModel = function (modelId) { - - if (models[modelId]) { - console.log("Model with id " + modelId + " already exists, won't recreate"); - return; - } - - var model = new xeogl.BIMModel(scene, {}); - - models[modelId] = model; - - collection.add(model); - - return model; - }; - - /** - * Creates an object. - * @param [modelId] Optional model ID - * @param roid - * @param oid - * @param objectId - * @param geometryIds - * @param type - * @param matrix - * @returns {xeogl.BIMObject} The new object - * @private - */ - this.createObject = function (modelId, roid, oid, objectId, geometryIds, type, matrix) { - - if (modelId) { - var model = models[modelId]; - if (!model) { - console.log("Can't create object - model not found: " + modelId); - return; - } - objectId = modelId + ":" + objectId; - } - - if (objects[objectId]) { - console.log("Object with id " + objectId + " already exists, won't recreate"); - return; - } - - var object = new xeogl.BIMObject(scene, { - id: objectId, - geometryIds: geometryIds, - matrix: matrix - }); - - object.transform.parent = scale; // Apply model scaling - - this._addObject(type, object); - - if (model) { - model.collection.add(object); - } - - // Hide objects of certain types by default - if (hiddenTypes.indexOf(type) !== -1) { - object.visibility.visible = false; - } - - return object; - }; - - /** - * Inserts an object into this viewer - * - * @param {String} type - * @param {xeogl.Entity | xeogl.BIMObject} object - * @private - */ - this._addObject = function (type, object) { - var guid; - if (object.id.indexOf("#") !== -1) { - guid = Utils.CompressGuid(object.id.split("#")[1].substr(8, 36).replace(/-/g, "")); - } - collection.add(object); - - // Register object against ID - objects[object.id] = object; - if (guid) { - (objects_by_guid[guid] || (objects_by_guid[guid] = [])).push(object); - } - - // Register object against IFC type - var types = (rfcTypes[type] || (rfcTypes[type] = {})); - types[object.id] = object; - - var color = DefaultMaterials[type] || DefaultMaterials["DEFAULT"]; - - if (!guid) { - object.material.diffuse = [color[0], color[1], color[2]]; - } - object.material.specular = [0, 0, 0]; - - if (color[3] < 1) { // Transparent object - object.material.opacity = color[3]; - object.modes.transparent = true; - } - if (object.material.opacity < 1) { // Transparent object - object.modes.transparent = true; - } - }; - - /** - * Loads glTF model. - * - * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. - * - * @param src - */ - this.loadglTF = function (src) { - - this.clear(); - - var model = new xeogl.GLTFModel(scene, { - src: src - }); - - collection.add(model); - - models[model.id] = model; - - model.on("loaded", - function () { - - // TODO: viewFit, but boundaries not yet ready on Model Entities - - model.iterate(function (component) { - if (component.isType("xeogl.Entity")) { - self._addObject("DEFAULT", component); - } - }); - - self.saveReset(); - }); - - return model; - }; - - /** - * Destroys a model and all its objects. - * - * @param modelId - */ - this.destroyModel = function (modelId) { - - var model = models[modelId]; - - if (!model) { - console.warn("Can't destroy model - model not found: " + modelId); - return; - } - - model.collection.iterate(function (component) { - component.destroy(); - }); - - model.destroy(); - - delete models[modelId]; - }; - - /** - * Clears the viewer. - * - * Subsequent calls to #reset will then set the viewer this clear state. - */ - this.clear = function () { - - var list = []; - - collection.iterate( - function (component) { - list.push(component); - }); - - while (list.length) { - list.pop().destroy(); - } - - objects = {}; - rfcTypes = {}; - visibleObjects = {}; - visibleObjectList = null; - selectedObjects = {}; - selectedObjectList = null; - - this.saveReset(); - }; - - /** - * Sets the visibility of objects specified by IDs or IFC types. - * If IFC types are specified, this will affect existing objects as well as subsequently loaded objects of these types - * - * @param params - * @param params.ids IDs of objects or IFC types to update. - * @param params.color Color to set. - */ - this.setVisibility = function (params) { - - var changed = false; // Only fire "visibility-changed" when visibility updates are actually made - params = params || {}; - - var ids = params.ids; - var types = params.types; - - if (!ids && !types) { - console.error("Param expected: ids or types"); - return; - } - - ids = ids || []; - types = types || []; - - //var recursive = !!params.recursive; - var visible = params.visible !== false; - - var i; - var len; - var id; - var objectId; - var object; - - if (params.clear) { - for (objectId in visibleObjects) { - if (visibleObjects.hasOwnProperty(objectId)) { - delete visibleObjects[objectId]; - changed = true; - } - } - } - - for (i = 0, len = types.length; i < len; i++) { - var type = types[i]; - var typedict = rfcTypes[type] || {}; - - Object.keys(typedict).forEach(function (id) { - var object = typedict[id]; - object.visibility.visible = visible; - changed = true; - }); - - var index = hiddenTypes.indexOf(type); - - if (index !== -1 && visible) { - hiddenTypes.splice(index, 1); // remove type from array - } else if (index === -1 && !visible) { - hiddenTypes.push(type); // add type to array + // Set camera just to establish the up vector as +Z; the following + // call to viewFit() will arrange the eye and target positions properly. + this.setCamera({ + eye: [0, 0, 0], + target: [centerX, centerY, centerZ], + up: [0, 0, 1] + }); + + this.viewFit(); + + this.saveReset(); + } + + /** + * Creates a geometry. + * + * @method createGeometry + * @param geometryId + * @param positions + * @param normals + * @param colors + * @param indices + * @returns {xeogl.Geometry} The new geometry + * @private + */ + createGeometry(geometryId, positions, normals, colors, indices) { + const geometry = new xeogl.Geometry(this.scene, { // http://xeoengine.org/docs/classes/Geometry.html + id: "geometry." + geometryId, + primitive: "triangles", + positions: positions, + normals: normals, + colors: colors, + indices: indices + }); + + this.collection.add(geometry); + + return geometry; + } + + + /** + * Creates a model. + * + * @param modelId + * @returns {xeogl.BIMModel} The new model + * @private + */ + createModel(modelId) { + + if (this.models[modelId]) { + console.log("Model with id " + modelId + " already exists, won't recreate"); + return; + } + + const model = new xeogl.BIMModel(this.scene, {}); + + this.models[modelId] = model; + + this.collection.add(model); + + return model; + } + + /** + * Creates an object. + * @param [modelId] Optional model ID + * @param roid + * @param oid + * @param objectId + * @param geometryIds + * @param type + * @param matrix + * @returns {xeogl.BIMObject} The new object + * @private + */ + createObject(modelId, roid, oid, objectId, geometryIds, type, matrix) { + let model; + + if (modelId) { + model = this.models[modelId]; + if (!model) { + console.log("Can't create object - model not found: " + modelId); + return; + } + objectId = modelId + ":" + objectId; + } + + if (this.objects[objectId]) { + console.log("Object with id " + objectId + " already exists, won't recreate"); + return; + } + + const object = new xeogl.BIMObject(this.scene, { + id: objectId, + geometryIds: geometryIds, + matrix: matrix + }); + + object.transform.parent = this.scale; // Apply model scaling + + this._addObject(type, object); + + if (model) { + model.collection.add(object); + } + + // Hide objects of certain types by default + if (this.hiddenTypes.indexOf(type) !== -1) { + object.visibility.visible = false; + } + + return object; + } + + /** + * Inserts an object into this viewer + * + * @param {String} type + * @param {xeogl.Entity | xeogl.BIMObject} object + * @private + */ + _addObject(type, object) { + let guid; + if (object.id.indexOf("#") !== -1) { + guid = Utils.CompressGuid(object.id.split("#")[1].substr(8, 36).replace(/-/g, "")); + } + this.collection.add(object); + + // Register object against ID + this.objects[object.id] = object; + if (guid) { + (this.objects_by_guid[guid] || (this.objects_by_guid[guid] = [])).push(object); + } + + // Register object against IFC type + const types = (this.rfcTypes[type] || (this.rfcTypes[type] = {})); + types[object.id] = object; + + const color = DefaultMaterials[type] || DefaultMaterials.DEFAULT; + + if (!guid) { + object.material.diffuse = [color[0], color[1], color[2]]; + } + object.material.specular = [0, 0, 0]; + + if (color[3] < 1) { // Transparent object + object.material.opacity = color[3]; + object.modes.transparent = true; + } + if (object.material.opacity < 1) { // Transparent object + object.modes.transparent = true; + } + } + + /** + * Loads glTF model. + * + * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. + * + * @param src + */ + loadglTF(src) { + + this.clear(); + + const model = new xeogl.GLTFModel(this.scene, { + src: src + }); + + this.collection.add(model); + + this.models[model.id] = model; + + model.on("loaded", () => { + + // TODO: viewFit, but boundaries not yet ready on Model Entities + + model.iterate((component) => { + if (component.isType("xeogl.Entity")) { + this._addObject("DEFAULT", component); } - } - - for (i = 0, len = ids.length; i < len; i++) { - id = ids[i]; - var fn = function(object) { - object.visibility.visible = visible; - changed = true; - } - var object_ = objects[id]; - if (!object_) objects_by_guid[id].forEach(fn) - else fn(object_); - } - - if (changed) { - visibleObjectList = Object.keys(visibleObjects); - - /** - * Fired whenever objects become invisible or invisible - * @event visibility-changed - * @params Array of IDs of all currently-visible objects. - */ - this.fire("visibility-changed", [visibleObjectList]); - } - }; - - /** - * Returns array of IDs of objects that are currently visible - */ - this.getVisibility = function () { - if (visibleObjectList) { - return visibleObjectList; - } - visibleObjectList = Object.keys(visibleObjects); - return visibleObjectList; - }; - - /** - * Select or deselect some objects. - * - * @param params - * @param params.ids IDs of objects. - * @param params.selected Whether to select or deselect the objects - * @param params.clear Whether to clear selection state prior to updating - */ - this.setSelection = function (params) { - - params = params || {}; - - var changed = false; // Only fire "selection-changed" when selection actually changes - var selected = !!params.selected; - var objectId; - var object; - - if (params.clear) { - for (objectId in selectedObjects) { - if (selectedObjects.hasOwnProperty(objectId)) { - object = selectedObjects[objectId]; - // object.highlighted = false; - delete selectedObjects[objectId]; - changed = true; - } - } - } - - var ids = params.ids; - - if (ids) { - - for (var i = 0, len = ids.length; i < len; i++) { - - var fn = function(object) { - - var objectId = object.id; - - if (!!selectedObjects[objectId] !== selected) { - changed = true; - } - - if (selected) { - selectedObjects[objectId] = object; - } else { - if (selectedObjects[objectId]) { - delete selectedObjects[objectId]; - } - } - - selectedObjectList = null; // Now needs lazy-rebuild - - } - - objectId = ids[i]; - var object_ = objects[objectId]; - if (!object_) objects_by_guid[objectId].forEach(fn) - else fn(object_); - - } - } - - if (changed) { - - selectedObjectList = Object.keys(selectedObjects); - - // Show boundary around selected objects - setBoundaryState({ - ids: selectedObjectList, - show: selectedObjectList.length > 0 - }); - - /** - * Fired whenever this xeoViewer's selection state changes. - * @event selection-changed - * @params Array of IDs of all currently-selected objects. - */ - this.fire("selection-changed", [selectedObjectList]); - } - }; - - /** - * Returns array of IDs of objects that are currently selected - */ - this.getSelection = function () { - if (selectedObjectList) { - return selectedObjectList; - } - selectedObjectList = Object.keys(selectedObjects); - return selectedObjectList; - }; - - /** - * Sets the color of objects specified by IDs or IFC types. - * - * @param params - * @param params.ids IDs of objects to update. - * @param params.types IFC type of objects to update. - * @param params.color Color to set. - */ - this.setColor = function (params) { - - params = params || {}; - - var ids = params.ids; - var types = params.types; - - if (!ids && !types) { - console.error("Param expected: ids or types"); - return; - } - - ids = ids || []; - types = types || []; - - var color = params.color; - - if (!color) { - console.error("Param expected: 'color'"); - return; - } - - var objectId; - var object; - - for (i = 0, len = types.length; i < len; i++) { - var typedict = rfcTypes[types[i]] || {}; - Object.keys(typedict).forEach(function (id) { - var object = typedict[id]; - self._setObjectColor(object, color); - }); - } - - for (var i = 0, len = ids.length; i < len; i++) { - - objectId = ids[i]; - object = objects[objectId] || objects_by_guid[objectId]; - - if (!object) { - // No return on purpose to continue changing color of - // other potentially valid object identifiers. - console.error("Object not found: '" + objectId + "'"); - } else { - this._setObjectColor(object, color); - } - } - }; - - this._setObjectColor = function (object, color) { - - var material = object.material; - material.diffuse = [color[0], color[1], color[2]]; - - var opacity = (color.length > 3) ? color[3] : 1; - if (opacity !== material.opacity) { - material.opacity = opacity; - object.modes.transparent = opacity < 1; - } - }; - - /** - * Sets the opacity of objects specified by IDs of IFC types. - * - * @param params - * @param params.ids IDs of objects to update. - * @param params.types IFC type of objects to update. - * @param params.opacity Opacity to set. - */ - this.setOpacity = function (params) { - - params = params || {}; - - var ids = params.ids; - var types = params.types; - - if (!ids && !types) { - console.error("Param expected: ids or types"); - return; - } - - ids = ids || []; - types = types || []; - - var opacity = params.opacity; - - if (opacity === undefined) { - console.error("Param expected: 'opacity'"); - return; - } - - var objectId; - var object; - - for (i = 0, len = types.length; i < len; i++) { - var typedict = rfcTypes[types[i]] || {}; - Object.keys(typedict).forEach(function (id) { - var object = typedict[id]; - self._setObjectOpacity(object, opacity); - }); - } - - for (var i = 0, len = ids.length; i < len; i++) { - - objectId = ids[i]; - object = objects[objectId] || objects_by_guid[objectId]; - - if (!object) { - // No return on purpose to continue changing opacity of - // other potentially valid object identifiers. - console.error("Object not found: '" + objectId + "'"); - } else { - this._setObjectOpacity(object, opacity); - } - } - }; - - this._setObjectOpacity = function (object, opacity) { + }); + + this.saveReset(); + }); + + return model; + } - var material = object.material; - - if (opacity !== material.opacity) { - material.opacity = opacity; - object.modes.transparent = opacity < 1; - } - }; + /** + * Destroys a model and all its objects. + * + * @param modelId + */ + destroyModel(modelId) { - /** - * Sets camera state. - * - * @param params - */ - this.setCamera = function (params) { + const model = this.models[modelId]; - params = params || {}; + if (!model) { + console.warn("Can't destroy model - model not found: " + modelId); + return; + } - // Set projection type + model.collection.iterate((component) => { + component.destroy(); + }); - var type = params.type; + model.destroy(); - if (type && type !== projectionType) { + delete this.models[modelId]; + } - var projection = projections[type]; + /** + * Clears the viewer. + * + * Subsequent calls to #reset will then set the viewer this clear state. + */ + clear() { - if (!projection) { - console.error("Unsupported camera projection type: " + type); - } else { - camera.project = projection; - projectionType = type; - } - } + const list = []; - // Set camera position + this.collection.iterate((component) => { + list.push(component); + }); - if (params.animate) { + while (list.length) { + list.pop().destroy(); + } - cameraFlight.flyTo({ - eye: params.eye, - look: params.target, - up: params.up, - fitFOV: params.fitFOV, - duration: params.duration - }); + this.objects = {}; + this.rfcTypes = {}; + this.visibleObjects = {}; + this.visibleObjectList = null; + this.selectedObjects = {}; + this.selectedObjectList = null; + + this.saveReset(); + } + + /** + * Sets the visibility of objects specified by IDs or IFC types. + * If IFC types are specified, this will affect existing objects as well as subsequently loaded objects of these types + * + * @param params + * @param params.ids IDs of objects or IFC types to update. + * @param params.color Color to set. + */ + setVisibility(params) { + + let changed = false; // Only fire "visibility-changed" when visibility updates are actually made + params = params || {}; + + let ids = params.ids; + let types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } - } else { + ids = ids || []; + types = types || []; - if (params.eye) { - camera.view.eye = params.eye; - } + //const recursive = !!params.recursive; + const visible = params.visible !== false; - if (params.target) { - camera.view.look = params.target; - cameraControl.rotatePos = camera.view.look; // Rotate about target initially - } + let i; + let len; + let id; + let objectId; - if (params.up) { - camera.view.up = params.up; - } - } + if (params.clear) { + for (objectId in this.visibleObjects) { + if (this.visibleObjects.hasOwnProperty(objectId)) { + delete this.visibleObjects[objectId]; + changed = true; + } + } + } - // Set camera FOV angle, only if currently perspective + for (i = 0, len = types.length; i < len; i++) { + const type = types[i]; + const typedict = this.rfcTypes[type] || {}; - if (params.fovy) { - if (projectionType !== "persp") { - console.error("Ignoring update to 'fovy' for current '" + projectionType + "' camera"); - } else { - camera.project.fovy = params.fovy; - } - } + Object.keys(typedict).forEach((id) => { + const object = typedict[id]; + object.visibility.visible = visible; + changed = true; + }); - // Set camera view volume size, only if currently orthographic + const index = this.hiddenTypes.indexOf(type); - if (params.scale) { - if (projectionType !== "ortho") { - console.error("Ignoring update to 'scale' for current '" + projectionType + "' camera"); - } else { - camera.project.scale = params.scale; - } - } - }; + if (index !== -1 && visible) { + this.hiddenTypes.splice(index, 1); // remove type from array + } else if (index === -1 && !visible) { + this.hiddenTypes.push(type); // add type to array + } + } - /** - * Gets camera state. - * - * @returns {{type: string, eye: (*|Array.), target: (*|Array.), up: (*|Array.)}} - */ - this.getCamera = function () { + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + const fn = (object) => { + object.visibility.visible = visible; + changed = true; + }; + const object_ = this.objects[id]; + if (!object_) { this.objects_by_guid[id].forEach(fn); } + else { fn(object_); } + } - var view = camera.view; + if (changed) { + this.visibleObjectList = Object.keys(this.visibleObjects); - var json = { - type: projectionType, - eye: view.eye.slice(0), - target: view.look.slice(0), - up: view.up.slice(0) - }; + /** + * Fired whenever objects become invisible or invisible + * @event visibility-changed + * @params Array of IDs of all currently-visible objects. + */ + this.fire("visibility-changed", [this.visibleObjectList]); + } + } + + /** + * Returns array of IDs of objects that are currently visible + */ + getVisibility() { + if (this.visibleObjectList) { + return this.visibleObjectList; + } + this.visibleObjectList = Object.keys(this.visibleObjects); + return this.visibleObjectList; + } + + /** + * Select or deselect some objects. + * + * @param params + * @param params.ids IDs of objects. + * @param params.selected Whether to select or deselect the objects + * @param params.clear Whether to clear selection state prior to updating + */ + setSelection(params) { + + params = params || {}; + + let changed = false; // Only fire "selection-changed" when selection actually changes + const selected = !!params.selected; + let objectId; + let object; + + if (params.clear) { + for (objectId in this.selectedObjects) { + if (this.selectedObjects.hasOwnProperty(objectId)) { + object = this.selectedObjects[objectId]; + // object.highlighted = false; + delete this.selectedObjects[objectId]; + changed = true; + } + } + } - var project = camera.project; + const ids = params.ids; - if (projectionType === "persp") { - json.fovy = project.fovy; - - } else if (projectionType === "ortho") { - json.size = [1, 1, 1]; // TODO: efficiently derive from cached value or otho volume - } + if (ids) { + + for (let i = 0, len = ids.length; i < len; i++) { + + const fn = (object) => { + + const objectId = object.id; + + if (!!this.selectedObjects[objectId] !== selected) { + changed = true; + } + + if (selected) { + this.selectedObjects[objectId] = object; + } else { + if (this.selectedObjects[objectId]) { + delete this.selectedObjects[objectId]; + } + } + + this.selectedObjectList = null; // Now needs lazy-rebuild + + }; + + objectId = ids[i]; + const object_ = this.objects[objectId]; + if (!object_) { + this.objects_by_guid[objectId].forEach(fn); + } + else { + fn(object_); + } - return json; - }; - - - /** - * Redefines light sources. - * - * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type - */ - this.setLights = function (params) { - lights = params; - - for (var i = scene.lights.lights.length - 1; i >= 0; i--) { - scene.lights.lights[i].destroy(); } + } - scene.lights.lights = buildLights(lights); - }; - - - /** - * Returns light sources. - * - * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - */ - this.getLights = function () { - return lights; + if (changed) { + + this.selectedObjectList = Object.keys(this.selectedObjects); + + // Show boundary around selected objects + this.setBoundaryState({ + ids: this.selectedObjectList, + show: this.selectedObjectList.length > 0 + }); + + /** + * Fired whenever this xeoViewer's selection state changes. + * @event selection-changed + * @params Array of IDs of all currently-selected objects. + */ + this.fire("selection-changed", [this.selectedObjectList]); + } + } + + /** + * Returns array of IDs of objects that are currently selected + */ + getSelection() { + if (this.selectedObjectList) { + return this.selectedObjectList; + } + this.selectedObjectList = Object.keys(this.selectedObjects); + return this.selectedObjectList; + } + + /** + * Sets the color of objects specified by IDs or IFC types. + * + * @param params + * @param params.ids IDs of objects to update. + * @param params.types IFC type of objects to update. + * @param params.color Color to set. + */ + setColor(params) { + + params = params || {}; + + let ids = params.ids; + let types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } + + ids = ids || []; + types = types || []; + + const color = params.color; + + if (!color) { + console.error("Param expected: 'color'"); + return; + } + + let objectId; + let object; + + for (let i = 0, len = types.length; i < len; i++) { + const typedict = this.rfcTypes[types[i]] || {}; + Object.keys(typedict).forEach((id) => { + const object = typedict[id]; + this._setObjectColor(object, color); + }); + } + + for (let i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + // No return on purpose to continue changing color of + // other potentially valid object identifiers. + console.error("Object not found: '" + objectId + "'"); + } else { + this._setObjectColor(object, color); + } + } + } + + _setObjectColor(object, color) { + + const material = object.material; + material.diffuse = [color[0], color[1], color[2]]; + + const opacity = (color.length > 3) ? color[3] : 1; + if (opacity !== material.opacity) { + material.opacity = opacity; + object.modes.transparent = opacity < 1; + } + } + + /** + * Sets the opacity of objects specified by IDs of IFC types. + * + * @param params + * @param params.ids IDs of objects to update. + * @param params.types IFC type of objects to update. + * @param params.opacity Opacity to set. + */ + setOpacity(params) { + + params = params || {}; + + let ids = params.ids; + let types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } + + ids = ids || []; + types = types || []; + + const opacity = params.opacity; + + if (opacity === undefined) { + console.error("Param expected: 'opacity'"); + return; + } + + let objectId; + let object; + + for (let i = 0, len = types.length; i < len; i++) { + const typedict = this.rfcTypes[types[i]] || {}; + Object.keys(typedict).forEach((id) => { + const object = typedict[id]; + this._setObjectOpacity(object, opacity); + }); + } + + for (let i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + // No return on purpose to continue changing opacity of + // other potentially valid object identifiers. + console.error("Object not found: '" + objectId + "'"); + } else { + this._setObjectOpacity(object, opacity); + } + } + } + + _setObjectOpacity(object, opacity) { + + const material = object.material; + + if (opacity !== material.opacity) { + material.opacity = opacity; + object.modes.transparent = opacity < 1; + } + } + + /** + * Sets camera state. + * + * @param params + */ + setCamera(params) { + + params = params || {}; + + // Set projection type + + const type = params.type; + + if (type && type !== this.projectionType) { + + const projection = this.projections[type]; + + if (!projection) { + console.error("Unsupported camera projection type: " + type); + } else { + this.camera.project = projection; + this.projectionType = type; + } + } + + // Set camera position + + if (params.animate) { + + this.cameraFlight.flyTo({ + eye: params.eye, + look: params.target, + up: params.up, + fitFOV: params.fitFOV, + duration: params.duration + }); + + } else { + + if (params.eye) { + this.camera.view.eye = params.eye; + } + + if (params.target) { + this.camera.view.look = params.target; + this.cameraControl.rotatePos = this.camera.view.look; // Rotate about target initially + } + + if (params.up) { + this.camera.view.up = params.up; + } + } + + // Set camera FOV angle, only if currently perspective + + if (params.fovy) { + if (this.projectionType !== "persp") { + console.error("Ignoring update to 'fovy' for current '" + this.projectionType + "' camera"); + } else { + this.camera.project.fovy = params.fovy; + } + } + + // Set camera view volume size, only if currently orthographic + + if (params.scale) { + if (this.projectionType !== "ortho") { + console.error("Ignoring update to 'scale' for current '" + this.projectionType + "' camera"); + } else { + this.camera.project.scale = params.scale; + } + } + } + + /** + * Gets camera state. + * + * @returns {{type: string, eye: (*|Array.), target: (*|Array.), up: (*|Array.)}} + */ + getCamera() { + + const view = this.camera.view; + + const json = { + type: this.projectionType, + eye: view.eye.slice(0), + target: view.look.slice(0), + up: view.up.slice(0) }; - - function buildLights(lights) { - return lights.map(function(light) { - if (light.type == "ambient") { - return new xeogl.AmbientLight(scene, light.params); - } else if (light.type == "dir") { - return new xeogl.DirLight(scene, light.params); - } else if (light.type == "point") { - return new xeogl.PointLight(scene, light.params); - } else { - console.log("Unknown light type: " + type); + + const project = this.camera.project; + + if (this.projectionType === "persp") { + json.fovy = project.fovy; + + } else if (this.projectionType === "ortho") { + json.size = [1, 1, 1]; // TODO: efficiently derive from cached value or otho volume + } + + return json; + } + + + /** + * Redefines light sources. + * + * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type + */ + setLights(params) { + this.lights = params; + + for (let i = this.scene.lights.lights.length - 1; i >= 0; i--) { + this.scene.lights.lights[i].destroy(); + } + + this.scene.lights.lights = this.buildLights(this.lights); + } + + + /** + * Returns light sources. + * + * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + */ + getLights() { + return this.lights; + } + + buildLights(lights) { + return lights.map((light) => { + if (light.type == "ambient") { + return new xeogl.AmbientLight(this.scene, light.params); + } else if (light.type == "dir") { + return new xeogl.DirLight(this.scene, light.params); + } else if (light.type == "point") { + return new xeogl.PointLight(this.scene, light.params); + } else { + console.log("Unknown light type: " + light.type); + } + }); + } + + + /** + * + * @param params + * @param ok + */ + viewFit(params, ok) { + + params = params || {}; + + const ids = params.ids; + let aabb; + + if (!ids || ids.length === 0) { + + // Fit everything in view by default + aabb = this.scene.worldBoundary.aabb; + + } else { + aabb = this.getObjectsAABB(ids); + } + + if (params.animate) { + + this.cameraFlight.flyTo({ + aabb: aabb, + fitFOV: params.fitFOV, + duration: params.duration + }, () => { + if (ok) { + ok(); } + + // Now orbiting the point we flew to + this.cameraControl.rotatePos = this.camera.view.look; + }); + + } else { + + this.cameraFlight.jumpTo({ + aabb: aabb, + fitFOV: 50.0 }); } - + } - /** - * - * @param params - * @param ok - */ - this.viewFit = function (params, ok) { - - params = params || {}; - - var ids = params.ids; - var aabb; - - if (!ids || ids.length === 0) { - - // Fit everything in view by default - aabb = scene.worldBoundary.aabb; - - } else { - aabb = getObjectsAABB(ids); - } - - if (params.animate) { - - cameraFlight.flyTo({ - aabb: aabb, - fitFOV: params.fitFOV, - duration: params.duration - }, - function () { - if (ok) { - ok(); - } - - // Now orbiting the point we flew to - cameraControl.rotatePos = camera.view.look; - }); - - } else { - - cameraFlight.jumpTo({ - aabb: aabb, - fitFOV: 50. - }); - } - }; + // Updates the boundary helper + setBoundaryState(params) { - // Updates the boundary helper - function setBoundaryState(params) { + if (params.aabb) { + throw new Error("Not supported"); + } else if (params.ids) { + this.boundaryHelper.setSelected(params.ids); - if (params.aabb) { - throw new Error("Not supported"); - } else if (params.ids) { - boundaryHelper.setSelected(params.ids); - - highlightEffect.clear(); + this.highlightEffect.clear(); - var ids = params.ids; - var objectId; - var object; + const ids = params.ids; + let objectId; + let object; - for (var i = 0, len = ids.length; i < len; i++) { - objectId = ids[i]; - object = objects[objectId]; - if (object) { + for (let i = 0, len = ids.length; i < len; i++) { + objectId = ids[i]; + object = this.objects[objectId]; + if (object) { - highlightEffect.add(object); - //object.highlighted = true; - } - } - } - - } + this.highlightEffect.add(object); + //object.highlighted = true; + } + } + } + + } + + // Returns an axis-aligned bounding box (AABB) that encloses the given objects + getObjectsAABB(ids_) { + + let ids; + if (Object.keys(this.objects_by_guid).length) { + ids = []; + ids_.forEach((i) => { + this.objects_by_guid[i].forEach((o) => { + ids.push(o.id); + }); + }); + } else { + ids = ids_; + } + + if (ids.length === 0) { + + // No object IDs given + return null; + } - // Returns an axis-aligned bounding box (AABB) that encloses the given objects - function getObjectsAABB(ids_) { - - var ids; - if (Object.keys(objects_by_guid).length) { - ids = []; - ids_.forEach(function(i) { - objects_by_guid[i].forEach(function(o) { - ids.push(o.id); - }); - }); - } else { - ids = ids_; - } + let objectId; + let object; + let worldBoundary; - if (ids.length === 0) { + if (ids.length === 1) { - // No object IDs given - return null; - } + // One object ID given - var objectId; - var object; - var worldBoundary; + objectId = ids[0]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (object) { + worldBoundary = object.worldBoundary; + + if (worldBoundary) { + + return worldBoundary.aabb; + + } else { + return null; + } + + } else { + return null; + } + } - if (ids.length === 1) { - - // One object ID given - - objectId = ids[0]; - object = objects[objectId] || objects_by_guid[objectId]; - - if (object) { - worldBoundary = object.worldBoundary; - - if (worldBoundary) { - - return worldBoundary.aabb; - - } else { - return null; - } - - } else { - return null; - } - } - - // Many object IDs given - - var i; - var len; - var min; - var max; - - var xmin = 100000; - var ymin = 100000; - var zmin = 100000; - var xmax = -100000; - var ymax = -100000; - var zmax = -100000; - - var aabb; - - for (i = 0, len = ids.length; i < len; i++) { - - objectId = ids[i]; - object = objects[objectId] || objects_by_guid[objectId]; - - if (!object) { - continue; - } - - worldBoundary = object.worldBoundary; - - if (!worldBoundary) { - continue; - } - - aabb = worldBoundary.aabb; - - min = aabb.slice(0); - max = aabb.slice(3); - - if (min[0] < xmin) { - xmin = min[0]; - } - - if (min[1] < ymin) { - ymin = min[1]; - } - - if (min[2] < zmin) { - zmin = min[2]; - } - - if (max[0] > xmax) { - xmax = max[0]; - } - - if (max[1] > ymax) { - ymax = max[1]; - } - - if (max[2] > zmax) { - zmax = max[2]; - } - } - - var result = xeogl.math.AABB3(); + // Many object IDs given - result[0+0] = xmin; - result[1+0] = ymin; - result[2+0] = zmin; - result[0+3] = xmax; - result[1+3] = ymax; - result[2+3] = zmax; - - return result; - } + let i; + let len; + let min; + let max; - /** - * Remembers the current state of the viewer so that it can be reset to this state with - * a subsequent call to #reset. - */ - this.saveReset = function () { - resetBookmark = this.getBookmark(); - }; - - this.getObject = function(id) { - return objects[id]; - }; - - /** - * Resets the state of this viewer to the state previously saved with #saveReset. - * @param {*} params A mask which specifies which aspects of viewer state to reset. - */ - this.reset = function (params) { - if (!resetBookmark) { - console.log("Ignoring call to xeoViewer.reset - xeoViewer.saveReset not called previously."); - return; - } - this.setBookmark(resetBookmark, params); - }; - - /** - * Returns a bookmark of xeoViewer state. - * @param {*} options A mask which specifies which aspects of viewer state to bookmark. - */ - this.getBookmark = function (options) { - - // Get everything by default - - var getVisible = !options || options.visible; - var getColors = !options || options.colors; - var getSelected = !options || options.selected; - var getCamera = !options || options.camera; - - var bookmark = {}; - - var objectId; - var object; - - if (getVisible) { - - var visible = []; - - for (objectId in objects) { - if (objects.hasOwnProperty(objectId)) { - - object = objects[objectId] || objects_by_guid[objectId]; - - if (getVisible && object.visibility.visible) { - visible.push(objectId); - } - } - } - bookmark.visible = visible; - } - - if (getColors) { - - var opacity; - var colors = {}; - var opacities = {}; - - for (objectId in objects) { - if (objects.hasOwnProperty(objectId)) { - object = objects[objectId] || objects_by_guid[objectId]; - colors[objectId] = object.material.diffuse.slice(); // RGB - opacities[objectId] = object.modes.transparent ? object.material.opacity : 1.0; - } - } - bookmark.colors = colors; - bookmark.opacities = opacities; - } - - if (getSelected) { - bookmark.selected = this.getSelection(); - } - - if (getCamera) { - var camera = this.getCamera(); - camera.animate = true; // Camera will fly to position when bookmark is restored - bookmark.camera = camera; - } - - return bookmark; - }; - - /** - * Restores xeoViewer to a bookmark. - * - * @param bookmark - * @param options - */ - this.setBookmark = function (bookmark, options) { - - // Set everything by default, where provided in bookmark - - var setVisible = bookmark.visible && (!options || options.visible); - var setColors = bookmark.colors && (!options || options.colors); - var setSelected = bookmark.selected && (!options || options.selected); - var setCamera = bookmark.camera && (!options || options.camera); - - if (setColors) { - - var objectId; - var object; - var colors = bookmark.colors; - var opacities = bookmark.opacities; - - for (objectId in colors) { - if (colors.hasOwnProperty(objectId)) { - object = objects[objectId] || objects_by_guid[objectId]; - if (object) { - this._setObjectColor(object, colors[objectId]); - this._setObjectOpacity(object, opacities[objectId]); - } - } - } - } - - if (setVisible) { - this.setVisibility({ - ids: bookmark.visible, - visible: true - }); - } - - if (setSelected) { - this.setSelection({ - ids: bookmark.selected, - selected: true - }); - } - - if (setCamera) { - this.setCamera(bookmark.camera); - } - }; - - /** - * Sets general configurations. - * - * @param params - * @param {Boolean} [params.mouseRayPick=true] When true, camera flies to orbit each clicked point, otherwise - * it flies to the boundary of the object that was clicked on. - * @param [params.viewFitFOV=25] {Number} How much of field-of-view, in degrees, that a target {{#crossLink "Entity"}}{{/crossLink}} or its AABB should - * fill the canvas when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}} or {{#crossLink "CameraFlightAnimation/jumpTo:method"}}{{/crossLink}}. - * @param [params.viewFitDuration=1] {Number} Flight duration, in seconds, when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}}. - */ - this.setConfigs = function (params) { - - params = params || {}; - - if (params.mouseRayPick != undefined) { - cameraControl.mousePickEntity.rayPick = params.mouseRayPick; - } - - if (params.viewFitFOV != undefined) { - cameraFlight.fitFOV = params.viewFitFOV; - } - - if (params.viewFitDuration != undefined) { - cameraFlight.duration = params.viewFitDuration; - } - }; - - /** - Returns a snapshot of this xeoViewer as a Base64-encoded image. - - #### Usage: - ````javascript - imageElement.src = xeoViewer.getSnapshot({ - width: 500, // Defaults to size of canvas - height: 500, - format: "png" // Options are "jpeg" (default), "png" and "bmp" - }); - ```` - - @method getSnapshot - @param {*} [params] Capture options. - @param {Number} [params.width] Desired width of result in pixels - defaults to width of canvas. - @param {Number} [params.height] Desired height of result in pixels - defaults to height of canvas. - @param {String} [params.format="jpeg"] Desired format; "jpeg", "png" or "bmp". - @returns {String} String-encoded image data. - */ - this.getSnapshot = function (params) { - return scene.canvas.getSnapshot(params); - }; - - /** - Returns a list of loaded IFC entity types in the model. - - @method getTypes - @returns {Array} List of loaded IFC entity types, with visibility flag - */ - this.getTypes = function() { - return Object.keys(rfcTypes).map(function(n) { - return {name: n, visible: hiddenTypes.indexOf(n) === -1}; - }); - }; - - /** - * Returns the world boundary of an object - * - * @method getWorldBoundary - * @param {String} objectId id of object - * @param {Object} result Existing boundary object - * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D - */ - this.getWorldBoundary = function(objectId, result) { - let object = objects[objectId] || objects_by_guid[objectId]; - - if (object === undefined) { - return null; - } else { - if (result === undefined) { - result = { - obb: new Float32Array(32), - aabb: new Float32Array(6), - center: xeogl.math.vec3(), - sphere: xeogl.math.vec4() - }; - } - - // the boundary needs to be scaled back to real world units - let s = 1 / scale.xyz[0], - scaled = object.worldBoundary; - - result.aabb[0] = scaled.aabb[0] * s; - result.aabb[1] = scaled.aabb[1] * s; - result.aabb[2] = scaled.aabb[2] * s; - result.aabb[3] = scaled.aabb[3] * s; - result.aabb[4] = scaled.aabb[4] * s; - result.aabb[5] = scaled.aabb[5] * s; - - xeogl.math.mulVec3Scalar(scaled.center, s, result.center); - xeogl.math.mulVec4Scalar(scaled.sphere, s, result.sphere); - - var obb = scaled.obb; - var buffer = result.obb.buffer; - for (var i = 0; i < 32; i += 4) { - var v = new Float32Array(buffer, 4 * i); - xeogl.math.mulVec3Scalar(obb.slice(i), s, v); - v[3] = 1.; - } - - return result; - } - }; - - /** - * Destroys the viewer - */ - this.destroy = function() { - scene.destroy(); - } - } - - xeoViewer.prototype = Object.create(EventHandler.prototype); - - return xeoViewer; -}); + let xmin = 100000; + let ymin = 100000; + let zmin = 100000; + let xmax = -100000; + let ymax = -100000; + let zmax = -100000; + + let aabb; + + for (i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + continue; + } + + worldBoundary = object.worldBoundary; + + if (!worldBoundary) { + continue; + } + + aabb = worldBoundary.aabb; + + min = aabb.slice(0); + max = aabb.slice(3); + + if (min[0] < xmin) { + xmin = min[0]; + } + + if (min[1] < ymin) { + ymin = min[1]; + } + + if (min[2] < zmin) { + zmin = min[2]; + } + + if (max[0] > xmax) { + xmax = max[0]; + } + + if (max[1] > ymax) { + ymax = max[1]; + } + + if (max[2] > zmax) { + zmax = max[2]; + } + } + + const result = xeogl.math.AABB3(); + + result[0 + 0] = xmin; + result[1 + 0] = ymin; + result[2 + 0] = zmin; + result[0 + 3] = xmax; + result[1 + 3] = ymax; + result[2 + 3] = zmax; + + return result; + } + + /** + * Remembers the current state of the viewer so that it can be reset to this state with + * a subsequent call to #reset. + */ + saveReset() { + this.resetBookmark = this.getBookmark(); + } + + getObject(id) { + return this.objects[id]; + } + + /** + * Resets the state of this viewer to the state previously saved with #saveReset. + * @param {*} params A mask which specifies which aspects of viewer state to reset. + */ + reset(params) { + if (!this.resetBookmark) { + console.log("Ignoring call to xeoViewer.reset - xeoViewer.saveReset not called previously."); + return; + } + this.setBookmark(this.resetBookmark, params); + } + + /** + * Returns a bookmark of xeoViewer state. + * @param {*} options A mask which specifies which aspects of viewer state to bookmark. + */ + getBookmark(options) { + + // Get everything by default + + const getVisible = !options || options.visible; + const getColors = !options || options.colors; + const getSelected = !options || options.selected; + const getCamera = !options || options.camera; + + const bookmark = {}; + + let objectId; + let object; + + if (getVisible) { + + const visible = []; + + for (objectId in this.objects) { + if (this.objects.hasOwnProperty(objectId)) { + + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (getVisible && object.visibility.visible) { + visible.push(objectId); + } + } + } + bookmark.visible = visible; + } + + if (getColors) { + + let opacity; + const colors = {}; + const opacities = {}; + + for (objectId in this.objects) { + if (this.objects.hasOwnProperty(objectId)) { + object = this.objects[objectId] || this.objects_by_guid[objectId]; + colors[objectId] = object.material.diffuse.slice(); // RGB + opacities[objectId] = object.modes.transparent ? object.material.opacity : 1.0; + } + } + bookmark.colors = colors; + bookmark.opacities = opacities; + } + + if (getSelected) { + bookmark.selected = this.getSelection(); + } + + if (getCamera) { + const camera = this.getCamera(); + camera.animate = true; // Camera will fly to position when bookmark is restored + bookmark.camera = camera; + } + + return bookmark; + } + + /** + * Restores xeoViewer to a bookmark. + * + * @param bookmark + * @param options + */ + setBookmark(bookmark, options) { + + // Set everything by default, where provided in bookmark + + const setVisible = bookmark.visible && (!options || options.visible); + const setColors = bookmark.colors && (!options || options.colors); + const setSelected = bookmark.selected && (!options || options.selected); + const setCamera = bookmark.camera && (!options || options.camera); + + if (setColors) { + + let objectId; + let object; + const colors = bookmark.colors; + const opacities = bookmark.opacities; + + for (objectId in colors) { + if (colors.hasOwnProperty(objectId)) { + object = this.objects[objectId] || this.objects_by_guid[objectId]; + if (object) { + this._setObjectColor(object, colors[objectId]); + this._setObjectOpacity(object, opacities[objectId]); + } + } + } + } + + if (setVisible) { + this.setVisibility({ + ids: bookmark.visible, + visible: true + }); + } + + if (setSelected) { + this.setSelection({ + ids: bookmark.selected, + selected: true + }); + } + + if (setCamera) { + this.setCamera(bookmark.camera); + } + } + + /** + * Sets general configurations. + * + * @param params + * @param {Boolean} [params.mouseRayPick=true] When true, camera flies to orbit each clicked point, otherwise + * it flies to the boundary of the object that was clicked on. + * @param [params.viewFitFOV=25] {Number} How much of field-of-view, in degrees, that a target {{#crossLink "Entity"}}{{/crossLink}} or its AABB should + * fill the canvas when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}} or {{#crossLink "CameraFlightAnimation/jumpTo:method"}}{{/crossLink}}. + * @param [params.viewFitDuration=1] {Number} Flight duration, in seconds, when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}}. + */ + setConfigs(params) { + + params = params || {}; + + if (params.mouseRayPick != undefined) { + this.cameraControl.mousePickEntity.rayPick = params.mouseRayPick; + } + + if (params.viewFitFOV != undefined) { + this.cameraFlight.fitFOV = params.viewFitFOV; + } + + if (params.viewFitDuration != undefined) { + this.cameraFlight.duration = params.viewFitDuration; + } + } + + /** + Returns a snapshot of this xeoViewer as a Base64-encoded image. + + #### Usage: + ````javascript + imageElement.src = xeoViewer.getSnapshot({ + width: 500, // Defaults to size of canvas + height: 500, + format: "png" // Options are "jpeg" (default), "png" and "bmp" + }); + ```` + + @method getSnapshot + @param {*} [params] Capture options. + @param {Number} [params.width] Desired width of result in pixels - defaults to width of canvas. + @param {Number} [params.height] Desired height of result in pixels - defaults to height of canvas. + @param {String} [params.format="jpeg"] Desired format; "jpeg", "png" or "bmp". + @returns {String} String-encoded image data. + */ + getSnapshot(params) { + return this.scene.canvas.getSnapshot(params); + } + + /** + Returns a list of loaded IFC entity types in the model. + + @method getTypes + @returns {Array} List of loaded IFC entity types, with visibility flag + */ + getTypes() { + return Object.keys(this.rfcTypes).map((n) => { + return { name: n, visible: this.hiddenTypes.indexOf(n) === -1 }; + }); + } + + /** + * Returns the world boundary of an object + * + * @method getWorldBoundary + * @param {String} objectId id of object + * @param {Object} result Existing boundary object + * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D + */ + getWorldBoundary(objectId, result) { + let object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (object === undefined) { + return null; + } else { + if (result === undefined) { + result = { + obb: new Float32Array(32), + aabb: new Float32Array(6), + center: xeogl.math.vec3(), + sphere: xeogl.math.vec4() + }; + } + + // the boundary needs to be scaled back to real world units + let s = 1 / this.scale.xyz[0], + scaled = object.worldBoundary; + + result.aabb[0] = scaled.aabb[0] * s; + result.aabb[1] = scaled.aabb[1] * s; + result.aabb[2] = scaled.aabb[2] * s; + result.aabb[3] = scaled.aabb[3] * s; + result.aabb[4] = scaled.aabb[4] * s; + result.aabb[5] = scaled.aabb[5] * s; + + xeogl.math.mulVec3Scalar(scaled.center, s, result.center); + xeogl.math.mulVec4Scalar(scaled.sphere, s, result.sphere); + + const obb = scaled.obb; + const buffer = result.obb.buffer; + for (let i = 0; i < 32; i += 4) { + const v = new Float32Array(buffer, 4 * i); + xeogl.math.mulVec3Scalar(obb.slice(i), s, v); + v[3] = 1.0; + } + + return result; + } + } + + /** + * Destroys the viewer + */ + destroy() { + this.scene.destroy(); + } +} \ No newline at end of file diff --git a/build/bimsurfer.js b/build/bimsurfer.js new file mode 100644 index 0000000..6b4c0cb --- /dev/null +++ b/build/bimsurfer.js @@ -0,0 +1,6284 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('xeogl'), require('bimserverapi')) : + typeof define === 'function' && define.amd ? define(['exports', 'xeogl', 'bimserverapi'], factory) : + (factory((global.bimsurfer = {}),null,global.BimServerClient)); +}(this, (function (exports,xeogl$1,BimServerClient) { 'use strict'; + +BimServerClient = BimServerClient && BimServerClient.hasOwnProperty('default') ? BimServerClient['default'] : BimServerClient; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + +var asyncGenerator = function () { + function AwaitValue(value) { + this.value = value; + } + + function AsyncGenerator(gen) { + var front, back; + + function send(key, arg) { + return new Promise(function (resolve, reject) { + var request = { + key: key, + arg: arg, + resolve: resolve, + reject: reject, + next: null + }; + + if (back) { + back = back.next = request; + } else { + front = back = request; + resume(key, arg); + } + }); + } + + function resume(key, arg) { + try { + var result = gen[key](arg); + var value = result.value; + + if (value instanceof AwaitValue) { + Promise.resolve(value.value).then(function (arg) { + resume("next", arg); + }, function (arg) { + resume("throw", arg); + }); + } else { + settle(result.done ? "return" : "normal", result.value); + } + } catch (err) { + settle("throw", err); + } + } + + function settle(type, value) { + switch (type) { + case "return": + front.resolve({ + value: value, + done: true + }); + break; + + case "throw": + front.reject(value); + break; + + default: + front.resolve({ + value: value, + done: false + }); + break; + } + + front = front.next; + + if (front) { + resume(front.key, front.arg); + } else { + back = null; + } + } + + this._invoke = send; + + if (typeof gen.return !== "function") { + this.return = undefined; + } + } + + if (typeof Symbol === "function" && Symbol.asyncIterator) { + AsyncGenerator.prototype[Symbol.asyncIterator] = function () { + return this; + }; + } + + AsyncGenerator.prototype.next = function (arg) { + return this._invoke("next", arg); + }; + + AsyncGenerator.prototype.throw = function (arg) { + return this._invoke("throw", arg); + }; + + AsyncGenerator.prototype.return = function (arg) { + return this._invoke("return", arg); + }; + + return { + wrap: function (fn) { + return function () { + return new AsyncGenerator(fn.apply(this, arguments)); + }; + }, + await: function (value) { + return new AwaitValue(value); + } + }; +}(); + + + + + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + + + + + + + + + +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + + + + + + + + + + + +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; + +var Notifier = function () { + function Notifier() { + classCallCheck(this, Notifier); + } + + createClass(Notifier, [{ + key: 'setSelector', + value: function setSelector(selector) { + console.log('setSelector', arguments); + } + }, { + key: 'clear', + value: function clear() { + console.log('clear', arguments); + } + }, { + key: 'resetStatus', + value: function resetStatus() { + console.log('status', arguments); + } + }, { + key: 'resetStatusQuick', + value: function resetStatusQuick() { + console.log('status', arguments); + } + }, { + key: 'setSuccess', + value: function setSuccess(status, timeToShow) { + console.log('success', arguments); + } + }, { + key: 'setInfo', + value: function setInfo(status, timeToShow) { + console.log('info', arguments); + } + }, { + key: 'setError', + value: function setError(error) { + console.log('error', arguments); + } + }]); + return Notifier; +}(); + +var BimServerModel = function () { + function BimServerModel(apiModel) { + classCallCheck(this, BimServerModel); + + this.apiModel = apiModel; + this.tree = null; + this.treePromise = null; + } + + createClass(BimServerModel, [{ + key: "getTree", + value: function getTree(args) { + + /* + // TODO: This is rather tricky. Never know when the list of Projects is exhausted. + // Luckily a valid IFC contains one and only one. Let's assume there is just one. + const projectEncountered = false; + + this.model.getAllOfType("IfcProject", false, function(project) { + if (projectEncountered) { + throw new Error("More than a single project encountered, bleh!"); + } + console.log('project', project); + }); + + */ + + var self = this; + + return self.treePromise || (self.treePromise = new Promise(function (resolve, reject) { + + if (self.tree) { + resolve(self.tree); + } + + var entities = { + 'IfcRelDecomposes': 1, + 'IfcRelAggregates': 1, + 'IfcRelContainedInSpatialStructure': 1, + 'IfcRelFillsElement': 1, + 'IfcRelVoidsElement': 1 + }; + + // Create a mapping from id->instance + var instance_by_id = {}; + var objects = []; + + for (var e in self.apiModel.objects) { + // The root node in a dojo store should have its parent + // set to null, not just something that evaluates to false + var o = self.apiModel.objects[e].object; + o.parent = null; + instance_by_id[o._i] = o; + objects.push(o); + } + + // Filter all instances based on relationship entities + var relationships = objects.filter(function (o) { + return entities[o._t]; + }); + + // Construct a tuple of {parent, child} ids + var parents = relationships.map(function (o) { + var ks = Object.keys(o); + var related = ks.filter(function (k) { + return k.indexOf('Related') !== -1; + }); + var relating = ks.filter(function (k) { + return k.indexOf('Relating') !== -1; + }); + return [o[relating[0]], o[related[0]]]; + }); + + var is_array = function is_array(o) { + return Object.prototype.toString.call(o) === '[object Array]'; + }; + + var data = []; + var visited = {}; + parents.forEach(function (a) { + // Relationships in IFC can be one to one/many + var ps = is_array(a[0]) ? a[0] : [a[0]]; + var cs = is_array(a[1]) ? a[1] : [a[1]]; + for (var i = 0; i < ps.length; ++i) { + for (var j = 0; j < cs.length; ++j) { + // Lookup the instance ids in the mapping + var p = instance_by_id[ps[i]._i]; + var c = instance_by_id[cs[j]._i]; + + // parent, id, hasChildren are significant attributes in a dojo store + c.parent = p.id = p._i; + c.id = c._i; + p.hasChildren = true; + + // Make sure to only add instances once + if (!visited[c.id]) { + data.push(c); + } + if (!visited[p.id]) { + data.push(p); + } + visited[p.id] = visited[c.id] = true; + } + } + }); + + var make_element = function make_element(o) { + return { name: o.Name, id: o.id, guid: o.GlobalId, parent: o.parent, gid: o._rgeometry == null ? null : o._rgeometry /*._i*/ }; + }; + + var fold = function () { + var root = null; + return function (li) { + var by_oid = {}; + li.forEach(function (elem) { + by_oid[elem.id] = elem; + }); + li.forEach(function (elem) { + if (elem.parent === null) { + root = elem; + } else { + var p = by_oid[elem.parent]; + (p.children || (p.children = [])).push(elem); + } + }); + return root; + }; + }(); + + resolve(self.tree = fold(data.map(make_element))); + // }); + })); + } + }]); + return BimServerModel; +}(); + +var PreloadQuery = { + defines: { + Representation: { + type: "IfcProduct", + fields: ["Representation", "geometry"] + }, + ContainsElementsDefine: { + type: "IfcSpatialStructureElement", + field: "ContainsElements", + include: { + type: "IfcRelContainedInSpatialStructure", + field: "RelatedElements", + includes: ["IsDecomposedByDefine", "ContainsElementsDefine", "Representation"] + } + }, + IsDecomposedByDefine: { + type: "IfcObjectDefinition", + field: "IsDecomposedBy", + include: { + type: "IfcRelDecomposes", + field: "RelatedObjects", + includes: ["IsDecomposedByDefine", "ContainsElementsDefine", "Representation"] + } + } + }, + queries: [{ + type: "IfcProject", + includes: ["IsDecomposedByDefine", "ContainsElementsDefine"] + }, { + type: "IfcRepresentation", + includeAllSubtypes: true + }, { + type: "IfcProductRepresentation" + }, { + type: "IfcPresentationLayerWithStyle" + }, { + type: "IfcProduct", + includeAllSubtypes: true + }, { + type: "IfcProductDefinitionShape" + }, { + type: "IfcPresentationLayerAssignment" + }, { + type: "IfcRelAssociatesClassification", + includes: [{ + type: "IfcRelAssociatesClassification", + field: "RelatedObjects" + }, { + type: "IfcRelAssociatesClassification", + field: "RelatingClassification" + }] + }, { + type: "IfcSIUnit" + }, { + type: "IfcPresentationLayerAssignment" + }] +}; + +/*\ +|*| +|*| :: Number.isInteger() polyfill :: +|*| +|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger +|*| +\*/ + +if (!Number.isInteger) { + Number.isInteger = function isInteger(nVal) { + return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal; + }; +} + +/*\ +|*| +|*| StringView - Mozilla Developer Network +|*| +|*| Revision #12, March 21st, 2017 +|*| +|*| https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView +|*| https://developer.mozilla.org/en-US/docs/User:fusionchess +|*| https://github.com/madmurphy/stringview.js +|*| +|*| This framework is released under the GNU Lesser General Public License, version 3 or later. +|*| http://www.gnu.org/licenses/lgpl-3.0.html +|*| +\*/ + +var StringView = function () { + function StringView(vInput, sEncoding /* optional (default: UTF-8) */, nOffset /* optional */, nLength /* optional */) { + classCallCheck(this, StringView); + + var fTAView = void 0, + aWhole = void 0, + aRaw = void 0, + fPutOutptCode = void 0, + fGetOutptChrSize = void 0, + nInptLen = void 0, + nTranscrType = 15, + nStartIdx = isFinite(nOffset) ? nOffset : 0; + + if (sEncoding) { + this.encoding = sEncoding.toString(); + } else { + this.encoding = "UTF-8"; + } + + encSwitch: switch (this.encoding) { + case "UTF-8": + fPutOutptCode = this.putUTF8CharCode; + fGetOutptChrSize = this.getUTF8CharLength; + fTAView = Uint8Array; + break encSwitch; + case "UTF-16": + fPutOutptCode = this.putUTF16CharCode; + fGetOutptChrSize = this.getUTF16CharLength; + fTAView = Uint16Array; + break encSwitch; + case "UTF-32": + fTAView = Uint32Array; + nTranscrType &= 14; + break encSwitch; + default: + /* case "ASCII", or case "BinaryString" or unknown cases */ + fTAView = Uint8Array; + nTranscrType &= 14; + } + + typeSwitch: switch (typeof vInput === "undefined" ? "undefined" : _typeof(vInput)) { + case "string": + /* the input argument is a primitive string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case "object": + classSwitch: switch (vInput.constructor) { + case StringView: + /* the input argument is a stringView: a new buffer will be created. */ + nTranscrType &= 3; + break typeSwitch; + case String: + /* the input argument is an objectified string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case ArrayBuffer: + /* the input argument is an arrayBuffer: the buffer will be shared. */ + aWhole = new fTAView(vInput); + nInptLen = this.encoding === "UTF-32" ? vInput.byteLength >>> 2 : this.encoding === "UTF-16" ? vInput.byteLength >>> 1 : vInput.byteLength; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? aWhole : new fTAView(vInput, nStartIdx, !isFinite(nLength) ? nInptLen - nStartIdx : nLength); + + break typeSwitch; + case Uint32Array: + case Uint16Array: + case Uint8Array: + /* the input argument is a typedArray: the buffer, and possibly the array itself, will be shared. */ + fTAView = vInput.constructor; + nInptLen = vInput.length; + aWhole = vInput.byteOffset === 0 && vInput.length === (fTAView === Uint32Array ? vInput.buffer.byteLength >>> 2 : fTAView === Uint16Array ? vInput.buffer.byteLength >>> 1 : vInput.buffer.byteLength) ? vInput : new fTAView(vInput.buffer); + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? vInput : vInput.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + + break typeSwitch; + default: + /* the input argument is an array or another serializable object: a new typedArray will be created. */ + aWhole = new fTAView(vInput); + nInptLen = aWhole.length; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? aWhole : aWhole.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + } + break typeSwitch; + default: + /* the input argument is a number, a boolean or a function: a new typedArray will be created. */ + aWhole = aRaw = new fTAView(Number(vInput) || 0); + + } + + if (nTranscrType < 8) { + + var vSource = void 0, + nOutptLen = void 0, + nCharStart = void 0, + nCharEnd = void 0, + nEndIdx = void 0, + fGetInptChrSize = void 0, + fGetInptChrCode = void 0; + + if (nTranscrType & 4) { + /* input is string */ + + vSource = vInput; + nOutptLen = nInptLen = vSource.length; + nTranscrType ^= this.encoding === "UTF-32" ? 0 : 2; + /* ...or...: nTranscrType ^= Number(this.encoding !== "UTF-32") << 1; */ + nStartIdx = nCharStart = nOffset ? Math.max((nOutptLen + nOffset) % nOutptLen, 0) : 0; + nEndIdx = nCharEnd = (Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0) + nStartIdx, nOutptLen) : nOutptLen) - 1; + } else { + /* input is stringView */ + + vSource = vInput.rawData; + nInptLen = vInput.makeIndex(); + nStartIdx = nCharStart = nOffset ? Math.max((nInptLen + nOffset) % nInptLen, 0) : 0; + nOutptLen = Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0), nInptLen - nCharStart) : nInptLen; + nEndIdx = nCharEnd = nOutptLen + nCharStart; + + if (vInput.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (vInput.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } else { + nTranscrType &= 1; + } + } + + if (nOutptLen === 0 || nTranscrType < 4 && vSource.encoding === this.encoding && nCharStart === 0 && nOutptLen === nInptLen) { + + /* the encoding is the same, the length too and the offset is 0... or the input is empty! */ + + nTranscrType = 7; + } + + conversionSwitch: switch (nTranscrType) { + + case 0: + + /* both the source and the new StringView have a fixed-length encoding... */ + + aWhole = new fTAView(nOutptLen); + for (var nOutptIdx = 0; nOutptIdx < nOutptLen; aWhole[nOutptIdx] = vSource[nStartIdx + nOutptIdx++]) {} + break conversionSwitch; + + case 1: + + /* the source has a fixed-length encoding but the new StringView has a variable-length encoding... */ + + /* mapping... */ + + nOutptLen = 0; + + for (var nInptIdx = nStartIdx; nInptIdx < nEndIdx; nInptIdx++) { + nOutptLen += fGetOutptChrSize(vSource[nInptIdx]); + } + + aWhole = new fTAView(nOutptLen); + + /* transcription of the source... */ + + for (var _nInptIdx = nStartIdx, _nOutptIdx = 0; _nOutptIdx < nOutptLen; _nInptIdx++) { + _nOutptIdx = fPutOutptCode(aWhole, vSource[_nInptIdx], _nOutptIdx); + } + + break conversionSwitch; + + case 2: + + /* the source has a variable-length encoding but the new StringView has a fixed-length encoding... */ + + /* mapping... */ + + nStartIdx = 0; + + var nChrCode = void 0; + + for (var nChrIdx = 0; nChrIdx < nCharStart; nChrIdx++) { + nChrCode = fGetInptChrCode(vSource, nStartIdx); + nStartIdx += fGetInptChrSize(nChrCode); + } + + aWhole = new fTAView(nOutptLen); + + /* transcription of the source... */ + + for (var _nInptIdx2 = nStartIdx, _nOutptIdx2 = 0; _nOutptIdx2 < nOutptLen; _nInptIdx2 += fGetInptChrSize(nChrCode), _nOutptIdx2++) { + nChrCode = fGetInptChrCode(vSource, _nInptIdx2); + aWhole[_nOutptIdx2] = nChrCode; + } + + break conversionSwitch; + + case 3: + + /* both the source and the new StringView have a variable-length encoding... */ + + /* mapping... */ + + nOutptLen = 0; + + for (var _nChrIdx = 0, _nInptIdx3 = 0; _nChrIdx < nCharEnd; _nInptIdx3 += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, _nInptIdx3); + if (_nChrIdx === nCharStart) { + nStartIdx = _nInptIdx3; + } + if (++_nChrIdx > nCharStart) { + nOutptLen += fGetOutptChrSize(nChrCode); + } + } + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var _nInptIdx4 = nStartIdx, _nOutptIdx3 = 0; _nOutptIdx3 < nOutptLen; _nInptIdx4 += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, _nInptIdx4); + _nOutptIdx3 = fPutOutptCode(aWhole, nChrCode, _nOutptIdx3); + } + + break conversionSwitch; + + case 4: + + /* DOMString to ASCII or BinaryString or other unknown encodings */ + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var nIdx = 0; nIdx < nOutptLen; nIdx++) { + aWhole[nIdx] = vSource.charCodeAt(nIdx) & 0xff; + } + + break conversionSwitch; + + case 5: + + /* DOMString to UTF-8 or to UTF-16 */ + + /* mapping... */ + + nOutptLen = 0; + + for (var nMapIdx = 0; nMapIdx < nInptLen; nMapIdx++) { + if (nMapIdx === nCharStart) { + nStartIdx = nOutptLen; + } + nOutptLen += fGetOutptChrSize(vSource.charCodeAt(nMapIdx)); + if (nMapIdx === nCharEnd) { + nEndIdx = nOutptLen; + } + } + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var _nOutptIdx4 = 0, _nChrIdx2 = 0; _nOutptIdx4 < nOutptLen; _nChrIdx2++) { + _nOutptIdx4 = fPutOutptCode(aWhole, vSource.charCodeAt(_nChrIdx2), _nOutptIdx4); + } + + break conversionSwitch; + + case 6: + + /* DOMString to UTF-32 */ + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var _nIdx = 0; _nIdx < nOutptLen; _nIdx++) { + aWhole[_nIdx] = vSource.charCodeAt(_nIdx); + } + + break conversionSwitch; + + case 7: + + aWhole = new fTAView(nOutptLen ? vSource : 0); + break conversionSwitch; + + } + + aRaw = nTranscrType > 3 && (nStartIdx > 0 || nEndIdx < aWhole.length - 1) ? aWhole.subarray(nStartIdx, nEndIdx) : aWhole; + } + + this.buffer = aWhole.buffer; + this.bufferView = aWhole; + this.rawData = aRaw; + + Object.freeze(this); + } + + createClass(StringView, [{ + key: "makeIndex", + + + /* INSTANCES' METHODS */ + + value: function makeIndex(nChrLength, nStartFrom) { + + var aTarget = this.rawData, + nChrEnd = void 0, + nRawLength = aTarget.length, + nStartIdx = nStartFrom || 0, + nIdxEnd = nStartIdx, + nStopAtChr = isNaN(nChrLength) ? Infinity : nChrLength; + + if (nChrLength + 1 > aTarget.length) { + throw new RangeError("prototype.makeIndex - The offset can\'t be major than the length of the array - 1."); + } + + switch (this.encoding) { + + case "UTF-8": + + var nPart = void 0; + + for (nChrEnd = 0; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nPart = aTarget[nIdxEnd]; + nIdxEnd += nPart > 251 && nPart < 254 && nIdxEnd + 5 < nRawLength ? 6 : nPart > 247 && nPart < 252 && nIdxEnd + 4 < nRawLength ? 5 : nPart > 239 && nPart < 248 && nIdxEnd + 3 < nRawLength ? 4 : nPart > 223 && nPart < 240 && nIdxEnd + 2 < nRawLength ? 3 : nPart > 191 && nPart < 224 && nIdxEnd + 1 < nRawLength ? 2 : 1; + } + + break; + + case "UTF-16": + + for (nChrEnd = nStartIdx; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nIdxEnd += aTarget[nIdxEnd] > 0xD7BF /* 55231 */ && nIdxEnd + 1 < aTarget.length ? 2 : 1; + } + + break; + + default: + + nIdxEnd = nChrEnd = isFinite(nChrLength) ? nChrLength : nRawLength - 1; + + } + + if (nChrLength) { + return nIdxEnd; + } + + return nChrEnd; + } + }, { + key: "toBase64", + value: function toBase64(bWholeBuffer) { + + return this.bytesToBase64(bWholeBuffer ? this.bufferView.constructor === Uint8Array ? this.bufferView : new Uint8Array(this.buffer) : this.rawData.constructor === Uint8Array ? this.rawData : new Uint8Array(this.buffer, this.rawData.byteOffset, this.rawData.length << (this.rawData.constructor === Uint16Array ? 1 : 2))); + } + }, { + key: "subview", + value: function subview(nCharOffset /* optional */, nCharLength /* optional */) { + + var nRawSubLen = void 0, + nRawSubOffset = void 0, + nSubOffset = void 0, + nSubLen = void 0, + bVariableLen = this.encoding === "UTF-8" || this.encoding === "UTF-16", + nThisLen = void 0, + nRawLen = this.rawData.length; + + if (nRawLen === 0) { + return new StringView(this.buffer, this.encoding); + } + + nThisLen = bVariableLen ? this.makeIndex() : nRawLen; + nSubOffset = nCharOffset ? nCharOffset + 1 > nThisLen ? nThisLen : Math.max((nThisLen + nCharOffset) % nThisLen, 0) : 0; + nSubLen = Number.isInteger(nCharLength) ? Math.max(nCharLength, 0) + nSubOffset > nThisLen ? nThisLen - nSubOffset : nCharLength : nThisLen - nSubOffset; + + if (nSubOffset === 0 && nSubLen === nThisLen) { + return this; + } + + if (bVariableLen) { + nRawSubOffset = nSubOffset < nThisLen ? this.makeIndex(nSubOffset) : nThisLen; + nRawSubLen = nSubLen ? this.makeIndex(nSubLen, nRawSubOffset) - nRawSubOffset : 0; + } else { + nRawSubOffset = nSubOffset; + nRawSubLen = nSubLen; + } + + if (this.encoding === "UTF-16") { + nRawSubOffset <<= 1; + } else if (this.encoding === "UTF-32") { + nRawSubOffset <<= 2; + } + + return new StringView(this.buffer, this.encoding, this.rawData.byteOffset + nRawSubOffset, nRawSubLen); + } + }, { + key: "forEachChar", + value: function forEachChar(fCallback, oThat, nChrOffset, nChrLen) { + + var aSource = this.rawData, + nRawEnd = void 0, + nRawIdx = void 0; + + if (this.encoding === "UTF-8" || this.encoding === "UTF-16") { + + var fGetInptChrSize = void 0, + fGetInptChrCode = void 0; + + if (this.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } + + nRawIdx = isFinite(nChrOffset) ? this.makeIndex(nChrOffset) : 0; + nRawEnd = isFinite(nChrLen) ? this.makeIndex(nChrLen, nRawIdx) : aSource.length; + + for (var nChrCode, nChrIdx = 0; nRawIdx < nRawEnd; nChrIdx++) { + nChrCode = fGetInptChrCode(aSource, nRawIdx); + if (!oThat) { + fCallback(nChrCode, nChrIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, nChrCode, nChrIdx, nRawIdx, aSource); + } + nRawIdx += fGetInptChrSize(nChrCode); + } + } else { + + nRawIdx = isFinite(nChrOffset) ? nChrOffset : 0; + nRawEnd = isFinite(nChrLen) ? nChrLen + nRawIdx : aSource.length; + + for (nRawIdx; nRawIdx < nRawEnd; nRawIdx++) { + if (!oThat) { + fCallback(aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } + } + } + } + }, { + key: "valueOf", + value: function valueOf() { + + if (this.encoding !== "UTF-8" && this.encoding !== "UTF-16") { + /* ASCII, UTF-32 or BinaryString to DOMString */ + return String.fromCharCode.apply(null, this.rawData); + } + + var fGetCode = void 0, + fGetIncr = void 0, + sView = ""; + + if (this.encoding === "UTF-8") { + fGetIncr = StringView.getUTF8CharLength; + fGetCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetIncr = StringView.getUTF16CharLength; + fGetCode = StringView.loadUTF16CharCode; + } + + for (var nChr, nLen = this.rawData.length, nIdx = 0; nIdx < nLen; nIdx += fGetIncr(nChr)) { + nChr = fGetCode(this.rawData, nIdx); + sView += String.fromCharCode(nChr); + } + + return sView; + } + }, { + key: "toString", + value: function toString() { + return this.valueOf(); + } + }], [{ + key: "loadUTF8CharCode", + value: function loadUTF8CharCode(aChars, nIdx) { + /* The ISO 10646 view of UTF-8 considers valid codepoints encoded by 1-6 bytes, + * while the Unicode view of UTF-8 in 2003 has limited them to 1-4 bytes in order to + * match UTF-16's codepoints. In front of a 5/6-byte sequence StringView tries to + * encode it in any case. + */ + var nLen = aChars.length, + nPart = aChars[nIdx]; + return nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? + /* (nPart - 252 << 30) may be not safe in ECMAScript! So...: */ + /* six bytes */ + (nPart - 252) * 1073741824 + (aChars[nIdx + 1] - 128 << 24) + (aChars[nIdx + 2] - 128 << 18) + (aChars[nIdx + 3] - 128 << 12) + (aChars[nIdx + 4] - 128 << 6) + aChars[nIdx + 5] - 128 : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? + /* five bytes */ + (nPart - 248 << 24) + (aChars[nIdx + 1] - 128 << 18) + (aChars[nIdx + 2] - 128 << 12) + (aChars[nIdx + 3] - 128 << 6) + aChars[nIdx + 4] - 128 : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? + /* four bytes */ + (nPart - 240 << 18) + (aChars[nIdx + 1] - 128 << 12) + (aChars[nIdx + 2] - 128 << 6) + aChars[nIdx + 3] - 128 : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? + /* three bytes */ + (nPart - 224 << 12) + (aChars[nIdx + 1] - 128 << 6) + aChars[nIdx + 2] - 128 : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? + /* two bytes */ + (nPart - 192 << 6) + aChars[nIdx + 1] - 128 : + /* one byte */ + nPart; + } + }, { + key: "putUTF8CharCode", + value: function putUTF8CharCode(aTarget, nChar, nPutAt) { + + var nIdx = nPutAt; + + if (nChar < 0x80 /* 128 */) { + /* one byte */ + aTarget[nIdx++] = nChar; + } else if (nChar < 0x800 /* 2048 */) { + /* two bytes */ + aTarget[nIdx++] = 0xc0 /* 192 */ + (nChar >>> 6); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x10000 /* 65536 */) { + /* three bytes */ + aTarget[nIdx++] = 0xe0 /* 224 */ + (nChar >>> 12); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 6 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x200000 /* 2097152 */) { + /* four bytes */ + aTarget[nIdx++] = 0xf0 /* 240 */ + (nChar >>> 18); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 12 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 6 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x4000000 /* 67108864 */) { + /* five bytes */ + aTarget[nIdx++] = 0xf8 /* 248 */ + (nChar >>> 24); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 18 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 12 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 6 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else /* if (nChar <= 0x7fffffff) */{ + /* 2147483647 */ + /* six bytes */ + aTarget[nIdx++] = 0xfc /* 252 */ + /* (nChar >>> 30) may be not safe in ECMAScript! So...: */nChar / 1073741824; + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 24 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 18 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 12 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar >>> 6 & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } + + return nIdx; + } + }, { + key: "getUTF8CharLength", + value: function getUTF8CharLength(nChar) { + return nChar < 0x80 ? 1 : nChar < 0x800 ? 2 : nChar < 0x10000 ? 3 : nChar < 0x200000 ? 4 : nChar < 0x4000000 ? 5 : 6; + } + }, { + key: "loadUTF16CharCode", + value: function loadUTF16CharCode(aChars, nIdx) { + + /* UTF-16 to DOMString decoding algorithm */ + var nFrstChr = aChars[nIdx]; + + return nFrstChr > 0xD7BF /* 55231 */ && nIdx + 1 < aChars.length ? (nFrstChr - 0xD800 /* 55296 */ << 10) + aChars[nIdx + 1] + 0x2400 /* 9216 */ : nFrstChr; + } + }, { + key: "putUTF16CharCode", + value: function putUTF16CharCode(aTarget, nChar, nPutAt) { + + var nIdx = nPutAt; + + if (nChar < 0x10000 /* 65536 */) { + /* one element */ + aTarget[nIdx++] = nChar; + } else { + /* two elements */ + aTarget[nIdx++] = 0xD7C0 /* 55232 */ + (nChar >>> 10); + aTarget[nIdx++] = 0xDC00 /* 56320 */ + (nChar & 0x3FF /* 1023 */); + } + + return nIdx; + } + }, { + key: "getUTF16CharLength", + value: function getUTF16CharLength(nChar) { + return nChar < 0x10000 ? 1 : 2; + } + + /* Array of bytes to base64 string decoding */ + + }, { + key: "b64ToUint6", + value: function b64ToUint6(nChr) { + + return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; + } + }, { + key: "uint6ToB64", + value: function uint6ToB64(nUint6) { + + return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; + } + + /* Base64 string to array encoding */ + + }, { + key: "bytesToBase64", + value: function bytesToBase64(aBytes) { + + var eqLen = (3 - aBytes.length % 3) % 3; + var sB64Enc = ""; + + for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + /* Uncomment the following line in order to split the output in lines 76-character long: */ + /* + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } + */ + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(this.uint6ToB64(nUint24 >>> 18 & 63), this.uint6ToB64(nUint24 >>> 12 & 63), this.uint6ToB64(nUint24 >>> 6 & 63), this.uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + + return eqLen === 0 ? sB64Enc : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "=="); + } + }, { + key: "base64ToBytes", + value: function base64ToBytes(sBase64, nBlockBytes) { + + var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), + nInLen = sB64Enc.length, + nOutLen = nBlockBytes ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockBytes) * nBlockBytes : nInLen * 3 + 1 >>> 2, + aBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + } + nUint24 = 0; + } + } + + return aBytes; + } + }, { + key: "makeFromBase64", + value: function makeFromBase64(sB64Inpt, sEncoding, nByteOffset, nLength) { + + return new StringView(sEncoding === "UTF-16" || sEncoding === "UTF-32" ? this.base64ToBytes(sB64Inpt, sEncoding === "UTF-16" ? 2 : 4).buffer : this.base64ToBytes(sB64Inpt), sEncoding, nByteOffset, nLength); + } + }]); + return StringView; +}(); + +var DataInputStreamReader = function () { + function DataInputStreamReader(arrayBuffer) { + classCallCheck(this, DataInputStreamReader); + + + this.arrayBuffer = arrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + this.pos = 0; + } + + createClass(DataInputStreamReader, [{ + key: 'readUTF8', + value: function readUTF8() { + var length = this.dataView.getInt16(this.pos); + this.pos += 2; + var view = this.arrayBuffer.slice(this.pos, this.pos + length); + var result = new StringView(view).toString(); + this.pos += length; + return result; + } + }, { + key: 'align4', + value: function align4() { + // Skips to the next alignment of 4 (source should have done the same!) + var skip = 4 - this.pos % 4; + if (skip > 0 && skip != 4) { + // console.log("Skip", skip); + this.pos += skip; + } + } + }, { + key: 'align8', + value: function align8() { + // Skips to the next alignment of 4 (source should have done the same!) + var skip = 8 - this.pos % 8; + if (skip > 0 && skip != 8) { + // console.log("Skip", skip); + this.pos += skip; + } + } + }, { + key: 'readDoubleArray', + value: function readDoubleArray(length) { + var result = new Float64Array(this.arrayBuffer, this.pos, length); + this.pos += length * 8; + return result; + } + }, { + key: 'readFloat', + value: function readFloat() { + var value = this.dataView.getFloat32(this.pos, true); + this.pos += 4; + return value; + } + }, { + key: 'readInt', + value: function readInt() { + var value = this.dataView.getInt32(this.pos, true); + this.pos += 4; + return value; + } + }, { + key: 'readByte', + value: function readByte() { + var value = this.dataView.getInt8(this.pos); + this.pos += 1; + return value; + } + }, { + key: 'readLong', + value: function readLong() { + var value = this.dataView.getUint32(this.pos, true) + 0x100000000 * this.dataView.getUint32(this.pos + 4, true); + this.pos += 8; + return value; + } + }, { + key: 'readFloatArray2', + value: function readFloatArray2(length) { + var results = []; + for (var i = 0; i < length; i++) { + var value = this.dataView.getFloat32(this.pos, true); + this.pos += 4; + results.push(value); + } + return results; + } + }, { + key: 'readFloatArray', + value: function readFloatArray(length) { + var result = new Float32Array(this.arrayBuffer, this.pos, length); + this.pos += length * 4; + return result; + } + }, { + key: 'readIntArray2', + value: function readIntArray2(length) { + var results = []; + for (var i = 0; i < length; i++) { + var value = this.dataView.getInt32(this.pos, true); + this.pos += 4; + results.push(value); + } + return results; + } + }, { + key: 'readIntArray', + value: function readIntArray(length) { + var result = new Int32Array(this.arrayBuffer, this.pos, length); + this.pos += length * 4; + return result; + } + }, { + key: 'readShortArray', + value: function readShortArray(length) { + try { + var result = new Int16Array(this.arrayBuffer, this.pos, length); + this.pos += length * 2; + return result; + } catch (e) { + console.log(e); + } + } + }]); + return DataInputStreamReader; +}(); + +var BimServerGeometryLoader = function () { + function BimServerGeometryLoader(bimServerApi, viewer, model, roid, globalTransformationMatrix) { + classCallCheck(this, BimServerGeometryLoader); + + this.bimServerApi = bimServerApi; + this.viewer = viewer; + this.state = {}; + this.progressListeners = []; + this.objectAddedListeners = []; + this.prepareReceived = false; + this.todo = []; + this.geometryIds = {}; + this.dataToInfo = {}; + this.globalTransformationMatrix = globalTransformationMatrix; + this.model = model; + this.roid = roid; + + console.log(globalTransformationMatrix); + } + + createClass(BimServerGeometryLoader, [{ + key: "addProgressListener", + value: function addProgressListener(progressListener) { + this.progressListeners.push(progressListener); + } + }, { + key: "process", + value: function process() { + var data = this.todo.shift(); + var stream = void 0; + + while (data != null) { + stream = new DataInputStreamReader(data); + + var channel = stream.readLong(); + var messageType = stream.readByte(); + + if (messageType == 0) { + this._readStart(stream); + } else if (messageType == 6) { + this._readEnd(stream); + } else { + this._readObject(stream, messageType); + } + + data = this.todo.shift(); + } + } + }, { + key: "setLoadOids", + value: function setLoadOids(oids) { + this.options = { + type: "oids", + oids: oids + }; + } + + /** + * Starts this loader. + */ + + }, { + key: "start", + value: function start() { + var _this = this; + + if (!this.options || this.options.type !== "oids") { + throw new Error("Invalid loader configuration"); + } + + var obj = []; + + this.groupId = this.roid; + this.infoToOid = this.options.oids; + + for (var k in this.infoToOid) { + var oid = parseInt(this.infoToOid[k]); + this.model.apiModel.get(oid, function (object) { + if (object.object._rgeometry != null) { + if (object.model.objects[object.object._rgeometry] != null) { + // Only if this data is preloaded, otherwise just don't include any gi + object.getGeometry(function (geometryInfo) { + obj.push({ + gid: object.object._rgeometry, + oid: object.oid, + object: object, + info: geometryInfo.object + }); + }); + } else { + obj.push({ + gid: object.object._rgeometry, + oid: object.oid, + object: object + }); + } + } + }); + } + + obj.sort(function (a, b) { + if (a.info != null && b.info != null) { + var topa = (a.info._emaxBounds.z + a.info._eminBounds.z) / 2; + var topb = (b.info._emaxBounds.z + b.info._eminBounds.z) / 2; + return topa - topb; + } else { + // Resort back to type + // TODO this is dodgy when some objects do have info, and others don't + return a.object.getType().localeCompare(b.object.getType()); + } + }); + + var oids = []; + obj.forEach(function (wrapper) { + oids.push(wrapper.object.object._rgeometry /*._i*/); + }); + + var query = { + type: "GeometryInfo", + oids: oids, + include: { + type: "GeometryInfo", + field: "data" + } + }; + + this.bimServerApi.getSerializerByPluginClassName("org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingStreamingSerializerPlugin3", function (serializer) { + _this.bimServerApi.call("ServiceInterface", "download", { + roids: [_this.roid], + query: JSON.stringify(query), + serializerOid: serializer.oid, + sync: false + }, function (topicId) { + _this.topicId = topicId; + _this.bimServerApi.registerProgressHandler(_this.topicId, _this._progressHandler.bind(_this)); + }); + }); + } + }, { + key: "_progressHandler", + value: function _progressHandler(topicId, state) { + if (topicId == this.topicId) { + if (state.title == "Done preparing") { + if (!this.prepareReceived) { + this.prepareReceived = true; + this._downloadInitiated(); + } + } + if (state.state == "FINISHED") { + this.bimServerApi.unregisterProgressHandler(this.topicId, this._progressHandler.bind(this)); + } + } + } + }, { + key: "_downloadInitiated", + value: function _downloadInitiated() { + this.state = { + mode: 0, + nrObjectsRead: 0, + nrObjects: 0 + }; + // this.viewer.SYSTEM.events.trigger('progressStarted', ['Loading Geometry']); + // this.viewer.SYSTEM.events.trigger('progressBarStyleChanged', BIMSURFER.Constants.ProgressBarStyle.Continuous); + var msg = { + longActionId: this.topicId, + topicId: this.topicId + }; + this.bimServerApi.setBinaryDataListener(this.topicId, this._binaryDataListener.bind(this)); + this.bimServerApi.downloadViaWebsocket(msg); + } + }, { + key: "_binaryDataListener", + value: function _binaryDataListener(data) { + this.todo.push(data); + } + }, { + key: "_afterRegistration", + value: function _afterRegistration(topicId) { + var _this2 = this; + + this.bimServerApi.call("Bimsie1NotificationRegistryInterface", "getProgress", { + topicId: this.topicId + }, function (state) { + _this2._progressHandler(_this2.topicId, state); + }); + } + }, { + key: "_readEnd", + value: function _readEnd(data) { + var _this3 = this; + + this.progressListeners.forEach(function (progressListener) { + progressListener("done", _this3.state.nrObjectsRead, _this3.state.nrObjectsRead); + }); + this.bimServerApi.call("ServiceInterface", "cleanupLongAction", { + topicId: this.topicId + }, function () {}); + } + }, { + key: "_readStart", + value: function _readStart(data) { + var _this4 = this; + + var start = data.readUTF8(); + + if (start != "BGS") { + console.error("data does not start with BGS (" + start + ")"); + return false; + } + + this.protocolVersion = data.readByte(); + + if (this.protocolVersion != 10 && this.protocolVersion != 11) { + console.error("Unimplemented version"); + return false; + } + + data.align8(); + + var boundary = data.readDoubleArray(6); + + this._initCamera(boundary); + + this.state.mode = 1; + + this.progressListeners.forEach(function (progressListener) { + progressListener("start", _this4.state.nrObjectsRead, _this4.state.nrObjectsRead); + }); + //this._updateProgress(); + } + }, { + key: "_initCamera", + value: function _initCamera(boundary) { + + if (!this._gotCamera) { + + this._gotCamera = true; + + // Bump scene origin to center the model + + var xmin = boundary[0]; + var ymin = boundary[1]; + var zmin = boundary[2]; + var xmax = boundary[3]; + var ymax = boundary[4]; + var zmax = boundary[5]; + + var diagonal = Math.sqrt(Math.pow(xmax - xmin, 2) + Math.pow(ymax - ymin, 2) + Math.pow(zmax - zmin, 2)); + + var scale = 100 / diagonal; + + this.viewer.setScale(scale); // Temporary until we find a better scaling system. + + var center = [scale * ((xmax + xmin) / 2), scale * ((ymax + ymin) / 2), scale * ((zmax + zmin) / 2)]; + + this.viewer.setCamera({ + type: "persp", + target: center, + up: [0, 0, 1], + eye: [center[0] - scale * diagonal, center[1] - scale * diagonal, center[2] + scale * diagonal], + far: 5000, + near: 0.1, + fovy: 35.8493 + }); + } + } + }, { + key: "_updateProgress", + value: function _updateProgress() { + // if (this.state.nrObjectsRead < this.state.nrObjects) { + // const progress = Math.ceil(100 * this.state.nrObjectsRead / this.state.nrObjects); + // if (progress != this.state.lastProgress) { + // this.progressListeners.forEach(function (progressListener) { + // progressListener(progress, this.state.nrObjectsRead, this.state.nrObjects); + // }); + // // TODO: Add events + // // this.viewer.SYSTEM.events.trigger('progressChanged', [progress]); + // this.state.lastProgress = progress; + // } + // } else { + // // this.viewer.SYSTEM.events.trigger('progressDone'); + // this.progressListeners.forEach(function (progressListener) { + // progressListener("done", this.state.nrObjectsRead, this.state.nrObjects); + // }); + // // this.viewer.events.trigger('sceneLoaded', [this.viewer.scene.scene]); + // + // const d = {}; + // d[BIMSERVER_VERSION == "1.4" ? "actionId" : "topicId"] = this.topicId; + // this.bimServerApi.call("ServiceInterface", "cleanupLongAction", d, function () {}); + // } + } + }, { + key: "_readObject", + value: function _readObject(stream, geometryType) { + var _this5 = this; + + stream.align8(); + + // const type = stream.readUTF8(); + // const roid = stream.readLong(); // TODO: Needed? + // const objectId = stream.readLong(); + // const oid = objectId; + + var geometryId = void 0; + var numParts = void 0; + var numIndices = void 0; + var indices = void 0; + var numPositions = void 0; + var positions = void 0; + var numNormals = void 0; + var normals = void 0; + var numColors = void 0; + var colors = null; + var color = void 0; + + var i = void 0; + + if (geometryType == 1) { + geometryId = stream.readLong(); + numIndices = stream.readInt(); + indices = stream.readShortArray(numIndices); + + if (this.protocolVersion == 11) { + var b = stream.readInt(); + if (b == 1) { + color = { + r: stream.readFloat(), + g: stream.readFloat(), + b: stream.readFloat(), + a: stream.readFloat() + }; + } + } + stream.align4(); + numPositions = stream.readInt(); + positions = stream.readFloatArray(numPositions); + numNormals = stream.readInt(); + normals = stream.readFloatArray(numNormals); + numColors = stream.readInt(); + if (numColors > 0) { + colors = stream.readFloatArray(numColors); + } else if (color != null) { + // Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors + colors = new Array(numPositions * 4); + for (var _i = 0; _i < numPositions; _i++) { + colors[_i * 4 + 0] = color.r; + colors[_i * 4 + 1] = color.g; + colors[_i * 4 + 2] = color.b; + colors[_i * 4 + 3] = color.a; + } + } + + this.geometryIds[geometryId] = [geometryId]; + this.viewer.createGeometry(geometryId, positions, normals, colors, indices); + + if (this.dataToInfo[geometryId] != null) { + this.dataToInfo[geometryId].forEach(function (oid) { + var ob = _this5.viewer.getObject(_this5.roid + ":" + oid); + ob.add(geometryId); + }); + delete this.dataToInfo[geometryId]; + } + } else if (geometryType == 2) { + console.log("Unimplemented", 2); + } else if (geometryType == 3) { + var geometryDataOid = stream.readLong(); + numParts = stream.readInt(); + this.geometryIds[geometryDataOid] = []; + + var geometryIds = []; + for (i = 0; i < numParts; i++) { + var partId = stream.readLong(); + geometryId = geometryDataOid + "_" + i; + numIndices = stream.readInt(); + indices = stream.readShortArray(numIndices); + + if (this.protocolVersion == 11) { + var _b = stream.readInt(); + if (_b == 1) { + var _color = { + r: stream.readFloat(), + g: stream.readFloat(), + b: stream.readFloat(), + a: stream.readFloat() + }; + } + } + stream.align4(); + + numPositions = stream.readInt(); + positions = stream.readFloatArray(numPositions); + numNormals = stream.readInt(); + normals = stream.readFloatArray(numNormals); + numColors = stream.readInt(); + if (numColors > 0) { + colors = stream.readFloatArray(numColors); + } else if (color != null) { + // Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors + colors = new Array(numPositions * 4); + for (var _i2 = 0; _i2 < numPositions; _i2++) { + colors[_i2 * 4 + 0] = color.r; + colors[_i2 * 4 + 1] = color.g; + colors[_i2 * 4 + 2] = color.b; + colors[_i2 * 4 + 3] = color.a; + } + } + + geometryIds.push(geometryId); + this.geometryIds[geometryDataOid].push(geometryId); + this.viewer.createGeometry(geometryId, positions, normals, colors, indices); + } + if (this.dataToInfo[geometryDataOid] != null) { + this.dataToInfo[geometryDataOid].forEach(function (oid) { + var ob = _this5.viewer.getObject(_this5.roid + ":" + oid); + geometryIds.forEach(function (geometryId) { + ob.add(geometryId); + }); + }); + delete this.dataToInfo[geometryDataOid]; + } + } else if (geometryType == 4) { + console.log("Unimplemented", 4); + } else if (geometryType == 5) { + var roid = stream.readLong(); + var geometryInfoOid = stream.readLong(); + var _objectBounds = stream.readDoubleArray(6); + var matrix = stream.readDoubleArray(16); + if (this.globalTransformationMatrix != null) { + xeogl.math.mulMat4(matrix, matrix, this.globalTransformationMatrix); + } + var _geometryDataOid = stream.readLong(); + var geometryDataOids = this.geometryIds[_geometryDataOid]; + var oid = this.infoToOid[geometryInfoOid]; + if (geometryDataOids == null) { + geometryDataOids = []; + var list = this.dataToInfo[_geometryDataOid]; + if (list == null) { + list = []; + this.dataToInfo[_geometryDataOid] = list; + } + list.push(oid); + } + if (oid == null) { + console.error("Not found", this.infoToOid, geometryInfoOid); + } else { + this.model.apiModel.get(oid, function (object) { + object.gid = geometryInfoOid; + var modelId = _this5.roid; // TODO: set to the model ID + _this5._createObject(modelId, roid, oid, oid, geometryDataOids, object.getType(), matrix); + }); + } + } else { + this.warn("Unsupported geometry type: " + geometryType); + return; + } + + this.state.nrObjectsRead++; + + this._updateProgress(); + } + }, { + key: "_createObject", + value: function _createObject(modelId, roid, oid, objectId, geometryIds, type, matrix) { + + if (this.state.mode == 0) { + console.log("Mode is still 0, should be 1"); + return; + } + + // this.models[roid].get(oid, + // function () { + if (this.viewer.createObject(modelId, roid, oid, objectId, geometryIds, type, matrix)) {} + + // this.objectAddedListeners.forEach(function (listener) { + // listener(objectId); + // }); + + + // }); + } + }]); + return BimServerGeometryLoader; +}(); + +var EventHandler = function () { + function EventHandler() { + classCallCheck(this, EventHandler); + + this.handlers = {}; + } + + createClass(EventHandler, [{ + key: "on", + value: function on(evt, handler) { + (this.handlers[evt] || (this.handlers[evt] = [])).push(handler); + } + }, { + key: "off", + value: function off(evt, handler) { + var h = this.handlers[evt]; + var found = false; + if (typeof h !== 'undefined') { + var i = h.indexOf(handler); + if (i >= -1) { + h.splice(i, 1); + found = true; + } + } + if (!found) { + throw new Error("Handler not found"); + } + } + }, { + key: "fire", + value: function fire(evt, args) { + var h = this.handlers[evt]; + if (!h) { + return; + } + for (var i = 0; i < h.length; ++i) { + h[i].apply(this, args); + } + } + }]); + return EventHandler; +}(); + +var DefaultMaterials = { + IfcSpace: [0.137255, 0.403922, 0.870588, 0.5], + IfcRoof: [0.837255, 0.203922, 0.270588, 1.0], + IfcSlab: [0.637255, 0.603922, 0.670588, 1.0], + IfcWall: [0.537255, 0.337255, 0.237255, 1.0], + IfcWallStandardCase: [0.537255, 0.337255, 0.237255, 1.0], + IfcDoor: [0.637255, 0.603922, 0.670588, 1.0], + IfcWindow: [0.137255, 0.403922, 0.870588, 0.5], + IfcOpeningElement: [0.137255, 0.403922, 0.870588, 0], + IfcRailing: [0.137255, 0.403922, 0.870588, 1.0], + IfcColumn: [0.137255, 0.403922, 0.870588, 1.0], + IfcBeam: [0.137255, 0.403922, 0.870588, 1.0], + IfcFurnishingElement: [0.137255, 0.403922, 0.870588, 1.0], + IfcCurtainWall: [0.137255, 0.403922, 0.870588, 1.0], + IfcStair: [0.637255, 0.603922, 0.670588, 1.0], + IfcStairFlight: [0.637255, 0.603922, 0.670588, 1.0], + IfcBuildingElementProxy: [0.5, 0.5, 0.5, 1.0], + IfcFlowSegment: [0.137255, 0.403922, 0.870588, 1.0], + IfcFlowitting: [0.137255, 0.403922, 0.870588, 1.0], + IfcFlowTerminal: [0.137255, 0.403922, 0.870588, 1.0], + IfcProxy: [0.137255, 0.403922, 0.870588, 1.0], + IfcSite: [0.137255, 0.403922, 0.870588, 1.0], + IfcLightFixture: [0.8470588235, 0.8470588235, 0.870588, 1.0], + IfcDuctSegment: [0.8470588235, 0.427450980392, 0, 1.0], + IfcDistributionFlowElement: [0.8470588235, 0.427450980392, 0, 1.0], + IfcDuctFitting: [0.8470588235, 0.427450980392, 0, 1.0], + IfcPlate: [0.8470588235, 0.427450980392, 0, 0.5], + IfcAirTerminal: [0.8470588235, 0.427450980392, 0, 1.0], + IfcMember: [0.8470588235, 0.427450980392, 0, 1.0], + IfcCovering: [0.8470588235, 0.427450980392, 0, 1.0], + IfcTransportElement: [0.8470588235, 0.427450980392, 0, 1.0], + IfcFlowController: [0.8470588235, 0.427450980392, 0, 1.0], + IfcFlowFitting: [0.8470588235, 0.427450980392, 0, 1.0], + IfcRamp: [0.8470588235, 0.427450980392, 0, 1.0], + IfcFurniture: [0.8470588235, 0.427450980392, 0, 1.0], + IfcFooting: [0.8470588235, 0.427450980392, 0, 1.0], + IfcSystemFurnitureElement: [0.8470588235, 0.427450980392, 0, 1.0], + //IfcSpace: [ 0.137255, 0.303922, 0.570588, 0.5], + DEFAULT: [0.5, 0.5, 0.5, 1.0] +}; + +var xmlToJson = function xmlToJson(node, attributeRenamer) { + if (node.nodeType === node.TEXT_NODE) { + var v = node.nodeValue; + if (v.match(/^\s+$/) === null) { + return v; + } + } else if (node.nodeType === node.ELEMENT_NODE || node.nodeType === node.DOCUMENT_NODE) { + var json = { + type: node.nodeName, + children: [] + }; + + if (node.nodeType === node.ELEMENT_NODE) { + for (var j = 0; j < node.attributes.length; j++) { + var attribute = node.attributes[j]; + var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName; + json[nm] = attribute.nodeValue; + } + } + + for (var i = 0; i < node.childNodes.length; i++) { + var item = node.childNodes[i]; + var _j = xmlToJson(item, attributeRenamer); + if (_j) { + json.children.push(_j); + } + } + + return json; + } +}; + +var clone = function clone(ob) { + return JSON.parse(JSON.stringify(ob)); +}; + +var guidChars = [["0", 10], ["A", 26], ["a", 26], ["_", 1], ["$", 1]].map(function (a) { + var li = []; + var st = a[0].charCodeAt(0); + var en = st + a[1]; + for (var i = st; i < en; ++i) { + li.push(i); + } + return String.fromCharCode.apply(null, li); +}).join(""); + +var b64 = function b64(v, len) { + var r = !len || len == 4 ? [0, 6, 12, 18] : [0, 6]; + return r.map(function (i) { + return guidChars.substr(parseInt(v / (1 << i)) % 64, 1); + }).reverse().join(""); +}; + +var compressGuid = function compressGuid(g) { + var bs = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30].map(function (i) { + return parseInt(g.substr(i, 2), 16); + }); + return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function (i) { + return b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]); + }).join(""); +}; + +var findNodeOfType = function findNodeOfType(m, t) { + var li = []; + var _ = function _(n) { + if (n.type === t) { + li.push(n); + } + (n.children || []).forEach(function (c) { + _(c); + }); + }; + + _(m); + return li; +}; + +var timeout = function timeout(dt) { + return new Promise(function (resolve, reject) { + setTimeout(resolve, dt); + }); +}; + +var Utils = { + 'XmlToJson': xmlToJson, + 'Clone': clone, + 'CompressGuid': compressGuid, + 'FindNodeOfType': findNodeOfType, + 'Delay': timeout +}; + +/** + Controls camera with mouse and keyboard, handles selection of entities and rotation point. + */ +xeogl.BIMCameraControl = xeogl.Component.extend({ + + type: "xeogl.BIMCameraControl", + + _init: function _init(cfg) { + + var self = this; + + var math = xeogl.math; + + // Configs + + var sensitivityKeyboardRotate = cfg.sensitivityKeyboardRotate || 0.5; + + var orthoScaleRate = 0.02; // Rate at which orthographic scale changes with zoom + + var canvasPickTolerance = 4; + var worldPickTolerance = 3; + + var pitchMat = math.mat4(); + + var camera = cfg.camera; + var view = camera.view; + var project = camera.project; + var scene = this.scene; + var input = scene.input; + + // Camera position on last mouse click + var rotateStartEye; + var rotateStartLook; + var rotateStartUp = math.vec3(); + + var orbitPitchAxis = math.vec3([1, 0, 0]); // The current axis for vertical orbit + + var pickHit; // Hit record from the most recent pick + var pickClicks = 0; // Number of times we've clicked on same spot on entity + + var mouseClickPos = math.vec2(); // Canvas position of last mouseDown + var firstPickCanvasPos = math.vec2(); // Canvas position of first pick + var firstPickWorldPos = math.vec2(); // World position of first pick + var firstPickTime; // Time of first pick + + var rotatePos = this._rotatePos = math.vec3([0, 0, 0]); // World-space pivot point we're currently rotating about + + var lastCanvasPos = math.vec2(); // Mouse's position in previous tick + var rotationDeltas = math.vec2(); // Accumulated angle deltas while rotating with keyboard or mouse + + var shiftDown = false; // True while shift key down + var mouseDown = false; // true while mouse down + + var flying = false; + + var lastHoverDistance = null; + + this._defaultDragAction = "orbit"; + + // Returns the inverse of the camera's current view transform matrix + var getInverseViewMat = function () { + var viewMatDirty = true; + camera.on("viewMatrix", function () { + viewMatDirty = true; + }); + var inverseViewMat = math.mat4(); + return function () { + if (viewMatDirty) { + math.inverseMat4(view.matrix, inverseViewMat); + } + return inverseViewMat; + }; + }(); + + // Returns the inverse of the camera's current projection transform matrix + var getInverseProjectMat = function () { + var projMatDirty = true; + camera.on("projectMatrix", function () { + projMatDirty = true; + }); + var inverseProjectMat = math.mat4(); + return function () { + if (projMatDirty) { + math.inverseMat4(project.matrix, inverseProjectMat); + } + return inverseProjectMat; + }; + }(); + + // Returns the transposed copy the camera's current projection transform matrix + var getTransposedProjectMat = function () { + var projMatDirty = true; + camera.on("projectMatrix", function () { + projMatDirty = true; + }); + var transposedProjectMat = math.mat4(); + return function () { + if (projMatDirty) { + math.transposeMat4(project.matrix, transposedProjectMat); + } + return transposedProjectMat; + }; + }(); + + // Get the current diagonal size of the scene + var getSceneDiagSize = function () { + var sceneSizeDirty = true; + var diag = 1; // Just in case + scene.worldBoundary.on("updated", function () { + sceneSizeDirty = true; + }); + return function () { + if (sceneSizeDirty) { + diag = math.getAABB3Diag(scene.worldBoundary.aabb); + } + return diag; + }; + }(); + + var rotate = function () { + var tempVec3a = math.vec3(); + var tempVec3b = math.vec3(); + var tempVec3c = math.vec3(); + return function (p) { + var p1 = math.subVec3(p, rotatePos, tempVec3a); + var p2 = math.transformVec3(pitchMat, p1, tempVec3b); + var p3 = math.addVec3(p2, rotatePos, tempVec3c); + return math.rotateVec3Z(p3, rotatePos, -rotationDeltas[0] * math.DEGTORAD, math.vec3()); + }; + }(); + + // Rotation point indicator + + var pickHelper = this.create({ + type: "xeogl.Entity", + geometry: this.create({ + type: "xeogl.SphereGeometry", + radius: 1.0 + }), + material: this.create({ + type: "xeogl.PhongMaterial", + diffuse: [0, 0, 0], + ambient: [0, 0, 0], + specular: [0, 0, 0], + emissive: [1.0, 1.0, 0.6], // Glowing + lineWidth: 4 + }), + transform: this.create({ + type: "xeogl.Translate", + xyz: [0, 0, 0] + }), + visibility: this.create({ + type: "xeogl.Visibility", + visible: false // Initially invisible + }), + modes: this.create({ + type: "xeogl.Modes", + collidable: false // This helper has no collision boundary of its own + }) + }); + + // Shows the rotation point indicator + // at the given position for one second + + var showRotationPoint = function () { + + var pickHelperHide = null; + + return function (pos) { + + pickHelper.transform.xyz = pos; + pickHelper.visibility.visible = true; + + if (pickHelperHide) { + clearTimeout(pickHelperHide); + pickHelperHide = null; + } + + pickHelperHide = setTimeout(function () { + pickHelper.visibility.visible = false; + pickHelperHide = null; + }, 1000); + }; + }(); + + var pickTimer; + + // Fires a "pick" after a timeout period unless clearPickTimer is called before then. + function startPickTimer() { + + if (pickTimer) { + clearPickTimer(); + } + + pickTimer = setTimeout(function () { + pickClicks = 0; + self.fire("pick", pickHit); + pickTimer = null; + }, 250); + } + + // Stops a previous call to startPickTimer from firing a "pick" + function clearPickTimer() { + clearTimeout(pickTimer); + pickTimer = null; + } + + function resetRotate() { + + pickClicks = 0; + + rotationDeltas[0] = 0; + rotationDeltas[1] = 0; + + rotateStartEye = view.eye.slice(); + rotateStartLook = view.look.slice(); + math.addVec3(rotateStartEye, view.up, rotateStartUp); + + setOrbitPitchAxis(); + } + + function setOrbitPitchAxis() { + math.cross3Vec3(math.normalizeVec3(math.subVec3(view.eye, view.look, math.vec3())), view.up, orbitPitchAxis); + } + + var setCursor = function () { + + var t; + + return function (cursor, persist) { + + clearTimeout(t); + + self.scene.canvas.overlay.style["cursor"] = cursor; + + if (!persist) { + t = setTimeout(function () { + self.scene.canvas.overlay.style["cursor"] = "auto"; + }, 100); + } + }; + }(); + + input.on("mousedown", function (canvasPos) { + + canvasPos = canvasPos.slice(); + + if (!input.mouseover) { + return; + } + + if (!input.mouseDownLeft) { + return; + } + + if (flying) { + return; + } + + clearPickTimer(); + + setOrbitPitchAxis(); + + rotateStartEye = view.eye.slice(); + rotateStartLook = view.look.slice(); + math.addVec3(rotateStartEye, view.up, rotateStartUp); + + pickHit = scene.pick({ + canvasPos: canvasPos, + pickSurface: true + }); + + if (pickHit && pickHit.worldPos) { + + var pickWorldPos = pickHit.worldPos.slice(); + var pickCanvasPos = canvasPos; + + var pickTime = Date.now(); + + if (pickClicks === 1) { + + if (pickTime - firstPickTime < 250 && closeEnoughCanvas(canvasPos, firstPickCanvasPos) && closeEnoughWorld(pickWorldPos, firstPickWorldPos)) { + + // Double-clicked + + rotatePos.set(pickWorldPos); + + showRotationPoint(pickWorldPos); + } + + pickClicks = 0; + } else { + + pickClicks = 1; + + firstPickWorldPos = pickWorldPos; + firstPickCanvasPos = pickCanvasPos; + firstPickTime = pickTime; + } + } else { + + pickClicks = 0; + } + + mouseClickPos[0] = canvasPos[0]; + mouseClickPos[1] = canvasPos[1]; + + rotationDeltas[0] = 0; + rotationDeltas[1] = 0; + + mouseDown = true; + }); + + // Returns true if the two Canvas-space points are + // close enough to be considered the same point + + function closeEnoughCanvas(p, q) { + return p[0] >= q[0] - canvasPickTolerance && p[0] <= q[0] + canvasPickTolerance && p[1] >= q[1] - canvasPickTolerance && p[1] <= q[1] + canvasPickTolerance; + } + + // Returns true if the two World-space points are + // close enough to be considered the same point + + function closeEnoughWorld(p, q) { + return p[0] >= q[0] - worldPickTolerance && p[0] <= q[0] + worldPickTolerance && p[1] >= q[1] - worldPickTolerance && p[1] >= q[1] - worldPickTolerance && p[2] <= q[2] + worldPickTolerance && p[2] <= q[2] + worldPickTolerance; + } + + var tempVecHover = math.vec3(); + + var updateHoverDistanceAndCursor = function updateHoverDistanceAndCursor(canvasPos) { + var hit = scene.pick({ + canvasPos: canvasPos || lastCanvasPos, + pickSurface: true + }); + + if (hit) { + setCursor("pointer", true); + if (hit.worldPos) { + // TODO: This should be somehow hit.viewPos.z, but doesn't seem to be + lastHoverDistance = math.lenVec3(math.subVec3(hit.worldPos, view.eye, tempVecHover)); + } + } else { + setCursor("auto", true); + } + }; + + input.on("mousemove", function (canvasPos) { + + if (!input.mouseover) { + return; + } + + if (flying) { + return; + } + + if (!mouseDown) { + + updateHoverDistanceAndCursor(canvasPos); + + lastCanvasPos[0] = canvasPos[0]; + lastCanvasPos[1] = canvasPos[1]; + + return; + } + + var sceneSize = getSceneDiagSize(); + + // Use normalized device coords + var canvas = scene.canvas.canvas; + var cw2 = canvas.offsetWidth / 2.; + var ch2 = canvas.offsetHeight / 2.; + + var inverseProjMat = getInverseProjectMat(); + var inverseViewMat = getInverseViewMat(); + + // Get last two columns of projection matrix + var transposedProjectMat = getTransposedProjectMat(); + var Pt3 = transposedProjectMat.subarray(8, 12); + var Pt4 = transposedProjectMat.subarray(12); + + // TODO: Should be simpler to get the projected Z value + var D = [0, 0, -(lastHoverDistance || sceneSize), 1]; + var Z = math.dotVec4(D, Pt3) / math.dotVec4(D, Pt4); + + // Returns in camera space and model space as array of two points + var unproject = function unproject(p) { + var cp = math.vec4(); + cp[0] = (p[0] - cw2) / cw2; + cp[1] = (p[1] - ch2) / ch2; + cp[2] = Z; + cp[3] = 1.; + cp = math.vec4(math.mulMat4v4(inverseProjMat, cp)); + + // Normalize homogeneous coord + math.mulVec3Scalar(cp, 1.0 / cp[3]); + cp[3] = 1.0; + + // TODO: Why is this reversed? + cp[0] *= -1; + + var cp2 = math.vec4(math.mulMat4v4(inverseViewMat, cp)); + return [cp, cp2]; + }; + + var A = unproject(canvasPos); + var B = unproject(lastCanvasPos); + + var panning = self._defaultDragAction === "pan"; + + if (input.keyDown[input.KEY_SHIFT] || input.mouseDownMiddle || input.mouseDownLeft && input.mouseDownRight) { + panning = !panning; + } + + if (panning) { + // TODO: view.pan is in view space? We have a world coord vector. + + // Subtract model space unproject points + math.subVec3(A[1], B[1], tempVecHover); + view.eye = math.addVec3(view.eye, tempVecHover); + view.look = math.addVec3(view.look, tempVecHover); + } else { + // If not panning, we are orbiting + + // Subtract camera space unproject points + math.subVec3(A[0], B[0], tempVecHover); + + // v because reversed above + var xDelta = -tempVecHover[0] * Math.PI; + var yDelta = tempVecHover[1] * Math.PI; + + rotationDeltas[0] += xDelta; + rotationDeltas[1] += yDelta; + + math.rotationMat4v(rotationDeltas[1] * math.DEGTORAD, orbitPitchAxis, pitchMat); + + view.eye = rotate(rotateStartEye); + view.look = rotate(rotateStartLook); + view.up = math.subVec3(rotate(rotateStartUp), view.eye, math.vec3()); + } + + lastCanvasPos[0] = canvasPos[0]; + lastCanvasPos[1] = canvasPos[1]; + }); + + input.on("keydown", function (keyCode) { + if (keyCode === input.KEY_SHIFT) { + shiftDown = true; + } + }); + + input.on("keyup", function (keyCode) { + if (keyCode === input.KEY_SHIFT) { + shiftDown = false; + resetRotate(); + } + }); + + input.on("mouseup", function (canvasPos) { + + if (!mouseDown) { + return; + } + + if (flying) { + return; + } + + mouseDown = false; + + if (input.mouseover) { + + if (firstPickCanvasPos && closeEnoughCanvas(canvasPos, firstPickCanvasPos)) { + + if (pickClicks === 1) { + + if (shiftDown) { + + pickClicks = 0; + + self.fire("pick", pickHit); + } else { + startPickTimer(); + } + } else { + // self.fire("nopick"); + } + } else if (pickClicks === 0) { + + if (mouseClickPos && closeEnoughCanvas(canvasPos, mouseClickPos)) { + + self.fire("nopick"); + } + } + } + }); + + input.on("dblclick", function () { + + if (flying) { + return; + } + + mouseDown = false; + }); + + //--------------------------------------------------------------------------------------------------------- + // Keyboard rotate camera + //--------------------------------------------------------------------------------------------------------- + + + scene.on("tick", function (params) { + + if (!input.mouseover) { + return; + } + + if (mouseDown) { + return; + } + + if (flying) { + return; + } + + if (!input.ctrlDown && !input.altDown) { + + var left = input.keyDown[input.KEY_LEFT_ARROW]; + var right = input.keyDown[input.KEY_RIGHT_ARROW]; + var up = input.keyDown[input.KEY_UP_ARROW]; + var down = input.keyDown[input.KEY_DOWN_ARROW]; + + if (left || right || up || down) { + + var elapsed = params.deltaTime; + var yawRate = sensitivityKeyboardRotate * 0.3; + var pitchRate = sensitivityKeyboardRotate * 0.3; + var yaw = 0; + var pitch = 0; + + if (right) { + yaw = -elapsed * yawRate; + } else if (left) { + yaw = elapsed * yawRate; + } + + if (down) { + pitch = elapsed * pitchRate; + } else if (up) { + pitch = -elapsed * pitchRate; + } + + if (Math.abs(yaw) > Math.abs(pitch)) { + pitch = 0; + } else { + yaw = 0; + } + + rotationDeltas[0] -= yaw; + rotationDeltas[1] += pitch; + + math.rotationMat4v(rotationDeltas[1] * math.DEGTORAD, orbitPitchAxis, pitchMat); + + view.eye = rotate(rotateStartEye); + view.look = rotate(rotateStartLook); + view.up = math.subVec3(rotate(rotateStartUp), view.eye, math.vec3()); + } + } + }); + + //--------------------------------------------------------------------------------------------------------- + // Keyboard zoom camera + //--------------------------------------------------------------------------------------------------------- + + (function () { + + var tempVec3a = math.vec3(); + var tempVec3b = math.vec3(); + var tempVec3c = math.vec3(); + var eyePivotVec = math.vec3(); + + scene.on("tick", function (params) { + + if (!input.mouseover) { + return; + } + + if (mouseDown) { + return; + } + + if (flying) { + return; + } + + var elapsed = params.deltaTime; + + if (!input.ctrlDown && !input.altDown) { + + var wkey = input.keyDown[input.KEY_ADD]; + var skey = input.keyDown[input.KEY_SUBTRACT]; + + if (wkey || skey) { + + var sceneSize = getSceneDiagSize(); + var rate = sceneSize / 5000.0; + + var delta = 0; + + if (skey) { + delta = elapsed * rate; // Want sensitivity configs in [0..1] range + } else if (wkey) { + delta = -elapsed * rate; + } + + var eye = view.eye; + var look = view.look; + + // Get vector from eye to center of rotation + math.mulVec3Scalar(math.normalizeVec3(math.subVec3(eye, rotatePos, tempVec3a), tempVec3b), delta, eyePivotVec); + + // Move eye and look along the vector + view.eye = math.addVec3(eye, eyePivotVec, tempVec3c); + view.look = math.addVec3(look, eyePivotVec, tempVec3c); + + if (project.isType("xeogl.Ortho")) { + project.scale += delta * orthoScaleRate; + } + + resetRotate(); + } + } + }); + })(); + + //--------------------------------------------------------------------------------------------------------- + // Mouse zoom + // Roll mouse wheel to move eye and look closer or further from center of rotationDeltas + //--------------------------------------------------------------------------------------------------------- + + (function () { + + var delta = 0; + var target = 0; + var newTarget = false; + var targeting = false; + var progress = 0; + + var tempVec3a = math.vec3(); + var tempVec3b = math.vec3(); + var newEye = math.vec3(); + var newLook = math.vec3(); + var eyePivotVec = math.vec3(); + + input.on("mousewheel", function (_delta) { + + if (mouseDown) { + return; + } + + if (flying) { + return; + } + + delta = -_delta; + + if (delta === 0) { + targeting = false; + newTarget = false; + } else { + newTarget = true; + } + }); + + var updateTimeout = null; + + scene.on("tick", function (e) { + + if (!targeting && !newTarget) { + return; + } + + if (mouseDown) { + return; + } + + if (flying) { + return; + } + + if (updateTimeout) { + clearTimeout(updateTimeout); + } + updateTimeout = setTimeout(function () { + updateHoverDistanceAndCursor(); + updateTimeout = null; + }, 50); + + var zoomTimeInSeconds = 0.2; + var viewDistance = getSceneDiagSize(); + if (lastHoverDistance) { + viewDistance = viewDistance * 0.02 + lastHoverDistance; + } + + var tickDeltaSecs = e.deltaTime / 1000.0; + var f = viewDistance * (delta < 0 ? -1 : 1) / zoomTimeInSeconds / 100.; + + if (newTarget) { + + target = zoomTimeInSeconds; + + progress = 0; + newTarget = false; + targeting = true; + } + + if (targeting) { + + progress += tickDeltaSecs; + + if (progress > target) { + targeting = false; + } + + if (targeting) { + + var eye = view.eye; + var look = view.look; + + math.mulVec3Scalar(xeogl.math.transposeMat4(view.matrix).slice(8), f, eyePivotVec); + math.addVec3(eye, eyePivotVec, newEye); + math.addVec3(look, eyePivotVec, newLook); + + var lenEyePivotVec = Math.abs(math.lenVec3(eyePivotVec)); + var currentEyePivotDist = Math.abs(math.lenVec3(math.subVec3(eye, rotatePos, math.vec3()))); + + // if (lenEyePivotVec < currentEyePivotDist - 10) { + + // Move eye and look along the vector + view.eye = newEye; + view.look = newLook; + + if (project.isType("xeogl.Ortho")) { + project.scale += delta * orthoScaleRate; + } + // } + + resetRotate(); + } + } + }); + })(); + + //--------------------------------------------------------------------------------------------------------- + // Keyboard axis view + // Press 1,2,3,4,5 or 6 to view center of model from along an axis + //--------------------------------------------------------------------------------------------------------- + + (function () { + + var flight = self.create({ + type: "xeogl.CameraFlightAnimation", + camera: camera, + duration: 1.0 // One second to fly to each new target + }); + + function fly(eye, look, up) { + + rotatePos.set(look); + + flying = true; + + flight.cancel(); + + flight.flyTo({ + look: look, + eye: eye, + up: up + }, function () { + resetRotate(); + + flying = false; + }); + } + + input.on("keydown", function (keyCode) { + + if (!input.mouseover) { + return; + } + + if (mouseDown) { + return; + } + + if (keyCode !== input.KEY_NUM_1 && keyCode !== input.KEY_NUM_2 && keyCode !== input.KEY_NUM_3 && keyCode !== input.KEY_NUM_4 && keyCode !== input.KEY_NUM_5 && keyCode !== input.KEY_NUM_6) { + return; + } + + var boundary = scene.worldBoundary; + var aabb = boundary.aabb; + var center = boundary.center; + var diag = math.getAABB3Diag(aabb); + var fitFOV = 55; + var dist = Math.abs(diag / Math.tan(fitFOV / 2)); + + switch (keyCode) { + + case input.KEY_NUM_1: + // Right view + fly(math.vec3([center[0] - dist, center[1], center[2]]), center, math.vec3([0, 0, 1])); + break; + + case input.KEY_NUM_2: + // Back view + fly(math.vec3([center[0], center[1] + dist, center[2]]), center, math.vec3([0, 0, 1])); + break; + + case input.KEY_NUM_3: + // Left view + fly(math.vec3([center[0] + dist, center[1], center[2]]), center, math.vec3([0, 0, 1])); + break; + + case input.KEY_NUM_4: + // Front view + fly(math.vec3([center[0], center[1] - dist, center[2]]), center, math.vec3([0, 0, 1])); + break; + + case input.KEY_NUM_5: + // Top view + fly(math.vec3([center[0], center[1], center[2] + dist]), center, math.vec3([0, 1, 0])); + break; + + case input.KEY_NUM_6: + // Bottom view + fly(math.vec3([center[0], center[1], center[2] - dist]), center, math.vec3([0, -1, 0])); + break; + + default: + return; + } + }); + })(); + + //--------------------------------------------------------------------------------------------------------- + // Keyboard pan camera + // Press W,S,A or D to pan the camera + //--------------------------------------------------------------------------------------------------------- + + scene.on("tick", function () { + + var tempVec3 = math.vec3(); + + return function (params) { + + if (mouseDown) { + return; + } + + if (!input.mouseover) { + return; + } + + if (flying) { + return; + } + + var elapsed = params.deltaTime; + + if (!input.ctrlDown && !input.altDown) { + + var wkey = input.keyDown[input.KEY_W]; + var skey = input.keyDown[input.KEY_S]; + var akey = input.keyDown[input.KEY_A]; + var dkey = input.keyDown[input.KEY_D]; + var zkey = input.keyDown[input.KEY_Z]; + var xkey = input.keyDown[input.KEY_X]; + + if (wkey || skey || akey || dkey || xkey || zkey) { + + var x = 0; + var y = 0; + var z = 0; + + var sceneSize = getSceneDiagSize(); + var sensitivity = sceneSize / 4000.0; + + if (skey) { + y = elapsed * sensitivity; + } else if (wkey) { + y = -elapsed * sensitivity; + } + + if (dkey) { + x = elapsed * sensitivity; + } else if (akey) { + x = -elapsed * sensitivity; + } + + if (xkey) { + z = elapsed * sensitivity; + } else if (zkey) { + z = -elapsed * sensitivity; + } + + tempVec3[0] = x; + tempVec3[1] = y; + tempVec3[2] = z; + + view.pan(tempVec3); + + resetRotate(); + } + } + }; + }()); + }, + + _props: { + + // The position we're currently orbiting + rotatePos: { + + set: function set(value) { + + if (value) { + this._rotatePos.set(value); + } + } + }, + + defaultDragAction: { + set: function set(value) { + if (value === "pan" || value === "orbit") { + this._defaultDragAction = value; + } + } + } + } +}); + +/** + Custom xeoEngine component that represents a BIMSurfer model within a xeoEngine scene. + @class BIMModel + @module XEO + @constructor + @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. + @param [cfg] {*} Configs + @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. + @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this xeogl.BIMModel. + @extends Component + */ +xeogl.BIMModel = xeogl.Component.extend({ + + // JavaScript class name for this xeogl.BIMModel. + type: "xeogl.BIMModel", + + // Constructor + _init: function _init(cfg) { + this.collection = this.create({ + type: "xeogl.Collection" // http://xeoengine.org/docs/classes/Collection.html + }); + }, + + // Adds a BIMObject to this BIMModel + addObject: function addObject(object) { + this.collection.add(object); + } +}); + +/** + Custom xeoEngine component that represents a BIMSurfer object within a xeoEngine scene. + An object consists of a set of xeogl.Entity's that share components between them. + The components control functionality for the Entity's as a group, while the Entity's + themselves each have their own xeogl.Geometry. + This way, we are able to have BIM objects containing multiple geometries. + @class BIMObject + @module XEO + @constructor + @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. + @param [cfg] {*} Configs + @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. + @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this xeogl.BIMObject. + @extends Component + */ +xeogl.BIMObject = xeogl.Component.extend({ + + /** + JavaScript class name for this xeogl.BIMObject. + @property type + @type String + @final + */ + type: "xeogl.BIMObject", + + // Constructor + + _init: function _init(cfg) { + + // Model this object belongs to, will be null when no model + this.model = cfg.model; // xeogl.BIMModel + + // Modelling transform component + this.transform = this.create({ + type: "xeogl.Transform", // http://xeoengine.org/docs/classes/Transform.html + matrix: cfg.matrix + }); + + // Visibility control component. + this.visibility = this.create({ + type: "xeogl.Visibility", // http://xeoengine.org/docs/classes/Visibility.html + visible: true + }); + + // Material component + this.material = this.create({ + type: "xeogl.PhongMaterial", // http://xeoengine.org/docs/classes/Material.html + emissive: [0, 0, 0], + diffuse: [Math.random(), Math.random(), Math.random()], // Random color until we set for type + opacity: 1.0 + }); + + // Rendering modes component + this.modes = this.create({ + type: "xeogl.Modes", // http://xeoengine.org/docs/classes/Modes.html + transparent: false, + backfaces: true + }); + + // When highlighting, causes this object to render after non-highlighted objects + this.stage = this.create({ + type: "xeogl.Stage", + priority: 0 + }); + + // When highlighting, we use this component to disable depth-testing so that this object + // appears to "float" over non-highlighted objects + this.depthBuf = this.create({ + type: "xeogl.DepthBuf", + active: true + }); + + // Create a xeogl.Entity for each xeogl.Geometry + // Each xeogl.Entity shares the components defined above + + // TODO: If all geometries are of same primitive, then we can combine them + + this.entities = []; + var entity; + + for (var i = 0, len = cfg.geometryIds.length; i < len; i++) { + + entity = this.create({ // http://xeoengine.org/docs/classes/Entity.html + type: "xeogl.Entity", + meta: { + objectId: this.id + }, + geometry: "geometry." + cfg.geometryIds[i], + transform: this.transform, + visibility: this.visibility, + material: this.material, + modes: this.modes, + stage: this.stage, + depthBuf: this.depthBuf + }); + + this.entities.push(entity); + } + }, + + add: function add(geometryId) { + var entity = this.create({ // http://xeoengine.org/docs/classes/Entity.html + type: "xeogl.Entity", + meta: { + objectId: this.id + }, + geometry: "geometry." + geometryId, + transform: this.transform, + visibility: this.visibility, + material: this.material, + modes: this.modes, + stage: this.stage, + depthBuf: this.depthBuf + }); + + this.entities.push(entity); + }, + + // Define read-only properties of xeogl.BIMObject + + _props: { + + // World-space bounding volume + worldBoundary: { + get: function get() { + return this.entities[0].worldBoundary; + } + }, + + // View-space bounding volume + viewBoundary: { + get: function get() { + return this.entities[0].viewBoundary; + } + }, + + // Canvas-space bounding volume + canvasBoundary: { + get: function get() { + return this.entities[0].viewBoundary; + } + }, + + // Whether or not this object is highlighted + highlighted: { + set: function set(highlight) { + this.depthBuf.active = !highlight; + this.stage.priority = highlight ? 2 : 0; + this.material.emissive = highlight ? [0.5, 0.5, 0.5] : [0, 0, 0]; + } + } + } +}); + +/** + Custom xeoEngine component that shows a wireframe box representing an non axis-aligned 3D boundary. + */ +var BIMBoundaryHelperEntity = xeogl.Entity.extend({ + + type: "xeogl.BIMBoundaryHelperEntity", + + _init: function _init(cfg) { + + var obbGeometry = this.create({ + type: "xeogl.OBBGeometry" + }); + + var phongMaterial = this.create({ + type: "xeogl.PhongMaterial", + diffuse: cfg.color || [0.5, 0.5, 0.5], + ambient: [0, 0, 0], + specular: [0, 0, 0], + lineWidth: 2 + }); + + var nonCollidableMode = this.create({ + type: "xeogl.Modes", + collidable: false // This helper has no collision boundary of its own + }); + + // Causes this entity to render after all other entities + var stagePriorityThree = this.create({ + type: "xeogl.Stage", + priority: 3 + }); + + var visible = this.create({ + type: "xeogl.Visibility", + visible: true + }); + + // Disables depth-testing so that this entity + // appears to "float" over other entities + var depthBufInactive = this.create({ + type: "xeogl.DepthBuf", + active: false + }); + + this._super(xeogl._apply({ + geometry: obbGeometry, + material: phongMaterial, + modes: nonCollidableMode, + stage: stagePriorityThree, + depthBuf: depthBufInactive, + visibility: visible + }, cfg)); + } +}); + +xeogl.BIMBoundaryHelper = function (scene, viewer, cfg) { + + var self = this; + + self._entities = {}; + self._pool = []; + + self.setSelected = function (ids) { + + var oldIds = Object.keys(self._entities); + + ids.forEach(function (id) { + if (!self._entities[id]) { + var h; + if (self._pool.length > 0) { + h = self._entities[id] = self._pool.shift(); + h.visibility.visible = true; + } else { + h = self._entities[id] = new BIMBoundaryHelperEntity(scene, cfg); + } + h.geometry.boundary = viewer.getObject(id).worldBoundary; + } + + var oldIdx = oldIds.indexOf(id); + if (oldIdx !== -1) { + oldIds.splice(oldIdx, 1); + } + }); + + oldIds.forEach(function (id) { + var h = self._entities[id]; + h.visibility.visible = false; + self._pool.push(h); + delete self._entities[id]; + }); + }; +}; + +xeogl.HighlightEffect = xeogl.Component.extend({ + + type: "xeogl.HighlightEffect", + + _init: function _init(cfg) { + + this._modes = this.create({ + type: "xeogl.Modes", + transparent: true, + collidable: false // Has no collision boundary of its own + }); + + this._stage = this.create({ + type: "xeogl.Stage", + priority: 2 + }); + + this._depthBuf = this.create({ + type: "xeogl.DepthBuf", + active: false + }); + + this._emissiveColor = (cfg.color || [0.2, 0.9, 0.2]).slice(0, 3); + this._opacity = cfg.color && cfg.color.length > 3 ? cfg.color[3] : 0.25; + + this._helpers = {}; + this._freeHelpers = []; + }, + + add: function add(bimObject) { + var entities = bimObject.entities; + if (entities) { + var entity; + for (var i = 0, len = entities.length; i < len; i++) { + entity = entities[i]; + this._createHelper(entity); + } + } else { + this._createHelper(bimObject); + } + }, + + _createHelper: function _createHelper(entity) { + var helper = this._freeHelpers.pop(); + if (!helper) { + helper = this.create({ + type: "xeogl.Entity", + geometry: entity.geometry, + transform: entity.transform, + material: this.create({ + type: "xeogl.PhongMaterial", + emissive: this._emissiveColor, + specular: [0, 0, 0], + diffuse: [0, 0, 0], + ambient: [0, 0, 0], + opacity: this._opacity + }), + modes: this._modes, + stage: this._stage, + depthBuf: this._depthBuf, + visibility: this.create({ + type: "xeogl.Visibility", + visible: true + }), + meta: { + entityId: entity.id + } + }); + } else { + helper.geometry = entity.geometry; + helper.material.diffuse = entity.material.diffuse; + helper.material.ambient = entity.material.ambient; + helper.transform = entity.transform; + helper.visibility.visible = true; + helper.meta.entityId = entity.id; + } + this._helpers[entity.id] = helper; + }, + + clear: function clear() { + var helper; + for (var id in this._helpers) { + if (this._helpers.hasOwnProperty(id)) { + helper = this._helpers[id]; + this._destroyHelper(helper); + } + } + }, + + remove: function remove(bimObject) { + var entities = bimObject.entities; + var entity; + for (var i = 0, len = entities.length; i < len; i++) { + entity = entities[i]; + var helper = this._helpers[entity.id]; + if (helper) { + this._destroyHelper(helper); + } + } + }, + + _destroyHelper: function _destroyHelper(helper) { + helper.visibility.visible = false; + this._freeHelpers.push(helper); + delete this._helpers[helper.meta.entityId]; + } + +}); + +/** + * Components for managing collections of components. + * + * @module xeogl + * @submodule collections + */ /** + A **Collection** is a set of {{#crossLink "Component"}}Components{{/crossLink}}. +
    +
  • A {{#crossLink "Component"}}Component{{/crossLink}} can be included in more than one Collection.
  • +
  • {{#crossLink "Component"}}Components{{/crossLink}} can be added to a Collection by instance, ID or type.
  • +
  • A Collection supports iteration over its {{#crossLink "Component"}}Components{{/crossLink}}.
  • +
  • A {{#crossLink "Model"}}Model{{/crossLink}} stores the {{#crossLink "Component"}}Components{{/crossLink}} it has loaded in a Collection.
  • +
  • A {{#crossLink "CollectionBoundary"}}CollectionBoundary{{/crossLink}} provides a World-space {{#crossLink "Boundary3D"}}{{/crossLink}} that encloses a Collection.
  • +
+ + ## Examples +
    +
  • [Adding Entities to a Collection](../../examples/#collections_Collection_creating_add)
  • +
  • [Adding components types to a Collection](../../examples/#collections_Collection_creating_type)
  • +
  • [Iterating a Collection](../../examples/#boundaries_Collection_iterating)
  • +
  • [Visualizing World-space boundary of a Collection](../../examples/#boundaries_CollectionBoundary)
  • +
  • [Visualizing World-space boundaries of a hierarchy of Collections](../../examples/#boundaries_CollectionBoundary_hierarchy)
  • +
+ ## Creating Collections + Our first Collection contains a {{#crossLink "PhongMaterial"}}{{/crossLink}}, added by ID, plus a {{#crossLink "BoxGeometry"}}{{/crossLink}} and + an {{#crossLink "Entity"}}{{/crossLink}}, both added by instance. + ````javascript + var material = new xeogl.PhongMaterial({ + id: "myMaterial", + diffuse: [0.5, 0.5, 0.0] + }); + var geometry = new xeogl.BoxGeometry(); + var entity = new xeogl.Entity({ + id: "myEntity", + material: material, + geometry: geometry + }); + var collection1 = new xeogl.Collection({ // Initialize with the three components + components: [ + "myMaterial", + geometry, + myEntity + ] + }); + ```` + Our second Collection includes the {{#crossLink "BoxGeometry"}}{{/crossLink}}, added by instance, + and the {{#crossLink "Entity"}}{{/crossLink}}, added by type. If there were more than + one {{#crossLink "Entity"}}{{/crossLink}} in the scene, then that type would ensure + that all the {{#crossLink "Entity"}}Entities{{/crossLink}} were in the Collection. + ````javascript + var collection2 = new xeogl.Collection(); + collection2.add([ // Add two components + geometry, + "xeogl.Entity", + ]); + ```` + ## Accessing Components + Iterate over the components in a Collection using the convenience iterator: + ````javascript + collection1.iterate(function(component) { + if (component.isType("xeogl.Entity")) { + this.log("Found the Entity: " + component.id); + } + //.. + }); + ```` + A Collection also registers its components by type: + ````javascript + var entities = collection1.types["xeogl.Entity"]; + var theEntity = entities["myEntity"]; + ```` + ## Removing Components + We can remove components from a Collection by instance, ID or type: + ````javascript + collection1.remove("myMaterial"); // Remove one component by ID + collection1.remove([geometry, myEntity]); // Remove two components by instance + collection2.remove("xeogl.Geometry"); // Remove all Geometries + ```` + ## Getting the boundary of a Collection + A {{#crossLink "CollectionBoundary"}}{{/crossLink}} provides a {{#crossLink "Boundary3D"}}{{/crossLink}} that + dynamically fits to the collective World-space boundary of all the Components in a Collection. + ````javascript + var collectionBoundary = new xeogl.CollectionBoundary({ + collection: collection1 + }); + var worldBoundary = collectionBoundary.worldBoundary; + ```` + The {{#crossLink "Boundary3D"}}{{/crossLink}} + will automatically update whenever we add, remove or update any Components that have World-space boundaries. We can subscribe + to updates on it like so: + ````javascript + worldBoundary.on("updated", function() { + obb = worldBoundary.obb; + aabb = worldBoundary.aabb; + center = worldBoundary.center; + //... + }); + ```` + Now, if we now re-insert our {{#crossLink "Entity"}}{{/crossLink}} into to our Collection, + the {{#crossLink "Boundary3D"}}{{/crossLink}} will fire our update handler. + ````javascript + collection1.add(myEntity); + ```` + @class Collection + @module xeogl + @submodule collections + @constructor + @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. + @param [cfg] {*} Configs + @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. + @param [cfg.meta] {String:Component} Optional map of user-defined metadata to attach to this Collection. + @param [cfg.components] {{Array of String|Component}} Array of {{#crossLink "Component"}}{{/crossLink}} IDs or instances. + @extends Component + */ +xeogl.Collection = xeogl.Component.extend({ + + /** + JavaScript class name for this Component. + @property type + @type String + @final + */ + type: "xeogl.Collection", + + _init: function _init(cfg) { + + /** + * The {{#crossLink "Components"}}{{/crossLink}} within this Collection, mapped to their IDs. + * + * Fires an {{#crossLink "Collection/updated:event"}}{{/crossLink}} event on change. + * + * @property components + * @type {{String:Component}} + */ + this.components = {}; + + /** + * The number of {{#crossLink "Components"}}{{/crossLink}} within this Collection. + * + * @property numComponents + * @type Number + */ + this.numComponents = 0; + + /** + * A map of maps; for each {{#crossLink "Component"}}{{/crossLink}} type in this Collection, + * a map to IDs to {{#crossLink "Component"}}{{/crossLink}} instances, eg. + * + * ```` + * "xeogl.Geometry": { + * "alpha": , + * "beta": + * }, + * "xeogl.Rotate": { + * "charlie": , + * "delta": , + * "echo": , + * }, + * //... + * ```` + * + * @property types + * @type {String:{String:xeogl.Component}} + */ + this.types = {}; + + // Subscriptions to "destroyed" events from components + this._destroyedSubs = {}; + + if (cfg.components) { + this.add(cfg.components); + } + }, + + /** + * Adds one or more {{#crossLink "Component"}}Components{{/crossLink}}s to this Collection. + * + * The {{#crossLink "Component"}}Component(s){{/crossLink}} may be specified by instance, ID or type. + * + * See class comment for usage examples. + * + * The {{#crossLink "Component"}}Components{{/crossLink}} must be in the same {{#crossLink "Scene"}}{{/crossLink}} as this Collection. + * + * Fires an {{#crossLink "Collection/added:event"}}{{/crossLink}} event. + * + * @method add + * @param {Array of Component} components Array of {{#crossLink "Component"}}Components{{/crossLink}} instances. + */ + add: function add(components) { + + components = xeogl._isArray(components) ? components : [components]; + + for (var i = 0, len = components.length; i < len; i++) { + this._add(components[i]); + } + }, + + _add: function _add(c) { + + var componentId; + var component; + var type; + var types; + + if (c.type) { + + // Component instance + + component = c; + } else if (xeogl._isNumeric(c) || xeogl._isString(c)) { + + if (this.scene.types[c]) { + + // Component type + + type = c; + + types = this.scene.types[type]; + + if (!types) { + this.warn("Component type not found: '" + type + "'"); + return; + } + + for (componentId in types) { + if (types.hasOwnProperty(componentId)) { + this._add(types[componentId]); + } + } + + return; + } else { + + // Component ID + + component = this.scene.components[c]; + + if (!component) { + this.warn("Component not found: " + xeogl._inQuotes(c)); + return; + } + } + } else { + + return; + } + + if (component.scene !== this.scene) { + + // Component in wrong Scene + + this.warn("Attempted to add component from different xeogl.Scene: " + xeogl._inQuotes(component.id)); + return; + } + + // Add component to this map + + if (this.components[component.id]) { + + // Component already in this Collection + return; + } + + this.components[component.id] = component; + + // Register component for its type + + types = this.types[component.type]; + + if (!types) { + types = this.types[component.type] = {}; + } + + types[component.id] = component; + + this.numComponents++; + + // Remove component when it's destroyed + + var self = this; + + this._destroyedSubs[component.id] = component.on("destroyed", function () { + self._remove(component); + }); + + /** + * Fired whenever an individual {{#crossLink "Component"}}{{/crossLink}} is added to this {{#crossLink "Collection"}}{{/crossLink}}. + * @event added + * @param value {Component} The {{#crossLink "Component"}}{{/crossLink}} that was added. + */ + this.fire("added", component); + + if (!this._dirty) { + this._scheduleUpdate(); + } + }, + + _scheduleUpdate: function _scheduleUpdate() { + if (!this._dirty) { + this._dirty = true; + xeogl.scheduleTask(this._notifyUpdated, this); + } + }, + + _notifyUpdated: function _notifyUpdated() { + + /* Fired on the next {{#crossLink "Scene/tick.animate:event"}}{{/crossLink}} whenever + * {{#crossLink "Component"}}Components{{/crossLink}} were added or removed since the + * last {{#crossLink "Scene/tick.animate:event"}}{{/crossLink}} event, to provide a batched change event + * for subscribers who don't want to react to every individual addition or removal on this Collection. + * + * @event updated + */ + this.fire("updated"); + this._dirty = false; + }, + + /** + * Removes all {{#crossLink "Component"}}Components{{/crossLink}} from this Collection. + * + * Fires an {{#crossLink "Collection/updated:event"}}{{/crossLink}} event. + * + * @method clear + */ + clear: function clear() { + + this.iterate(function (component) { + this._remove(component); + }); + }, + + /** + * Destroys all {{#crossLink "Component"}}Components{{/crossLink}} in this Collection. + * + * @method destroyAll + */ + destroyAll: function destroyAll() { + + this.iterate(function (component) { + component.destroy(); + }); + }, + + /** + * Removes one or more {{#crossLink "Component"}}Components{{/crossLink}} from this Collection. + * + * The {{#crossLink "Component"}}Component(s){{/crossLink}} may be specified by instance, ID or type. + * + * See class comment for usage examples. + * + * Fires a {{#crossLink "Collection/removed:event"}}{{/crossLink}} event. + * + * @method remove + * @param {Array of Components} components Array of {{#crossLink "Component"}}Components{{/crossLink}} instances. + */ + remove: function remove(components) { + + components = xeogl._isArray(components) ? components : [components]; + + for (var i = 0, len = components.length; i < len; i++) { + this._remove(components[i]); + } + }, + + _remove: function _remove(component) { + + var componentId = component.id; + + if (component.scene !== this.scene) { + this.warn("Attempted to remove component that's not in same xeogl.Scene: '" + componentId + "'"); + return; + } + + delete this.components[componentId]; + + // Unsubscribe from component destruction + + component.off(this._destroyedSubs[componentId]); + + delete this._destroyedSubs[componentId]; + + // Unregister component for its type + + var types = this.types[component.type]; + + if (types) { + delete types[component.id]; + } + + this.numComponents--; + + /** + * Fired whenever an individual {{#crossLink "Component"}}{{/crossLink}} is removed from this {{#crossLink "Collection"}}{{/crossLink}}. + * @event removed + * @param value {Component} The {{#crossLink "Component"}}{{/crossLink}} that was removed. + */ + this.fire("removed", component); + + if (!this._dirty) { + this._scheduleUpdate(); + } + }, + + /** + * Iterates with a callback over the {{#crossLink "Component"}}Components{{/crossLink}} in this Collection. + * + * @method iterate + * @param {Function} callback Callback called for each {{#crossLink "Component"}}{{/crossLink}}. + * @param {Object} [scope=this] Optional scope for the callback, defaults to this Collection. + */ + iterate: function iterate(callback, scope) { + scope = scope || this; + var components = this.components; + for (var componentId in components) { + if (components.hasOwnProperty(componentId)) { + callback.call(scope, components[componentId]); + } + } + }, + + _getJSON: function _getJSON() { + + var componentIds = []; + + for (var componentId in this.components) { + if (this.components.hasOwnProperty(componentId)) { + componentIds.push(this.components[componentId].id); // Don't convert numbers into strings + } + } + + return { + components: componentIds + }; + }, + + _destroy: function _destroy() { + + this.clear(); + } +}); + +var xeoViewer = function (_EventHandler) { + inherits(xeoViewer, _EventHandler); + + function xeoViewer(cfg) { + classCallCheck(this, xeoViewer); + + // Distance to WebGL's far clipping plane. + var _this = possibleConstructorReturn(this, (xeoViewer.__proto__ || Object.getPrototypeOf(xeoViewer)).call(this)); + // Create xeoViewer + + + var FAR_CLIP = 5000; + + var domNode = document.getElementById(cfg.domNode); + var canvas = document.createElement("canvas"); + + domNode.appendChild(canvas); + + // Create a Scene + _this.scene = new xeogl.Scene({ // http://xeoengine.org/docs/classes/Scene.html + canvas: canvas, + transparent: true + }); + + // Redefine default light sources; + _this.lights = [{ + type: "ambient", + params: { + color: [0.65, 0.65, 0.75], + intensity: 1 + } + }, { + type: "dir", + params: { + dir: [0.0, 0.0, -1.0], + color: [1.0, 1.0, 1.0], + intensity: 1.0, + space: "view" + } + }]; + _this.scene.lights.lights = _this.buildLights(_this.lights); + + // Attached to all objects to fit the model inside the view volume + _this.scale = new xeogl.Scale(_this.scene, { + xyz: [1, 1, 1] + }); + + // Provides user input + _this.input = _this.scene.input; + + // Using the scene's default camera + _this.camera = _this.scene.camera; + _this.camera.project.far = FAR_CLIP; + + // Flies cameras to objects + _this.cameraFlight = new xeogl.CameraFlightAnimation(_this.scene, { // http://xeoengine.org/docs/classes/CameraFlightAnimation.html + fitFOV: 25, + duration: 1 + }); + + // Registers loaded xeoEngine components for easy destruction + _this.collection = new xeogl.Collection(_this.scene); // http://xeoengine.org/docs/classes/Collection.html + + // Shows a wireframe box at the given boundary + _this.boundaryHelper = new xeogl.BIMBoundaryHelper(_this.scene, _this, { color: cfg.selectionBorderColor }); + + _this.highlightEffect = new xeogl.HighlightEffect(_this.scene, { color: cfg.selectionColor }); + + // Models mapped to their IDs + _this.models = {}; + + // Objects mapped to IDs + _this.objects = {}; + + _this.objects_by_guid = {}; + + // For each RFC type, a map of objects mapped to their IDs + _this.rfcTypes = {}; + + // Objects that are currently visible, mapped to IDs + _this.visibleObjects = {}; + + // Lazy-generated array of visible object IDs, for return by #getVisibility() + _this.visibleObjectList = null; + + // Array of objects RFC types hidden by default + _this.hiddenTypes = ["IfcOpeningElement", "IfcSpace"]; + + // Objects that are currently selected, mapped to IDs + _this.selectedObjects = {}; + + // Lazy-generated array of selected object IDs, for return by #getSelection() + _this.selectedObjectList = null; + + // Bookmark of initial state to reset to - captured with #saveReset(), applied with #reset(). + _this.resetBookmark = null; + + // Component for each projection type, + // to swap on the camera when we switch projection types + _this.projections = { + + persp: _this.camera.project, // Camera has a xeogl.Perspective by default + + ortho: new xeogl.Ortho(_this.scene, { + scale: 1.0, + near: 0.1, + far: FAR_CLIP + }) + }; + + // The current projection type + _this.projectionType = "persp"; + + //----------------------------------------------------------------------------------------------------------- + // Camera notifications + //----------------------------------------------------------------------------------------------------------- + + + // Fold xeoEngine's separate events for view and projection updates + // into a single "camera-changed" event, deferred to fire on next scene tick. + + var cameraUpdated = false; + + _this.camera.on("projectMatrix", function () { + cameraUpdated = true; + }); + + _this.camera.on("viewMatrix", function () { + cameraUpdated = true; + }); + + _this.scene.on("tick", function () { + + /** + * Fired on the iteration of each "game loop" for this xeoViewer. + * @event tick + * @param {String} sceneID The ID of this Scene. + * @param {Number} startTime The time in seconds since 1970 that this xeoViewer was instantiated. + * @param {Number} time The time in seconds since 1970 of this "tick" event. + * @param {Number} prevTime The time of the previous "tick" event from this xeoViewer. + * @param {Number} deltaTime The time in seconds since the previous "tick" event from this xeoViewer. + */ + _this.fire("tick"); + + if (cameraUpdated) { + + /** + * Fired whenever this xeoViewer's camera changes. + * @event camera-changed + * @params New camera state, same as that got with #getCamera. + */ + _this.fire("camera-changed", [_this.getCamera()]); + cameraUpdated = false; + } + }); + + //----------------------------------------------------------------------------------------------------------- + // Camera control + //----------------------------------------------------------------------------------------------------------- + + _this.cameraControl = new xeogl.BIMCameraControl(_this.scene, { + camera: _this.camera + }); + + _this.cameraControl.on("pick", function (hit) { + // Get BIM object ID from entity metadata + var entity = hit.entity; + + if (!entity.meta) { + return; + } + + var objectId = entity.meta.objectId || entity.id; + + if (objectId === undefined) { + return; + } + + var selected = !!_this.selectedObjects[objectId]; // Object currently selected? + var shiftDown = _this.scene.input.keyDown[_this.input.KEY_SHIFT]; // Shift key down? + + _this.setSelection({ + ids: [objectId], + selected: !selected, // Picking an object toggles its selection status + clear: !shiftDown // Clear selection first if shift not down + }); + }); + + _this.cameraControl.on("nopick", function (hit) { + _this.setSelection({ + clear: true + }); + }); + return _this; + } + + /** + * Sets the default behaviour of mouse and touch drag input + * + * @method setDefaultDragAction + * @param {String} action ("pan" | "orbit") + */ + + + createClass(xeoViewer, [{ + key: 'setDefaultDragAction', + value: function setDefaultDragAction(action) { + this.cameraControl.defaultDragAction = action; + } + + /** + * Sets the global scale for models loaded into the viewer. + * + * @method setScale + * @param {Number} s Scale factor. + */ + + }, { + key: 'setScale', + value: function setScale(s) { + this.scale.xyz = [s, s, s]; + } + + /** + * Notifies the viewer that a task (such as loading a model) has started. Use #taskFinished + * to signal that the task has finished. + * + * Whenever the number of tasks is greater than zero, the viewer will display a spinner, + * and reduce rendering speed so as to allow scene updates to happen faster. + */ + + }, { + key: 'taskStarted', + value: function taskStarted() { + this.scene.canvas.spinner.processes++; + this.scene.ticksPerRender = 15; // Tweak this if necessary + } + + /** + * Signals that a task has finished (see #taskStarted). + */ + + }, { + key: 'taskFinished', + value: function taskFinished() { + var spinner = this.scene.canvas.spinner; + if (spinner.processes === 0) { + return; + } + spinner.processes--; + if (spinner.processes === 0) { + this.scene.ticksPerRender = 1; // Back to max speed, one render per tick + } + } + + /** + * Loads random objects into the viewer for testing. + * + * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. + * + * @method loadRandom + * @param {*} params Parameters + * @param {Number} [params.numEntities=200] Number of entities to create. + * @param {Number} [params.size=200] Size of model on every axis. + * @param {Float32Array} [params.center] Center point of model. + */ + + }, { + key: 'loadRandom', + value: function loadRandom(params) { + + params = params || {}; + + this.clear(); + + var geometry = new xeogl.BoxGeometry(this.scene, { + id: "geometry.test" + }); + + this.collection.add(geometry); + + var modelId = "test"; + var roid = "test"; + var oid = void 0; + var type = void 0; + var objectId = void 0; + var translate = void 0; + var scale = void 0; + var matrix = void 0; + var types = Object.keys(DefaultMaterials); + + var numEntities = params.numEntities || 200; + var size = params.size || 200; + var halfSize = size / 2; + var centerX = params.center ? params.center[0] : 0; + var centerY = params.center ? params.center[1] : 0; + var centerZ = params.center ? params.center[2] : 0; + + this.createModel(modelId); + + for (var i = 0; i < numEntities; i++) { + objectId = "object" + i; + oid = objectId; + translate = xeogl.math.translationMat4c(Math.random() * size - halfSize + centerX, Math.random() * size - halfSize + centerY, Math.random() * size - halfSize + centerZ); + scale = xeogl.math.scalingMat4c(Math.random() * 32 + 0.2, Math.random() * 32 + 0.2, Math.random() * 10 + 0.2); + matrix = xeogl.math.mulMat4(translate, scale, xeogl.math.mat4()); + type = types[Math.round(Math.random() * types.length)]; + this.createObject(modelId, roid, oid, objectId, ["test"], type, matrix); + } + + // Set camera just to establish the up vector as +Z; the following + // call to viewFit() will arrange the eye and target positions properly. + this.setCamera({ + eye: [0, 0, 0], + target: [centerX, centerY, centerZ], + up: [0, 0, 1] + }); + + this.viewFit(); + + this.saveReset(); + } + + /** + * Creates a geometry. + * + * @method createGeometry + * @param geometryId + * @param positions + * @param normals + * @param colors + * @param indices + * @returns {xeogl.Geometry} The new geometry + * @private + */ + + }, { + key: 'createGeometry', + value: function createGeometry(geometryId, positions, normals, colors, indices) { + var geometry = new xeogl.Geometry(this.scene, { // http://xeoengine.org/docs/classes/Geometry.html + id: "geometry." + geometryId, + primitive: "triangles", + positions: positions, + normals: normals, + colors: colors, + indices: indices + }); + + this.collection.add(geometry); + + return geometry; + } + + /** + * Creates a model. + * + * @param modelId + * @returns {xeogl.BIMModel} The new model + * @private + */ + + }, { + key: 'createModel', + value: function createModel(modelId) { + + if (this.models[modelId]) { + console.log("Model with id " + modelId + " already exists, won't recreate"); + return; + } + + var model = new xeogl.BIMModel(this.scene, {}); + + this.models[modelId] = model; + + this.collection.add(model); + + return model; + } + + /** + * Creates an object. + * @param [modelId] Optional model ID + * @param roid + * @param oid + * @param objectId + * @param geometryIds + * @param type + * @param matrix + * @returns {xeogl.BIMObject} The new object + * @private + */ + + }, { + key: 'createObject', + value: function createObject(modelId, roid, oid, objectId, geometryIds, type, matrix) { + var model = void 0; + + if (modelId) { + model = this.models[modelId]; + if (!model) { + console.log("Can't create object - model not found: " + modelId); + return; + } + objectId = modelId + ":" + objectId; + } + + if (this.objects[objectId]) { + console.log("Object with id " + objectId + " already exists, won't recreate"); + return; + } + + var object = new xeogl.BIMObject(this.scene, { + id: objectId, + geometryIds: geometryIds, + matrix: matrix + }); + + object.transform.parent = this.scale; // Apply model scaling + + this._addObject(type, object); + + if (model) { + model.collection.add(object); + } + + // Hide objects of certain types by default + if (this.hiddenTypes.indexOf(type) !== -1) { + object.visibility.visible = false; + } + + return object; + } + + /** + * Inserts an object into this viewer + * + * @param {String} type + * @param {xeogl.Entity | xeogl.BIMObject} object + * @private + */ + + }, { + key: '_addObject', + value: function _addObject(type, object) { + var guid = void 0; + if (object.id.indexOf("#") !== -1) { + guid = Utils.CompressGuid(object.id.split("#")[1].substr(8, 36).replace(/-/g, "")); + } + this.collection.add(object); + + // Register object against ID + this.objects[object.id] = object; + if (guid) { + (this.objects_by_guid[guid] || (this.objects_by_guid[guid] = [])).push(object); + } + + // Register object against IFC type + var types = this.rfcTypes[type] || (this.rfcTypes[type] = {}); + types[object.id] = object; + + var color = DefaultMaterials[type] || DefaultMaterials.DEFAULT; + + if (!guid) { + object.material.diffuse = [color[0], color[1], color[2]]; + } + object.material.specular = [0, 0, 0]; + + if (color[3] < 1) { + // Transparent object + object.material.opacity = color[3]; + object.modes.transparent = true; + } + if (object.material.opacity < 1) { + // Transparent object + object.modes.transparent = true; + } + } + + /** + * Loads glTF model. + * + * Subsequent calls to #reset will then set the viewer to the state right after the model was loaded. + * + * @param src + */ + + }, { + key: 'loadglTF', + value: function loadglTF(src) { + var _this2 = this; + + this.clear(); + + var model = new xeogl.GLTFModel(this.scene, { + src: src + }); + + this.collection.add(model); + + this.models[model.id] = model; + + model.on("loaded", function () { + + // TODO: viewFit, but boundaries not yet ready on Model Entities + + model.iterate(function (component) { + if (component.isType("xeogl.Entity")) { + _this2._addObject("DEFAULT", component); + } + }); + + _this2.saveReset(); + }); + + return model; + } + + /** + * Destroys a model and all its objects. + * + * @param modelId + */ + + }, { + key: 'destroyModel', + value: function destroyModel(modelId) { + + var model = this.models[modelId]; + + if (!model) { + console.warn("Can't destroy model - model not found: " + modelId); + return; + } + + model.collection.iterate(function (component) { + component.destroy(); + }); + + model.destroy(); + + delete this.models[modelId]; + } + + /** + * Clears the viewer. + * + * Subsequent calls to #reset will then set the viewer this clear state. + */ + + }, { + key: 'clear', + value: function clear() { + + var list = []; + + this.collection.iterate(function (component) { + list.push(component); + }); + + while (list.length) { + list.pop().destroy(); + } + + this.objects = {}; + this.rfcTypes = {}; + this.visibleObjects = {}; + this.visibleObjectList = null; + this.selectedObjects = {}; + this.selectedObjectList = null; + + this.saveReset(); + } + + /** + * Sets the visibility of objects specified by IDs or IFC types. + * If IFC types are specified, this will affect existing objects as well as subsequently loaded objects of these types + * + * @param params + * @param params.ids IDs of objects or IFC types to update. + * @param params.color Color to set. + */ + + }, { + key: 'setVisibility', + value: function setVisibility(params) { + var _this3 = this; + + var changed = false; // Only fire "visibility-changed" when visibility updates are actually made + params = params || {}; + + var ids = params.ids; + var types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } + + ids = ids || []; + types = types || []; + + //const recursive = !!params.recursive; + var visible = params.visible !== false; + + var i = void 0; + var len = void 0; + var id = void 0; + var objectId = void 0; + + if (params.clear) { + for (objectId in this.visibleObjects) { + if (this.visibleObjects.hasOwnProperty(objectId)) { + delete this.visibleObjects[objectId]; + changed = true; + } + } + } + + var _loop = function _loop() { + var type = types[i]; + var typedict = _this3.rfcTypes[type] || {}; + + Object.keys(typedict).forEach(function (id) { + var object = typedict[id]; + object.visibility.visible = visible; + changed = true; + }); + + var index = _this3.hiddenTypes.indexOf(type); + + if (index !== -1 && visible) { + _this3.hiddenTypes.splice(index, 1); // remove type from array + } else if (index === -1 && !visible) { + _this3.hiddenTypes.push(type); // add type to array + } + }; + + for (i = 0, len = types.length; i < len; i++) { + _loop(); + } + + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + var fn = function fn(object) { + object.visibility.visible = visible; + changed = true; + }; + var object_ = this.objects[id]; + if (!object_) { + this.objects_by_guid[id].forEach(fn); + } else { + fn(object_); + } + } + + if (changed) { + this.visibleObjectList = Object.keys(this.visibleObjects); + + /** + * Fired whenever objects become invisible or invisible + * @event visibility-changed + * @params Array of IDs of all currently-visible objects. + */ + this.fire("visibility-changed", [this.visibleObjectList]); + } + } + + /** + * Returns array of IDs of objects that are currently visible + */ + + }, { + key: 'getVisibility', + value: function getVisibility() { + if (this.visibleObjectList) { + return this.visibleObjectList; + } + this.visibleObjectList = Object.keys(this.visibleObjects); + return this.visibleObjectList; + } + + /** + * Select or deselect some objects. + * + * @param params + * @param params.ids IDs of objects. + * @param params.selected Whether to select or deselect the objects + * @param params.clear Whether to clear selection state prior to updating + */ + + }, { + key: 'setSelection', + value: function setSelection(params) { + var _this4 = this; + + params = params || {}; + + var changed = false; // Only fire "selection-changed" when selection actually changes + var selected = !!params.selected; + var objectId = void 0; + if (params.clear) { + for (objectId in this.selectedObjects) { + if (this.selectedObjects.hasOwnProperty(objectId)) { + delete this.selectedObjects[objectId]; + changed = true; + } + } + } + + var ids = params.ids; + + if (ids) { + + for (var i = 0, len = ids.length; i < len; i++) { + + var fn = function fn(object) { + + var objectId = object.id; + + if (!!_this4.selectedObjects[objectId] !== selected) { + changed = true; + } + + if (selected) { + _this4.selectedObjects[objectId] = object; + } else { + if (_this4.selectedObjects[objectId]) { + delete _this4.selectedObjects[objectId]; + } + } + + _this4.selectedObjectList = null; // Now needs lazy-rebuild + }; + + objectId = ids[i]; + var object_ = this.objects[objectId]; + if (!object_) { + this.objects_by_guid[objectId].forEach(fn); + } else { + fn(object_); + } + } + } + + if (changed) { + + this.selectedObjectList = Object.keys(this.selectedObjects); + + // Show boundary around selected objects + this.setBoundaryState({ + ids: this.selectedObjectList, + show: this.selectedObjectList.length > 0 + }); + + /** + * Fired whenever this xeoViewer's selection state changes. + * @event selection-changed + * @params Array of IDs of all currently-selected objects. + */ + this.fire("selection-changed", [this.selectedObjectList]); + } + } + + /** + * Returns array of IDs of objects that are currently selected + */ + + }, { + key: 'getSelection', + value: function getSelection() { + if (this.selectedObjectList) { + return this.selectedObjectList; + } + this.selectedObjectList = Object.keys(this.selectedObjects); + return this.selectedObjectList; + } + + /** + * Sets the color of objects specified by IDs or IFC types. + * + * @param params + * @param params.ids IDs of objects to update. + * @param params.types IFC type of objects to update. + * @param params.color Color to set. + */ + + }, { + key: 'setColor', + value: function setColor(params) { + var _this5 = this; + + params = params || {}; + + var ids = params.ids; + var types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } + + ids = ids || []; + types = types || []; + + var color = params.color; + + if (!color) { + console.error("Param expected: 'color'"); + return; + } + + var objectId = void 0; + var object = void 0; + + var _loop2 = function _loop2(i, len) { + var typedict = _this5.rfcTypes[types[i]] || {}; + Object.keys(typedict).forEach(function (id) { + var object = typedict[id]; + _this5._setObjectColor(object, color); + }); + }; + + for (var i = 0, len = types.length; i < len; i++) { + _loop2(i, len); + } + + for (var i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + // No return on purpose to continue changing color of + // other potentially valid object identifiers. + console.error("Object not found: '" + objectId + "'"); + } else { + this._setObjectColor(object, color); + } + } + } + }, { + key: '_setObjectColor', + value: function _setObjectColor(object, color) { + + var material = object.material; + material.diffuse = [color[0], color[1], color[2]]; + + var opacity = color.length > 3 ? color[3] : 1; + if (opacity !== material.opacity) { + material.opacity = opacity; + object.modes.transparent = opacity < 1; + } + } + + /** + * Sets the opacity of objects specified by IDs of IFC types. + * + * @param params + * @param params.ids IDs of objects to update. + * @param params.types IFC type of objects to update. + * @param params.opacity Opacity to set. + */ + + }, { + key: 'setOpacity', + value: function setOpacity(params) { + var _this6 = this; + + params = params || {}; + + var ids = params.ids; + var types = params.types; + + if (!ids && !types) { + console.error("Param expected: ids or types"); + return; + } + + ids = ids || []; + types = types || []; + + var opacity = params.opacity; + + if (opacity === undefined) { + console.error("Param expected: 'opacity'"); + return; + } + + var objectId = void 0; + var object = void 0; + + var _loop3 = function _loop3(i, len) { + var typedict = _this6.rfcTypes[types[i]] || {}; + Object.keys(typedict).forEach(function (id) { + var object = typedict[id]; + _this6._setObjectOpacity(object, opacity); + }); + }; + + for (var i = 0, len = types.length; i < len; i++) { + _loop3(i, len); + } + + for (var i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + // No return on purpose to continue changing opacity of + // other potentially valid object identifiers. + console.error("Object not found: '" + objectId + "'"); + } else { + this._setObjectOpacity(object, opacity); + } + } + } + }, { + key: '_setObjectOpacity', + value: function _setObjectOpacity(object, opacity) { + + var material = object.material; + + if (opacity !== material.opacity) { + material.opacity = opacity; + object.modes.transparent = opacity < 1; + } + } + + /** + * Sets camera state. + * + * @param params + */ + + }, { + key: 'setCamera', + value: function setCamera(params) { + + params = params || {}; + + // Set projection type + + var type = params.type; + + if (type && type !== this.projectionType) { + + var projection = this.projections[type]; + + if (!projection) { + console.error("Unsupported camera projection type: " + type); + } else { + this.camera.project = projection; + this.projectionType = type; + } + } + + // Set camera position + + if (params.animate) { + + this.cameraFlight.flyTo({ + eye: params.eye, + look: params.target, + up: params.up, + fitFOV: params.fitFOV, + duration: params.duration + }); + } else { + + if (params.eye) { + this.camera.view.eye = params.eye; + } + + if (params.target) { + this.camera.view.look = params.target; + this.cameraControl.rotatePos = this.camera.view.look; // Rotate about target initially + } + + if (params.up) { + this.camera.view.up = params.up; + } + } + + // Set camera FOV angle, only if currently perspective + + if (params.fovy) { + if (this.projectionType !== "persp") { + console.error("Ignoring update to 'fovy' for current '" + this.projectionType + "' camera"); + } else { + this.camera.project.fovy = params.fovy; + } + } + + // Set camera view volume size, only if currently orthographic + + if (params.scale) { + if (this.projectionType !== "ortho") { + console.error("Ignoring update to 'scale' for current '" + this.projectionType + "' camera"); + } else { + this.camera.project.scale = params.scale; + } + } + } + + /** + * Gets camera state. + * + * @returns {{type: string, eye: (*|Array.), target: (*|Array.), up: (*|Array.)}} + */ + + }, { + key: 'getCamera', + value: function getCamera() { + + var view = this.camera.view; + + var json = { + type: this.projectionType, + eye: view.eye.slice(0), + target: view.look.slice(0), + up: view.up.slice(0) + }; + + var project = this.camera.project; + + if (this.projectionType === "persp") { + json.fovy = project.fovy; + } else if (this.projectionType === "ortho") { + json.size = [1, 1, 1]; // TODO: efficiently derive from cached value or otho volume + } + + return json; + } + + /** + * Redefines light sources. + * + * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type + */ + + }, { + key: 'setLights', + value: function setLights(params) { + this.lights = params; + + for (var i = this.scene.lights.lights.length - 1; i >= 0; i--) { + this.scene.lights.lights[i].destroy(); + } + + this.scene.lights.lights = this.buildLights(this.lights); + } + + /** + * Returns light sources. + * + * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + */ + + }, { + key: 'getLights', + value: function getLights() { + return this.lights; + } + }, { + key: 'buildLights', + value: function buildLights(lights) { + var _this7 = this; + + return lights.map(function (light) { + if (light.type == "ambient") { + return new xeogl.AmbientLight(_this7.scene, light.params); + } else if (light.type == "dir") { + return new xeogl.DirLight(_this7.scene, light.params); + } else if (light.type == "point") { + return new xeogl.PointLight(_this7.scene, light.params); + } else { + console.log("Unknown light type: " + light.type); + } + }); + } + + /** + * + * @param params + * @param ok + */ + + }, { + key: 'viewFit', + value: function viewFit(params, ok) { + var _this8 = this; + + params = params || {}; + + var ids = params.ids; + var aabb = void 0; + + if (!ids || ids.length === 0) { + + // Fit everything in view by default + aabb = this.scene.worldBoundary.aabb; + } else { + aabb = this.getObjectsAABB(ids); + } + + if (params.animate) { + + this.cameraFlight.flyTo({ + aabb: aabb, + fitFOV: params.fitFOV, + duration: params.duration + }, function () { + if (ok) { + ok(); + } + + // Now orbiting the point we flew to + _this8.cameraControl.rotatePos = _this8.camera.view.look; + }); + } else { + + this.cameraFlight.jumpTo({ + aabb: aabb, + fitFOV: 50.0 + }); + } + } + + // Updates the boundary helper + + }, { + key: 'setBoundaryState', + value: function setBoundaryState(params) { + + if (params.aabb) { + throw new Error("Not supported"); + } else if (params.ids) { + this.boundaryHelper.setSelected(params.ids); + + this.highlightEffect.clear(); + + var ids = params.ids; + var objectId = void 0; + var object = void 0; + + for (var i = 0, len = ids.length; i < len; i++) { + objectId = ids[i]; + object = this.objects[objectId]; + if (object) { + + this.highlightEffect.add(object); + //object.highlighted = true; + } + } + } + } + + // Returns an axis-aligned bounding box (AABB) that encloses the given objects + + }, { + key: 'getObjectsAABB', + value: function getObjectsAABB(ids_) { + var _this9 = this; + + var ids = void 0; + if (Object.keys(this.objects_by_guid).length) { + ids = []; + ids_.forEach(function (i) { + _this9.objects_by_guid[i].forEach(function (o) { + ids.push(o.id); + }); + }); + } else { + ids = ids_; + } + + if (ids.length === 0) { + + // No object IDs given + return null; + } + + var objectId = void 0; + var object = void 0; + var worldBoundary = void 0; + + if (ids.length === 1) { + + // One object ID given + + objectId = ids[0]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (object) { + worldBoundary = object.worldBoundary; + + if (worldBoundary) { + + return worldBoundary.aabb; + } else { + return null; + } + } else { + return null; + } + } + + // Many object IDs given + + var i = void 0; + var len = void 0; + var min = void 0; + var max = void 0; + + var xmin = 100000; + var ymin = 100000; + var zmin = 100000; + var xmax = -100000; + var ymax = -100000; + var zmax = -100000; + + var aabb = void 0; + + for (i = 0, len = ids.length; i < len; i++) { + + objectId = ids[i]; + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (!object) { + continue; + } + + worldBoundary = object.worldBoundary; + + if (!worldBoundary) { + continue; + } + + aabb = worldBoundary.aabb; + + min = aabb.slice(0); + max = aabb.slice(3); + + if (min[0] < xmin) { + xmin = min[0]; + } + + if (min[1] < ymin) { + ymin = min[1]; + } + + if (min[2] < zmin) { + zmin = min[2]; + } + + if (max[0] > xmax) { + xmax = max[0]; + } + + if (max[1] > ymax) { + ymax = max[1]; + } + + if (max[2] > zmax) { + zmax = max[2]; + } + } + + var result = xeogl.math.AABB3(); + + result[0 + 0] = xmin; + result[1 + 0] = ymin; + result[2 + 0] = zmin; + result[0 + 3] = xmax; + result[1 + 3] = ymax; + result[2 + 3] = zmax; + + return result; + } + + /** + * Remembers the current state of the viewer so that it can be reset to this state with + * a subsequent call to #reset. + */ + + }, { + key: 'saveReset', + value: function saveReset() { + this.resetBookmark = this.getBookmark(); + } + }, { + key: 'getObject', + value: function getObject(id) { + return this.objects[id]; + } + + /** + * Resets the state of this viewer to the state previously saved with #saveReset. + * @param {*} params A mask which specifies which aspects of viewer state to reset. + */ + + }, { + key: 'reset', + value: function reset(params) { + if (!this.resetBookmark) { + console.log("Ignoring call to xeoViewer.reset - xeoViewer.saveReset not called previously."); + return; + } + this.setBookmark(this.resetBookmark, params); + } + + /** + * Returns a bookmark of xeoViewer state. + * @param {*} options A mask which specifies which aspects of viewer state to bookmark. + */ + + }, { + key: 'getBookmark', + value: function getBookmark(options) { + + // Get everything by default + + var getVisible = !options || options.visible; + var getColors = !options || options.colors; + var getSelected = !options || options.selected; + var getCamera = !options || options.camera; + + var bookmark = {}; + + var objectId = void 0; + var object = void 0; + + if (getVisible) { + + var visible = []; + + for (objectId in this.objects) { + if (this.objects.hasOwnProperty(objectId)) { + + object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (getVisible && object.visibility.visible) { + visible.push(objectId); + } + } + } + bookmark.visible = visible; + } + + if (getColors) { + + var colors = {}; + var opacities = {}; + + for (objectId in this.objects) { + if (this.objects.hasOwnProperty(objectId)) { + object = this.objects[objectId] || this.objects_by_guid[objectId]; + colors[objectId] = object.material.diffuse.slice(); // RGB + opacities[objectId] = object.modes.transparent ? object.material.opacity : 1.0; + } + } + bookmark.colors = colors; + bookmark.opacities = opacities; + } + + if (getSelected) { + bookmark.selected = this.getSelection(); + } + + if (getCamera) { + var camera = this.getCamera(); + camera.animate = true; // Camera will fly to position when bookmark is restored + bookmark.camera = camera; + } + + return bookmark; + } + + /** + * Restores xeoViewer to a bookmark. + * + * @param bookmark + * @param options + */ + + }, { + key: 'setBookmark', + value: function setBookmark(bookmark, options) { + + // Set everything by default, where provided in bookmark + + var setVisible = bookmark.visible && (!options || options.visible); + var setColors = bookmark.colors && (!options || options.colors); + var setSelected = bookmark.selected && (!options || options.selected); + var setCamera = bookmark.camera && (!options || options.camera); + + if (setColors) { + + var objectId = void 0; + var object = void 0; + var colors = bookmark.colors; + var opacities = bookmark.opacities; + + for (objectId in colors) { + if (colors.hasOwnProperty(objectId)) { + object = this.objects[objectId] || this.objects_by_guid[objectId]; + if (object) { + this._setObjectColor(object, colors[objectId]); + this._setObjectOpacity(object, opacities[objectId]); + } + } + } + } + + if (setVisible) { + this.setVisibility({ + ids: bookmark.visible, + visible: true + }); + } + + if (setSelected) { + this.setSelection({ + ids: bookmark.selected, + selected: true + }); + } + + if (setCamera) { + this.setCamera(bookmark.camera); + } + } + + /** + * Sets general configurations. + * + * @param params + * @param {Boolean} [params.mouseRayPick=true] When true, camera flies to orbit each clicked point, otherwise + * it flies to the boundary of the object that was clicked on. + * @param [params.viewFitFOV=25] {Number} How much of field-of-view, in degrees, that a target {{#crossLink "Entity"}}{{/crossLink}} or its AABB should + * fill the canvas when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}} or {{#crossLink "CameraFlightAnimation/jumpTo:method"}}{{/crossLink}}. + * @param [params.viewFitDuration=1] {Number} Flight duration, in seconds, when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}{{/crossLink}}. + */ + + }, { + key: 'setConfigs', + value: function setConfigs(params) { + + params = params || {}; + + if (params.mouseRayPick != undefined) { + this.cameraControl.mousePickEntity.rayPick = params.mouseRayPick; + } + + if (params.viewFitFOV != undefined) { + this.cameraFlight.fitFOV = params.viewFitFOV; + } + + if (params.viewFitDuration != undefined) { + this.cameraFlight.duration = params.viewFitDuration; + } + } + + /** + Returns a snapshot of this xeoViewer as a Base64-encoded image. + + #### Usage: + ````javascript + imageElement.src = xeoViewer.getSnapshot({ + width: 500, // Defaults to size of canvas + height: 500, + format: "png" // Options are "jpeg" (default), "png" and "bmp" + }); + ```` + + @method getSnapshot + @param {*} [params] Capture options. + @param {Number} [params.width] Desired width of result in pixels - defaults to width of canvas. + @param {Number} [params.height] Desired height of result in pixels - defaults to height of canvas. + @param {String} [params.format="jpeg"] Desired format; "jpeg", "png" or "bmp". + @returns {String} String-encoded image data. + */ + + }, { + key: 'getSnapshot', + value: function getSnapshot(params) { + return this.scene.canvas.getSnapshot(params); + } + + /** + Returns a list of loaded IFC entity types in the model. + + @method getTypes + @returns {Array} List of loaded IFC entity types, with visibility flag + */ + + }, { + key: 'getTypes', + value: function getTypes() { + var _this10 = this; + + return Object.keys(this.rfcTypes).map(function (n) { + return { name: n, visible: _this10.hiddenTypes.indexOf(n) === -1 }; + }); + } + + /** + * Returns the world boundary of an object + * + * @method getWorldBoundary + * @param {String} objectId id of object + * @param {Object} result Existing boundary object + * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D + */ + + }, { + key: 'getWorldBoundary', + value: function getWorldBoundary(objectId, result) { + var object = this.objects[objectId] || this.objects_by_guid[objectId]; + + if (object === undefined) { + return null; + } else { + if (result === undefined) { + result = { + obb: new Float32Array(32), + aabb: new Float32Array(6), + center: xeogl.math.vec3(), + sphere: xeogl.math.vec4() + }; + } + + // the boundary needs to be scaled back to real world units + var s = 1 / this.scale.xyz[0], + scaled = object.worldBoundary; + + result.aabb[0] = scaled.aabb[0] * s; + result.aabb[1] = scaled.aabb[1] * s; + result.aabb[2] = scaled.aabb[2] * s; + result.aabb[3] = scaled.aabb[3] * s; + result.aabb[4] = scaled.aabb[4] * s; + result.aabb[5] = scaled.aabb[5] * s; + + xeogl.math.mulVec3Scalar(scaled.center, s, result.center); + xeogl.math.mulVec4Scalar(scaled.sphere, s, result.sphere); + + var obb = scaled.obb; + var buffer = result.obb.buffer; + for (var i = 0; i < 32; i += 4) { + var v = new Float32Array(buffer, 4 * i); + xeogl.math.mulVec3Scalar(obb.slice(i), s, v); + v[3] = 1.0; + } + + return result; + } + } + + /** + * Destroys the viewer + */ + + }, { + key: 'destroy', + value: function destroy() { + this.scene.destroy(); + } + }]); + return xeoViewer; +}(EventHandler); + +var BimSurfer = function (_EventHandler) { + inherits(BimSurfer, _EventHandler); + + function BimSurfer(cfg) { + classCallCheck(this, BimSurfer); + + var _this = possibleConstructorReturn(this, (BimSurfer.__proto__ || Object.getPrototypeOf(BimSurfer)).call(this)); + + _this.BimServerApi = BimServerClient; + + cfg = cfg || {}; + + _this.viewer = new xeoViewer(cfg); + + /** + * Fired whenever this BIMSurfer's camera changes. + * @event camera-changed + */ + _this.viewer.on("camera-changed", function (args) { + _this.fire("camera-changed", args); + }); + + /** + * Fired whenever this BIMSurfer's selection changes. + * @event selection-changed + */ + _this.viewer.on("selection-changed", function (args) { + _this.fire("selection-changed", args); + }); + + // This are arrays as multiple models might be loaded or unloaded. + _this._idMapping = { + 'toGuid': [], + 'toId': [] + }; + return _this; + } + /** + * Loads a model into this BIMSurfer. + * @param params + */ + + + createClass(BimSurfer, [{ + key: 'load', + value: function load(params) { + + if (params.test) { + this.viewer.loadRandom(params); + return null; + } else if (params.bimserver) { + return this._loadFromServer(params); + } else if (params.api) { + return this._loadFromAPI(params); + } else if (params.src) { + return this._loadFrom_glTF(params); + } + } + }, { + key: '_loadFromServer', + value: function _loadFromServer(params) { + + var notifier = new Notifier(); + var bimServerApi = new this.BimServerApi(params.bimserver, notifier); + + params.api = bimServerApi; // TODO: Make copy of params + + return this._initApi(params).then(this._loginToServer).then(this._getRevisionFromServer.bind(this)).then(this._loadFromAPI.bind(this)); + } + }, { + key: '_initApi', + value: function _initApi(params) { + return new Promise(function (resolve, reject) { + params.api.init(function () { + resolve(params); + }); + }); + } + }, { + key: '_loginToServer', + value: function _loginToServer(params) { + return new Promise(function (resolve, reject) { + if (params.token) { + params.api.setToken(params.token, function () { + resolve(params); + }, reject); + } else { + params.api.login(params.username, params.password, function () { + resolve(params); + }, reject); + } + }); + } + }, { + key: '_getRevisionFromServer', + value: function _getRevisionFromServer(params) { + var _this2 = this; + + return new Promise(function (resolve, reject) { + if (params.roid) { + resolve(params); + } else { + params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, function (data) { + var resolved = false; + + data.forEach(function (projectData) { + if (projectData.oid == params.poid) { + params.roid = projectData.lastRevisionId; + params.schema = projectData.schema; + if (!_this2.models) { + _this2.models = []; + } + _this2.models.push(projectData); + resolved = true; + resolve(params); + } + }); + + if (!resolved) { + reject(); + } + }, reject); + } + }); + } + }, { + key: '_loadFrom_glTF', + value: function _loadFrom_glTF(params) { + var _this3 = this; + + if (params.src) { + return new Promise(function (resolve, reject) { + var m = _this3.viewer.loadglTF(params.src); + m.on("loaded", function () { + + var numComponents = 0, + componentsLoaded = 0; + + m.iterate(function (component) { + if (component.isType("xeogl.Entity")) { + ++numComponents; + (function (c) { + var timesUpdated = 0; + c.worldBoundary.on("updated", function () { + if (++timesUpdated == 2) { + ++componentsLoaded; + if (componentsLoaded == numComponents) { + _this3.viewer.viewFit({}); + + resolve(m); + } + } + }); + })(component); + } + }); + }); + }); + } + } + }, { + key: '_loadFromAPI', + value: function _loadFromAPI(params) { + var _this4 = this; + + return new Promise(function (resolve, reject) { + + params.api.getModel(params.poid, params.roid, params.schema, false, function (model) { + + // TODO: Preload not necessary combined with the bruteforce tree + var fired = false; + + model.query(PreloadQuery, function () { + if (!fired) { + fired = true; + var vmodel = new BimServerModel(params.api, model); + + _this4._loadModel(vmodel); + + resolve(vmodel); + } + }); + }); + }); + } + }, { + key: '_loadModel', + value: function _loadModel(model) { + var _this5 = this; + + model.getTree().then(function (tree) { + + var oids = []; + var oidToGuid = {}; + var guidToOid = {}; + + var visit = function visit(n) { + oids[n.gid] = n.id; + oidToGuid[n.id] = n.guid; + guidToOid[n.guid] = n.id; + + for (var i = 0; i < (n.children || []).length; ++i) { + visit(n.children[i]); + } + }; + + visit(tree); + + _this5._idMapping.toGuid.push(oidToGuid); + _this5._idMapping.toId.push(guidToOid); + + var models = {}; + + // TODO: Ugh. Undecorate some of the newly created classes + models[model.model.roid] = model.model; + + // Notify viewer that things are loading, so viewer can + // reduce rendering speed and show a spinner. + _this5.viewer.taskStarted(); + + _this5.viewer.createModel(model.model.roid); + + var loader = new BimServerGeometryLoader(model.api, models, _this5.viewer); + + loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { + if (progress == "start") { + console.log("Started loading geometries"); + _this5.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + _this5.fire("loading-finished"); + _this5.viewer.taskFinished(); + } + }); + + loader.setLoadOids([model.model.roid], oids); + + // viewer.clear(); // For now, until we support multiple models through the API + + _this5.viewer.on("tick", function () { + // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); + + loader.start(); + }); + } + + // Helper function to traverse over the mappings for individually loaded models + + }, { + key: 'toId', + + + /** + * Returns a list of object ids (oid) for the list of guids (GlobalId) + * + * @param guids List of globally unique identifiers from the IFC model + */ + value: function toId(guids) { + return guids.map(this._traverseMappings(this._idMapping.toId)); + } + + /** + * Returns a list of guids (GlobalId) for the list of object ids (oid) + * + * @param ids List of internal object ids from the BIMserver / glTF file + */ + + }, { + key: 'toGuid', + value: function toGuid(ids) { + return ids.map(this._traverseMappings(this._idMapping.toGuid)); + } + + /** + * Shows/hides objects specified by id or entity type, e.g IfcWall. + * + * When recursive is set to true, hides children (aggregates, spatial structures etc) or + * subtypes (IfcWallStandardCase ⊆ IfcWall). + * + * @param params + */ + + }, { + key: 'setVisibility', + value: function setVisibility(params) { + this.viewer.setVisibility(params); + } + + /** + * Selects/deselects objects specified by id. + ** + * @param params + */ + + }, { + key: 'setSelection', + value: function setSelection(params) { + return this.viewer.setSelection(params); + } + + /** + * Gets a list of selected elements. + */ + + }, { + key: 'getSelection', + value: function getSelection() { + return this.viewer.getSelection(); + } + + /** + * Sets color of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ + + }, { + key: 'setColor', + value: function setColor(params) { + this.viewer.setColor(params); + } + + /** + * Sets opacity of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ + + }, { + key: 'setOpacity', + value: function setOpacity(params) { + this.viewer.setOpacity(params); + } + + /** + * Fits the elements into view. + * + * Fits the entire model into view if ids is an empty array, null or undefined. + * Animate allows to specify a transition period in milliseconds in which the view is altered. + * + * @param params + */ + + }, { + key: 'viewFit', + value: function viewFit(params) { + this.viewer.viewFit(params); + } + + /** + * + */ + + }, { + key: 'getCamera', + value: function getCamera() { + return this.viewer.getCamera(); + } + + /** + * + * @param params + */ + + }, { + key: 'setCamera', + value: function setCamera(params) { + this.viewer.setCamera(params); + } + + /** + * Redefines light sources. + * + * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type + */ + + }, { + key: 'setLights', + value: function setLights(params) { + this.viewer.setLights(params); + } + + /** + * Returns light sources. + * + * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + */ + + }, { + key: 'getLights', + value: function getLights() { + return this.viewer.getLights; + } + + /** + * + * @param params + */ + + }, { + key: 'reset', + value: function reset(params) { + this.viewer.reset(params); + } + + /** + * Returns a list of loaded IFC entity types in the model. + * + * @method getTypes + * @returns {Array} List of loaded IFC entity types, with visibility flag + */ + + }, { + key: 'getTypes', + value: function getTypes() { + return this.viewer.getTypes(); + } + + /** + * Sets the default behaviour of mouse and touch drag input + * + * @method setDefaultDragAction + * @param {String} action ("pan" | "orbit") + */ + + }, { + key: 'setDefaultDragAction', + value: function setDefaultDragAction(action) { + this.viewer.setDefaultDragAction(action); + } + + /** + * Returns the world boundary of an object + * + * @method getWorldBoundary + * @param {String} objectId id of object + * @param {Object} result Existing boundary object + * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D + */ + + }, { + key: 'getWorldBoundary', + value: function getWorldBoundary(objectId, result) { + return this.viewer.getWorldBoundary(objectId, result); + } + + /** + * Destroys the BIMSurfer + */ + + }, { + key: 'destroy', + value: function destroy() { + this.viewer.destroy(); + } + }], [{ + key: '_traverseMappings', + value: function _traverseMappings(mappings) { + return function (k) { + for (var i = 0; i < mappings.length; ++i) { + var v = mappings[i][k]; + if (v) { + return v; + } + } + return null; + }; + } + }]); + return BimSurfer; +}(EventHandler); + +var BimServerModelLoader = function () { + + //define(["./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./BimSurfer"], function(BimServerModel, PreloadQuery, BimServerGeometryLoader, BimSufer) { + + function BimServerModelLoader(bimServerClient, bimSurfer) { + classCallCheck(this, BimServerModelLoader); + + this.bimServerClient = bimServerClient; + this.bimSurfer = bimSurfer; + this.globalTransformationMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } + + createClass(BimServerModelLoader, [{ + key: 'loadFullModel', + value: function loadFullModel(apiModel) { + var _this = this; + + return new Promise(function (resolve, reject) { + var model = new BimServerModel(apiModel); + + apiModel.query(PreloadQuery, function () {}).done(function () { + var oids = []; + apiModel.getAllOfType("IfcProduct", true, function (object) { + oids.push(object.oid); + }); + _this.loadOids(model, oids); + resolve(model); + }); + }); + } + }, { + key: 'loadObjects', + value: function loadObjects(apiModel, objects) { + var _this2 = this; + + return new Promise(function (resolve, reject) { + var model = new BimServerModel(apiModel); + + var oids = []; + objects.forEach(function (object) { + oids.push(object.oid); + }); + _this2.loadOids(model, oids); + resolve(model); + }); + } + }, { + key: 'loadOids', + value: function loadOids(model, oids) { + var _this3 = this; + + var oidToGuid = {}; + var guidToOid = {}; + + var oidGid = {}; + + oids.forEach(function (oid) { + model.apiModel.get(oid, function (object) { + if (object.object._rgeometry != null) { + var gid = object.object._rgeometry; //._i; + var guid = object.object.GlobalId; + oidToGuid[oid] = guid; + guidToOid[guid] = oid; + oidGid[gid] = oid; + } + }); + }); + + this.bimSurfer._idMapping.toGuid.push(oidToGuid); + this.bimSurfer._idMapping.toId.push(guidToOid); + + var viewer = this.bimSurfer.viewer; + viewer.taskStarted(); + + viewer.createModel(model.apiModel.roid); + + var loader = new BimServerGeometryLoader(model.apiModel.bimServerApi, viewer, model, model.apiModel.roid, this.globalTransformationMatrix); + + loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { + if (progress == "start") { + console.log("Started loading geometries"); + _this3.bimSurfer.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + _this3.bimSurfer.fire("loading-finished"); + viewer.taskFinished(); + } + }); + + loader.setLoadOids(oidGid); + + // viewer.clear(); // For now, until we support multiple models through the API + + viewer.on("tick", function () { + // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); + + loader.start(); + } + }, { + key: 'setGlobalTransformationMatrix', + value: function setGlobalTransformationMatrix(globalTransformationMatrix) { + this.globalTransformationMatrix = globalTransformationMatrix; + } + }]); + return BimServerModelLoader; +}(); + +function make(args) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open(args.method || "GET", args.url, true); + xhr.onload = function (e) { + console.log(args.url, xhr.readyState, xhr.status); + if (xhr.readyState === 4) { + if (xhr.status === 200) { + resolve(xhr.responseXML); + } else { + reject(xhr.statusText); + } + } + }; + xhr.send(null); + }); +} + +var StaticTreeRenderer = function (_EventHandler) { + inherits(StaticTreeRenderer, _EventHandler); + + function StaticTreeRenderer(args) { + classCallCheck(this, StaticTreeRenderer); + + var _this = possibleConstructorReturn(this, (StaticTreeRenderer.__proto__ || Object.getPrototypeOf(StaticTreeRenderer)).call(this)); + + _this.args = args; + _this.TOGGLE = 0; + _this.SELECT = 1; + _this.SELECT_EXCLUSIVE = 2; + _this.DESELECT = 3; + + _this.fromXml = false; + + _this.domNodes = {}; + _this.selectionState = {}; + _this.models = []; + return _this; + } + + createClass(StaticTreeRenderer, [{ + key: 'getOffset', + value: function getOffset(elem) { + var reference = document.getElementById(this.args.domNode); + var y = 0; + + while (true) { + y += elem.offsetTop; + if (elem == reference) { + break; + } + elem = elem.offsetParent; + } + return y; + } + }, { + key: 'setSelected', + value: function setSelected(ids, mode) { + var _this2 = this; + + if (mode == this.SELECT_EXCLUSIVE) { + this.setSelected(this.getSelected(true), this.DESELECT); + } + + ids.forEach(function (id) { + var s = null; + + if (mode == _this2.TOGGLE) { + s = _this2.selectionState[id] = !_this2.selectionState[id]; + } else if (mode == _this2.SELECT || mode == _this2.SELECT_EXCLUSIVE) { + s = _this2.selectionState[id] = true; + } else if (mode == _this2.DESELECT) { + s = _this2.selectionState[id] = false; + } + + _this2.domNodes[id].className = s ? "label selected" : "label"; + }); + + var desiredViewRange = this.getSelected().map(function (id) { + return _this2.getOffset(_this2.domNodes[id]); + }); + + if (desiredViewRange.length) { + desiredViewRange.sort(); + desiredViewRange = [desiredViewRange[0], desiredViewRange[desiredViewRange.length - 1]]; + + var domNode = document.getElementById(this.args.domNode); + var currentViewRange = [domNode.scrollTop, domNode.scrollTop + domNode.offsetHeight]; + + if (!(desiredViewRange[0] >= currentViewRange[0] && desiredViewRange[1] <= currentViewRange[1])) { + if (desiredViewRange[1] - desiredViewRange[0] > currentViewRange[1] - currentViewRange[0]) { + domNode.scrollTop = desiredViewRange[0]; + } else { + var l = parseInt((desiredViewRange[1] + desiredViewRange[0]) / 2.0 - (currentViewRange[1] - currentViewRange[0]) / 2.0, 10); + + l = Math.max(l, 0); + l = Math.min(l, domNode.scrollHeight - domNode.offsetHeight); + domNode.scrollTop = l; + } + } + } + + this.fire("selection-changed", [this.getSelected(true)]); + } + }, { + key: 'getSelected', + value: function getSelected(b) { + var _this3 = this; + + b = typeof b === 'undefined' ? true : !!b; + var l = []; + Object.keys(this.selectionState).forEach(function (k) { + if (!!_this3.selectionState[k] === b) { + l.push(k); + } + }); + return l; + } + }, { + key: 'addModel', + value: function addModel(args) { + this.models.push(args); + if (args.src) { + this.fromXml = true; + } + } + }, { + key: 'qualifyInstance', + value: function qualifyInstance(modelId, id) { + if (this.fromXml) { + return id; + } else { + return modelId + ":" + id; + } + } + }, { + key: 'build', + value: function build() { + var _this4 = this; + + var build = function build(modelId, d, n) { + var qid = _this4.qualifyInstance(modelId, _this4.fromXml ? n.guid : n.id); + var label = document.createElement("div"); + var children = document.createElement("div"); + + label.className = "label"; + label.appendChild(document.createTextNode(n.name || n.guid)); + d.appendChild(label); + children.className = "children"; + d.appendChild(children); + _this4.domNodes[qid] = label; + + label.onclick = function (evt) { + evt.stopPropagation(); + evt.preventDefault(); + _this4.setSelected([qid], evt.shiftKey ? _this4.TOGGLE : _this4.SELECT_EXCLUSIVE); + _this4.fire("click", [qid, _this4.getSelected(true)]); + return false; + }; + + for (var _i = 0; _i < (n.children || []).length; ++_i) { + var child = n.children[_i]; + if (_this4.fromXml) { + if (child["xlink:href"]) { + continue; + } + if (child.type === "IfcOpeningElement") { + continue; + } + } + var d2 = document.createElement("div"); + d2.className = "item"; + children.appendChild(d2); + build(modelId, d2, child); + } + }; + + this.models.forEach(function (m) { + var d = document.createElement("div"); + d.className = "item"; + if (m.tree) { + build(m.id, d, m.tree); + } else if (m.src) { + make({ url: m.src }).then(function (xml) { + var json = Utils.XmlToJson(xml, { 'Name': 'name', 'id': 'guid' }); + var project = Utils.FindNodeOfType(json.children[0], "decomposition")[0].children[0]; + build(m.id || i, d, project); + }); + } + document.getElementById(_this4.args.domNode).appendChild(d); + }); + } + }]); + return StaticTreeRenderer; +}(EventHandler); + +var Row = function () { + function Row(args) { + classCallCheck(this, Row); + + this.args = args; + this.num_names = 0; + this.num_values = 0; + } + + createClass(Row, [{ + key: 'setName', + value: function setName(name) { + if (this.num_names++ > 0) { + this.args.name.appendChild(document.createTextNode(" ")); + } + this.args.name.appendChild(document.createTextNode(name)); + } + }, { + key: 'setValue', + value: function setValue(value) { + if (this.num_values++ > 0) { + this.args.value.appendChild(document.createTextNode(", ")); + } + this.args.value.appendChild(document.createTextNode(value)); + } + }]); + return Row; +}(); + +var Section = function () { + function Section(args) { + classCallCheck(this, Section); + + this.args = args; + + this.div = document.createElement("div"); + this.nameh = document.createElement("h3"); + this.table = document.createElement("table"); + + this.tr = document.createElement("tr"); + this.table.appendChild(this.tr); + + this.nameth = document.createElement("th"); + this.valueth = document.createElement("th"); + + this.nameth.appendChild(document.createTextNode("Name")); + this.valueth.appendChild(document.createTextNode("Value")); + this.tr.appendChild(this.nameth); + this.tr.appendChild(this.valueth); + + this.div.appendChild(this.nameh); + this.div.appendChild(this.table); + + args.domNode.appendChild(this.div); + + this.setSelected([]); + } + + createClass(Section, [{ + key: 'setName', + value: function setName(name) { + this.nameh.appendChild(document.createTextNode(name)); + } + }, { + key: 'addRow', + value: function addRow() { + var tr = document.createElement("tr"); + this.table.appendChild(tr); + var nametd = document.createElement("td"); + var valuetd = document.createElement("td"); + tr.appendChild(nametd); + tr.appendChild(valuetd); + return new Row({ name: nametd, value: valuetd }); + } + }]); + return Section; +}(); + +function loadModelFromSource(src) { + return new Promise(function (resolve, reject) { + make({ url: src }).then(function (xml) { + var json = Utils.XmlToJson(xml, { 'Name': 'name', 'id': 'guid' }); + + var psets = Utils.FindNodeOfType(json, "properties")[0]; + var project = Utils.FindNodeOfType(json, "decomposition")[0].children[0]; + var types = Utils.FindNodeOfType(json, "types")[0]; + + var objects = {}; + var typeObjects = {}; + var properties = {}; + psets.children.forEach(function (pset) { + properties[pset.guid] = pset; + }); + + var visitObject = function visitObject(parent, node) { + var o = parent && parent.ObjectPlacement ? objects : typeObjects; + + if (node["xlink:href"]) { + if (!o[parent.guid]) { + var _p = Utils.Clone(parent); + _p.GlobalId = _p.guid; + o[_p.guid] = _p; + o[_p.guid].properties = []; + } + var g = node["xlink:href"].substr(1); + var p = properties[g]; + if (p) { + o[parent.guid].properties.push(p); + } else if (typeObjects[g]) { + // If not a pset, it is a type, so concatenate type props + o[parent.guid].properties = o[parent.guid].properties.concat(typeObjects[g].properties); + } + } + node.children.forEach(function (n) { + visitObject(node, n); + }); + }; + + visitObject(null, types); + visitObject(null, project); + + resolve({ model: { objects: objects, source: 'XML' } }); + }); + }); +} + +var MetaDataRenderer = function (_EventHandler) { + inherits(MetaDataRenderer, _EventHandler); + + function MetaDataRenderer(args) { + classCallCheck(this, MetaDataRenderer); + + var _this = possibleConstructorReturn(this, (MetaDataRenderer.__proto__ || Object.getPrototypeOf(MetaDataRenderer)).call(this)); + + _this.args = args; + + _this.models = {}; + _this.domNode = document.getElementById(_this.args.domNode); + return _this; + } + + createClass(MetaDataRenderer, [{ + key: 'addModel', + value: function addModel(args) { + var _this2 = this; + + return new Promise(function (resolve, reject) { + if (args.model) { + _this2.models[args.id] = args.model; + resolve(args.model); + } else { + loadModelFromSource(args.src).then(function (m) { + _this2.models[args.id] = m; + resolve(m); + }); + } + }); + } + }, { + key: 'renderAttributes', + value: function renderAttributes(elem) { + var s = new Section({ domNode: this.domNode }); + s.setName(elem.type || elem.getType()); + + ["GlobalId", "Name", "OverallWidth", "OverallHeight", "Tag"].forEach(function (k) { + var v = elem[k]; + if (typeof v === 'undefined') { + var fn = elem["get" + k]; + if (fn) { + v = fn.apply(elem); + } + } + if (typeof v !== 'undefined') { + var r = s.addRow(); + r.setName(k); + r.setValue(v); + } + }); + return s; + } + }, { + key: 'renderPSet', + value: function renderPSet(pset) { + var s = new Section({ domNode: this.domNode }); + if (pset.name && pset.children) { + s.setName(pset.name); + pset.children.forEach(function (v) { + var r = s.addRow(); + r.setName(v.name); + r.setValue(v.NominalValue); + }); + } else { + pset.getName(function (name) { + s.setName(name); + }); + var render = function render(prop, index, row) { + var r = row || s.addRow(); + prop.getName(function (name) { + r.setName(name); + }); + if (prop.getNominalValue) { + prop.getNominalValue(function (value) { + r.setValue(value._v); + }); + } + if (prop.getHasProperties) { + prop.getHasProperties(function (prop, index) { + render(prop, index, r); + }); + } + }; + pset.getHasProperties(render); + } + return s; + } + }, { + key: 'setSelected', + value: function setSelected(oid) { + var _this3 = this; + + if (oid.length !== 1) { + this.domNode.innerHTML = " 
Select a single element in order to see object properties."; + return; + } + + this.domNode.innerHTML = ""; + + oid = oid[0]; + + if (oid.indexOf(':') !== -1) { + oid = oid.split(':'); + var o = this.models[oid[0]].model.objects[oid[1]]; + + this.renderAttributes(o); + + o.getIsDefinedBy(function (isDefinedBy) { + if (isDefinedBy.getType() == "IfcRelDefinesByProperties") { + isDefinedBy.getRelatingPropertyDefinition(function (pset) { + if (pset.getType() == "IfcPropertySet") { + _this3.renderPSet(pset); + } + }); + } + }); + } else { + var _o = this.models["1"].model.objects[oid]; + this.renderAttributes(_o); + _o.properties.forEach(function (pset) { + _this3.renderPSet(pset); + }); + } + } + }]); + return MetaDataRenderer; +}(EventHandler); + +exports.BimSurfer = BimSurfer; +exports.BimServerModelLoader = BimServerModelLoader; +exports.StaticTreeRenderer = StaticTreeRenderer; +exports.MetaDataRenderer = MetaDataRenderer; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/docs/example_BIMServer.html b/docs/example_BIMServer.html index 97d54c6..596a909 100644 --- a/docs/example_BIMServer.html +++ b/docs/example_BIMServer.html @@ -9,12 +9,14 @@ - - - + + + + + - - - diff --git a/docs/example_glTF.html b/docs/example_glTF.html index 3895b9d..9aec9c0 100644 --- a/docs/example_glTF.html +++ b/docs/example_glTF.html @@ -2,115 +2,14 @@ - Example + BIMsurfer glTF example - - - - - - + + +
@@ -139,5 +38,84 @@

BIMsurfer demo

+ + \ No newline at end of file diff --git a/docs/example_testModel.html b/docs/example_testModel.html index 1d6a369..a46f2b4 100644 --- a/docs/example_testModel.html +++ b/docs/example_testModel.html @@ -2,66 +2,12 @@ - Example + BIMsurfer example - - - - - - + + +
@@ -83,6 +29,39 @@
+ \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab1e383 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "bimsurfer", + "description": "", + "main": "build/bimsurfer.js", + "module": "bimsurfer/src/index.js", + "version": "0.0.1", + "scripts": { + "build": "rollup -c" + }, + "dependencies": { + "bimserverapi": "git+https://github.com/opensourceBIM/BIMserver-JavaScript-API", + "xeogl": "git+https://github.com/xeolabs/xeogl.git" + }, + "devDependencies": { + "babel-core": "^6.26.0", + "babel-plugin-external-helpers": "^6.22.0", + "babel-preset-env": "^1.6.1", + "rollup": "^0.50.0", + "rollup-plugin-babel": "^3.0.2", + "rollup-plugin-commonjs": "^8.2.3", + "rollup-plugin-ignore": "^1.0.3", + "rollup-plugin-node-builtins": "^2.1.2", + "rollup-plugin-node-globals": "^1.1.0", + "rollup-plugin-node-resolve": "^3.0.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..aa4b92f --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,32 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import builtins from 'rollup-plugin-node-builtins'; +import babel from 'rollup-plugin-babel'; + +export default [{ + input: 'bimsurfer/src/index.js', + name: 'bimsurfer', + output: { + file: 'build/bimsurfer.js', + format: 'umd' + }, + external: ['bimserverapi', 'xeogl'], + globals: { + bimserverapi: 'BimServerClient' + }, + plugins: [ + resolve({ + jsnext: true, + main: true, + preferBuiltins: true, + browser: true + }), + commonjs({ + sourceMap: false + }), + builtins(), + babel({ + exclude: ['node_modules/**'] + }) + ] +}]; \ No newline at end of file From fbbb36a825c3634d6d5e983d6d24584410377a18 Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Wed, 6 Dec 2017 10:53:37 +0000 Subject: [PATCH 2/6] Loading finished event --- bimsurfer/src/BimSurfer.js | 557 ++++++++++++++------------- bimsurfer/src/xeoViewer/xeoViewer.js | 3 +- 2 files changed, 284 insertions(+), 276 deletions(-) diff --git a/bimsurfer/src/BimSurfer.js b/bimsurfer/src/BimSurfer.js index 37d3b84..018bfb3 100644 --- a/bimsurfer/src/BimSurfer.js +++ b/bimsurfer/src/BimSurfer.js @@ -8,265 +8,272 @@ import xeoViewer from './xeoViewer/xeoViewer.js'; import BimServerClient from "bimserverapi"; export default class BimSurfer extends EventHandler { - constructor(cfg) { - super(); + constructor(cfg) { + super(); - this.BimServerApi = BimServerClient; + this.BimServerApi = BimServerClient; - cfg = cfg || {}; + cfg = cfg || {}; - this.viewer = new xeoViewer(cfg); + this.viewer = new xeoViewer(cfg); /** * Fired whenever this BIMSurfer's camera changes. * @event camera-changed */ - this.viewer.on("camera-changed", (args) => { - this.fire("camera-changed", args); - }); + this.viewer.on("camera-changed", (args) => { + this.fire("camera-changed", args); + }); /** * Fired whenever this BIMSurfer's selection changes. * @event selection-changed */ - this.viewer.on("selection-changed", (args) => { - this.fire("selection-changed", args); - }); - - // This are arrays as multiple models might be loaded or unloaded. - this._idMapping = { - 'toGuid': [], - 'toId': [] - }; - } + this.viewer.on("selection-changed", (args) => { + this.fire("selection-changed", args); + }); + + /** + * Fired when all models have finished loading + * @event loading-finished + */ + this.viewer.on("loading-finished", (args) => { + this.fire("loading-finished", args); + }); + + // This are arrays as multiple models might be loaded or unloaded. + this._idMapping = { + 'toGuid': [], + 'toId': [] + }; + } /** * Loads a model into this BIMSurfer. * @param params */ - load(params) { - - if (params.test) { - this.viewer.loadRandom(params); - return null; - - } else if (params.bimserver) { - return this._loadFromServer(params); - - } else if (params.api) { - return this._loadFromAPI(params); - - } else if (params.src) { - return this._loadFrom_glTF(params); - } - } - - _loadFromServer(params) { - - const notifier = new Notifier(); - const bimServerApi = new this.BimServerApi(params.bimserver, notifier); - - params.api = bimServerApi; // TODO: Make copy of params - - return this._initApi(params) - .then(this._loginToServer) - .then(this._getRevisionFromServer.bind(this)) - .then(this._loadFromAPI.bind(this)); - } - - _initApi(params) { - return new Promise((resolve, reject) => { - params.api.init(() => { - resolve(params); - }); - }); - } - - _loginToServer(params) { - return new Promise((resolve, reject) => { - if (params.token) { - params.api.setToken(params.token, () => { - resolve(params); - }, reject); - } else { - params.api.login(params.username, params.password, () => { - resolve(params); - }, reject); - } - }); - } - - _getRevisionFromServer(params) { - return new Promise((resolve, reject) => { - if (params.roid) { - resolve(params); - } else { - params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, (data) => { - let resolved = false; - - data.forEach((projectData) => { - if (projectData.oid == params.poid) { - params.roid = projectData.lastRevisionId; - params.schema = projectData.schema; - if (!this.models) { - this.models = []; - } - this.models.push(projectData); - resolved = true; - resolve(params); - } - }); - - if (!resolved) { - reject(); - } - }, reject); - } - }); - } - - _loadFrom_glTF(params) { - if (params.src) { - return new Promise((resolve, reject) => { - const m = this.viewer.loadglTF(params.src); - m.on("loaded", () => { - - let numComponents = 0, componentsLoaded = 0; - - m.iterate((component) => { - if (component.isType("xeogl.Entity")) { - ++numComponents; - ((c) => { - let timesUpdated = 0; - c.worldBoundary.on("updated", () => { - if (++timesUpdated == 2) { - ++componentsLoaded; - if (componentsLoaded == numComponents) { - this.viewer.viewFit({}); - - resolve(m); - } - } - }); - })(component); - } - }); - }); - }); - } - } - - _loadFromAPI(params) { - - return new Promise((resolve, reject) => { - - params.api.getModel(params.poid, params.roid, params.schema, false, - (model) => { - - // TODO: Preload not necessary combined with the bruteforce tree - let fired = false; - - model.query(PreloadQuery, - () => { - if (!fired) { - fired = true; - const vmodel = new Model(params.api, model); - - this._loadModel(vmodel); - - resolve(vmodel); - } - }); - }); - }); - } - - _loadModel(model) { - - model.getTree().then((tree) => { - - const oids = []; - const oidToGuid = {}; - const guidToOid = {}; - - const visit = (n) => { - oids[n.gid] = n.id; - oidToGuid[n.id] = n.guid; - guidToOid[n.guid] = n.id; - - for (let i = 0; i < (n.children || []).length; ++i) { - visit(n.children[i]); - } - }; - - visit(tree); - - this._idMapping.toGuid.push(oidToGuid); - this._idMapping.toId.push(guidToOid); - - const models = {}; - - // TODO: Ugh. Undecorate some of the newly created classes - models[model.model.roid] = model.model; - - // Notify viewer that things are loading, so viewer can - // reduce rendering speed and show a spinner. - this.viewer.taskStarted(); - - this.viewer.createModel(model.model.roid); - - const loader = new GeometryLoader(model.api, models, this.viewer); - - loader.addProgressListener((progress, nrObjectsRead, totalNrObjects) => { - if (progress == "start") { - console.log("Started loading geometries"); - this.fire("loading-started"); - } else if (progress == "done") { - console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); - this.fire("loading-finished"); - this.viewer.taskFinished(); - } - }); - - loader.setLoadOids([model.model.roid], oids); - - // viewer.clear(); // For now, until we support multiple models through the API - - this.viewer.on("tick", () => { // TODO: Fire "tick" event from xeoViewer - loader.process(); - }); - - loader.start(); - }); - } - - // Helper function to traverse over the mappings for individually loaded models - static _traverseMappings(mappings) { - return (k) => { - for (let i = 0; i < mappings.length; ++i) { - const v = mappings[i][k]; - if (v) { return v; } - } - return null; - }; - } + load(params) { + + if (params.test) { + this.viewer.loadRandom(params); + return null; + + } else if (params.bimserver) { + return this._loadFromServer(params); + + } else if (params.api) { + return this._loadFromAPI(params); + + } else if (params.src) { + return this._loadFrom_glTF(params); + } + } + + _loadFromServer(params) { + + const notifier = new Notifier(); + const bimServerApi = new this.BimServerApi(params.bimserver, notifier); + + params.api = bimServerApi; // TODO: Make copy of params + + return this._initApi(params) + .then(this._loginToServer) + .then(this._getRevisionFromServer.bind(this)) + .then(this._loadFromAPI.bind(this)); + } + + _initApi(params) { + return new Promise((resolve, reject) => { + params.api.init(() => { + resolve(params); + }); + }); + } + + _loginToServer(params) { + return new Promise((resolve, reject) => { + if (params.token) { + params.api.setToken(params.token, () => { + resolve(params); + }, reject); + } else { + params.api.login(params.username, params.password, () => { + resolve(params); + }, reject); + } + }); + } + + _getRevisionFromServer(params) { + return new Promise((resolve, reject) => { + if (params.roid) { + resolve(params); + } else { + params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, (data) => { + let resolved = false; + + data.forEach((projectData) => { + if (projectData.oid == params.poid) { + params.roid = projectData.lastRevisionId; + params.schema = projectData.schema; + if (!this.models) { + this.models = []; + } + this.models.push(projectData); + resolved = true; + resolve(params); + } + }); + + if (!resolved) { + reject(); + } + }, reject); + } + }); + } + + _loadFrom_glTF(params) { + if (params.src) { + return new Promise((resolve, reject) => { + const m = this.viewer.loadglTF(params.src); + m.on("loaded", () => { + + let numComponents = 0, componentsLoaded = 0; + + m.iterate((component) => { + if (component.isType("xeogl.Entity")) { + ++numComponents; + ((c) => { + let timesUpdated = 0; + c.worldBoundary.on("updated", () => { + if (++timesUpdated == 2) { + ++componentsLoaded; + if (componentsLoaded == numComponents) { + this.viewer.viewFit({}); + + resolve(m); + } + } + }); + })(component); + } + }); + }); + }); + } + } + + _loadFromAPI(params) { + + return new Promise((resolve, reject) => { + + params.api.getModel(params.poid, params.roid, params.schema, false, + (model) => { + + // TODO: Preload not necessary combined with the bruteforce tree + let fired = false; + + model.query(PreloadQuery, + () => { + if (!fired) { + fired = true; + const vmodel = new Model(params.api, model); + + this._loadModel(vmodel); + + resolve(vmodel); + } + }); + }); + }); + } + + _loadModel(model) { + + model.getTree().then((tree) => { + + const oids = []; + const oidToGuid = {}; + const guidToOid = {}; + + const visit = (n) => { + oids[n.gid] = n.id; + oidToGuid[n.id] = n.guid; + guidToOid[n.guid] = n.id; + + for (let i = 0; i < (n.children || []).length; ++i) { + visit(n.children[i]); + } + }; + + visit(tree); + + this._idMapping.toGuid.push(oidToGuid); + this._idMapping.toId.push(guidToOid); + + const models = {}; + + // TODO: Ugh. Undecorate some of the newly created classes + models[model.model.roid] = model.model; + + // Notify viewer that things are loading, so viewer can + // reduce rendering speed and show a spinner. + this.viewer.taskStarted(); + + this.viewer.createModel(model.model.roid); + + const loader = new GeometryLoader(model.api, models, this.viewer); + + loader.addProgressListener((progress, nrObjectsRead, totalNrObjects) => { + if (progress == "start") { + console.log("Started loading geometries"); + //this.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + this.viewer.taskFinished(); + } + }); + + loader.setLoadOids([model.model.roid], oids); + + // viewer.clear(); // For now, until we support multiple models through the API + + this.viewer.on("tick", () => { // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); + + loader.start(); + }); + } + + // Helper function to traverse over the mappings for individually loaded models + static _traverseMappings(mappings) { + return (k) => { + for (let i = 0; i < mappings.length; ++i) { + const v = mappings[i][k]; + if (v) { return v; } + } + return null; + }; + } /** * Returns a list of object ids (oid) for the list of guids (GlobalId) * * @param guids List of globally unique identifiers from the IFC model */ - toId(guids) { - return guids.map(this._traverseMappings(this._idMapping.toId)); - } + toId(guids) { + return guids.map(this._traverseMappings(this._idMapping.toId)); + } /** * Returns a list of guids (GlobalId) for the list of object ids (oid) * * @param ids List of internal object ids from the BIMserver / glTF file */ - toGuid(ids) { - return ids.map(this._traverseMappings(this._idMapping.toGuid)); - } + toGuid(ids) { + return ids.map(this._traverseMappings(this._idMapping.toGuid)); + } /** * Shows/hides objects specified by id or entity type, e.g IfcWall. @@ -276,43 +283,43 @@ export default class BimSurfer extends EventHandler { * * @param params */ - setVisibility(params) { - this.viewer.setVisibility(params); - } + setVisibility(params) { + this.viewer.setVisibility(params); + } /** * Selects/deselects objects specified by id. ** * @param params */ - setSelection(params) { - return this.viewer.setSelection(params); - } + setSelection(params) { + return this.viewer.setSelection(params); + } /** * Gets a list of selected elements. */ - getSelection() { - return this.viewer.getSelection(); - } + getSelection() { + return this.viewer.getSelection(); + } /** * Sets color of objects specified by ids or entity type, e.g IfcWall. ** * @param params */ - setColor(params) { - this.viewer.setColor(params); - } + setColor(params) { + this.viewer.setColor(params); + } /** * Sets opacity of objects specified by ids or entity type, e.g IfcWall. ** * @param params */ - setOpacity(params) { - this.viewer.setOpacity(params); - } + setOpacity(params) { + this.viewer.setOpacity(params); + } /** * Fits the elements into view. @@ -322,24 +329,24 @@ export default class BimSurfer extends EventHandler { * * @param params */ - viewFit(params) { - this.viewer.viewFit(params); - } + viewFit(params) { + this.viewer.viewFit(params); + } /** * */ - getCamera() { - return this.viewer.getCamera(); - } + getCamera() { + return this.viewer.getCamera(); + } /** * * @param params */ - setCamera(params) { - this.viewer.setCamera(params); - } + setCamera(params) { + this.viewer.setCamera(params); + } /** * Redefines light sources. @@ -347,26 +354,26 @@ export default class BimSurfer extends EventHandler { * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type */ - setLights(params) { - this.viewer.setLights(params); - } + setLights(params) { + this.viewer.setLights(params); + } /** * Returns light sources. * * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} */ - getLights() { - return this.viewer.getLights; - } + getLights() { + return this.viewer.getLights; + } /** * * @param params */ - reset(params) { - this.viewer.reset(params); - } + reset(params) { + this.viewer.reset(params); + } /** * Returns a list of loaded IFC entity types in the model. @@ -374,9 +381,9 @@ export default class BimSurfer extends EventHandler { * @method getTypes * @returns {Array} List of loaded IFC entity types, with visibility flag */ - getTypes() { - return this.viewer.getTypes(); - } + getTypes() { + return this.viewer.getTypes(); + } /** * Sets the default behaviour of mouse and touch drag input @@ -384,9 +391,9 @@ export default class BimSurfer extends EventHandler { * @method setDefaultDragAction * @param {String} action ("pan" | "orbit") */ - setDefaultDragAction(action) { - this.viewer.setDefaultDragAction(action); - } + setDefaultDragAction(action) { + this.viewer.setDefaultDragAction(action); + } /** * Returns the world boundary of an object @@ -396,14 +403,14 @@ export default class BimSurfer extends EventHandler { * @param {Object} result Existing boundary object * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D */ - getWorldBoundary(objectId, result) { - return this.viewer.getWorldBoundary(objectId, result); - } + getWorldBoundary(objectId, result) { + return this.viewer.getWorldBoundary(objectId, result); + } /** * Destroys the BIMSurfer */ - destroy() { - this.viewer.destroy(); - } + destroy() { + this.viewer.destroy(); + } } diff --git a/bimsurfer/src/xeoViewer/xeoViewer.js b/bimsurfer/src/xeoViewer/xeoViewer.js index 58337b2..88fc40d 100644 --- a/bimsurfer/src/xeoViewer/xeoViewer.js +++ b/bimsurfer/src/xeoViewer/xeoViewer.js @@ -245,7 +245,8 @@ export default class xeoViewer extends EventHandler { } spinner.processes--; if (spinner.processes === 0) { - this.scene.ticksPerRender = 1; // Back to max speed, one render per tick + this.scene.ticksPerRender = 1; // Back to max speed, one render per tick + this.fire("loading-finished"); } } From 3eb6036c920336e0dcc0264b6cf728a104553bd5 Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Wed, 6 Dec 2017 11:44:40 +0000 Subject: [PATCH 3/6] update build and set dependencies as external --- build/bimsurfer.js | 816 +++++++++++++++++++++++---------------------- package.json | 2 - 2 files changed, 412 insertions(+), 406 deletions(-) diff --git a/build/bimsurfer.js b/build/bimsurfer.js index 6b4c0cb..13030d2 100644 --- a/build/bimsurfer.js +++ b/build/bimsurfer.js @@ -3917,6 +3917,7 @@ var xeoViewer = function (_EventHandler) { spinner.processes--; if (spinner.processes === 0) { this.scene.ticksPerRender = 1; // Back to max speed, one render per tick + this.fire("loading-finished"); } } @@ -5239,478 +5240,485 @@ var xeoViewer = function (_EventHandler) { }(EventHandler); var BimSurfer = function (_EventHandler) { - inherits(BimSurfer, _EventHandler); + inherits(BimSurfer, _EventHandler); - function BimSurfer(cfg) { - classCallCheck(this, BimSurfer); + function BimSurfer(cfg) { + classCallCheck(this, BimSurfer); - var _this = possibleConstructorReturn(this, (BimSurfer.__proto__ || Object.getPrototypeOf(BimSurfer)).call(this)); + var _this = possibleConstructorReturn(this, (BimSurfer.__proto__ || Object.getPrototypeOf(BimSurfer)).call(this)); - _this.BimServerApi = BimServerClient; + _this.BimServerApi = BimServerClient; - cfg = cfg || {}; + cfg = cfg || {}; - _this.viewer = new xeoViewer(cfg); + _this.viewer = new xeoViewer(cfg); - /** - * Fired whenever this BIMSurfer's camera changes. - * @event camera-changed - */ - _this.viewer.on("camera-changed", function (args) { - _this.fire("camera-changed", args); - }); - - /** - * Fired whenever this BIMSurfer's selection changes. - * @event selection-changed - */ - _this.viewer.on("selection-changed", function (args) { - _this.fire("selection-changed", args); - }); - - // This are arrays as multiple models might be loaded or unloaded. - _this._idMapping = { - 'toGuid': [], - 'toId': [] - }; - return _this; - } - /** - * Loads a model into this BIMSurfer. - * @param params - */ - - - createClass(BimSurfer, [{ - key: 'load', - value: function load(params) { - - if (params.test) { - this.viewer.loadRandom(params); - return null; - } else if (params.bimserver) { - return this._loadFromServer(params); - } else if (params.api) { - return this._loadFromAPI(params); - } else if (params.src) { - return this._loadFrom_glTF(params); - } - } - }, { - key: '_loadFromServer', - value: function _loadFromServer(params) { - - var notifier = new Notifier(); - var bimServerApi = new this.BimServerApi(params.bimserver, notifier); - - params.api = bimServerApi; // TODO: Make copy of params + /** + * Fired whenever this BIMSurfer's camera changes. + * @event camera-changed + */ + _this.viewer.on("camera-changed", function (args) { + _this.fire("camera-changed", args); + }); - return this._initApi(params).then(this._loginToServer).then(this._getRevisionFromServer.bind(this)).then(this._loadFromAPI.bind(this)); - } - }, { - key: '_initApi', - value: function _initApi(params) { - return new Promise(function (resolve, reject) { - params.api.init(function () { - resolve(params); - }); - }); - } - }, { - key: '_loginToServer', - value: function _loginToServer(params) { - return new Promise(function (resolve, reject) { - if (params.token) { - params.api.setToken(params.token, function () { - resolve(params); - }, reject); - } else { - params.api.login(params.username, params.password, function () { - resolve(params); - }, reject); - } - }); - } - }, { - key: '_getRevisionFromServer', - value: function _getRevisionFromServer(params) { - var _this2 = this; + /** + * Fired whenever this BIMSurfer's selection changes. + * @event selection-changed + */ + _this.viewer.on("selection-changed", function (args) { + _this.fire("selection-changed", args); + }); - return new Promise(function (resolve, reject) { - if (params.roid) { - resolve(params); - } else { - params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, function (data) { - var resolved = false; - - data.forEach(function (projectData) { - if (projectData.oid == params.poid) { - params.roid = projectData.lastRevisionId; - params.schema = projectData.schema; - if (!_this2.models) { - _this2.models = []; - } - _this2.models.push(projectData); - resolved = true; - resolve(params); - } - }); + /** + * Fired when all models have finished loading + * @event loading-finished + */ + _this.viewer.on("loading-finished", function (args) { + _this.fire("loading-finished", args); + }); - if (!resolved) { - reject(); - } - }, reject); - } - }); - } - }, { - key: '_loadFrom_glTF', - value: function _loadFrom_glTF(params) { - var _this3 = this; + // This are arrays as multiple models might be loaded or unloaded. + _this._idMapping = { + 'toGuid': [], + 'toId': [] + }; + return _this; + } + /** + * Loads a model into this BIMSurfer. + * @param params + */ - if (params.src) { - return new Promise(function (resolve, reject) { - var m = _this3.viewer.loadglTF(params.src); - m.on("loaded", function () { - - var numComponents = 0, - componentsLoaded = 0; - - m.iterate(function (component) { - if (component.isType("xeogl.Entity")) { - ++numComponents; - (function (c) { - var timesUpdated = 0; - c.worldBoundary.on("updated", function () { - if (++timesUpdated == 2) { - ++componentsLoaded; - if (componentsLoaded == numComponents) { - _this3.viewer.viewFit({}); - - resolve(m); - } - } - }); - })(component); - } - }); - }); - }); - } - } - }, { - key: '_loadFromAPI', - value: function _loadFromAPI(params) { - var _this4 = this; - return new Promise(function (resolve, reject) { + createClass(BimSurfer, [{ + key: 'load', + value: function load(params) { - params.api.getModel(params.poid, params.roid, params.schema, false, function (model) { + if (params.test) { + this.viewer.loadRandom(params); + return null; + } else if (params.bimserver) { + return this._loadFromServer(params); + } else if (params.api) { + return this._loadFromAPI(params); + } else if (params.src) { + return this._loadFrom_glTF(params); + } + } + }, { + key: '_loadFromServer', + value: function _loadFromServer(params) { - // TODO: Preload not necessary combined with the bruteforce tree - var fired = false; + var notifier = new Notifier(); + var bimServerApi = new this.BimServerApi(params.bimserver, notifier); - model.query(PreloadQuery, function () { - if (!fired) { - fired = true; - var vmodel = new BimServerModel(params.api, model); + params.api = bimServerApi; // TODO: Make copy of params - _this4._loadModel(vmodel); + return this._initApi(params).then(this._loginToServer).then(this._getRevisionFromServer.bind(this)).then(this._loadFromAPI.bind(this)); + } + }, { + key: '_initApi', + value: function _initApi(params) { + return new Promise(function (resolve, reject) { + params.api.init(function () { + resolve(params); + }); + }); + } + }, { + key: '_loginToServer', + value: function _loginToServer(params) { + return new Promise(function (resolve, reject) { + if (params.token) { + params.api.setToken(params.token, function () { + resolve(params); + }, reject); + } else { + params.api.login(params.username, params.password, function () { + resolve(params); + }, reject); + } + }); + } + }, { + key: '_getRevisionFromServer', + value: function _getRevisionFromServer(params) { + var _this2 = this; + + return new Promise(function (resolve, reject) { + if (params.roid) { + resolve(params); + } else { + params.api.call("ServiceInterface", "getAllRelatedProjects", { poid: params.poid }, function (data) { + var resolved = false; + + data.forEach(function (projectData) { + if (projectData.oid == params.poid) { + params.roid = projectData.lastRevisionId; + params.schema = projectData.schema; + if (!_this2.models) { + _this2.models = []; + } + _this2.models.push(projectData); + resolved = true; + resolve(params); + } + }); - resolve(vmodel); - } - }); - }); - }); - } - }, { - key: '_loadModel', - value: function _loadModel(model) { - var _this5 = this; + if (!resolved) { + reject(); + } + }, reject); + } + }); + } + }, { + key: '_loadFrom_glTF', + value: function _loadFrom_glTF(params) { + var _this3 = this; + + if (params.src) { + return new Promise(function (resolve, reject) { + var m = _this3.viewer.loadglTF(params.src); + m.on("loaded", function () { + + var numComponents = 0, + componentsLoaded = 0; + + m.iterate(function (component) { + if (component.isType("xeogl.Entity")) { + ++numComponents; + (function (c) { + var timesUpdated = 0; + c.worldBoundary.on("updated", function () { + if (++timesUpdated == 2) { + ++componentsLoaded; + if (componentsLoaded == numComponents) { + _this3.viewer.viewFit({}); + + resolve(m); + } + } + }); + })(component); + } + }); + }); + }); + } + } + }, { + key: '_loadFromAPI', + value: function _loadFromAPI(params) { + var _this4 = this; - model.getTree().then(function (tree) { + return new Promise(function (resolve, reject) { - var oids = []; - var oidToGuid = {}; - var guidToOid = {}; + params.api.getModel(params.poid, params.roid, params.schema, false, function (model) { - var visit = function visit(n) { - oids[n.gid] = n.id; - oidToGuid[n.id] = n.guid; - guidToOid[n.guid] = n.id; + // TODO: Preload not necessary combined with the bruteforce tree + var fired = false; - for (var i = 0; i < (n.children || []).length; ++i) { - visit(n.children[i]); - } - }; + model.query(PreloadQuery, function () { + if (!fired) { + fired = true; + var vmodel = new BimServerModel(params.api, model); - visit(tree); + _this4._loadModel(vmodel); - _this5._idMapping.toGuid.push(oidToGuid); - _this5._idMapping.toId.push(guidToOid); + resolve(vmodel); + } + }); + }); + }); + } + }, { + key: '_loadModel', + value: function _loadModel(model) { + var _this5 = this; - var models = {}; + model.getTree().then(function (tree) { - // TODO: Ugh. Undecorate some of the newly created classes - models[model.model.roid] = model.model; + var oids = []; + var oidToGuid = {}; + var guidToOid = {}; - // Notify viewer that things are loading, so viewer can - // reduce rendering speed and show a spinner. - _this5.viewer.taskStarted(); + var visit = function visit(n) { + oids[n.gid] = n.id; + oidToGuid[n.id] = n.guid; + guidToOid[n.guid] = n.id; - _this5.viewer.createModel(model.model.roid); + for (var i = 0; i < (n.children || []).length; ++i) { + visit(n.children[i]); + } + }; - var loader = new BimServerGeometryLoader(model.api, models, _this5.viewer); + visit(tree); - loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { - if (progress == "start") { - console.log("Started loading geometries"); - _this5.fire("loading-started"); - } else if (progress == "done") { - console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); - _this5.fire("loading-finished"); - _this5.viewer.taskFinished(); - } - }); + _this5._idMapping.toGuid.push(oidToGuid); + _this5._idMapping.toId.push(guidToOid); - loader.setLoadOids([model.model.roid], oids); + var models = {}; - // viewer.clear(); // For now, until we support multiple models through the API + // TODO: Ugh. Undecorate some of the newly created classes + models[model.model.roid] = model.model; - _this5.viewer.on("tick", function () { - // TODO: Fire "tick" event from xeoViewer - loader.process(); - }); + // Notify viewer that things are loading, so viewer can + // reduce rendering speed and show a spinner. + _this5.viewer.taskStarted(); - loader.start(); - }); - } + _this5.viewer.createModel(model.model.roid); - // Helper function to traverse over the mappings for individually loaded models + var loader = new BimServerGeometryLoader(model.api, models, _this5.viewer); - }, { - key: 'toId', + loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { + if (progress == "start") { + console.log("Started loading geometries"); + //this.fire("loading-started"); + } else if (progress == "done") { + console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); + _this5.viewer.taskFinished(); + } + }); + loader.setLoadOids([model.model.roid], oids); - /** - * Returns a list of object ids (oid) for the list of guids (GlobalId) - * - * @param guids List of globally unique identifiers from the IFC model - */ - value: function toId(guids) { - return guids.map(this._traverseMappings(this._idMapping.toId)); - } + // viewer.clear(); // For now, until we support multiple models through the API - /** - * Returns a list of guids (GlobalId) for the list of object ids (oid) - * - * @param ids List of internal object ids from the BIMserver / glTF file - */ + _this5.viewer.on("tick", function () { + // TODO: Fire "tick" event from xeoViewer + loader.process(); + }); - }, { - key: 'toGuid', - value: function toGuid(ids) { - return ids.map(this._traverseMappings(this._idMapping.toGuid)); - } + loader.start(); + }); + } - /** - * Shows/hides objects specified by id or entity type, e.g IfcWall. - * - * When recursive is set to true, hides children (aggregates, spatial structures etc) or - * subtypes (IfcWallStandardCase ⊆ IfcWall). - * - * @param params - */ + // Helper function to traverse over the mappings for individually loaded models - }, { - key: 'setVisibility', - value: function setVisibility(params) { - this.viewer.setVisibility(params); - } + }, { + key: 'toId', - /** - * Selects/deselects objects specified by id. - ** - * @param params - */ - }, { - key: 'setSelection', - value: function setSelection(params) { - return this.viewer.setSelection(params); - } + /** + * Returns a list of object ids (oid) for the list of guids (GlobalId) + * + * @param guids List of globally unique identifiers from the IFC model + */ + value: function toId(guids) { + return guids.map(this._traverseMappings(this._idMapping.toId)); + } - /** - * Gets a list of selected elements. - */ + /** + * Returns a list of guids (GlobalId) for the list of object ids (oid) + * + * @param ids List of internal object ids from the BIMserver / glTF file + */ - }, { - key: 'getSelection', - value: function getSelection() { - return this.viewer.getSelection(); - } + }, { + key: 'toGuid', + value: function toGuid(ids) { + return ids.map(this._traverseMappings(this._idMapping.toGuid)); + } - /** - * Sets color of objects specified by ids or entity type, e.g IfcWall. - ** - * @param params - */ + /** + * Shows/hides objects specified by id or entity type, e.g IfcWall. + * + * When recursive is set to true, hides children (aggregates, spatial structures etc) or + * subtypes (IfcWallStandardCase ⊆ IfcWall). + * + * @param params + */ + + }, { + key: 'setVisibility', + value: function setVisibility(params) { + this.viewer.setVisibility(params); + } - }, { - key: 'setColor', - value: function setColor(params) { - this.viewer.setColor(params); - } + /** + * Selects/deselects objects specified by id. + ** + * @param params + */ - /** - * Sets opacity of objects specified by ids or entity type, e.g IfcWall. - ** - * @param params - */ + }, { + key: 'setSelection', + value: function setSelection(params) { + return this.viewer.setSelection(params); + } - }, { - key: 'setOpacity', - value: function setOpacity(params) { - this.viewer.setOpacity(params); - } + /** + * Gets a list of selected elements. + */ - /** - * Fits the elements into view. - * - * Fits the entire model into view if ids is an empty array, null or undefined. - * Animate allows to specify a transition period in milliseconds in which the view is altered. - * - * @param params - */ + }, { + key: 'getSelection', + value: function getSelection() { + return this.viewer.getSelection(); + } - }, { - key: 'viewFit', - value: function viewFit(params) { - this.viewer.viewFit(params); - } + /** + * Sets color of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ - /** - * - */ + }, { + key: 'setColor', + value: function setColor(params) { + this.viewer.setColor(params); + } - }, { - key: 'getCamera', - value: function getCamera() { - return this.viewer.getCamera(); - } + /** + * Sets opacity of objects specified by ids or entity type, e.g IfcWall. + ** + * @param params + */ - /** - * - * @param params - */ + }, { + key: 'setOpacity', + value: function setOpacity(params) { + this.viewer.setOpacity(params); + } - }, { - key: 'setCamera', - value: function setCamera(params) { - this.viewer.setCamera(params); - } + /** + * Fits the elements into view. + * + * Fits the entire model into view if ids is an empty array, null or undefined. + * Animate allows to specify a transition period in milliseconds in which the view is altered. + * + * @param params + */ + + }, { + key: 'viewFit', + value: function viewFit(params) { + this.viewer.viewFit(params); + } - /** - * Redefines light sources. - * - * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type - */ + /** + * + */ - }, { - key: 'setLights', - value: function setLights(params) { - this.viewer.setLights(params); - } + }, { + key: 'getCamera', + value: function getCamera() { + return this.viewer.getCamera(); + } - /** - * Returns light sources. - * - * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} - */ + /** + * + * @param params + */ - }, { - key: 'getLights', - value: function getLights() { - return this.viewer.getLights; - } + }, { + key: 'setCamera', + value: function setCamera(params) { + this.viewer.setCamera(params); + } - /** - * - * @param params - */ + /** + * Redefines light sources. + * + * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type + */ + + }, { + key: 'setLights', + value: function setLights(params) { + this.viewer.setLights(params); + } - }, { - key: 'reset', - value: function reset(params) { - this.viewer.reset(params); - } + /** + * Returns light sources. + * + * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} + */ - /** - * Returns a list of loaded IFC entity types in the model. - * - * @method getTypes - * @returns {Array} List of loaded IFC entity types, with visibility flag - */ + }, { + key: 'getLights', + value: function getLights() { + return this.viewer.getLights; + } - }, { - key: 'getTypes', - value: function getTypes() { - return this.viewer.getTypes(); - } + /** + * + * @param params + */ - /** - * Sets the default behaviour of mouse and touch drag input - * - * @method setDefaultDragAction - * @param {String} action ("pan" | "orbit") - */ + }, { + key: 'reset', + value: function reset(params) { + this.viewer.reset(params); + } - }, { - key: 'setDefaultDragAction', - value: function setDefaultDragAction(action) { - this.viewer.setDefaultDragAction(action); - } + /** + * Returns a list of loaded IFC entity types in the model. + * + * @method getTypes + * @returns {Array} List of loaded IFC entity types, with visibility flag + */ + + }, { + key: 'getTypes', + value: function getTypes() { + return this.viewer.getTypes(); + } - /** - * Returns the world boundary of an object - * - * @method getWorldBoundary - * @param {String} objectId id of object - * @param {Object} result Existing boundary object - * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D - */ + /** + * Sets the default behaviour of mouse and touch drag input + * + * @method setDefaultDragAction + * @param {String} action ("pan" | "orbit") + */ + + }, { + key: 'setDefaultDragAction', + value: function setDefaultDragAction(action) { + this.viewer.setDefaultDragAction(action); + } - }, { - key: 'getWorldBoundary', - value: function getWorldBoundary(objectId, result) { - return this.viewer.getWorldBoundary(objectId, result); - } + /** + * Returns the world boundary of an object + * + * @method getWorldBoundary + * @param {String} objectId id of object + * @param {Object} result Existing boundary object + * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D + */ + + }, { + key: 'getWorldBoundary', + value: function getWorldBoundary(objectId, result) { + return this.viewer.getWorldBoundary(objectId, result); + } - /** - * Destroys the BIMSurfer - */ + /** + * Destroys the BIMSurfer + */ - }, { - key: 'destroy', - value: function destroy() { - this.viewer.destroy(); - } - }], [{ - key: '_traverseMappings', - value: function _traverseMappings(mappings) { - return function (k) { - for (var i = 0; i < mappings.length; ++i) { - var v = mappings[i][k]; - if (v) { - return v; - } - } - return null; - }; - } - }]); - return BimSurfer; + }, { + key: 'destroy', + value: function destroy() { + this.viewer.destroy(); + } + }], [{ + key: '_traverseMappings', + value: function _traverseMappings(mappings) { + return function (k) { + for (var i = 0; i < mappings.length; ++i) { + var v = mappings[i][k]; + if (v) { + return v; + } + } + return null; + }; + } + }]); + return BimSurfer; }(EventHandler); var BimServerModelLoader = function () { diff --git a/package.json b/package.json index ab1e383..3887eed 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,6 @@ "build": "rollup -c" }, "dependencies": { - "bimserverapi": "git+https://github.com/opensourceBIM/BIMserver-JavaScript-API", - "xeogl": "git+https://github.com/xeolabs/xeogl.git" }, "devDependencies": { "babel-core": "^6.26.0", From b848bf3ed2482e8e1502caf250ee800ff9d2ca3b Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Thu, 7 Dec 2017 15:34:42 +0000 Subject: [PATCH 4/6] update package.json and build file name --- build/{bimsurfer.js => bimsurfer.umd.js} | 1 + package.json | 18 ++++++++++++++---- rollup.config.js | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) rename build/{bimsurfer.js => bimsurfer.umd.js} (99%) diff --git a/build/bimsurfer.js b/build/bimsurfer.umd.js similarity index 99% rename from build/bimsurfer.js rename to build/bimsurfer.umd.js index 13030d2..d1946bf 100644 --- a/build/bimsurfer.js +++ b/build/bimsurfer.umd.js @@ -4512,6 +4512,7 @@ var xeoViewer = function (_EventHandler) { var material = object.material; material.diffuse = [color[0], color[1], color[2]]; + material.emissive = [color[0], color[1], color[2]]; var opacity = color.length > 3 ? color[3] : 1; if (opacity !== material.opacity) { diff --git a/package.json b/package.json index 3887eed..f4d907d 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,17 @@ { "name": "bimsurfer", - "description": "", - "main": "build/bimsurfer.js", + "description": "The first open source WebGL based IFC viewer", + "organization": "OpenSource BIM", + "main": "build/bimsurfer.umd.js", "module": "bimsurfer/src/index.js", - "version": "0.0.1", + "version": "0.0.44", + "repository": { + "type": "git", + "url": "https://github.com/opensourceBIM/BIMsurfer" + }, + "bugs": { + "url": "https://github.com/opensourceBIM/BIMsurfer/issues" + }, "scripts": { "build": "rollup -c" }, @@ -20,5 +28,7 @@ "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.1.0", "rollup-plugin-node-resolve": "^3.0.0" - } + }, + "readmeFilename": "README.markdown", + "license": "MIT" } diff --git a/rollup.config.js b/rollup.config.js index aa4b92f..596faf6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,7 +7,7 @@ export default [{ input: 'bimsurfer/src/index.js', name: 'bimsurfer', output: { - file: 'build/bimsurfer.js', + file: 'build/bimsurfer.umd.js', format: 'umd' }, external: ['bimserverapi', 'xeogl'], From 2b330fd4807f8d540808579c46c3cbd45ffc0fd9 Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Thu, 7 Dec 2017 16:22:22 +0000 Subject: [PATCH 5/6] Update readme and examples --- README.markdown | 153 ++++++++++++++++++++++++++---------- docs/example_BIMServer.html | 8 +- docs/example_glTF.html | 2 +- docs/example_testModel.html | 2 +- 4 files changed, 119 insertions(+), 46 deletions(-) diff --git a/README.markdown b/README.markdown index 84efadf..0a63434 100644 --- a/README.markdown +++ b/README.markdown @@ -2,70 +2,126 @@ Copyright 2017, bimsurfer.org BIM Surfer is licensed under the MIT License. -#Table of Contents +# Table of Contents - [Introduction](#introduction) - [Usage](#usage) - - [BIMSurfer](#bimsurfer) - - [Objects](#objects) - - [Selecting and deselecting objects](#selecting-and-deselecting-objects) - - [Showing and hiding objects](#showing-and-hiding-objects) - - [Changing color and transparency of objects](#changing-color-and-transparency-of-objects) + - [Basic usage](#basic-usage) + - [Import as a module](#import-as-a-module) + - [Loading a test model](#loading-a-test-model) + - [Loading a model from BIMserver](#loading-a-model-from-bimserver) + - [Loading a model from a glTF file](#loading-a-model-from-a-gltf-file) +- [API documentation](#api-documentation) + - [Selecting and deselecting objects](#selecting-and-deselecting-objects) + - [Showing and hiding objects](#showing-and-hiding-objects) + - [Changing color and transparency of objects](#changing-color-and-transparency-of-objects) - [Camera](#camera) - [Controlling the camera](#controlling-the-camera) - [Fitting objects in view](#fitting-objects-in-view) - [Resetting](#resetting) - [Camera](#camera-1) - [Objects](#objects-1) +- [Build BIMsurfer](#build-bimsurfer) # Introduction -BIMSurfer is a WebGL-based 3D viewer for [BIMServer]() that's built on [xeoEngine](http://xeoengine.org). +BIMSurfer is a WebGL-based 3D viewer for [BIMServer](https://github.com/opensourceBIM/BIMserver) that is built on the [xeogl](http://xeogl.org) engine. TODO: More info # Usage -## BIMSurfer +## Basic usage -Creating a [BIMSurfer](bimsurfer/src/BimSurfer.js): +Download the [combined minified library](https://raw.githubusercontent.com/opensourceBIM/BIMsurfer/master/build/bimsurfer.umd.js) and include it in your HTML. -````javascript -var bimSurfer = new BimSurfer({ +```html + +``` + +BIMsurfer components are loaded under the ``bimsurfer`` namespace. Instanciate the components as follow: + +```javascript +var bimSurfer = new bimsurfer.BimSurfer({ domNode: "viewerContainer" }); -```` -Loading a model from BIMServer: - -````javascript -bimSurfer.load({ - bimserver: ADDRESS, - username: USERNAME, - password: PASSWORD, - poid: 131073, - roid: 65539, - schema: "ifc2x3tc1" // < TODO: Deduce automatically - }) - .then(function (model) { - - // Model is now loaded and rendering. - // The following sections show what you can do with BIMSurfer at this point. - //... - }); -```` +var domtree = new bimsurfer.StaticTreeRenderer({ + domNode: 'treeContainer' +}); -Generate a random test model if you want to test BIMSurfer without loading anything from BIMServer: +var metadata = new bimsurfer.MetaDataRenderer({ + domNode: 'dataContainer' +}); +``` + +## Import as a module +If you are using a transpiler such as [Typescript](https://www.typescriptlang.org/) or [Babel](http://babeljs.io/), or a bundler such as [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/), you can import the module using the [Import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) syntax. + +It is also possible to load the module directly into the browser, however not all browser support this yet. + +```javascript +import { Bimsurfer, BimServerModelLoader, StaticTreeRenderer, MetaDataRenderer } from './bimsurfer/src/index.js'; +``` + +## Loading a test model +Generate a random test model if you want to test BIMsurfer without loading anything from BIMserver: + +```javascript +var bimSurfer = new bimsurfer.BimSurfer({ + domNode: "viewerContainer" +}); -````javascript bimSurfer.loadRandom(); -```` +``` -The following usage examples in this guide will refer to objects from the generated test model. +## Loading a model from BIMServer +You need to load the [BIMserver javascript API](https://github.com/opensourceBIM/BIMserver-JavaScript-API) first. + +```javascript +var bimSurfer = new bimsurfer.BimSurfer({ + domNode: "viewerContainer" +}); -## Objects +var bimServerClient = new bimserverapi.BimServerClient(BIMSERVER_ADDRESS, null); + +bimServerClient.init(function() { + bimServerClient.login(BIMSERVER_USERNAME, BIMSERVER_PASSWORD, function() { + var modelLoader = new bimsurfer.BimServerModelLoader(bimServerClient, bimSurfer); + + bimServerClient.call("ServiceInterface", "getAllRelatedProjects", { poid: POID }, (projects) => { + projects.forEach(function(project) { + if (project.lastRevisionId != -1 && (project.nrSubProjects == 0 || project.oid != POID)) { + bimServerClient.getModel(project.oid, project.lastRevisionId, project.schema, false, function(model) { + modelLoader.loadFullModel(model).then(function (bimSurferModel) { + // Model is now loaded and rendering. + }); + }); + } + }); + }); + }); +}); +``` -### Selecting and deselecting objects +## Loading a model from a glTF file +```javascript +var bimSurfer = new bimsurfer.BimSurfer({ + domNode: "viewerContainer" +}); + +bimSurfer.load({ + src: "model_file_name.gltf" +}).then(function (bimSurferModel) { + // Model is now loaded and rendering. +}); +``` + +# API documentation + +The following usage examples in this guide will refer to objects from the generated test model. + +## Selecting and deselecting objects Selecting four objects: @@ -108,7 +164,7 @@ bimSurfer.on("selection-changed", }); ```` -### Showing and hiding objects +## Showing and hiding objects Hiding three objects by ID: @@ -128,7 +184,7 @@ Hiding all objects of IFC types "IfcSlab" and "IfcWall": bimSurfer.setVisibility({types: ["IfcSlab", "IfcWall"], visible: false }); ```` -### Changing color and transparency of objects +## Changing color and transparency of objects Making two objects pink: @@ -291,5 +347,22 @@ Deselecting all objects: bimSurfer.reset({ selectionState: true }); ```` - - +# Build BIMsurfer +* Install [Node.js](https://nodejs.org/) +* Clone (or download and unzip) the project to your file system: +``` +git clone https://github.com/opensourceBIM/BIMsurfer.git +``` +* Go to the project directory +``` +cd BIMsurfer +``` +* Install build dependencies +``` +npm install +``` +* Run the build script +``` +npm run build +``` +The compiled file is located at ``build/bimsurfer.umd.js`` diff --git a/docs/example_BIMServer.html b/docs/example_BIMServer.html index 596a909..4a395e6 100644 --- a/docs/example_BIMServer.html +++ b/docs/example_BIMServer.html @@ -15,7 +15,7 @@ - + - +
diff --git a/docs/example_testModel.html b/docs/example_testModel.html index a46f2b4..dba626b 100644 --- a/docs/example_testModel.html +++ b/docs/example_testModel.html @@ -7,7 +7,7 @@ - +
From bd2ad406c072880197722fa07392b23c38d32ca6 Mon Sep 17 00:00:00 2001 From: bastienmenis Date: Thu, 7 Dec 2017 16:54:26 +0000 Subject: [PATCH 6/6] Fix example --- docs/example_testModel.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/example_testModel.html b/docs/example_testModel.html index dba626b..afe1818 100644 --- a/docs/example_testModel.html +++ b/docs/example_testModel.html @@ -7,7 +7,7 @@ - +