Source: loaders/SceneLoader.js

import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { Relationship } from '../relationship/Relationship';
import { EventType, RelationshipDirection } from '../const';
import { CubeTexture } from '../resources/CubeTexture';
import { TSFResolver } from '../resolvers/TSFResolver';
import { PathResolver } from '../resolvers/PathResolver';
import { Selector } from '../selector/Selector';

const cStyleTextureKeys = [
	'map'
];
const cCanIgnoreKeys = {
	'relationships': false,
	'blueprints': false,
	'selections': false,
	'plugins': false,
	'files': false,
	'predownloads': false,
	'rendersettings': true
};
const __ = {
	private: Symbol('private'),
}
function _initPrivateMembers(that) {
	that[__.private] = {};
	const _private = that[__.private];

	_private.checkIgnore = (key) => {
		if (!cCanIgnoreKeys.hasOwnProperty(key)) {
			return false;
		}
		if (_private.ignore.hasOwnProperty(key)) {
			return _private.ignore[key];
		}
		else {
			return cCanIgnoreKeys[key];
		}
	}
	_private.getPathFromURL = (url) => {
		if (!url) {
			return '';
		}
		let end = url.lastIndexOf('/');
		if (end < 0) {
			end = url.lastIndexOf('\\');
		}
		let path = url.substr(0, end);
		return path;
	}
	_private.processOptions = (options) => {
		_private.options = Object.assign(_private.options, options);
		_private.dynamic = Utils.parseValue(options['dynamic'], false);
		_private.hidden = Utils.parseValue(options['hidden'], false);
		_private.isEditor = Utils.parseValue(options['isEditor'], false);
		_private.useLatestModel = Utils.parseValue(options['useLatestModel'], true);
		_private.customObjectProcessCallback = Utils.parseValue(options['CustomObjectProcess'], null);

		const loadIndex = options['_index_'];
		if (Utils.isValid(loadIndex)) {
			_private.progressParam.index = loadIndex;
		}

		_private.currentPath = _private.getPathFromURL(options.url);
		_private.root = options['root'];
		_private.onProgress = options['onProgress'];
		_private.onComplete = options['onComplete'];
		_private.onError = options['onError'];
		_private.onCreateObject = options['onCreateObject'];

		let ignore = options['ignore'];
		if (ignore) {
			_private.ignore = ignore;
		}
	}

	// #region parse
	_private.checkAndParse = (data, parseKey, parseCallback) => {
		let parseData = data[parseKey];
		let ignore = _private.checkIgnore(parseKey);
		if (!ignore && parseData && parseCallback) {
			parseCallback(parseData);
		}
	}
	_private.parseBaseURL = (baseURL) => {
		_private.baseURL = baseURL;
		if (!_private.baseURL._endsWith('/')) {
			_private.baseURL += '/';
		}
	}
	_private.parseTags = (tags) => {
		_private.tags = tags;
	}
	_private.parseURIs = (URIs) => {
		_private.URIs = URIs;
	}
	_private.parseStyles = (styles) => {
		_private.styles = styles;

		_private.styles.forEach((style) => {
			if (!style) {
				return;
			}

			for (let key in style) {
				if (cStyleTextureKeys.includes(key)) {
					let styleTexture = style[key];

					if (Array.isArray(styleTexture)) {
						styleTexture.forEach((data, index) => {
							styleTexture[index] = _private.URIs[data];
						});
						_private.info.totalNumber += 6;
					}
					else {
						style[key] = _private.URIs[styleTexture];
						_private.info.totalNumber++;
					}
				}
			}
		});
	}
	_private.parseScripts = (scripts) => {
		scripts.forEach(script => {
			let url = _private.toolkit.pathResolver.resolveURL(script);
			_private.scripts.push(url);
			_private.info.totalNumber++;
		});
	}
	_private.parseFiles = (files) => {
		files.forEach(file => {
			_private.files.push(file);
			_private.info.totalNumber++;
		});
	}
	_private.parsePredownloads = (predownloads) => {
		predownloads.forEach(predownload => {
			let url = _private.toolkit.pathResolver.resolveURL(predownload);
			_private.predownloads.push(url);
			_private.info.totalNumber++;
		});
	}
	_private.parseResolvers = (resolvers) => {
		resolvers.forEach(resolver => {
			_private.resolvers.set(resolver.type, resolver.data);
			_private.info.totalNumber++;
		})
	}
	_private.getObjectClassType = (object) => {
		// Get the class type
		let classType = Utils.getRegisteredClasses()[object.type];

		// Try to get class type from outside
		let onGetClassType = _private.options['onGetClassType'];
		if (onGetClassType) {
			classType = onGetClassType(object) || classType;
		}
		return classType;
	}
	_private.parseObjectRes = (index) => {
		let uri, nodeName, version;
		let url = _private.URIs[index];
		if (!url) {
			return null;
		}
		uri = _private.toolkit.pathResolver.resolveModelURL(url);

		if (uri.indexOf('?') != -1) {
			let uriContent = uri.split('?');
			uri = uriContent[0];
			let params = uriContent[1].split('&');
			for (let i = 0; i < params.length; i++) {
				let param = params[i].split('=');
				if (param[0] == 'nodename') {
					nodeName = param[1];
				}
				else if (param[0] == 'version') {
					version = param[1];
				}
			}
		}

		return { uri, nodeName, version };
	}
	_private.parseObjectSize = (size) => {
		if (!size) {
			return null;
		}
		MathUtils.fixScaleFactor(size);
		let initialLocalBoundingBox = {
			// object center are at the foot,however the boundingbox center in the center of box
			// so we up the bounding half of y,make the two center coinside
			center: [0, size[1] / 2, 0],
			halfSize: MathUtils.divideVector(size, 2)
		};
		return { size, initialLocalBoundingBox };
	}
	_private.parseObject = (object) => {
		let data = {};
		// extras
		let extras = object.extras;
		if (extras) {
			data.extras = extras;
		}

		// postion
		let position = object.position;
		if (position) {
			data.position = position;
		}

		// scale
		let scale = object.scale;
		if (scale) {
			MathUtils.fixScaleFactor(scale);
			data.scale = scale;
		}

		// rotation
		let rotation = object.rotation;
		if (rotation) {
			if (rotation.length == 3) {
				data.quaternion = MathUtils.getQuatFromAngles(rotation);
			}
			else {
				data.quaternion = rotation;
			}
		}

		// tags
		let tags = [];
		if (object.tags) {
			tags = object.tags.map(index => {
				return _private.tags[index];
			});
			data.tags = tags;
		}

		data.res = _private.parseObjectRes(object.uri);
		// TODO:
		// data.size = _private.parseObjectSize(object.size);

		Utils.setAttributeIfExist(data, 'id', object, (v) => { return v != ''; });
		Utils.setAttributeIfExist(data, 'name', object, (v) => { return v != ''; });
		Utils.setAttributeIfExist(data, 'type', object);
		Utils.setAttributeIfExist(data, 'parent', object);
		Utils.setAttributeIfExist(data, 'uuid', object);
		Utils.setAttributeIfExist(data, 'active', object);
		Utils.setAttributeIfExist(data, 'visible', object);
		Utils.setAttributeIfExist(data, 'style', object);
		Utils.setAttributeIfExist(data, 'components', object);
		Utils.setAttributeIfExist(data, 'children', object);
		Utils.setAttributeIfExist(data, 'userData', object);

		return data;
	}
	_private.parseObjects = (objects) => {
		objects.forEach(object => {
			let data = _private.parseObject(object);
			if (data) {
				_private.info.totalNumber++;

				if (data.hasOwnProperty('parent')) {
					_private.rootObjectIndices.push(_private.objects.length);
				}
				_private.objects.push(data);
			}
		});
	}
	_private.parseComponents = (components) => {
		for (let i = 0; i < components.length; i++) {
			let element = components[i];

			const classType = _private.getComponentClassType(element);
			element['classType'] = classType;
		}
	}
	_private.parseRelationship = (relationship) => {
		if (!relationship.type) {
			return null;
		}

		let data = Object.assign({}, relationship);
		if (!data.name) {
			data.name = "";
		}
		if (!data.queryDirection) {
			data.queryDirection = RelationshipDirection.Out;
		}

		return data;
	};
	_private.parseRelationships = (relationships) => {
		if (!relationships || relationships.length <= 0) {
			return;
		}
		relationships.forEach(relationship => {
			let data = _private.parseRelationship(relationship);
			if (data) {
				_private.relationships.push(data);
			}
		})
		_private.info.totalNumber++;
	};
	_private.parseBlueprints = (blueprints) => {
		_private.blueprints = blueprints;
		_private.info.totalNumber += blueprints.length;
	}
	_private.parseSelections = (selections) => {
		_private.selections = selections;
		_private.info.totalNumber += selections.length;
	}
	_private.parsePlugins = (plugins) => {
		_private.plugins = plugins;
		_private.info.totalNumber += plugins.length;
	}
	_private.parseTexture = (object, key) => {
		if (!object || !object[key]) {
			return;
		}

		let value = object[key];
		if (Array.isArray(value)) {
			object[key] = [];
			value.forEach(index => {
				object[key].push(_private.URIs[index]);
			})
			_private.info.totalNumber += 6;
		}
		else if (value instanceof Number) {
			object[key] = _private.URIs[value];
			_private.info.totalNumber++;
		}
	}
	_private.parseRenderSettings = (renderSettings) => {
		if (!renderSettings) {
			return;
		}
		_private.renderSettings = renderSettings;
		_private.parseTexture(_private.renderSettings, 'background');

		let environment = _private.renderSettings.environment;
		_private.parseTexture(environment, 'envMap');
	}
	_private.parseExtensions = (extensions) => {
		for (let key in extensions) {
			_private.extensions[key] = extensions[key];
		}
	}
	_private.parseViewpoint = (viewpoint) => {
		for (let key in viewpoint) {
			_private.viewpoint[key] = viewpoint[key];
		}
	}
	// #endregion

	// #region load
	_private.checkAndLoad = (loadKey, loadCallback) => {
		let ignore = _private.checkIgnore(loadKey);
		if (!ignore && loadCallback) {
			loadCallback();
		}
	}
	_private.getLoadResult = () => {
		let result = {};
		if (_private.info.objects.length > 0) {
			result.root = _private.root;
			result.objects = _private.info.objects;
		}
		if (_private.info.images.length > 0) {
			result.images = _private.info.images;
		}
		if (_private.selections.length > 0) {
			result.selections = _private.selections;
		}
		if (_private.blueprints.length > 0) {
			result.blueprints = _private.blueprints;
		}
		if (_private.files.length > 0) {
			result.files = _private.files;
		}
		if (_private.scripts.length > 0) {
			result.scripts = _private.scripts;
		}
		if (_private.extractClasses && !Utils.isEmptyObject(_private.extractClasses)) {
			result.extractClasses = _private.extractClasses;
		}
		if (_private.predownloads.length > 0) {
			result.predownloads = _private.predownloads;
		}
		if (_private.plugins.length > 0) {
			result.plugins = _private.plugins;
		}
		if (_private.relationships.length > 0) {
			result.relationships = _private.relationships;
		}
		if (_private.renderSettings && !Utils.isEmptyObject(_private.renderSettings)) {
			result.renderSettings = _private.renderSettings;
		}
		if (_private.extensions && !Utils.isEmptyObject(_private.extensions)) {
			result.extensions = _private.extensions;
		}
		if (_private.viewpoint && !Utils.isEmptyObject(_private.viewpoint)) {
			result.viewpoint = _private.viewpoint;
		}
		return result;
	}
	_private.updateProgress = (count = 1) => {
		let info = _private.info;

		info.number += count;

		// Notify loading progress
		if (_private.onProgress) {
			let progress = info.progress();
			if (Utils.isValid(_private.progressParam.index)) {
				_private.progressParam.progress = progress;
				_private.onProgress(_private.progressParam);
			}
			else {
				_private.onProgress(progress);
			}
		}

		// Check whether load completed
		if (_private.info.isComplete()) {
			let result = _private.getLoadResult();
			if (_private.onComplete) {
				_private.onComplete(result, _private.toolkit);
			}
			_private.resolve(result);
		}
	}
	_private.loadImage = (data) => {
		let imageTexture = null;
		if (Array.isArray(data)) {
			imageTexture = new CubeTexture({
				url: data,
				onLoad: function () {
					_private.updateProgress(6);
				}
			});
		}
		else {
			let app = Utils.getCurrentApp();
			let textureManager = app.resourceManager.getTextureManager();
			let url = _private.toolkit.pathResolver.resolveTextureURL(data);
			let flipY = false;
			let uri = url;
			if (uri.indexOf('?') != -1) {
				let uriContent = uri.split('?');
				uri = uriContent[0];
				let params = uriContent[1].split('&');
				for (let i = 0; i < params.length; i++) {
					let param = params[i].split('=');
					if (param[0] == 'flipy') {
						flipY = Utils.parseBoolean(param[1], false);
					}
				}
			}

			imageTexture = textureManager.load(uri, undefined, { flipY: flipY });
			_private.updateProgress();
		}
		_private.info.images.push(imageTexture);
		return imageTexture;
	}
	_private.processStyles = () => {
		_private.styles.forEach((style) => {
			if (!style) {
				return;
			}
			for (let key in style) {
				if (cStyleTextureKeys.includes(key)) {
					let data = style[key];
					let image = _private.loadImage(data);
					if (!image) {
						continue;
					}
					style[key] = image;
				}
			}
		});
	}
	_private.loadScripts = () => {
		let useExport = (txt) => {
			const exportTxt = txt.replaceAll(' ', '');
			return exportTxt.includes('export*') || exportTxt.includes('exportdefault') || exportTxt.includes('export{');
		}

		return new Promise((resolve, reject) => {
			let current = 0;
			let total = _private.scripts.length;
			// check empty
			if (total == 0) {
				resolve();
			}
			let checkComplete = () => {
				current++;
				if (current >= total) {
					resolve();
				}
			}
			_private.scripts.forEach(script => {
				Utils.loadTextFile(script).then(txt => {
					if (useExport(txt)) {
						Utils.extractClassesFromScript(script).then((classes) => {
							for (const [key, value] of classes.componentClasses) {
								_private.extractClasses.componentClasses.set(key, value);
							}
							for (const [key, value] of classes.bpnodeClasses) {
								_private.extractClasses.bpnodeClasses.set(key, value);
							}
							for (const [key, value] of classes.objectClasses) {
								_private.extractClasses.objectClasses.set(key, value);
							}
							for (const [key, value] of classes.unknownClasses) {
								_private.extractClasses.unknownClasses.set(key, value);
							}
							_private.updateProgress();
							checkComplete();
						});
					}
					else {
						Utils.loadFile(script, {
							onLoad: function () {
								_private.updateProgress();
								checkComplete();
							},
							onError: function () {
								_private.updateProgress();
								reject();
							}
						})
					}
				});
			})
		})
	}
	_private.loadFiles = () => {
		_private.files.forEach(file => {
			that.load(file).then(() => {
				_private.updateProgress();
			});
		});
	}
	_private.loadPredownloads = () => {
		_private.predownloads.forEach(predownload => {
			// todo: download
			_private.updateProgress();
		});
	}
	_private.loadViewpoint = () => {
		if (!_private.options.useDefaultViewpoint) {
			return;
		}
		let app = Utils.getCurrentApp();
		if (_private.viewpoint.position) {
			app.camera.position = _private.viewpoint.position;
		}
		if (_private.viewpoint.target) {
			app.camera.target = _private.viewpoint.target;
		}
	}
	_private.loadResolver = (key, data) => {
		let classType = Utils.getRegisteredClass(key);
		let resolver = classType ? new classType({ data: data }) : null;
		return resolver;
	}
	_private.loadResolvers = () => {
		let app = Utils.getCurrentApp();
		app.trigger(EventType.BeforeLoadResolvers, { loader: that });

		const keys = Array.from(_private.resolvers.keys());
		keys.forEach(key => {
			let data = _private.resolvers.get(key);
			let resolver = _private.loadResolver(key, data);
			_private.resolvers.set(key, resolver);
			_private.updateProgress();
		})
	}
	_private.onUpdateCreateOptions = (classType, createOptions, options) => {
		if (classType.onParseCreateOptions) {
			classType.onParseCreateOptions(createOptions, options);
		}

		let onUpdateCreateOptions = options['onUpdateCreateOptions'];
		if (onUpdateCreateOptions) {
			let _private = that[__.private];

			onUpdateCreateOptions(_private.root, classType, createOptions);
		}
	}
	_private.getComponentClassType = (component) => {
		let componentClassType = null;

		// Try to get component type from outside
		let onGetComponentClassType = _private.options['onGetComponentClassType'];
		if (onGetComponentClassType) {
			componentClassType = onGetComponentClassType(component) || componentClassType;
		}

		// Try to get the component type form scripts of tsf
		if (!componentClassType && _private.extractClasses.componentClasses) {
			componentClassType = _private.extractClasses.componentClasses.get(component.type);
		}

		// Try to get the component type form register classes
		if (!componentClassType) {
			componentClassType = Utils.getRegisteredClasses()[component.type];
		}

		return componentClassType;
	}
	_private.loadComponents = (object, components) => {
		const {
			url,
			data,
			root,
			parent,
			onComplete,
			complete,
			onProgress,
			progress,
			position,
			localPosition,
			rotation,
			localRotation,
			angles,
			localAngles,
			quaternion,
			localQuaternion,
			scale,
			localScale,
			...filteredOptions } = _private.options;
		let options = Object.assign(_private.toolkit, filteredOptions);
		object.onImportComponents(components, options);
	}
	_private.generateObject = (objectdata, createOptions) => {
		const {
			url,
			data,
			onComplete,
			complete,
			onProgress,
			progress,
			position,
			localPosition,
			rotation,
			localRotation,
			angles,
			localAngles,
			quaternion,
			localQuaternion,
			scale,
			localScale,
			...filteredOptions } = _private.options;
		let params = Object.assign(createOptions, filteredOptions);
		let object = new objectdata.classType(params);
		return object;
	}
	_private.processObjectData = (data) => {
		if (_private.customObjectProcessCallback) {
			data = _private.customObjectProcessCallback(data);
		}
		return data;
	}
	_private.loadObjectByIndex = (index, parent) => {
		let data = _private.objects[index];
		if (!data) {
			return null;
		}

		data = _private.processObjectData(data);

		// Build create options
		let createOptions = {
			id: data.id,
			name: data.name,
			parent: parent,
			style: _private.styles[data.style],
			visible: data.visible,
			active: data.active,
			localPosition: data.position,
			localQuaternion: data.quaternion,
			localScale: data.scale,
			userData: data.userData,
			tags: data.tags,
			dynamic: _private.dynamic,
			components: data.components,
			extras: data.extras,
			toolkit: _private.toolkit,
			error: () => {
				// Need to update progress even it's error ...
				_private.updateProgress();
			},
			syncComplete: () => {
				_private.updateProgress();
			}
		};

		if (data.res) {
			if (data.res.nodeName) {
				createOptions.nodeName = data.res.nodeName;
			}
			if (data.res.uri) {
				createOptions.url = data.res.uri;
			}
		}

		// process uuid;
		let app = Utils.getCurrentApp();
		let baseObject = app.objectManager.getBaseObjectFromUUID(data.uuid)
		if (!baseObject) {
			createOptions.uuid = data.uuid;
		}

		// classType
		let classType = _private.getObjectClassType(data);
		if (!classType) {
			if (_private.onError) {
				_private.onError({ err: `can not get the object class type, due to 'THING[${data.type}]' is not existing` });
			}
			return null;
		}
		data.classType = classType;

		// Update create options
		_private.onUpdateCreateOptions(data.classType, createOptions, _private.options);

		// Create object
		let object = _private.generateObject(data, createOptions);

		// set visible
		if (_private.hidden) {
			object.visible = false;
		}

		// Set the initial bound size to calculate bounding box
		// If object had been loaded then skip to set initial local bounding box
		if (!object.loaded && data.size) {
			object.initialLocalBoundingBox = data.size.initialLocalBoundingBox;
		}

		// onCreateObject
		// todo:
		if (_private.onCreateObject) {
			_private.onCreateObject(_private.root, object, createOptions);
		}

		// load children
		if (data.children) {
			data.children.forEach(index => {
				_private.loadObjectByIndex(index, object);
			});
		}
		_private.info.objects.push(object);
		_private.objects[index] = object;
		return object;
	}
	_private.loadPrefabByIndex = (index) => {
		let _private = that[__.private];

		let data = _private.objects[index];
		if (!data) {
			return;
		}

		let prefab = _private.root;
		if (data.tags) {
			if (_private.options.tags) {
				prefab.tags = _private.options.tags;
			}
			else if (prefab.tags) {
				prefab.tags = new Set([...prefab.tags, ...data.tags]);
			}
			else {
				prefab.tags = data.tags;
			}
		}
		if (data.id) {
			if (_private.options.id) {
				prefab.id = _private.options.id;
			}
			else {
				prefab.id = data.id;
			}
		}
		if (data.name) {
			if (_private.options.name) {
				prefab.name = _private.options.name;
			}
			else {
				prefab.name = data.name;
			}
		}
		if (data.extras) {
			let extras = _private.options.extras || _private.options.external;
			if (extras) {
				prefab.external = extras;
			}
			else {
				prefab.external = Object.assign(prefab.external, data.extras);
			}
		}

		let hasComponent = (name) => {
			for (let i = 0; i < _private.options.components.length; i++) {
				const element = _private.options.components[i];
				if (element.name === name) {
					return false;
				}
			}

			return true;
		}
		// Start to load components of prefab
		let components = data.components;
		if (components) {
			if (_private.options.components) {
				components = components.filter(component =>
					hasComponent(component.name)
				);
			}

			_private.loadComponents(prefab, components);
		}

		// Load body resource
		let url = data.res ? data.res.uri : null;
		if (url) {
			let resourceManager = Utils.getCurrentApp().resourceManager;
			resourceManager.loadModelAsync(url).then((ev) => {
				prefab.setBodyNode(ev.node);
				_private.updateProgress();
			}).catch((ev) => {
				_private.reject(ev);
			});
		}
		else {
			_private.updateProgress();
		}

		// load children
		if (data.children) {
			data.children.forEach(index => {
				let child = _private.loadObjectByIndex(index);
				prefab.add(child);
			})
		}

		// set visible
		if (_private.hidden) {
			prefab.visible = false;
		}

		_private.objects[index] = prefab;
	}
	_private.loadRootObjectByIndex = (index) => {
		let data = _private.objects[index];
		if (!data) {
			return;
		}

		let parent = _private.root;
		if (data.parent) {
			let uuid = data.parent.uuid;
			let result = _private.root.query('[uuid==' + uuid + ']')[0];
			if (result) {
				parent = result;
			}
		}

		let object = _private.loadObjectByIndex(index, parent);
		_private.objects[index] = object;
	}
	_private.loadRootObjects = () => {
		if (!_private.rootObjectIndices) {
			return;
		}
		let isPrefab = _private.extensions && _private.extensions.isPrefab;
		let count = _private.rootObjectIndices.length;

		if (isPrefab && count == 1) {
			let index = _private.rootObjectIndices[0];
			_private.loadPrefabByIndex(index);
		}
		else {
			_private.rootObjectIndices.forEach(index => {
				_private.loadRootObjectByIndex(index);
			})
		}
	}
	_private.makeInstancedDrawing = () => {
		if (_private.extensions && !_private.extensions.instances) {
			return;
		}
		_private.extensions.instances.forEach(index => {
			_private.objects[index].makeInstancedDrawing(true);
		})
	}

	_private.mapFromArray = (data, arr, callback) => {
		let ret;
		if (Utils.isArray(data)) {
			ret = [];
			data.forEach(item => {
				ret[i] = callback(item, arr);
			})
		}
		else {
			ret = callback(data, arr);
		}
		return ret;
	}
	_private.loadRelationship = (relationship) => {
		let app = Utils.getCurrentApp();
		let data = app.queryRelationships({ uuid: relationship.uuid });
		if (data && data.length > 0) {
			return;
		}

		let callback = (index, arr) => {
			if (typeof index == 'number') {
				return arr[index];
			}
			let object = _private.root.query('[uuid==' + index.uuid + ']')[0];
			return object;
		}
		if (relationship.source) {
			relationship.source = _private.mapFromArray(relationship.source, _private.objects, callback);
		}
		if (relationship.target) {
			relationship.target = _private.mapFromArray(relationship.target, _private.objects, callback);
		}

		let item = new Relationship({
			uuid: relationship.uuid,
			type: relationship.type,
			source: relationship.source,
			target: relationship.target,
			name: relationship.name,
			queryDirection: relationship.queryDirection
		});
		return item;
	}
	_private.loadRelationships = () => {
		if (!_private.relationships || _private.relationships.length <= 0) {
			return;
		}
		_private.relationships.forEach((data, index) => {
			let relationship = _private.loadRelationship(data);
			_private.relationships[index] = relationship;
		})
		_private.updateProgress();
	}
	_private.loadBlueprints = () => {
		if (!_private.blueprints.length) {
			return;
		}
		_private.blueprints.forEach((data, index) => {
			_private.root.blueprint.load({ data: data }).then(blueprint => {
				_private.blueprints[index] = blueprint[0];
			});
			_private.updateProgress();
		});
		_private.root.blueprint.run();
	}
	_private.loadSelections = () => {
		_private.selections.forEach((data, index) => {
			let selector = null;
			if (data.condition) {
				selector = new Selector();
				selector.select(data.condition, _private.objects);
			}
			else if (data.objects) {
				data.objects.forEach((objIndex, index) => {
					data.objects[index] = _private.objects[objIndex];
				})
				selector = new Selector(data.objects);
			}

			selector.forEach(object => {
				if (data.hasOwnProperty('visible')) {
					object.setVisible(data.visible, true);
				}

				if (data.style) {
					object.style.copyFromProperties(data.style);
				}
			})
			_private.selections[index] = selector;
			_private.updateProgress();
		})
	}
	_private.loadPlugins = () => {
		let app = Utils.getCurrentApp();
		_private.plugins.forEach((data, index) => {
			let url = data.url;
			let params = data.params;
			app.loadPlugin(url, params).then(plugin => {
				_private.plugins[index] = plugin;
				_private.updateProgress();
			});
		})
	}
	_private.loadTexture = (object, key) => {
		if (!object || !object[key]) {
			return;
		}
		let index = object[key];
		let uri = _private.URIs[index];
		object[key] = _private.loadImage(uri);
	}
	_private.loadRenderSettings = () => {
		if (!_private.renderSettings || Utils.isEmptyObject(_private.renderSettings)) {
			return;
		}

		_private.loadTexture(_private.renderSettings, 'background');
		let environment = _private.renderSettings.environment;
		_private.loadTexture(environment, 'envMap');

		let app = Utils.getCurrentApp();
		app.renderSettings = _private.renderSettings;
	}
	// #endregion

	_private.load = (options) => {
		_private.reset();
		_private.processOptions(options);

		return new Promise((resolve, reject) => {
			_private.resolve = resolve;
			_private.reject = reject;
			_private.checkAndParse(options.data, 'extensions', _private.parseExtensions);
			_private.checkAndParse(options.data, 'viewpoint', _private.parseViewpoint);
			_private.checkAndParse(options.data, 'baseURL', _private.parseBaseURL);
			_private.checkAndParse(options.data, 'tags', _private.parseTags);
			_private.checkAndParse(options.data, 'URIs', _private.parseURIs);
			_private.checkAndParse(options.data, 'styles', _private.parseStyles);
			_private.checkAndParse(options.data, 'objects', _private.parseObjects);
			_private.checkAndParse(options.data, 'components', _private.parseComponents);
			_private.checkAndParse(options.data, 'relationships', _private.parseRelationships);
			_private.checkAndParse(options.data, 'blueprints', _private.parseBlueprints);
			_private.checkAndParse(options.data, 'selections', _private.parseSelections);
			_private.checkAndParse(options.data, 'plugins', _private.parsePlugins);
			_private.checkAndParse(options.data, 'files', _private.parseFiles);
			_private.checkAndParse(options.data, 'predownloads', _private.parsePredownloads);
			_private.checkAndParse(options.data, 'renderSettings', _private.parseRenderSettings);
			_private.checkAndParse(options.data, 'scripts', _private.parseScripts);
			_private.checkAndParse(options.data, 'resolvers', _private.parseResolvers);

			_private.processStyles();
			_private.loadScripts().then(() => {
				_private.loadViewpoint();
				_private.loadResolvers();
				_private.loadRootObjects();
				_private.makeInstancedDrawing();

				_private.checkAndLoad('relationships', _private.loadRelationships);
				_private.checkAndLoad('blueprints', _private.loadBlueprints);
				_private.checkAndLoad('selections', _private.loadSelections);
				_private.checkAndLoad('plugins', _private.loadPlugins);
				_private.checkAndLoad('files', _private.loadFiles);
				_private.checkAndLoad('predownloads', _private.loadPredownloads);
				_private.checkAndLoad('renderSettings', _private.loadRenderSettings);
			});
			// check complete; because the loader maybe do nothing
			_private.updateProgress(0);
		}).catch((ev) => {
			if (_private.onError) {
				_private.onError(ev);
				_private.reject(ev);
			}
		})
	}
	_private.reset = () => {
		_private.baseURL = '';
		_private.currentPath = '';
		_private.tags.length = 0;
		_private.URIs.length = 0;
		_private.rootObjectIndices.length = 0;
		_private.objects.length = 0;
		_private.styles.length = 0;
		_private.relationships.length = 0;
		_private.blueprints.length = 0;
		_private.selections.length = 0;
		_private.plugins.length = 0;
		_private.renderSettings = {};
		_private.extensions = {};
		_private.viewpoint = {};
		_private.options = {};
		_private.scripts.length = 0;
		_private.extractClasses.reset();
		_private.files.length = 0;
		_private.predownloads.length = 0;
		_private.resolvers.clear();
		_private.info.reset();
		_private.ignore = {};
		_private.progressParam.index = null;
		_private.progressParam.progress = 0;
	}
	_private.init = () => {
		_private.baseURL = '';
		_private.currentPath = '';
		_private.tags = [];
		_private.URIs = [];
		_private.rootObjectIndices = [];
		_private.objects = [];
		_private.styles = [];
		_private.relationships = [];
		_private.blueprints = [];
		_private.selections = [];
		_private.plugins = [];
		_private.renderSettings = {};
		_private.extensions = {};
		_private.viewpoint = {};
		_private.scripts = [];
		_private.extractClasses = {
			componentClasses: new Map(),
			bpnodeClasses: new Map(),
			objectClasses: new Map(),
			unknownClasses: new Map(),
			reset: function () {
				this.componentClasses.clear();
				this.bpnodeClasses.clear();
				this.objectClasses.clear();
				this.unknownClasses.clear();
			}
		};
		_private.files = [];
		_private.predownloads = [];
		_private.resolvers = new Map();
		_private.options = {};
		_private.ignore = {};
		_private.progressParam = {
			index: null,
			progress: 0
		};

		_private.info = {
			number: 0,
			totalNumber: 0,
			objects: [],
			images: [],
			progress: function () {
				if (this.number >= this.totalNumber || this.totalNumber <= 0) {
					return 1;
				}
				return this.number / this.totalNumber;
			},
			isComplete: function () {
				return this.number >= this.totalNumber;
			},
			reset: function () {
				this.number = 0;
				this.totalNumber = 0;
				this.objects.length = 0;
				this.images.length = 0;
			}
		};
		_private.toolkit = {
			tsfResolver: new TSFResolver({
				getURIs: function () {
					return _private.URIs;
				}
			}),
			pathResolver: new PathResolver({
				getOptions: function () {
					return _private.options;
				},
				// this is the url which will be attached by XXXX
				getBaseURL: function () {
					return _private.baseURL;
				},
				// this is the path which will be attached by ./ or ../
				getCurrentPath: function () {
					return _private.currentPath;
				}
			}),
			getResolver: function (type) {
				return _private.resolvers.get(type);
			},
			args: {
				isTemporary: true,
				isEditor: _private.isEditor
			},
			onGetObjectClassType: (component) => {
				return _private.getComponentClassType(component);
			},
			onGetComponentExternalData: _private.options['onGetComponentExternalData'],
			onError: _private.onError
		}
	}
	_private.init();
}

/**
 * @class SceneLoader
 * The scene object loader.
 * @memberof THING.EXTEND
 */
class SceneLoader {

	/**
	 * The loader of scene
	 */
	constructor() {
		_initPrivateMembers(this);
	}

	reset() {
		let _private = this[__.private];
		_private.reset();
	}

	load(options = {}) {
		let _private = this[__.private];
		_private.load(options);
	}

}

export { SceneLoader }