mirror of
https://github.com/jiawanlong/Cesium-Examples.git
synced 2025-07-04 15:17:36 +00:00
1776 lines
51 KiB
JavaScript
1776 lines
51 KiB
JavaScript
(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.<string, Material>} 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.<string, Material>}
|
|
* @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.<String, Material>} 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.<string, *>} params
|
|
* @return {DataTransport}
|
|
*/
|
|
|
|
|
|
setParams(params) {
|
|
if (params !== null && params !== undefined) {
|
|
this.main.params = params;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
/**
|
|
* Return the parameter object
|
|
* @return {object.<string, *>}
|
|
*/
|
|
|
|
|
|
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.<string, Material>} */
|
|
|
|
this.main.materials = {};
|
|
/** {object.<number, string>} */
|
|
|
|
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.<string, *>} params
|
|
* @return {MaterialsTransport}
|
|
*/
|
|
|
|
|
|
setParams(params) {
|
|
super.setParams(params);
|
|
return this;
|
|
}
|
|
/**
|
|
* Set an object containing named materials.
|
|
* @param {object.<string, Material>} materials
|
|
*/
|
|
|
|
|
|
setMaterials(materials) {
|
|
if (materials !== undefined && materials !== null && Object.keys(materials).length > 0) this.main.materials = materials;
|
|
return this;
|
|
}
|
|
/**
|
|
* Returns all maerials
|
|
* @return {object.<string, Material>}
|
|
*/
|
|
|
|
|
|
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.<string, Material>} 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<string, Material>} 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<string, WorkerTypeDefinition>}
|
|
*/
|
|
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() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***/ })
|
|
/******/ ]);
|
|
}); |