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/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/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..018bfb3 100644
--- a/bimsurfer/src/BimSurfer.js
+++ b/bimsurfer/src/BimSurfer.js
@@ -1,435 +1,416 @@
-// 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";
+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';
-define(deps, function (Notifier, Model, PreloadQuery, GeometryLoader, xeoViewer, EventHandler, _BimServerApi) {
-
- // Backwards compatibility
- var BimServerApi;
- if (_BimServerApi) {
- BimServerApi = _BimServerApi;
- } else {
- BimServerApi = window.BimServerClient;
- }
-
- function BimSurfer(cfg) {
+import BimServerClient from "bimserverapi";
- var self = this;
+export default class BimSurfer extends EventHandler {
+ constructor(cfg) {
+ super();
- EventHandler.call(this);
+ this.BimServerApi = BimServerClient;
cfg = cfg || {};
- var viewer = this.viewer = new xeoViewer(cfg);
+ 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 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);
});
/**
- * Fired whenever this BIMSurfer's selection changes.
- * @event selection-changed
+ * Fired when all models have finished loading
+ * @event loading-finished
*/
- viewer.on("selection-changed", function() {
- self.fire("selection-changed", arguments);
+ 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' : []
+ 'toId': []
};
+ }
+ /**
+ * Loads a model into this BIMSurfer.
+ * @param params
+ */
+ load(params) {
- /**
- * Loads a model into this BIMSurfer.
- * @param params
- */
- this.load = function (params) {
+ if (params.test) {
+ this.viewer.loadRandom(params);
+ return null;
- if (params.test) {
- viewer.loadRandom(params);
- return null;
+ } else if (params.bimserver) {
+ return this._loadFromServer(params);
- } else if (params.bimserver) {
- return this._loadFromServer(params);
+ } else if (params.api) {
+ return this._loadFromAPI(params);
- } else if (params.api) {
- return this._loadFromAPI(params);
+ } else if (params.src) {
+ return this._loadFrom_glTF(params);
+ }
+ }
- } else if (params.src) {
- return this._loadFrom_glTF(params);
- }
- };
+ _loadFromServer(params) {
- this._loadFromServer = function (params) {
+ const notifier = new Notifier();
+ const bimServerApi = new this.BimServerApi(params.bimserver, notifier);
- var notifier = new Notifier();
- var bimServerApi = new BimServerApi(params.bimserver, notifier);
-
- params.api = bimServerApi; // TODO: Make copy of params
+ 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 () {
- resolve(params);
- });
- });
- };
-
- 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);
- }
- });
- };
-
- 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);
+ 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 = [];
}
- });
-
- if (!resolved) {
- reject();
- }
- }, reject);
- }
- });
- };
-
- 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);
- }
+ 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);
+ }
+ });
+ })(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);
}
});
- });
});
- }
- };
+ });
+ }
- this._loadFromAPI = function (params) {
+ _loadModel(model) {
- return new Promise(function (resolve, reject) {
+ model.getTree().then((tree) => {
- params.api.getModel(params.poid, params.roid, params.schema, false,
- function (model) {
+ const oids = [];
+ const oidToGuid = {};
+ const guidToOid = {};
- // TODO: Preload not necessary combined with the bruteforce tree
- var fired = false;
+ const visit = (n) => {
+ oids[n.gid] = n.id;
+ oidToGuid[n.id] = n.guid;
+ guidToOid[n.guid] = n.id;
- model.query(PreloadQuery,
- function () {
- if (!fired) {
- fired = true;
- var vmodel = new Model(params.api, model);
+ for (let i = 0; i < (n.children || []).length; ++i) {
+ visit(n.children[i]);
+ }
+ };
- self._loadModel(vmodel);
+ visit(tree);
- resolve(vmodel);
- }
- });
- });
- });
- };
+ this._idMapping.toGuid.push(oidToGuid);
+ this._idMapping.toId.push(guidToOid);
- this._loadModel = function (model) {
-
- model.getTree().then(function (tree) {
+ const models = {};
- var oids = [];
- var oidToGuid = {};
- var guidToOid = {};
+ // TODO: Ugh. Undecorate some of the newly created classes
+ models[model.model.roid] = model.model;
- 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();
- }
- });
+ // Notify viewer that things are loading, so viewer can
+ // reduce rendering speed and show a spinner.
+ this.viewer.taskStarted();
- loader.setLoadOids([model.model.roid], oids);
+ this.viewer.createModel(model.model.roid);
- // viewer.clear(); // For now, until we support multiple models through the API
+ const loader = new GeometryLoader(model.api, models, this.viewer);
- viewer.on("tick", function () { // TODO: Fire "tick" event from xeoViewer
- loader.process();
- });
+ 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
- loader.start();
+ this.viewer.on("tick", () => { // TODO: Fire "tick" event from xeoViewer
+ loader.process();
});
- };
-
- // 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;
+
+ 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
- */
- this.toId = function(guids) {
- return guids.map(_traverseMappings(self._idMapping.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
+ */
+ 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
- */
- this.toGuid = function(ids) {
- return ids.map(_traverseMappings(self._idMapping.toGuid));
- };
+ /**
+ * 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
- */
- this.setVisibility = function (params) {
- viewer.setVisibility(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
+ */
+ setVisibility(params) {
+ this.viewer.setVisibility(params);
+ }
- /**
- * Selects/deselects objects specified by id.
- **
- * @param params
- */
- this.setSelection = function (params) {
- return viewer.setSelection(params);
- };
+ /**
+ * Selects/deselects objects specified by id.
+ **
+ * @param params
+ */
+ setSelection(params) {
+ return this.viewer.setSelection(params);
+ }
- /**
- * Gets a list of selected elements.
- */
- this.getSelection = function () {
- return viewer.getSelection();
- };
+ /**
+ * 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
- */
- 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);
- };
+ /**
+ * Sets color of objects specified by ids or entity type, e.g IfcWall.
+ **
+ * @param params
+ */
+ setColor(params) {
+ this.viewer.setColor(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);
- };
+ /**
+ * Sets opacity of objects specified by ids or entity type, e.g IfcWall.
+ **
+ * @param params
+ */
+ setOpacity(params) {
+ this.viewer.setOpacity(params);
+ }
- /**
- *
- */
- this.getCamera = function () {
- return viewer.getCamera();
- };
+ /**
+ * 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);
+ }
- /**
- *
- * @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;
- };
+ /**
+ *
+ */
+ getCamera() {
+ return this.viewer.getCamera();
+ }
- /**
- *
- * @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();
- };
+ /**
+ *
+ * @param params
+ */
+ setCamera(params) {
+ this.viewer.setCamera(params);
+ }
- /**
- * Sets the default behaviour of mouse and touch drag input
- *
- * @method setDefaultDragAction
- * @param {String} action ("pan" | "orbit")
- */
- this.setDefaultDragAction = function (action) {
- viewer.setDefaultDragAction(action);
- };
+ /**
+ * 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 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);
- };
+ /**
+ * Returns light sources.
+ *
+ * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}}
+ */
+ getLights() {
+ return this.viewer.getLights;
+ }
- /**
- * Destroys the BIMSurfer
- */
- this.destroy = function() {
- viewer.destroy();
- }
+ /**
+ *
+ * @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();
}
- BimSurfer.prototype = Object.create(EventHandler.prototype);
+ /**
+ * Sets the default behaviour of mouse and touch drag input
+ *
+ * @method setDefaultDragAction
+ * @param {String} action ("pan" | "orbit")
+ */
+ setDefaultDragAction(action) {
+ this.viewer.setDefaultDragAction(action);
+ }
- return BimSurfer;
+ /**
+ * 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..88fc40d 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,1420 @@ 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
+ this.fire("loading-finished");
+ }
+ }
+
+ /**
+ * 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.umd.js b/build/bimsurfer.umd.js
new file mode 100644
index 0000000..d1946bf
--- /dev/null
+++ b/build/bimsurfer.umd.js
@@ -0,0 +1,6293 @@
+(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
+ this.fire("loading-finished");
+ }
+ }
+
+ /**
+ * 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]];
+ material.emissive = [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);
+ });
+
+ /**
+ * Fired when all models have finished loading
+ * @event loading-finished
+ */
+ _this.viewer.on("loading-finished", function (args) {
+ _this.fire("loading-finished", 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");
+ //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);
+
+ // 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..4a395e6 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..a467854 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..afe1818 100644
--- a/docs/example_testModel.html
+++ b/docs/example_testModel.html
@@ -2,66 +2,12 @@
- Example
+ BIMsurfer example
-
-
-
-
-
-
+
+
+