(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("three")); else if(typeof define === 'function' && define.amd) define(["three"], factory); else if(typeof exports === 'object') exports["threeWtm"] = factory(require("three")); else root["threeWtm"] = factory(root["THREE"]); })(window, function(__WEBPACK_EXTERNAL_MODULE__0__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE__0__; /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DataTransport", function() { return DataTransport; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DeUglify", function() { return DeUglify; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GeometryTransport", function() { return GeometryTransport; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MaterialStore", function() { return MaterialStore; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MaterialUtils", function() { return MaterialUtils; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MaterialsTransport", function() { return MaterialsTransport; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MeshTransport", function() { return MeshTransport; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectManipulator", function() { return ObjectManipulator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectUtils", function() { return ObjectUtils; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WorkerTaskManager", function() { return WorkerTaskManager; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WorkerTaskManagerDefaultRouting", function() { return WorkerTaskManagerDefaultRouting; }); /* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0); /* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(three__WEBPACK_IMPORTED_MODULE_0__); /** * Development repository: https://github.com/kaisalmen/three-wtm */ /** * Static functions useful in the context of handling materials. */ class MaterialUtils { /** * Adds the provided material to the provided materials object if the material does not exists. * Use force override existing material. * * @param {object.} materialsObject * @param {Material} material * @param {string} materialName * @param {boolean} force * @param {boolean} [log] Log messages to the console */ static addMaterial(materialsObject, material, materialName, force, log) { let existingMaterial; // ensure materialName is set material.name = materialName; if (!force) { existingMaterial = materialsObject[materialName]; if (existingMaterial) { if (existingMaterial.uuid !== existingMaterial.uuid) { if (log) console.log('Same material name "' + existingMaterial.name + '" different uuid [' + existingMaterial.uuid + '|' + material.uuid + ']'); } } else { materialsObject[materialName] = material; if (log) console.info('Material with name "' + materialName + '" was added.'); } } else { materialsObject[materialName] = material; if (log) console.info('Material with name "' + materialName + '" was forcefully overridden.'); } } /** * Transforms the named materials object to an object with named jsonified materials. * * @param {object.} * @returns {Object} Map of Materials in JSON representation */ static getMaterialsJSON(materialsObject) { const materialsJSON = {}; let material; for (const materialName in materialsObject) { material = materialsObject[materialName]; if (typeof material.toJSON === 'function') { materialsJSON[materialName] = material.toJSON(); } } return materialsJSON; } /** * Clones a material according the provided instructions. * * @param {object.} materials * @param {object} materialCloneInstruction * @param {boolean} [log] */ static cloneMaterial(materials, materialCloneInstruction, log) { let material; if (materialCloneInstruction) { let materialNameOrg = materialCloneInstruction.materialNameOrg; materialNameOrg = materialNameOrg !== undefined && materialNameOrg !== null ? materialNameOrg : ''; const materialOrg = materials[materialNameOrg]; if (materialOrg) { material = materialOrg.clone(); Object.assign(material, materialCloneInstruction.materialProperties); MaterialUtils.addMaterial(materials, material, materialCloneInstruction.materialProperties.name, true); } else { if (log) console.info('Requested material "' + materialNameOrg + '" is not available!'); } } return material; } } /** * Development repository: https://github.com/kaisalmen/three-wtm */ class DeUglify { static buildThreeConst() { return 'const EventDispatcher = THREE.EventDispatcher;\n' + 'const BufferGeometry = THREE.BufferGeometry;\n' + 'const BufferAttribute = THREE.BufferAttribute;\n' + 'const Box3 = THREE.Box3;\n' + 'const Sphere = THREE.Sphere;\n' + 'const Texture = THREE.Texture;\n' + 'const MaterialLoader = THREE.MaterialLoader;\n'; } static buildUglifiedThreeMapping() { function _BufferGeometry() { return three__WEBPACK_IMPORTED_MODULE_0__["BufferGeometry"]; } function _BufferAttribute() { return three__WEBPACK_IMPORTED_MODULE_0__["BufferAttribute"]; } function _Box3() { return three__WEBPACK_IMPORTED_MODULE_0__["Box3"]; } function _Sphere() { return three__WEBPACK_IMPORTED_MODULE_0__["Sphere"]; } function _Texture() { return three__WEBPACK_IMPORTED_MODULE_0__["Texture"]; } function _MaterialLoader() { return three__WEBPACK_IMPORTED_MODULE_0__["MaterialLoader"]; } return DeUglify.buildUglifiedNameAssignment(_BufferGeometry, 'BufferGeometry', /_BufferGeometry/, false) + DeUglify.buildUglifiedNameAssignment(_BufferAttribute, 'BufferAttribute', /_BufferAttribute/, false) + DeUglify.buildUglifiedNameAssignment(_Box3, 'Box3', /_Box3/, false) + DeUglify.buildUglifiedNameAssignment(_Sphere, 'Sphere', /_Sphere/, false) + DeUglify.buildUglifiedNameAssignment(_Texture, 'Texture', /_Texture/, false) + DeUglify.buildUglifiedNameAssignment(_MaterialLoader, 'MaterialLoader', /_MaterialLoader/, false); } static buildUglifiedThreeWtmMapping() { function _DataTransport() { return DataTransport; } function _GeometryTransport() { return GeometryTransport; } function _MeshTransport() { return MeshTransport; } function _MaterialsTransport() { return MaterialsTransport; } function _MaterialUtils() { return MaterialUtils; } return DeUglify.buildUglifiedNameAssignment(_DataTransport, 'DataTransport', /_DataTransport/, true) + DeUglify.buildUglifiedNameAssignment(_GeometryTransport, 'GeometryTransport', /_GeometryTransport/, true) + DeUglify.buildUglifiedNameAssignment(_MeshTransport, 'MeshTransport', /_MeshTransport/, true) + DeUglify.buildUglifiedNameAssignment(_MaterialsTransport, 'MaterialsTransport', /_MaterialsTransport/, true) + DeUglify.buildUglifiedNameAssignment(_MaterialUtils, 'MaterialUtils', /_MaterialUtils/, true); } static buildUglifiedNameAssignment(func, name, methodPattern, invert) { let funcStr = func.toString(); // remove the method name and any line breaks (rollup lib creation, non-uglify case funcStr = funcStr.replace(methodPattern, "").replace(/[\r\n]+/gm, ""); // remove return and any semi-colons funcStr = funcStr.replace(/.*return/, "").replace(/\}/, "").replace(/;/gm, ""); const retrieveNamed = funcStr.trim(); // return non-empty string in uglified case (name!=retrieveNamed); e.g. "const BufferGeometry = e"; // return empty string in case of non-uglified lib/src let output = ""; if (retrieveNamed !== name) { const left = invert ? name : retrieveNamed; const right = invert ? retrieveNamed : name; output = "const " + left + " = " + right + ";\n"; } return output; } } /** * Define a base structure that is used to ship data in between main and workers. */ class DataTransport { /** * Creates a new {@link DataTransport}. * @param {string} [cmd] * @param {string} [id] */ constructor(cmd, id) { this.main = { cmd: cmd !== undefined ? cmd : 'unknown', id: id !== undefined ? id : 0, type: 'DataTransport', /** @type {number} */ progress: 0, buffers: {}, params: {} }; /** @type {ArrayBuffer[]} */ this.transferables = []; } /** * Populate this object with previously serialized data. * @param {object} transportObject * @return {DataTransport} */ loadData(transportObject) { this.main.cmd = transportObject.cmd; this.main.id = transportObject.id; this.main.type = 'DataTransport'; this.setProgress(transportObject.progress); this.setParams(transportObject.params); if (transportObject.buffers) { Object.entries(transportObject.buffers).forEach(([name, buffer]) => { this.main.buffers[name] = buffer; }); } return this; } /** * Returns the value of the command. * @return {string} */ getCmd() { return this.main.cmd; } /** * Returns the id. * @return {string} */ getId() { return this.main.id; } /** * Set a parameter object which is a map with string keys and strings or objects as values. * @param {object.} params * @return {DataTransport} */ setParams(params) { if (params !== null && params !== undefined) { this.main.params = params; } return this; } /** * Return the parameter object * @return {object.} */ getParams() { return this.main.params; } /** * Set the current progress (e.g. percentage of progress) * @param {number} numericalValue * @return {DataTransport} */ setProgress(numericalValue) { this.main.progress = numericalValue; return this; } /** * Add a named {@link ArrayBuffer} * @param {string} name * @param {ArrayBuffer} buffer * @return {DataTransport} */ addBuffer(name, buffer) { this.main.buffers[name] = buffer; return this; } /** * Retrieve an {@link ArrayBuffer} by name * @param {string} name * @return {ArrayBuffer} */ getBuffer(name) { return this.main.buffers[name]; } /** * Package all data buffers into the transferable array. Clone if data needs to stay in current context. * @param {boolean} cloneBuffers * @return {DataTransport} */ package(cloneBuffers) { for (let buffer of Object.values(this.main.buffers)) { if (buffer !== null && buffer !== undefined) { const potentialClone = cloneBuffers ? buffer.slice(0) : buffer; this.transferables.push(potentialClone); } } return this; } /** * Return main data object * @return {object} */ getMain() { return this.main; } /** * Return all transferable in one array. * @return {ArrayBuffer[]} */ getTransferables() { return this.transferables; } /** * Posts a message by invoking the method on the provided object. * @param {object} postMessageImpl * @return {DataTransport} */ postMessage(postMessageImpl) { postMessageImpl.postMessage(this.main, this.transferables); return this; } } /** * Define a structure that is used to ship materials data between main and workers. */ class MaterialsTransport extends DataTransport { /** * Creates a new {@link MeshMessageStructure}. * @param {string} [cmd] * @param {string} [id] */ constructor(cmd, id) { super(cmd, id); this.main.type = 'MaterialsTransport'; /** {object.} */ this.main.materials = {}; /** {object.} */ this.main.multiMaterialNames = {}; this.main.cloneInstructions = []; } /** * See {@link DataTransport#loadData} * @param {object} transportObject * @return {MaterialsTransport} */ loadData(transportObject) { super.loadData(transportObject); this.main.type = 'MaterialsTransport'; Object.assign(this.main, transportObject); const materialLoader = new three__WEBPACK_IMPORTED_MODULE_0__["MaterialLoader"](); Object.entries(this.main.materials).forEach(([name, materialObject]) => { this.main.materials[name] = materialLoader.parse(materialObject); }); return this; } _cleanMaterial(material) { Object.entries(material).forEach(([key, value]) => { if (value instanceof three__WEBPACK_IMPORTED_MODULE_0__["Texture"] || value === null) { material[key] = undefined; } }); return material; } /** * See {@link DataTransport#loadData} * @param {string} name * @param {ArrayBuffer} buffer * @return {MaterialsTransport} */ addBuffer(name, buffer) { super.addBuffer(name, buffer); return this; } /** * See {@link DataTransport#setParams} * @param {object.} params * @return {MaterialsTransport} */ setParams(params) { super.setParams(params); return this; } /** * Set an object containing named materials. * @param {object.} materials */ setMaterials(materials) { if (materials !== undefined && materials !== null && Object.keys(materials).length > 0) this.main.materials = materials; return this; } /** * Returns all maerials * @return {object.} */ getMaterials() { return this.main.materials; } /** * Removes all textures and null values from all materials */ cleanMaterials() { let clonedMaterials = {}; let clonedMaterial; for (let material of Object.values(this.main.materials)) { if (typeof material.clone === 'function') { clonedMaterial = material.clone(); clonedMaterials[clonedMaterial.name] = this._cleanMaterial(clonedMaterial); } } this.setMaterials(clonedMaterials); return this; } /** * See {@link DataTransport#package} * @param {boolean} cloneBuffers * @return {DataTransport} */ package(cloneBuffers) { super.package(cloneBuffers); this.main.materials = MaterialUtils.getMaterialsJSON(this.main.materials); return this; } /** * Tell whether a multi-material was defined * @return {boolean} */ hasMultiMaterial() { return Object.keys(this.main.multiMaterialNames).length > 0; } /** * Returns a single material if it is defined or null. * @return {Material|null} */ getSingleMaterial() { if (Object.keys(this.main.materials).length > 0) { return Object.entries(this.main.materials)[0][1]; } else { return null; } } /** * Adds contained material or multi-material the provided materials object or it clones and adds new materials according clone instructions. * * @param {Object.} materials * @param {boolean} log * * @return {Material|Material[]} */ processMaterialTransport(materials, log) { for (let i = 0; i < this.main.cloneInstructions.length; i++) { MaterialUtils.cloneMaterial(materials, this.main.cloneInstructions[i], log); } let outputMaterial; if (this.hasMultiMaterial()) { // multi-material outputMaterial = []; Object.entries(this.main.multiMaterialNames).forEach(([materialIndex, materialName]) => { outputMaterial[materialIndex] = materials[materialName]; }); } else { const singleMaterial = this.getSingleMaterial(); if (singleMaterial !== null) { outputMaterial = materials[singleMaterial.name]; if (!outputMaterial) outputMaterial = singleMaterial; } } return outputMaterial; } } /** * Define a structure that is used to send geometry data between main and workers. */ class GeometryTransport extends DataTransport { /** * Creates a new {@link GeometryTransport}. * @param {string} [cmd] * @param {string} [id] */ constructor(cmd, id) { super(cmd, id); this.main.type = 'GeometryTransport'; // 0: mesh, 1: line, 2: point /** @type {number} */ this.main.geometryType = 0; /** @type {object} */ this.main.geometry = {}; /** @type {BufferGeometry} */ this.main.bufferGeometry = null; } /** * See {@link DataTransport#loadData} * @param {object} transportObject * @return {GeometryTransport} */ loadData(transportObject) { super.loadData(transportObject); this.main.type = 'GeometryTransport'; return this.setGeometry(transportObject.geometry, transportObject.geometryType); } /** * Returns the geometry type [0=Mesh|1=LineSegments|2=Points] * @return {number} */ getGeometryType() { return this.main.geometryType; } /** * See {@link DataTransport#setParams} * @param {object} params * @return {GeometryTransport} */ setParams(params) { super.setParams(params); return this; } /** * Set the {@link BufferGeometry} and geometry type that can be used when a mesh is created. * * @param {BufferGeometry} geometry * @param {number} geometryType [0=Mesh|1=LineSegments|2=Points] * @return {GeometryTransport} */ setGeometry(geometry, geometryType) { this.main.geometry = geometry; this.main.geometryType = geometryType; if (geometry instanceof three__WEBPACK_IMPORTED_MODULE_0__["BufferGeometry"]) this.main.bufferGeometry = geometry; return this; } /** * Package {@link BufferGeometry} and prepare it for transport. * * @param {boolean} cloneBuffers Clone buffers if their content shall stay in the current context. * @return {GeometryTransport} */ package(cloneBuffers) { super.package(cloneBuffers); const vertexBA = this.main.geometry.getAttribute('position'); const normalBA = this.main.geometry.getAttribute('normal'); const uvBA = this.main.geometry.getAttribute('uv'); const colorBA = this.main.geometry.getAttribute('color'); const skinIndexBA = this.main.geometry.getAttribute('skinIndex'); const skinWeightBA = this.main.geometry.getAttribute('skinWeight'); const indexBA = this.main.geometry.getIndex(); this._addBufferAttributeToTransferable(vertexBA, cloneBuffers); this._addBufferAttributeToTransferable(normalBA, cloneBuffers); this._addBufferAttributeToTransferable(uvBA, cloneBuffers); this._addBufferAttributeToTransferable(colorBA, cloneBuffers); this._addBufferAttributeToTransferable(skinIndexBA, cloneBuffers); this._addBufferAttributeToTransferable(skinWeightBA, cloneBuffers); this._addBufferAttributeToTransferable(indexBA, cloneBuffers); return this; } /** * Reconstructs the {@link BufferGeometry} from the raw buffers. * @param {boolean} cloneBuffers * @return {GeometryTransport} */ reconstruct(cloneBuffers) { if (this.main.bufferGeometry instanceof three__WEBPACK_IMPORTED_MODULE_0__["BufferGeometry"]) return this; this.main.bufferGeometry = new three__WEBPACK_IMPORTED_MODULE_0__["BufferGeometry"](); const transferredGeometry = this.main.geometry; this._assignAttribute(transferredGeometry.attributes.position, 'position', cloneBuffers); this._assignAttribute(transferredGeometry.attributes.normal, 'normal', cloneBuffers); this._assignAttribute(transferredGeometry.attributes.uv, 'uv', cloneBuffers); this._assignAttribute(transferredGeometry.attributes.color, 'color', cloneBuffers); this._assignAttribute(transferredGeometry.attributes.skinIndex, 'skinIndex', cloneBuffers); this._assignAttribute(transferredGeometry.attributes.skinWeight, 'skinWeight', cloneBuffers); const index = transferredGeometry.index; if (index !== null && index !== undefined) { const indexBuffer = cloneBuffers ? index.array.slice(0) : index.array; this.main.bufferGeometry.setIndex(new three__WEBPACK_IMPORTED_MODULE_0__["BufferAttribute"](indexBuffer, index.itemSize, index.normalized)); } const boundingBox = transferredGeometry.boundingBox; if (boundingBox !== null) this.main.bufferGeometry.boundingBox = Object.assign(new three__WEBPACK_IMPORTED_MODULE_0__["Box3"](), boundingBox); const boundingSphere = transferredGeometry.boundingSphere; if (boundingSphere !== null) this.main.bufferGeometry.boundingSphere = Object.assign(new three__WEBPACK_IMPORTED_MODULE_0__["Sphere"](), boundingSphere); this.main.bufferGeometry.uuid = transferredGeometry.uuid; this.main.bufferGeometry.name = transferredGeometry.name; this.main.bufferGeometry.type = transferredGeometry.type; this.main.bufferGeometry.groups = transferredGeometry.groups; this.main.bufferGeometry.drawRange = transferredGeometry.drawRange; this.main.bufferGeometry.userData = transferredGeometry.userData; return this; } /** * Returns the {@link BufferGeometry}. * @return {BufferGeometry|null} */ getBufferGeometry() { return this.main.bufferGeometry; } _addBufferAttributeToTransferable(input, cloneBuffer) { if (input !== null && input !== undefined) { const arrayBuffer = cloneBuffer ? input.array.slice(0) : input.array; this.transferables.push(arrayBuffer.buffer); } return this; } _assignAttribute(attr, attrName, cloneBuffer) { if (attr) { const arrayBuffer = cloneBuffer ? attr.array.slice(0) : attr.array; this.main.bufferGeometry.setAttribute(attrName, new three__WEBPACK_IMPORTED_MODULE_0__["BufferAttribute"](arrayBuffer, attr.itemSize, attr.normalized)); } return this; } } /** * Define a structure that is used to send mesh data between main and workers. */ class MeshTransport extends GeometryTransport { /** * Creates a new {@link MeshTransport}. * @param {string} [cmd] * @param {string} [id] */ constructor(cmd, id) { super(cmd, id); this.main.type = 'MeshTransport'; // needs to be added as we cannot inherit from both materials and geometry this.main.materialsTransport = new MaterialsTransport(); } /** * See {@link GeometryTransport#loadData} * @param {object} transportObject * @return {MeshTransport} */ loadData(transportObject) { super.loadData(transportObject); this.main.type = 'MeshTransport'; this.main.meshName = transportObject.meshName; this.main.materialsTransport = new MaterialsTransport().loadData(transportObject.materialsTransport.main); return this; } /** * See {@link GeometryTransport#loadData} * @param {object} params * @return {MeshTransport} */ setParams(params) { super.setParams(params); return this; } /** * The {@link MaterialsTransport} wraps all info regarding the material for the mesh. * @param {MaterialsTransport} materialsTransport * @return {MeshTransport} */ setMaterialsTransport(materialsTransport) { if (materialsTransport instanceof MaterialsTransport) this.main.materialsTransport = materialsTransport; return this; } /** * @return {MaterialsTransport} */ getMaterialsTransport() { return this.main.materialsTransport; } /** * Sets the mesh and the geometry type [0=Mesh|1=LineSegments|2=Points] * @param {Mesh} mesh * @param {number} geometryType * @return {MeshTransport} */ setMesh(mesh, geometryType) { this.main.meshName = mesh.name; super.setGeometry(mesh.geometry, geometryType); return this; } /** * See {@link GeometryTransport#package} * @param {boolean} cloneBuffers * @return {MeshTransport} */ package(cloneBuffers) { super.package(cloneBuffers); if (this.main.materialsTransport !== null) this.main.materialsTransport.package(cloneBuffers); return this; } /** * See {@link GeometryTransport#reconstruct} * @param {boolean} cloneBuffers * @return {MeshTransport} */ reconstruct(cloneBuffers) { super.reconstruct(cloneBuffers); // so far nothing needs to be done for material return this; } } /** * Utility for serializing object in memory */ class ObjectUtils { /** * Serializes a class with an optional prototype * @param targetClass * @param targetPrototype * @param fullObjectName * @param processPrototype * @return {string} */ static serializePrototype(targetClass, targetPrototype, fullObjectName, processPrototype) { let prototypeFunctions = []; let objectString = ''; let target; if (processPrototype) { objectString = targetClass.toString() + "\n\n"; target = targetPrototype; } else { target = targetClass; } for (let name in target) { let objectPart = target[name]; let code = objectPart.toString(); if (typeof objectPart === 'function') { prototypeFunctions.push('\t' + name + ': ' + code + ',\n\n'); } } let protoString = processPrototype ? '.prototype' : ''; objectString += fullObjectName + protoString + ' = {\n\n'; for (let i = 0; i < prototypeFunctions.length; i++) { objectString += prototypeFunctions[i]; } objectString += '\n}\n;'; return objectString; } /** * Serializes a class. * @param {object} targetClass An ES6+ class * @return {string} */ static serializeClass(targetClass) { return targetClass.toString() + "\n\n"; } } /** * Object manipulation utilities. */ class ObjectManipulator { /** * Applies values from parameter object via set functions or via direct assignment. * * @param {Object} objToAlter The objToAlter instance * @param {Object} params The parameter object * @param {boolean} forceCreation Force the creation of a property */ static applyProperties(objToAlter, params, forceCreation) { // fast-fail if (objToAlter === undefined || objToAlter === null || params === undefined || params === null) return; let property, funcName, values; for (property in params) { funcName = 'set' + property.substring(0, 1).toLocaleUpperCase() + property.substring(1); values = params[property]; if (typeof objToAlter[funcName] === 'function') { objToAlter[funcName](values); } else if (objToAlter.hasOwnProperty(property) || forceCreation) { objToAlter[property] = values; } } } } /** * Development repository: https://github.com/kaisalmen/three-wtm */ /** * Helper class around an object storing materials by name. * Optionally, create and store default materials. */ class MaterialStore { /** * Creates a new {@link MaterialStore}. * @param {boolean} createDefaultMaterials */ constructor(createDefaultMaterials) { this.materials = {}; if (createDefaultMaterials) { const defaultMaterial = new three__WEBPACK_IMPORTED_MODULE_0__["MeshStandardMaterial"]({ color: 0xDCF1FF }); defaultMaterial.name = 'defaultMaterial'; const defaultVertexColorMaterial = new three__WEBPACK_IMPORTED_MODULE_0__["MeshStandardMaterial"]({ color: 0xDCF1FF }); defaultVertexColorMaterial.name = 'defaultVertexColorMaterial'; defaultVertexColorMaterial.vertexColors = three__WEBPACK_IMPORTED_MODULE_0__["VertexColors"]; const defaultLineMaterial = new three__WEBPACK_IMPORTED_MODULE_0__["LineBasicMaterial"](); defaultLineMaterial.name = 'defaultLineMaterial'; const defaultPointMaterial = new three__WEBPACK_IMPORTED_MODULE_0__["PointsMaterial"]({ size: 0.1 }); defaultPointMaterial.name = 'defaultPointMaterial'; this.materials[defaultMaterial.name] = defaultMaterial; this.materials[defaultVertexColorMaterial.name] = defaultVertexColorMaterial; this.materials[defaultLineMaterial.name] = defaultLineMaterial; this.materials[defaultPointMaterial.name] = defaultPointMaterial; } } /** * Set materials loaded by any supplier of an Array of {@link Material}. * * @param {object} newMaterials Object with named {@link Material} * @param {boolean} forceOverrideExisting boolean Override existing material */ addMaterials(newMaterials, forceOverrideExisting) { if (newMaterials === undefined || newMaterials === null) newMaterials = {}; if (Object.keys(newMaterials).length > 0) { let material; for (const materialName in newMaterials) { material = newMaterials[materialName]; MaterialUtils.addMaterial(this.materials, material, materialName, forceOverrideExisting === true); } } } /** * Returns the mapping object of material name and corresponding material. * * @returns {Object} Map of {@link Material} */ getMaterials() { return this.materials; } /** * * @param {String} materialName * @returns {Material} */ getMaterial(materialName) { return this.materials[materialName]; } /** * Removes all materials */ clearMaterials() { this.materials = {}; } } /** * Development repository: https://github.com/kaisalmen/three-wtm */ class WorkerTaskManagerDefaultRouting { static comRouting(context, message, object, initFunction, executeFunction) { let payload = message.data; if (payload.cmd === 'init') { if (object !== undefined && object !== null) { object[initFunction](context, payload.workerId, payload.config); } else { initFunction(context, payload.workerId, payload.config); } } else if (payload.cmd === 'execute') { if (object !== undefined && object !== null) { object[executeFunction](context, payload.workerId, payload.config); } else { executeFunction(context, payload.workerId, payload.config); } } } } /** * Development repository: https://github.com/kaisalmen/three-wtm * Initial idea by Don McCurdy / https://www.donmccurdy.com */ /** * Register one to many tasks type to the WorkerTaskManager. Then init and enqueue a worker based execution by passing * configuration and buffers. The WorkerTaskManager allows to execute a maximum number of executions in parallel. */ class WorkerTaskManager { /** * Creates a new WorkerTaskManager instance. * * @param {number} [maxParallelExecutions] How many workers are allowed to be executed in parallel. */ constructor(maxParallelExecutions) { /** * @type {Map} */ this.taskTypes = new Map(); this.verbose = false; this.maxParallelExecutions = maxParallelExecutions ? maxParallelExecutions : 4; this.actualExecutionCount = 0; /** * @type {StoredExecution[]} */ this.storedExecutions = []; this.teardown = false; } /** * Set if logging should be verbose * * @param {boolean} verbose * @return {WorkerTaskManager} */ setVerbose(verbose) { this.verbose = verbose; return this; } /** * Set the maximum number of parallel executions. * * @param {number} maxParallelExecutions How many workers are allowed to be executed in parallel. * @return {WorkerTaskManager} */ setMaxParallelExecutions(maxParallelExecutions) { this.maxParallelExecutions = maxParallelExecutions; return this; } /** * Returns the maximum number of parallel executions. * @return {number} */ getMaxParallelExecutions() { return this.maxParallelExecutions; } /** * Returns true if support for the given task type is available. * * @param {string} taskType The task type as string * @return boolean */ supportsTaskType(taskType) { return this.taskTypes.has(taskType); } /** * Registers functions and dependencies for a new task type. * * @param {string} taskType The name to be used for registration. * @param {Function} initFunction The function to be called when the worker is initialised * @param {Function} executeFunction The function to be called when the worker is executed * @param {Function} comRoutingFunction The function that should handle communication, leave undefined for default behavior * @param {boolean} fallback Set to true if execution should be performed in main * @param {Object[]} [dependencyDescriptions] * @return {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) */ registerTaskType(taskType, initFunction, executeFunction, comRoutingFunction, fallback, dependencyDescriptions) { let allowedToRegister = !this.supportsTaskType(taskType); if (allowedToRegister) { let workerTypeDefinition = new WorkerTypeDefinition(taskType, this.maxParallelExecutions, fallback, this.verbose); workerTypeDefinition.setFunctions(initFunction, executeFunction, comRoutingFunction); workerTypeDefinition.setDependencyDescriptions(dependencyDescriptions); this.taskTypes.set(taskType, workerTypeDefinition); } return allowedToRegister; } /** * Registers functionality for a new task type based on module file. * * @param {string} taskType The name to be used for registration. * @param {string} workerModuleUrl The URL to be used for the Worker. Module must provide logic to handle "init" and "execute" messages. * @return {boolean} Tells if registration is possible (new=true) or if task was already registered (existing=false) */ registerTaskTypeModule(taskType, workerModuleUrl) { let allowedToRegister = !this.supportsTaskType(taskType); if (allowedToRegister) { let workerTypeDefinition = new WorkerTypeDefinition(taskType, this.maxParallelExecutions, false, this.verbose); workerTypeDefinition.setWorkerModule(workerModuleUrl); this.taskTypes.set(taskType, workerTypeDefinition); } return allowedToRegister; } /** * Provides initialization configuration and transferable objects. * * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. */ async initTaskType(taskType, config, transferables) { let workerTypeDefinition = this.taskTypes.get(taskType); if (workerTypeDefinition) { if (!workerTypeDefinition.status.initStarted) { workerTypeDefinition.status.initStarted = true; if (workerTypeDefinition.isWorkerModule()) { await workerTypeDefinition.createWorkerModules().then(() => workerTypeDefinition.initWorkers(config, transferables)).then(() => workerTypeDefinition.status.initComplete = true).catch(e => console.error(e)); } else { await workerTypeDefinition.loadDependencies().then(() => workerTypeDefinition.createWorkers()).then(() => workerTypeDefinition.initWorkers(config, transferables)).then(() => workerTypeDefinition.status.initComplete = true).catch(e => console.error(e)); } } else { while (!workerTypeDefinition.status.initComplete) { await this._wait(10); } } } } async _wait(milliseconds) { return new Promise(resolve => { setTimeout(resolve, milliseconds); }); } /** * Queues a new task of the given type. Task will not execute until initialization completes. * * @param {string} taskType The name of the registered task type. * @param {object} config Configuration properties as serializable string. * @param {Function} assetAvailableFunction Invoke this function if an asset become intermediately available * @param {Transferable[]} [transferables] Any optional {@link ArrayBuffer} encapsulated in object. * @return {Promise} */ async enqueueForExecution(taskType, config, assetAvailableFunction, transferables) { let localPromise = new Promise((resolveUser, rejectUser) => { this.storedExecutions.push(new StoredExecution(taskType, config, assetAvailableFunction, resolveUser, rejectUser, transferables)); this._depleteExecutions(); }); return localPromise; } _depleteExecutions() { let counter = 0; while (this.actualExecutionCount < this.maxParallelExecutions && counter < this.storedExecutions.length) { // TODO: storedExecutions and results from worker seem to get mixed up let storedExecution = this.storedExecutions[counter]; let workerTypeDefinition = this.taskTypes.get(storedExecution.taskType); let taskWorker = workerTypeDefinition.getAvailableTask(); if (taskWorker) { this.storedExecutions.splice(counter, 1); this.actualExecutionCount++; let promiseWorker = new Promise((resolveWorker, rejectWorker) => { taskWorker.onmessage = message => { // allow intermediate asset provision before flagging execComplete if (message.data.cmd === 'assetAvailable') { if (storedExecution.assetAvailableFunction instanceof Function) { storedExecution.assetAvailableFunction(message.data); } } else { resolveWorker(message); } }; taskWorker.onerror = rejectWorker; taskWorker.postMessage({ cmd: "execute", workerId: taskWorker.getId(), config: storedExecution.config }, storedExecution.transferables); }); promiseWorker.then(message => { workerTypeDefinition.returnAvailableTask(taskWorker); storedExecution.resolve(message.data); this.actualExecutionCount--; this._depleteExecutions(); }).catch(e => { storedExecution.reject("Execution error: " + e); }); } else { counter++; } } } /** * Destroys all workers and associated resources. * @return {WorkerTaskManager} */ dispose() { this.teardown = true; for (let workerTypeDefinition of this.taskTypes.values()) { workerTypeDefinition.dispose(); } return this; } } /** * Defines a worker type: functions, dependencies and runtime information once it was created. */ class WorkerTypeDefinition { /** * Creates a new instance of {@link WorkerTypeDefinition}. * * @param {string} taskType The name of the registered task type. * @param {Number} maximumCount Maximum worker count * @param {boolean} fallback Set to true if execution should be performed in main * @param {boolean} [verbose] Set if logging should be verbose */ constructor(taskType, maximumCount, fallback, verbose) { this.taskType = taskType; this.fallback = fallback; this.verbose = verbose === true; this.initialised = false; this.functions = { /** @type {Function} */ init: null, /** @type {Function} */ execute: null, /** @type {Function} */ comRouting: null, dependencies: { /** @type {Object[]} */ descriptions: [], /** @type {string[]} */ code: [] }, /** * @type {URL} */ workerModuleUrl: null }; this.workers = { /** @type {string[]} */ code: [], /** @type {TaskWorker[]|MockedTaskWorker[]} */ instances: new Array(maximumCount), /** @type {TaskWorker[]|MockedTaskWorker[]} */ available: [] }; this.status = { initStarted: false, initComplete: false }; } getTaskType() { return this.taskType; } /** * Set the three functions. A default comRouting function is used if it is not passed here. * Then it creates the code fr. * * @param {Function} initFunction The function to be called when the worker is initialised * @param {Function} executeFunction The function to be called when the worker is executed * @param {Function} [comRoutingFunction] The function that should handle communication, leave undefined for default behavior */ setFunctions(initFunction, executeFunction, comRoutingFunction) { this.functions.init = initFunction; this.functions.execute = executeFunction; this.functions.comRouting = comRoutingFunction; if (this.functions.comRouting === undefined || this.functions.comRouting === null) { this.functions.comRouting = WorkerTaskManagerDefaultRouting.comRouting; } this._addWorkerCode('init', this.functions.init.toString()); this._addWorkerCode('execute', this.functions.execute.toString()); this._addWorkerCode('comRouting', this.functions.comRouting.toString()); this.workers.code.push('self.addEventListener( "message", message => comRouting( self, message, null, init, execute ), false );'); } /** * * @param {string} functionName Name of the function * @param {string} functionString A function as string * @private */ _addWorkerCode(functionName, functionString) { if (functionString.startsWith('function')) { this.workers.code.push('const ' + functionName + ' = ' + functionString + ';\n\n'); } else { this.workers.code.push('function ' + functionString + ';\n\n'); } } /** * Set the url of all dependent libraries (only used in non-module case). * * @param {Object[]} dependencyDescriptions URLs of code init and execute functions rely on. */ setDependencyDescriptions(dependencyDescriptions) { if (dependencyDescriptions) { dependencyDescriptions.forEach(description => { this.functions.dependencies.descriptions.push(description); }); } } /** * Set the url of the module worker. * * @param {string} workerModuleUrl The URL is created from this string. */ setWorkerModule(workerModuleUrl) { this.functions.workerModuleUrl = new URL(workerModuleUrl, window.location.href); } /** * Is it a module worker? * * @return {boolean} True or false */ isWorkerModule() { return this.functions.workerModuleUrl !== null; } /** * Loads all dependencies and stores each as {@link ArrayBuffer} into the array. Returns if all loading is completed. * * @return {String[]} */ async loadDependencies() { let promises = []; let fileLoader = new three__WEBPACK_IMPORTED_MODULE_0__["FileLoader"](); fileLoader.setResponseType('arraybuffer'); for (let description of this.functions.dependencies.descriptions) { if (description.url) { let url = new URL(description.url, window.location.href); promises.push(fileLoader.loadAsync(url.href, report => { if (this.verbose) console.log(report); })); } if (description.code) { promises.push(new Promise(resolve => resolve(description.code))); } } if (this.verbose) console.log('Task: ' + this.getTaskType() + ': Waiting for completion of loading of all dependencies.'); this.functions.dependencies.code = await Promise.all(promises); } /** * Creates workers based on the configured function and dependency strings. * */ async createWorkers() { let worker; if (!this.fallback) { let workerBlob = new Blob(this.functions.dependencies.code.concat(this.workers.code), { type: 'application/javascript' }); let objectURL = window.URL.createObjectURL(workerBlob); for (let i = 0; i < this.workers.instances.length; i++) { worker = new TaskWorker(i, objectURL); this.workers.instances[i] = worker; } } else { for (let i = 0; i < this.workers.instances.length; i++) { worker = new MockedTaskWorker(i, this.functions.init, this.functions.execute); this.workers.instances[i] = worker; } } } /** * Creates module workers. * */ async createWorkerModules() { for (let worker, i = 0; i < this.workers.instances.length; i++) { worker = new TaskWorker(i, this.functions.workerModuleUrl.href, { type: "module" }); this.workers.instances[i] = worker; } } /** * Initialises all workers with common configuration data. * * @param {object} config * @param {Transferable[]} transferables */ async initWorkers(config, transferables) { let promises = []; for (let taskWorker of this.workers.instances) { let taskWorkerPromise = new Promise((resolveWorker, rejectWorker) => { taskWorker.onmessage = message => { if (this.verbose) console.log('Init Complete: ' + message.data.id); resolveWorker(message); }; taskWorker.onerror = rejectWorker; // ensure all transferables are copies to all workers on init! let transferablesToWorker; if (transferables) { transferablesToWorker = []; for (let i = 0; i < transferables.length; i++) { transferablesToWorker.push(transferables[i].slice(0)); } } taskWorker.postMessage({ cmd: "init", workerId: taskWorker.getId(), config: config }, transferablesToWorker); }); promises.push(taskWorkerPromise); } if (this.verbose) console.log('Task: ' + this.getTaskType() + ': Waiting for completion of initialization of all workers.'); await Promise.all(promises); this.workers.available = this.workers.instances; } /** * Returns the first {@link TaskWorker} or {@link MockedTaskWorker} from array of available workers. * * @return {TaskWorker|MockedTaskWorker|undefined} */ getAvailableTask() { let task = undefined; if (this.hasTask()) { task = this.workers.available.shift(); } return task; } /** * Returns if a task is available or not. * * @return {boolean} */ hasTask() { return this.workers.available.length > 0; } /** * * @param {TaskWorker|MockedTaskWorker} taskWorker */ returnAvailableTask(taskWorker) { this.workers.available.push(taskWorker); } /** * Dispose all worker instances. */ dispose() { for (let taskWorker of this.workers.instances) { taskWorker.terminate(); } } } /** * Contains all things required for later executions of Worker. */ class StoredExecution { /** * Creates a new instance. * * @param {string} taskType * @param {object} config * @param {Function} assetAvailableFunction * @param {Function} resolve * @param {Function} reject * @param {Transferable[]} [transferables] */ constructor(taskType, config, assetAvailableFunction, resolve, reject, transferables) { this.taskType = taskType; this.config = config; this.assetAvailableFunction = assetAvailableFunction; this.resolve = resolve; this.reject = reject; this.transferables = transferables; } } /** * Extends the {@link Worker} with an id. */ class TaskWorker extends Worker { /** * Creates a new instance. * * @param {number} id Numerical id of the task. * @param {string} aURL * @param {object} [options] */ constructor(id, aURL, options) { super(aURL, options); this.id = id; } /** * Returns the id. * @return {number} */ getId() { return this.id; } } /** * This is a mock of a worker to be used on Main. It defines necessary functions, so it can be handled like * a regular {@link TaskWorker}. */ class MockedTaskWorker { /** * Creates a new instance. * * @param {number} id * @param {Function} initFunction * @param {Function} executeFunction */ constructor(id, initFunction, executeFunction) { this.id = id; this.functions = { init: initFunction, execute: executeFunction }; } /** * Returns the id. * @return {number} */ getId() { return this.id; } /** * Delegates the message to the registered functions * @param {String} message * @param {Transferable[]} [transfer] */ postMessage(message, transfer) { let scope = this; let self = { postMessage: function (m) { scope.onmessage({ data: m }); } }; WorkerTaskManagerDefaultRouting.comRouting(self, { data: message }, null, scope.functions.init, scope.functions.execute); } /** * Mocking termination */ terminate() {} } /***/ }) /******/ ]); });