import { Empty, ImageSlotType, ImageFilterType, InheritType, ImageMappingType, SideType, ImageWrapType, EventType } from '../const';
import { MathUtils } from '../math/MathUtils';
import { Utils } from '../common/Utils';
import { Object3D } from '../objects/Object3D';
import { ParticleSystem } from '../objects/ParticleSystem';
import { EffectGroundObject } from '../objects/EffectGroundObject.js';
import { ImageTexture } from '../resources/ImageTexture';
import { Box } from '../objects/Box';
import { PixelLine } from '../objects/PixelLine';
import { BaseComponent } from '../components/BaseComponent';
// One thing that can be optimized is that these textures are not registered and saved when they are used
const _pixel = 128; // gradient texture size
const _emptyTex = Empty.Texture;
/**
* @class ThemeManager
* 效果模板管理器 - 用于管理、注册、应用和切换场景效果模板
* @memberof THING
* @extends THING.BaseComponent
* @public
* @example
* const manager = app.themeManager;
*/
class ThemeManager extends BaseComponent {
/**
* 效果模板管理器 - 用于管理、注册、应用和切换场景效果模板
*/
constructor() {
super();
this._themeMap = new Map(); // save theme json
this._gradTexMap = new Map(); // save grad tex
this._scrollMap = new Map(); // save scroll offset
this._scrollTexMap = new Map(); // save scrollTex
this._envTexMap = new Map(); // save env map tex
this._particleItemsMap = new Map(); // particle items
this._colorTexMap = new Map(); // color image tex
this._effectGroundObjects = new WeakMap();
this._particleObjects = new WeakMap();
}
onRemove() {
super.onRemove();
}
register(name, json, baseUrl) {
this._themeMap.set(name, { baseUrl: baseUrl, json }); // save theme json // baseUrl, json, strategy {each class} can improve performance
const { styles } = json;
const app = this.object;
if (styles) {
this._gradTexMap.set(name, generateGradientTextures(styles.class));
this._scrollMap.set(name, generateScroll(styles.class));
this._scrollTexMap.set(name, generateScrollTex(styles.class, baseUrl));
this._envTexMap.set(name, generateEnvTex(app, styles.environment, baseUrl));
this._colorTexMap.set(name, generateColorTex(styles.class));
}
updateScroll(app, this._scrollMap, name);
}
_clearMaps(name) {
const gradMap = this._gradTexMap.get(name);
if (gradMap) {
gradMap.forEach((value, key) => {
if (value) {
value.dispose();
}
})
gradMap.clear();
}
this._gradTexMap.delete(name);
const _scrollTex = this._scrollTexMap.get(name);
if (_scrollTex) {
_scrollTex.forEach((value, key) => {
if (value) {
value.dispose();
}
})
this._scrollTexMap.clear();
}
this._scrollTexMap.delete(name);
const envTex = this._envTexMap.get(name);
if (envTex) {
envTex.dispose();
}
this._envTexMap.delete(name);
// this._scrollMap
const scrollMap = this._scrollMap.get(name);
if (scrollMap) {
scrollMap.clear();
}
this._scrollMap.delete(name);
}
unregister(name) {
this._themeMap.delete(name);
this._particleItemsMap.delete(name);
stopUpdateScroll(this.object, name, this._scrollMap);
this._clearMaps(name);
}
getTheme(name) {
const theme = this._themeMap.get(name);
return theme ? theme.json : null;
}
loadParticles(name) {
if (!this._themeMap.has(name)) return;
const { baseUrl, json } = this._themeMap.get(name);
const particles = json.particles;
if (!particles) {
console.warn(name + " theme doesn't have particles!");
return;
}
if (particles.enable) {
const promises = [];
const particleItems = {};
particles.item.forEach((item) => {
let itemUrl;
if (item.url.endsWith("index.json")) {
itemUrl = baseUrl._appendPath(item.url);
}
else { // @compatible
itemUrl = baseUrl._appendPath(item.url)._appendPath('./index.json');
}
item.themePkg = baseUrl;
const promise = new Promise((resolve) => {
Utils.loadJSONFileAsync(itemUrl).then(data => {
if (!data.version || !data.version[0] == 2) {
data = convertParticle(data);
}
particleItems[item.themePkg + item.url] = data;
resolve();
})
})
promises.push(promise);
});
return Promise.all(promises).then(() => {
this._particleItemsMap.set(name, particleItems);
});
}
else {
return Promise.resolve();
}
}
/**
* 应用效果模版的全局设置
* @param {string} name 主题名称,必须是已注册的主题
* @returns {void}
* @example
* // 应用名为 'night' 的主题渲染设置
* app.themeManager.applyRenderSettings('night');
*
* // 保存当前渲染设置以便后续还原
* const config = app.renderSettings;
*
* // 应用主题渲染设置
* app.themeManager.applyRenderSettings('night');
*
* // 还原之前保存的渲染设置
* app.renderSettings = config;
* @public
*/
applyRenderSettings(name) {
if (!this._themeMap.has(name)) return;
const { baseUrl, json } = this._themeMap.get(name);
const renderSettings = json.renderSettings;
if (!renderSettings) {
console.warn(name + " theme doesn't have renderSettings!");
return;
}
const appRenderSettings = {};
const app = this.object;
app.query('[userData/themeLight]').forEach(light => {
app.scene[light.userData.themeLight] = null; // may cause bug, for mainLight?
light.destroy();
});
const background = renderSettings.background;
const environmentMap = renderSettings.environmentMap || renderSettings.environment; // @compatible
if (background) { // @compatible app.renderSettigs
if (background.type !== undefined) {
const backgroundType = background.type;
const backgroundValue = background.value;
let backgroundTex;
switch (backgroundType) {
case 'cube':
if (isLocalResource(backgroundValue[0])) {
let resourceURL = getEnvTexUrl(backgroundValue, baseUrl);
backgroundTex = app.loadCubeTexture(resourceURL); // 这个需不需在组件remove的时候释放资源?TODO
}
else {
backgroundTex = app.loadCubeTexture(backgroundValue);
}
appRenderSettings.background = backgroundTex;
break;
case 'image':
if (isLocalResource(backgroundValue)) {
let resourceURL = baseUrl._appendPath(backgroundValue);
backgroundTex = app.loadImageTexture(resourceURL);
}
else {
backgroundTex = app.loadImageTexture(backgroundValue);
}
appRenderSettings.background = backgroundTex;
break;
case 'color':
appRenderSettings.background = backgroundValue;
break;
case 'none':
appRenderSettings.background = null;
default:
break;
}
}
else {
appRenderSettings.background = background;
}
}
appRenderSettings.environment = {};
if (environmentMap) {
if (environmentMap.type !== undefined) { // @compatible app.renderSettings
const environmentMapType = environmentMap && environmentMap.type;
const environmentMapValue = environmentMap && environmentMap.value;
const environmentMapLightIntensity = environmentMap && environmentMap.diffuseIntensity;
// envDiffuse and envSpecular
const envSpecularIntensity = environmentMap.specularIntensity;
let envMapTex;
switch (environmentMapType) {
case 'cube':
if (isLocalResource(environmentMapValue[0])) {
let resourceURL = getEnvTexUrl(environmentMapValue, baseUrl);
envMapTex = app.loadCubeTexture(resourceURL); // 这个需不需在组件remove的时候释放资源?TODO
}
else {
envMapTex = app.loadCubeTexture(environmentMapValue);
}
appRenderSettings.environment.envMap = envMapTex;
break;
case 'image':
if (isLocalResource(environmentMapValue)) {
let resourceURL = baseUrl._appendPath(environmentMapValue);
envMapTex = app.loadImageTexture(resourceURL, { mappingType: ImageMappingType.EquirectangularReflection });
}
else {
envMapTex = app.loadImageTexture(environmentMapValue, { mappingType: ImageMappingType.EquirectangularReflection });
}
appRenderSettings.environment.envMap = envMapTex;
break;
case 'none':
appRenderSettings.environment.envMap = null;
break;
default:
break;
}
if (environmentMapLightIntensity !== undefined) {
appRenderSettings.environment.diffuseIntensity = environmentMapLightIntensity;
}
if (Utils.isValid(envSpecularIntensity)) {
appRenderSettings.environment.specularIntensity = envSpecularIntensity;
}
}
else {
appRenderSettings.environment.envMap = environmentMap.envMap; // maybe null
appRenderSettings.environment.diffuseIntensity = environmentMap.diffuseIntensity;
}
}
// set fog config
const fogConfig = renderSettings.fog;
if (fogConfig) {
appRenderSettings.fog = fogConfig;
}
// set lights
const lightConfig = renderSettings.lights || renderSettings.light; // @compatible
appRenderSettings.lights = lightConfig;
// compatible some renderSettings has this lights
const pointLightConfigs = lightConfig.pointLights;
if (pointLightConfigs) {
pointLightConfigs.forEach((pointLightConfig, index) => {
let pointLight;
const result = app.query('.PointLight')[index];
if (!!result) {
pointLight = result;
}
else {
pointLight = new THING.PointLight();
// editor's light
pointLight.userData = pointLight.userData || {};
pointLight.userData['themeLight'] = 'pointLight';
}
for (let key in pointLightConfig) {
pointLight[key] = pointLightConfig[key];
}
})
}
const spotLightConfigs = lightConfig.spotLights; // maybe not have
if (spotLightConfigs) {
spotLightConfigs.forEach((spotLightConfig, index) => {
let spotLight;
const result = app.query('.SpotLight')[index];
if (!!result) {
spotLight = result;
}
else {
spotLight = new THING.SpotLight();
// editor's light
spotLight.userData = spotLight.userData || {};
spotLight.userData['themeLight'] = 'spotLight'
}
for (let key in spotLightConfig) {
spotLight[key] = spotLightConfig[key];
}
})
}
appRenderSettings.postEffects = renderSettings.postEffects;
appRenderSettings.cameraEffects = renderSettings.effects || renderSettings.cameraEffect || renderSettings.cameraEffects; // @compatible
app.renderSettings = appRenderSettings;
}
_getTag(styleClass, node) {
if (!styleClass) {
return '';
}
let tag;
const userData = node.userData;
const internalData = node.internalData;
const type = node.type;
if (internalData && internalData._styleTag_ && styleClass[internalData._styleTag_]) {
tag = internalData._styleTag_;
}
else if (userData && userData._styleTag_ && styleClass[userData._styleTag_]) {
tag = userData._styleTag_;
}
else if (styleClass[type]) {
tag = type;
}
else {
tag = Utils.getClassNamesFromProto(this.object).find(name => !!styleClass[name]);
}
return tag;
}
_applyStyle(node, config, name, tag) {
const compatible = this.object.view._compatibleRender;
beginDefaultValues(node);
setStyleColorMapping(node, config, name, tag, this._gradTexMap);
setStyleColorImage(node, config, name, tag, this._colorTexMap); // @Compatible colorImage
setStyleColor(node, config);
setStyleRemoveDiffuseMap(node, config);
setStyleRemoveNormalMap(node, config);
setStyleRemoveAoMap(node, config);
setStyleOpacity(node, config);
setStyleWireFrame(node, config);
setStyleEnvMap(node, config, name, this._envTexMap, compatible);
setStyleEdge(node, config);
setStyleEffect(node, config);
setStyleReflection(node, config);
setStyleScrollTex(node, config, name, tag, this._scrollMap, this._scrollTexMap); // TODO No switch off
setStyleSpecularFactor(node, config);
setStyleFresnel(node, config);
setStyleEmissive(node, config);
setStyleLightingGroup(node, config);
endDefaultValues(node);
if (node.body.style.isInstancedDrawing || Utils.isValid(node.resource.instanceId)) {
node.makeInstancedDrawing(true);
}
}
/**
* 应用效果模版样式 - 将效果模版样式应用到指定的根节点及其子节点
*
* 【功能说明】
* 这是应用效果模板的核心方法,将主题中定义的样式配置应用到场景对象上。
* 样式包括颜色、透明度、线框模式、环境光贴图、边缘效果、反射效果、
* 滚动纹理、高光因子、菲涅尔效果、自发光等多种视觉效果。
*
* 【样式匹配规则】
* 1. 优先匹配 internalData._styleTag_ 指定的样式类
* 2. 其次匹配 userData._styleTag_ 指定的样式类
* 3. 再次匹配对象类型(node.type)
* 4. 最后匹配对象的原型类名
*
* 【使用时机】
* 在应用渲染设置后调用,通常在场景加载完成或切换效果模版时调用。
*
* 【参数说明】
* @param {string} name - 效果模板名称,必须是已注册的效果模板
* @param {Object3D} rootNode - 要应用效果模板的根节点对象
* @param {boolean} [traverse=true] - 是否遍历子节点并应用效果模板,默认为true
* @returns {void}
* @example
* // 应用名为 'dark' 的主题到场景中的所有对象
* app.themeManager.applyStyles('dark', app.root);
*
* // 仅应用主题到指定对象,不遍历子节点
* app.themeManager.applyStyles('dark', myEntity, false);
* @public
*/
applyStyles(name, rootNode, traverse = true) {
if (rootNode.userData['_skipTheme_']) {
return;
}
if (rootNode.isLight || rootNode.isCamera) {
return;
}
if (!this._themeMap.has(name)) return;
const { baseUrl, json } = this._themeMap.get(name);
const styles = json.styles;
if (!styles) {
console.warn(name + " theme doesn't have styles!");
return;
}
this._applyStylesInternal(name, rootNode, styles, traverse);
}
_applyStylesInternal(name, rootNode, styles, traverse) {
if (rootNode.userData['_skipTheme_']) {
return;
}
if (rootNode.isLight || rootNode.isCamera) {
return;
}
rootNode.internalData._themeTag = name;
if (traverse) {
rootNode.children.forEach(child => {
this._applyStylesInternal(name, child, styles, traverse);
});
}
const tag = this._getTag(styles.class, rootNode); // optimize: cache node => tag
if (!tag) return;
const config = styles.class[tag];
// enable is false will clear current style(theme style/default style)
if (config.enable == false) {
_initStyle(rootNode);
return;
}
// _themeTag will add on root and node which applied styles
this._applyStyle(rootNode, config, name, tag);
}
/**
* 清除效果模版样式 - 清除指定节点及其子节点的效果模版样式
*
* 【功能说明】
* 移除对象上应用的效果模版样式,恢复到默认状态。
* 通常在切换效果模版或不再需要效果样式时调用。
*
* 清除整个场景的主题样式
* app.themeManager.clearStyles(app.root);
*
* 【相关方法】
* - applyStyles: 应用效果模版样式
*
* 【关键词】
* 清除样式、clearStyles、移除样式、清除效果模版样式、应用效果模板
*
*
* 【参数说明】
* @param {Object3D} rootNode - 要清除样式的根节点对象
* @returns {void}
* @example
* // 清除指定对象及其子对象的效果模版样式
* app.themeManager.clearStyles(myEntity);
* @public
*/
clearStyles(rootNode) {
_initStyle(rootNode);
rootNode.children.forEach(child => {
this.clearStyles(child);
})
}
/**
* 创建主题粒子特效 - 根据主题配置创建粒子系统
*
* 【功能说明】
* 根据主题中定义的粒子配置创建粒子系统特效,如雨雪天气、烟雾等。
* 创建前必须先调用 loadParticles 加载粒子数据。
*
* 【使用时机】
* 在应用渲染设置和样式后调用,通常在主题初始化的最后阶段。
*
* 【参数说明】
* @param {string} name - 主题名称,必须是已注册的主题
* @param {Object3D} rootNode - 粒子系统的父节点
* @param {Function} [onCreateThemeObjects] - 创建主题对象时的回调函数,可用于修改粒子参数
* @returns {Array<ParticleSystem>|undefined} 返回创建的粒子系统数组,如果主题不存在或未启用粒子则返回undefined
* @example
* // 创建主题粒子特效
* const particles = app.themeManager.createEffectParticles('snow', campus);
*
* // 销毁创建的粒子特效
* if (particles) {
* particles.forEach(particle => {
* particle.destroy();
* });
* }
* @public
*/
createEffectParticles(name, rootNode, onCreateThemeObjects) {
if (!this._themeMap.has(name)) return;
const { baseUrl, json } = this._themeMap.get(name);
const particles = json.particles;
if (!particles) {
console.warn(name + " theme doesn't have particles!");
return;
}
if (particles.enable) {
const particleItems = this._particleItemsMap.get(name);
if (!particleItems) return;
const particleParents = [];
particles.item.forEach((item) => {
const data = particleItems[item.themePkg + item.url];
const particleParent = _createParticle(item, data, rootNode, onCreateThemeObjects);
particleParent.userData['_themeParticle_'] = true;
particleParents.push(particleParent);
});
return particleParents;
}
}
/**
* 创建主题地面特效 - 根据主题配置创建地面特效对象
*
* 【功能说明】
* 根据主题中定义的地面特效配置创建地面特效,如水面反射、地面流动纹理等。
* 地面特效可以包含水面反射效果,支持动态更新和反射因子配置。
*
* 【使用时机】
* 在应用渲染设置和样式后调用,通常在主题初始化阶段。
*
* 【参数说明】
* @param {string} name - 主题名称,必须是已注册的主题
* @param {Object3D} rootNode - 地面特效的父节点
* @param {Function} [onCreateThemeObjects] - 创建主题对象时的回调函数,可用于修改地面特效参数
* @returns {Array<EffectGroundObject>|undefined} 返回创建的地面特效对象数组,如果主题不存在或未启用地面特效则返回undefined
* @example
* // 创建主题地面特效
* const grounds = app.themeManager.createEffectGrounds('water', campus);
*
* // 销毁创建的地面特效
* if (grounds) {
* grounds.forEach(ground => {
* ground.destroy();
* });
* }
* @public
*/
createEffectGrounds(name, rootNode, onCreateThemeObjects) {
if (!this._themeMap.has(name)) return;
const { baseUrl, json } = this._themeMap.get(name);
const effectGrounds = json.effectGrounds;
if (!effectGrounds) {
console.warn(name + " theme doesn't have effectGrounds!");
return;
}
if (effectGrounds.enable) {
const grounds = [];
effectGrounds.item.forEach(item => {
const curParam = JSON.parse(JSON.stringify(item)); // Need to keep the original data
curParam.url = baseUrl._appendPath(curParam.url);
if (curParam.maskUrl) curParam.maskUrl = baseUrl._appendPath(curParam.maskUrl);
if (curParam.alphaMap) curParam.alphaMap = baseUrl._appendPath(curParam.alphaMap);
onCreateThemeObjects && onCreateThemeObjects(curParam);
Object.assign(curParam, {
parent: rootNode
});
const ground = new EffectGroundObject(curParam);
ground.userData['_themeGround_'] = true;
ground.renderOrder = -10;
grounds.push(ground);
});
if (effectGrounds.groundReflect) {
const reflectParams = {
groundClearance: 0.3,
groundReflect: true,
parent: rootNode,
reflectFactor: effectGrounds.reflectFactor
}
onCreateThemeObjects && onCreateThemeObjects(reflectParams);
const ground = new EffectGroundObject(reflectParams);
// ground.userData['_themeGround_'] = true;
ground.userData['_themeReflectGround_'] = true;
ground.renderOrder = 5;
grounds.push(ground);
}
return grounds;
}
}
createEffectFacades(name, rootNode, onCreateThemeObjects) {
if (!this._themeMap.has(name)) return;
const { json } = this._themeMap.get(name);
const effectFacades = json.effectFacades;
if (!effectFacades) {
console.warn(name + " theme doesn't have effectFacades!");
return;
}
if (effectFacades.enable) {
const facades = [];
const facade = rootNode.queryByTags('Facade')[0];
if (facade) {
rootNode = facade;
}
effectFacades.item.forEach(item => {
const effectFacade = _createEffectFacade(item, rootNode, onCreateThemeObjects);
facades.push(...effectFacade);
});
return facades;
}
}
// 1.0 bundle name, this will add inner and outer
applyLevelThemeChange(name, root) {
// whether remove level events before
if (!root || root.type !== 'Campus') {
return;
}
const outerThemeName = name + '_' + 'outer';
const innerThemeName = name + '_' + 'inner';
// must have theme
if (!this._themeMap.has(outerThemeName) || !this._themeMap.has(innerThemeName)) {
return;
}
this.initEffectObjectsCache(root);
const innerGrounds = this._effectGroundObjects.get(root).inner;
const outerGrounds = this._effectGroundObjects.get(root).outer;
const particlesObject = this._particleObjects.get(root);
root.on(EventType.BeforeLeaveLevel, '.Building', (ev) => {
if (!!ev.next && ev.next.type == 'Campus') {
this.applyRenderSettings(outerThemeName);
if (outerGrounds.length <= 0) {
const outerEffectGrounds = this.createEffectGrounds(outerThemeName, ev.next);
outerEffectGrounds && outerGrounds.push(...outerEffectGrounds);
}
else {
outerGrounds.forEach(ground => {
if (!ground.destroyed) { // currently fixed
ground.updateGround(ev.next);
}
});
}
if (particlesObject.length <= 0) {
const outerEffectParticles = this.createEffectParticles(outerThemeName, ev.next);
outerEffectParticles && particlesObject.push(...outerEffectParticles);
}
else {
particlesObject.forEach(particle => {
particle.destroy();
});
particlesObject.push(...this.createEffectParticles(outerThemeName, ev.next));
}
}
}, root.uuid + 'Theme1.x' + 'BuildingToCampus');
root.on(EventType.BeforeEnterLevel, '.Building', (ev) => {
if (ev.prev.type == 'Campus') {
this.applyRenderSettings(innerThemeName);
if (innerGrounds.length <= 0) {
const innerEffectGrounds = this.createEffectGrounds(innerThemeName, ev.object);
innerEffectGrounds && innerGrounds.push(...innerEffectGrounds);
}
else {
innerGrounds.forEach(ground => {
if (!ground.destroyed) { // currently fixed
ground.updateGround(ev.object);
}
});
}
}
}, root.uuid + 'Theme1.x' + 'CampusToBuilding');
root.on(EventType.BeforeEnterLevel, '.Floor', (ev) => {
if (ev.prev.type == 'Building') {
if (innerGrounds.length <= 0) {
const innerEffectGrounds = this.createEffectGrounds(innerThemeName, ev.object);
innerEffectGrounds && innerGrounds.push(...innerEffectGrounds);
}
else {
innerGrounds.forEach(ground => {
if (!ground.destroyed) { // currently fixed
ground.updateGround(ev.object);
}
});
}
}
}, root.uuid + 'Theme1.x' + 'BuildingToFloor');
root.on(EventType.BeforeLeaveLevel, '.Floor', (ev) => {
if (!!ev.next && ev.next.type == 'Building') {
if (innerGrounds.length <= 0) {
const innerEffectGrounds = this.createEffectGrounds(innerThemeName, ev.object);
innerEffectGrounds && innerGrounds.push(...innerEffectGrounds);
}
else {
innerGrounds.forEach(ground => {
if (!ground.destroyed) { // currently fixed
ground.updateGround(ev.next);
}
});
}
}
}, root.uuid + 'Theme1.x' + +'FloorToBuilding');
}
initEffectObjectsCache(root) {
if (!this._effectGroundObjects.has(root)) {
const effectGroundObjects = {
inner: [],
outer: []
}
this._effectGroundObjects.set(root, effectGroundObjects);
root.on(EventType.AfterDestroy, () => {
const effectGroundObjects = this._effectGroundObjects.get(root);
effectGroundObjects.inner.forEach((ground) => {
ground.destroy()
});
effectGroundObjects.inner.length = 0;
effectGroundObjects.outer.forEach((ground) => {
ground.destroy()
});
effectGroundObjects.outer.length = 0;
this._effectGroundObjects.delete(root);
// off events
root.off(EventType.AfterDestroy, root.uuid + 'destroyThemeGrounds');
}, root.uuid + 'destroyThemeGrounds');
}
if (!this._particleObjects.has(root)) {
const particleObjects = [];
this._particleObjects.set(root, particleObjects);
root.on(EventType.AfterDestroy, () => {
const particleObjects = this._particleObjects.get(root);
particleObjects.forEach((particle) => {
particle.destroy();
});
particleObjects.length = 0;
this._particleObjects.delete(root);
// off events
root.off(EventType.AfterDestroy, root.uuid + 'destroyThemeParticles');
}, root.uuid + 'destroyThemeParticles');
}
}
exportRenderSettings() {
const app = this.object;
const renderSettings = {
'background': {
'type': 'export',
'value': app.background
},
'environment': {
'type': 'export',
'value': app.envMap,
'diffuseIntensity': app.scene.envMapLightIntensity
},
};
const lights = {
}
renderSettings.lights = lights;
if (app.scene.mainLight) {
const mainLight = app.scene.mainLight;
lights.mainLight = {
"enableShadow": mainLight.castShadow,
"intensity": mainLight.intensity,
"color": mainLight.color,
"shadowBias": mainLight.shadowBias,
"helperVisible": mainLight.helper.visible,
"shadowQuality": mainLight.shadowQuality,
"adapter": {
"enable": mainLight.adapter.enable,
"horzAngle": mainLight.adapter.horzAngle,
"vertAngle": mainLight.adapter.vertAngle
}
}
}
if (app.scene.ambientLight) {
const ambientLight = app.scene.ambientLight;
lights.ambientLight = {
'intensity': ambientLight.intensity,
'color': ambientLight.color
}
}
if (app.scene.hemisphereLight) {
const hemisphereLight = app.scene.hemisphereLight;
lights.hemisphereLight = {
'intensity': hemisphereLight.intensity,
'color': hemisphereLight.color,
'groundColor': hemisphereLight.groundColor
}
}
if (app.scene.secondaryLight) {
const secondaryLight = app.scene.secondaryLight;
lights.secondaryLight = {
"enableShadow": secondaryLight.castShadow,
"intensity": secondaryLight.intensity,
"color": secondaryLight.color,
"shadowBias": secondaryLight.shadowBias,
"helperVisible": secondaryLight.helper.visible,
"shadowQuality": secondaryLight.shadowQuality,
"adapter": {
"enable": secondaryLight.adapter.enable,
"horzAngle": secondaryLight.adapter.horzAngle,
"vertAngle": secondaryLight.adapter.vertAngle
}
}
}
renderSettings.postEffects = Utils.cloneObject(app.camera.postEffect.config, false);
renderSettings.effects = Utils.cloneObject(app.camera.effect.config, false);
return renderSettings;
}
_setStylesConfig(name, strategy, styleConfig, tag) {
if (strategy.useColorMap !== undefined) {
if (strategy.useColorMap) { // colorMapping
const colorMapTex = this._gradTexMap.get(name).get(tag);
styleConfig.colorMapping = colorMapTex;
}
}
else {
if (strategy.colorMapping && strategy.colorMapping.enable) { // colorMapping
const colorMapTex = this._gradTexMap.get(name).get(tag);
styleConfig.colorMapping = colorMapTex;
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['ColorMappingIntensity'] = strategy.colorMapping.intensity;
}
}
if (strategy.color.enable == true) { // color
styleConfig.color = strategy.color.value;
}
if (strategy.removeDiffuseMap) {
styleConfig.map = _emptyTex;
}
if (strategy.removeNormalMap) {
styleConfig.normalMap = _emptyTex;
}
if (strategy.removeAoMap) {
styleConfig.aoMap = _emptyTex;
}
if (strategy.transparent) {
styleConfig.transparent = strategy.transparent;
}
if (strategy.opacity !== undefined) {
styleConfig.opacity = strategy.opacity;
if (styleConfig.opacity < 1) { // if need transparent
styleConfig.transparent = true;
}
}
if (strategy.wireframe) {
styleConfig.wireframe = strategy.wireframe;
}
if (strategy.useEnvMapType) {
switch (strategy.useEnvMapType) {
case 'global':
styleConfig.envMap = null;
styleConfig.envMapping = true;
break;
case 'styleFiction':
styleConfig.envMapping = true;
styleConfig.envMap = this._envTexMap.get(name);
break;
case 'none':
styleConfig.envMap = null;
styleConfig.envMapping = false;
break;
default:
break;
}
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['EnvMapIntensity'] = Utils.parseNumber(strategy.envMapIntensity, 1);
}
if (strategy.edge.enable) {
styleConfig.edge = styleConfig.edge || {};
styleConfig.edge.enable = strategy.edge.enable;
styleConfig.edge.color = strategy.edge.color;
styleConfig.edge.glow = strategy.edge.glow;
styleConfig.edge.opacity = strategy.edge.opacity;
}
if (strategy.effect) {
styleConfig.effect = styleConfig.effect || {};
styleConfig.effect.glow = strategy.effect.glow || null;
styleConfig.effect.innerGlow = strategy.effect.innerGlow || null;
styleConfig.effect.lineBloom = strategy.effect.lineBloom || null;
styleConfig.effect.tailing = strategy.effect.tailing || null;
styleConfig.effect.radial = strategy.effect.radial || null;
styleConfig.effect.ghosting = strategy.effect.ghosting || null;
}
if (strategy.reflection.enable) {
styleConfig.metalness = strategy.reflection.metalness;
styleConfig.roughness = strategy.reflection.roughness;
}
if (strategy.useScrollTex !== undefined) { // @ compatible
if (strategy.useScrollTex) { // @ compatible
// const { texture, color } = strategy.scroll;
const texture = strategy.scrollTex;
const color = strategy.scrollColor;
const scrollTex = this._scrollTexMap.get(name).get(texture);
styleConfig.imageSlotType = ImageSlotType.EmissiveMap;
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['VerticalEmissive'] = true;
styleConfig.emissiveMap = scrollTex;
styleConfig.emissive = color;
}
}
else {
if (strategy.scroll && strategy.scroll.enable) {
const { texture, color } = strategy.scroll;
const scrollTex = this._scrollTexMap.get(name).get(texture);
styleConfig.imageSlotType = ImageSlotType.EmissiveMap;
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['VerticalEmissive'] = true;
styleConfig.emissiveMap = scrollTex;
styleConfig.emissive = color;
}
}
if (strategy.specularFactor !== undefined) {
const specularFactor = strategy.specularFactor;
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['SpecularFactor'] = specularFactor;
}
if (strategy.fresnel) {
const fresnelPower = strategy.fresnel.power;
const fresnelInverse = strategy.fresnel.inverse;
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['FresnelPower'] = fresnelPower;
styleConfig.attributes['FresnelInverse'] = fresnelInverse;
}
if (strategy.emissive && strategy.emissive.enable) {
styleConfig.emissive = strategy.emissive.color;
}
if (strategy.lightGroupMask) {
styleConfig.attributes = styleConfig.attributes || {};
styleConfig.attributes['LightingGroup'] = strategy.lightGroupMask;
}
}
_updateScrollMapUv(name, styleConfig, object, tag) { // @ compatible
if (styleConfig.useScrollTex !== undefined) { // @ compatible
if (!styleConfig.useScrollTex) return false;
object.on('update', () => {
if (this._scrollMap.has(name)) {
const scrollMap = this._scrollMap.get(name);
if (scrollMap.has(tag)) {
object.style.uv.offset = scrollMap.get(tag).arr;
}
}
}, 'updateScrollTexUv')
return true;
}
else {
if (!styleConfig.scroll.enable) return false;
object.on('update', () => {
if (this._scrollMap.has(name)) {
const scrollMap = this._scrollMap.get(name);
if (scrollMap.has(tag)) {
object.style.uv.offset = scrollMap.get(tag).arr;
}
}
}, 'updateScrollTexUv')
return true;
}
}
}
export { ThemeManager };
function _createParticle(config, data, rootNode, onCreateThemeObjects) {
const bbx = rootNode.getOBB(true, false);
const radius = bbx.halfSize[1] + 0.2;
const rDis = MathUtils.scaleVector(rootNode.up, radius);
const pos = MathUtils.subVector(bbx.center, rDis);
const getParticleData = (source, style) => {
for (let key in style) {
if (key !== 'maxParticleCount' && key !== 'position' && key !== 'particleCount') {
if (source[key] instanceof Array) {
if (source[key][0] instanceof Object || source[key][0] instanceof Array) {
getParticleData(source[key], style[key]);
}
else if (source[key].length > 0) {
source[key] = style[key];
}
}
else if (source[key] instanceof Object) {
getParticleData(source[key], style[key]);
}
else {
if (style[key] !== '') {
source[key] = style[key];
}
}
}
}
return source;
}
data.groups = getParticleData(data.groups, config.content.groups);
for (let i = 0; i < data.groups.length; i++) {
if (config.content.groups[i].texture.url === '') {
if (!(data.groups[i].texture.url.startsWith('http://') || data.groups[i].texture.url.startsWith('https://'))) {
if (config.url.endsWith("index.json")) {
config.url = config.url.replace('/index.json', '');
}
data.groups[i].texture.url = Utils.resolveURL(config.themePkg._appendPath(config.url._appendPath(data.groups[i].texture.url)));
}
}
else {
data.groups[i].texture.url = Utils.resolveURL(config.themePkg._appendPath(config.content.groups[i].texture.url));
}
let maxCount = [];
let count = [];
for (let j = 0; j < data.groups[i].emitters.length; j++) {
const dividend = data.groups[i].emitters[j].position.spread;
maxCount[j] = MathUtils.ceil(MathUtils.min(
10000,
((
config.content.density *
data.groups[i].maxParticleCount *
bbx.size[0] *
bbx.size[2]) /
dividend[0] /
dividend[2]) *
4
));
count[j] = MathUtils.ceil(
(data.groups[i].emitters[j].particleCount /
data.groups[i].maxParticleCount) *
maxCount[j]
);
data.groups[i].emitters[j].position.spread = [
bbx.size[0] * 2,
config.content.height,
bbx.size[2] * 2,
];
}
data.groups[i].maxParticleCount = MathUtils.max(...maxCount);
const maxParticleCount = data.groups[i].maxParticleCount;
let start = 0;
for (let k = 0; k < count.length; k++) {
let end = MathUtils.min(start + count[k], maxParticleCount);
data.groups[i].emitters[k].particleCount = end - start;
start = end;
}
}
const pBoxParams = {
id: `ParticleDecorationModelParent_${config.code}`,
parent: rootNode,
name: `ParticleDecorationModelParent_${config.code}`,
position: pos,
visible: true,
}
onCreateThemeObjects && onCreateThemeObjects(pBoxParams);
const pBox = new Object3D(pBoxParams);
const particleParams = {
id: `ParticleDecorationModel_${config.code}`,
type: 'ParticleSystem',
name: `ParticleDecorationModel_${config.code}`,
data: data,
parent: pBox,
localPosition: [0, config.content.offsetHeight + config.content.height / 2, 0],
angle: 0,
visible: true,
}
onCreateThemeObjects && onCreateThemeObjects(particleParams);
const particle = new ParticleSystem(particleParams);
particle.bounding.inheritType = InheritType.Jump;
particle.userData.cfg = particle.userData.cfg || {};
particle.userData.cfg.offsetHeight = config.content.offsetHeight + config.content.height / 2;
return pBox;
}
function beginDefaultValues(node) {
node.body.style.begin();
node.body.style.beginDefaultValues();
}
function endDefaultValues(node) {
node.body.style.endDefaultValues();
node.body.style.end();
}
function setStyleColorMapping(node, styleConfig, name, tag, _gradTexMap) {
if (styleConfig.useColorMap !== undefined) {
if (styleConfig.useColorMap) { // colorMapping
const colorMapTex = _gradTexMap.get(name).get(tag);
node.body.style.colorMapping = colorMapTex;
}
}
else {
if (styleConfig.colorMapping && styleConfig.colorMapping.enable) {
const colorMapTex = _gradTexMap.get(name).get(tag);
node.body.style.colorMapping = colorMapTex;
node.body.style.setAttribute('ColorMappingIntensity', styleConfig.colorMapping.intensity);
}
else {
node.body.style.colorMapping = null;
}
}
}
function setStyleColorImage(node, styleConfig, name, tag, _colorTexMap) {
if (styleConfig.colorImage && styleConfig.colorImage.enable) {
const colorImageTex = _colorTexMap.get(name).get(tag);
node.body.style.map = colorImageTex;
}
else {
node.body.style.map = null;
}
}
function setStyleColor(node, styleConfig) {
if (styleConfig.color.enable !== true) {
node.body.style.color = null;
return;
}
node.body.style.color = styleConfig.color.value;
}
function setStyleRemoveDiffuseMap(node, styleConfig) {
if (!styleConfig.removeDiffuseMap) {
node.body.style.map = null;
return;
}
node.body.style.map = _emptyTex;
}
function setStyleRemoveAoMap(node, styleConfig) {
if (!styleConfig.removeAoMap) {
node.body.style.aoMap = null;
return;
}
node.body.style.aoMap = _emptyTex;
}
function setStyleRemoveNormalMap(node, styleConfig) {
if (!styleConfig.removeNormalMap) {
node.body.style.normalMap = null;
return;
}
node.body.style.normalMap = _emptyTex;
}
function setStyleOpacity(node, styleConfig) {
node.body.style.transparent = Utils.parseBoolean(styleConfig.transparent, null);
// currently fix use number
// body.style.opacity = function(baseValue) {
// return baseValue * styleConfig.opacity;
// }
if (styleConfig.opacity < 1) { // if need transparent
node.body.style.transparent = true;
}
node.body.style.opacity = Utils.parseNumber(styleConfig.opacity, null);
}
function setStyleWireFrame(node, styleConfig) {
node.body.style.wireframe = Utils.parseBoolean(styleConfig.wireframe, null);
}
function setStyleEnvMap(node, styleConfig, name, _envTexMap, compatible) {
node.body.style.setAttribute('EnvMapIntensity', Utils.parseNumber(styleConfig.envMapIntensity, 1));
switch (styleConfig.useEnvMapType) {
case 'global':
node.body.style.envMap = null;
node.body.style.envMapping = true;
break;
case 'styleFiction':
const envMap = _envTexMap.get(name);
node.body.style.envMapping = true;
node.body.style.envMap = envMap;
if (compatible) {
node.body.style.setAttribute('EnvMapIntensity', 1.5);
}
break;
case 'none':
node.body.style.envMap = null;
node.body.style.envMapping = false;
break;
default:
node.body.style.envMap = null;
node.body.style.envMapping = null;
break;
}
}
function setStyleEdge(node, styleConfig) {
if (!styleConfig.edge.enable) {
node.body.style.edge.enable = false;
node.body.style.edge.color = [1, 1, 1];
node.body.style.edge.glow = false;
node.body.style.edge.opacity = 1;
return;
}
node.body.style.edge.enable = styleConfig.edge.enable;
node.body.style.edge.color = styleConfig.edge.color;
node.body.style.edge.glow = styleConfig.edge.glow;
node.body.style.edge.opacity = styleConfig.edge.opacity;
}
function setStyleEffect(node, styleConfig) {
if (!styleConfig.effect) {
node.body.style.effect.glow = null;
node.body.style.effect.innerGlow = null;
node.body.style.effect.lineBloom = null;
node.body.style.effect.tailing = null;
node.body.style.effect.radial = null;
node.body.style.effect.ghosting = null;
return;
}
node.body.style.effect.glow = styleConfig.effect.glow !== undefined ? styleConfig.effect.glow : null;
node.body.style.effect.innerGlow = styleConfig.effect.innerGlow !== undefined ? styleConfig.effect.innerGlow : null;
node.body.style.effect.lineBloom = styleConfig.effect.lineBloom !== undefined ? styleConfig.effect.lineBloom : null;
node.body.style.effect.tailing = styleConfig.effect.tailing !== undefined ? styleConfig.effect.tailing : null;
node.body.style.effect.radial = styleConfig.effect.radial !== undefined ? styleConfig.effect.radial : null;
node.body.style.effect.ghosting = styleConfig.effect.ghosting !== undefined ? styleConfig.effect.ghosting : null;
}
function setStyleReflection(node, styleConfig) {
if (!styleConfig.reflection.enable) {
node.body.style.metalness = null;
node.body.style.roughness = null;
return;
}
// const metalness = styleConfig.reflection.metalness;
// const roughness = styleConfig.reflection.roughness;
// const useType = styleConfig.reflection.useType; // use for call back, now can't
node.body.style.metalness = styleConfig.reflection.metalness;
node.body.style.roughness = styleConfig.reflection.roughness;
}
function setStyleScrollTex(node, styleConfig, name, tag, _scrollMap, _scrollTexMap) {
if (styleConfig.useScrollTex !== undefined) {
if (!styleConfig.useScrollTex) {
if (styleConfig.colorImage && styleConfig.colorImage.enable) {
node.body.style.emissive = [0, 0, 0];
}
node.off('update', 'updateScrollTexUv');
// scroll
node.body.style.emissiveMap = null;
node.body.style.emissive = null;
node.body.style.setAttribute('VerticalEmissive', false);
node.body.style.imageSlotType = THING.ImageSlotType.EmissiveMap; // fix will change diffuseMap uv
node.body.style.uv.offset = [0, 0];
return
}
const texture = styleConfig.scrollTex;
const color = styleConfig.scrollColor;
const scrollTex = _scrollTexMap.get(name).get(texture);
node.body.style.imageSlotType = ImageSlotType.EmissiveMap;
node.body.style.setAttribute('VerticalEmissive', true);
node.body.style.emissiveMap = scrollTex;
node.body.style.emissive = changeColorArray(color);
node.on('update', () => {
if (_scrollMap.has(name)) {
const scrollMap = _scrollMap.get(name);
if (scrollMap.has(tag)) {
node.body.style.uv.offset = scrollMap.get(tag).arr;
}
}
}, 'updateScrollTexUv');
}
else {
if (!styleConfig.scroll.enable) {
if (styleConfig.colorImage && styleConfig.colorImage.enable) {
node.body.style.emissive = [0, 0, 0];
}
node.off('update', 'updateScrollTexUv');
// scroll
node.body.style.emissiveMap = null;
node.body.style.emissive = null;
node.body.style.setAttribute('VerticalEmissive', false);
node.body.style.imageSlotType = THING.ImageSlotType.EmissiveMap; // fix will change diffuseMap uv
node.body.style.uv.offset = [0, 0];
return
}
const { texture, color } = styleConfig.scroll;
const scrollTex = _scrollTexMap.get(name).get(texture);
node.body.style.imageSlotType = ImageSlotType.EmissiveMap;
node.body.style.setAttribute('VerticalEmissive', true);
node.body.style.emissiveMap = scrollTex;
node.body.style.emissive = color;
node.on('update', () => {
if (_scrollMap.has(name)) {
const scrollMap = _scrollMap.get(name);
if (scrollMap.has(tag)) {
node.body.style.uv.offset = scrollMap.get(tag).arr;
}
}
}, 'updateScrollTexUv')
}
}
function setStyleSpecularFactor(node, styleConfig) {
if (styleConfig.specularFactor == undefined) {
node.body.style.setAttribute('SpecularFactor', 1);
return;
}
const specularFactor = styleConfig.specularFactor;
node.body.style.setAttribute('SpecularFactor', specularFactor)
}
function setStyleFresnel(node, styleConfig) {
if (!styleConfig.fresnel || !styleConfig.fresnel.power) {
node.body.style.setAttribute('FresnelPower', 0);
node.body.style.setAttribute('FresnelInverse', false);
return;
}
const fresnelPower = styleConfig.fresnel.power;
const fresnelInverse = styleConfig.fresnel.inverse;
node.body.style.setAttribute('FresnelPower', fresnelPower);
node.body.style.setAttribute('FresnelInverse', fresnelInverse);
}
function setStyleEmissive(node, styleConfig) {
if (!styleConfig.emissive || !styleConfig.emissive.enable) return; // if scrollTex is disenabled whether need clear emissive?
const emissive = styleConfig.emissive.color;
node.body.style.emissive = emissive;
}
function setStyleLightingGroup(node, styleConfig) {
const lightGroupMask = styleConfig.lightGroupMask;
if (!Utils.isValid(lightGroupMask)) {
node.body.style.setAttribute('LightingGroup', 0);
return;
}
node.body.style.setAttribute('LightingGroup', lightGroupMask);
}
function _initStyle(node) {
node.internalData._themeTag = null; // clear style tag
const style = node.body.style;
if (!style) {
return;
}
node.off('update', 'updateScrollTexUv');
style.begin();
style.beginDefaultValues();
style.color = null;
style.colorMapping = null;
// envMap
style.envMap = null;
style.envMapping = null;
// scroll
style.emissiveMap = null;
style.emissive = null;
style.setAttribute('VerticalEmissive', false);
style.imageSlotType = THING.ImageSlotType.EmissiveMap; // fix will change diffuseMap uv
style.uv.offset = [0, 0];
// edge
style.edge.enable = false;
style.edge.color = [1, 1, 1];
style.edge.glow = false;
style.edge.opacity = 1;
style.map = null;
style.normalMap = null;
style.metalness = null;
style.roughness = null;
style.aoMap = null;
style.wireframe = null;
style.transparent = null;
style.opacity = null;
style.setAttribute('SpecularFactor', 1);
style.setAttribute('FresnelPower', 0);
style.setAttribute('FresnelInverse', false);
style.setAttribute('EnvMapIntensity', 1);
style.setAttribute('LightingGroup', 0);
style.effect.glow = null;
style.effect.innerGlow = null;
style.effect.lineBloom = null;
style.effect.tailing = null;
style.effect.radial = null;
style.effect.ghosting = null;
style.endDefaultValues();
style.end();
}
function updateScroll(app, scrollMap, name) {
const map = scrollMap.get(name);
if (map) {
map.forEach((value, key) => {
if (value.speed == 0) {
value.arr[1] = value.offset;
}
else {
app.on('update', () => { // will caught loading the same theme bundle invalidates the previous one
value.arr[1] -= value.speed;
}, name + key + '_scroll');
// app.on('update', () => { // may cause update method not closed
// scroll.arr[1] -= scroll.speed;
// });
}
})
}
}
function generateColorTex(strategy) { // @compatible
let map = new Map();
for (let key in strategy) {
if (strategy[key].colorImage !== undefined) {
const color = strategy[key].colorImage.color;
if (!map.has(key)) {
map.set(key, createColorImage(color));
}
}
}
return map;
}
function createColorImage(color) {
const colorArray = Utils.parseColor(color);
const ui8 = new Uint8Array([
255 * colorArray[0], 255 * colorArray[1], 255 * colorArray[2], 255, 255 * colorArray[0], 255 * colorArray[1], 255 * colorArray[2], 255,
255 * colorArray[0], 255 * colorArray[1], 255 * colorArray[2], 255, 255 * colorArray[0], 255 * colorArray[1], 255 * colorArray[2], 255,
255, 255, 255, 255,
]);
const image = new ImageTexture({ data: ui8, width: 2, height: 2 });
return image;
}
function generateGradientTextures(strategy) { // @compatible
let map = new Map();
for (let key in strategy) {
if (strategy[key].useColorMap !== undefined) { // @compatible colorMapping
let grad = strategy[key].colorMapping;
if (grad) {
if (!map.has(key)) {
map.set(key, createGradientTexture(grad));
}
}
}
else {
let grad = strategy[key].colorMapping.gradient;
if (grad) {
if (!map.has(key)) {
map.set(key, createGradientTexture(grad));
}
}
}
}
return map;
}
function createGradientTexture(gradient) {
const imageData = MathUtils.getGradientPixelData(gradient, 128);
const texture = new ImageTexture({
data: imageData,
width: 128,
height: 1,
generateMipmaps: false,
flipY: false,
anisotropy: 1,
wrapTypeS: ImageWrapType.ClampToEdge, // 1.0's config
wrapTypeT: ImageWrapType.ClampToEdge
});
texture.magFilterType = texture.minFilterType = ImageFilterType.LinearFilter;
return texture;
}
function generateScroll(strategy) { // @compatible
let map = new Map();
for (let key in strategy) {
if (strategy[key].useScrollTex !== undefined) { // @compatible scroll
const enable = strategy[key].useScrollTex;
if (enable) {
let speed = strategy[key].scrollSpeed;
let offset = strategy[key].scrollOffset;
map.set(key, { arr: [0, 0], speed, offset });
}
}
else {
const enable = strategy[key].scroll.enable;
if (enable) {
let speed = strategy[key].scroll.speed;
let offset = strategy[key].scroll.offset;
map.set(key, { arr: [0, 0], speed, offset });
}
}
}
return map;
}
function generateScrollTex(strategy, baseUrl) { // @ compatible
let map = new Map();
for (let key in strategy) {
if (strategy[key].useScrollTex !== undefined) { // @ compatible
const enable = strategy[key].useScrollTex;
const url = strategy[key].scrollTex;
if (enable) {
if (!map.has(url)) {
map.set(url, createScrollTex(url, baseUrl));
}
}
}
else {
const enable = strategy[key].scroll.enable;
const url = strategy[key].scroll.texture;
if (enable) {
if (!map.has(url)) {
map.set(url, createScrollTex(url, baseUrl));
}
}
}
}
return map;
}
function createScrollTex(url, baseUrl) {
if (!url) return null;
let clonePath = url;
if (isLocalResource(url)) {
clonePath = baseUrl._appendPath(url);
}
const texture = new ImageTexture({ url: clonePath });
texture.wrapType = 'Repeat';
texture.premultiplyAlpha = true;
return texture;
}
function generateEnvTex(app, environment, baseUrl) { // @compatible
if (!environment) return;
if (environment.path !== undefined) { // @compatible envTex
const path = environment.path;
if (environment.name == 'none' || !path) {
return;
}
let texture;
if (Array.isArray(path)) {
const clonepath = path.slice(0);
if (isLocalResource(clonepath[0])) {
for (let i = 0; i < clonepath.length; i++) {
clonepath[i] = baseUrl._appendPath(clonepath[i]);
}
}
texture = app.loadCubeTexture(clonepath); // Array url
}
else {
let clonePath = path;
if (isLocalResource(path)) {
clonePath = baseUrl._appendPath(path);
}
texture = app.loadImageTexture(clonePath, { mappingType: ImageMappingType.EquirectangularReflection });
}
return texture;
}
else {
const { type, value } = environment;
let envTex;
switch (type) {
case "cube":
const path = value.slice(0);
if (isLocalResource(path[0])) {
for (let i = 0; i < path.length; i++) {
path[i] = baseUrl._appendPath(path[i]);
}
}
envTex = app.loadCubeTexture(path); // Array url
break;
case "image":
let url = value;
if (isLocalResource(url)) {
url = baseUrl._appendPath(url);
}
envTex = app.loadImageTexture(url, { mappingType: ImageMappingType.EquirectangularReflection });
break;
case "none":
default:
break;
}
return envTex;
}
}
function stopUpdateScroll(app, name, _scrollMap) {
const map = _scrollMap.get(name);
if (map) {
map.forEach((value, key) => {
if (value.speed !== 0) {
app.off('update', name + key + '_scroll');
}
})
}
}
// Get gradient color
function getBetwColors(startNumber, startColor, endNumber, endColor) {
const colors = [];
const leftColor = Utils.parseColor(startColor);
const rightColor = Utils.parseColor(endColor);
let lRed = leftColor[0] * 255, lGreen = leftColor[1] * 255, lBlue = leftColor[2] * 255;
let rRed = rightColor[0] * 255, rGreen = rightColor[1] * 255, rBlue = rightColor[2] * 255;
let alpha = 255;
const maxRed = Math.abs(lRed - rRed),
maxGreen = Math.abs(lGreen - rGreen),
maxBlue = Math.abs(lBlue - rBlue);
const colorLevel = parseInt(Math.abs(startNumber - endNumber) * _pixel);
let level = colorLevel;
while (level--) {
lRed <= rRed ? lRed += maxRed / colorLevel : lRed -= maxRed / colorLevel;
lGreen <= rGreen ? lGreen += maxGreen / colorLevel : lGreen -= maxGreen / colorLevel;
lBlue <= rBlue ? lBlue += maxBlue / colorLevel : lBlue -= maxBlue / colorLevel;
colors.push(lRed); colors.push(lGreen); colors.push(lBlue); colors.push(alpha);
}
return colors;
}
function isLocalResource(str) {
return str.indexOf('http') == -1 ? true : false;
}
function getEnvTexUrl(envURL, baseUrl) {
let url = [];
for (let i = 0; i < envURL.length; i++) {
url[i] = baseUrl._appendPath(envURL[i]);
}
return url;
}
function changeColorArray(arr) {
const intArr = [];
arr.forEach(arg => {
intArr.push(arg / 255);
})
return intArr;
}
function convertParticle(data) {
const systemOptions = {};
systemOptions["version"] = "2.0.1";
systemOptions["groups"] = [];
for (let i = 0; i < data.listGroups.length; i++) {
const group = data.listGroups[i];
const groupOptions = _getGroupOptions(group);
systemOptions["groups"].push(groupOptions);
}
return systemOptions;
}
function _getGroupOptions(group) {
const groupOptions = {
texture: {
animation: {
frames: [
group.texture.vec2Frames.x,
group.texture.vec2Frames.y
],
frameCount: group.texture.iFrameCount,
loop: group.texture.iLoop
},
url: group.texture.url,
},
fixedTimeStep: group.fFixedTimeStep,
meshParams: group.arrayMeshParams,
hasPerspective: group.strHasPerspective,
isColorize: group.strIsColorize,
blendingMode: group.iBlendingMode,
transparent: group.strIsTransparent,
alphaTest: group.fAlphaTest,
depthWrite: group.strIsDepthWrite,
depthTest: group.strIsDepthTest,
fog: group.strIsFog,
scale: group.fScale,
maxParticleCount: group.iMaxParticleCount,
useMesh: group.strUseMesh,
meshType: group.strMeshUrl,
emitters: [],
};
for (let i = 0; i < group.listEmitters.length; i++) {
const emitter = group.listEmitters[i];
const emitterOptions = _getEmitterOptions(emitter);
groupOptions["emitters"].push(emitterOptions);
}
return groupOptions;
}
function _getEmitterOptions(emitter) {
const clrArray = [];
for (let i = 0, len = emitter.listColor.length; i < len; i++) {
const colorOption = {
randomise: emitter.listColor[i].strIsRandomise,
value: [
emitter.listColor[i].vec3Value.x,
emitter.listColor[i].vec3Value.y,
emitter.listColor[i].vec3Value.z,
],
spread: [
emitter.listColor[i].vec3Spread.x,
emitter.listColor[i].vec3Spread.y,
emitter.listColor[i].vec3Spread.z,
],
};
clrArray.push(colorOption);
}
const opacityArray = [];
for (let i = 0, len = emitter.listOpacity.length; i < len; i++) {
const opacityOption = {
randomise: emitter.listOpacity[i].strIsRandomise,
value: emitter.listOpacity[i].fValue,
spread: emitter.listOpacity[i].fSpread,
};
opacityArray.push(opacityOption);
}
const sizeArray = [];
for (let i = 0, len = emitter.listSize.length; i < len; i++) {
const sizeOption = {
randomise: emitter.listSize[i].strIsRandomise,
value: emitter.listSize[i].fValue,
spread: emitter.listSize[i].fSpread,
};
sizeArray.push(sizeOption);
}
const angleArray = [];
for (let i = 0, len = emitter.listAngle.length; i < len; i++) {
const angleOption = {
randomise: emitter.listAngle[i].strIsRandomise,
value: emitter.listAngle[i].fValue,
spread: emitter.listAngle[i].fSpread,
};
angleArray.push(angleOption);
}
const emitterOptions = {
distribution: emitter.iDistribution,
particleCount: emitter.iParticleCount,
duration: emitter.fDuration,
isStatic: emitter.strIsStatic,
isLookAtCamera: emitter.strIsLookAtCamera,
isLookAtCameraOnlyY: emitter.strIsLookAtCameraOnlyY,
activeMultiplier: emitter.fActiveMultiplier,
direction: emitter.iDirection,
maxAge: {
value: emitter.maxAge.fValue,
spread: emitter.maxAge.fSpread,
},
position: {
value: [
emitter.position.vec3Value.x,
emitter.position.vec3Value.y,
emitter.position.vec3Value.z,
],
spread: [
emitter.position.vec3Spread.x,
emitter.position.vec3Spread.y,
emitter.position.vec3Spread.z,
],
spreadClamp: [
emitter.position.vec3SpreadClamp.x,
emitter.position.vec3SpreadClamp.y,
emitter.position.vec3SpreadClamp.z,
],
radius: emitter.position.fRadius,
radiusScale: [
emitter.position.vec3RadiusScale.x,
emitter.position.vec3RadiusScale.y,
emitter.position.vec3RadiusScale.z,
],
distribution: emitter.position.iDistribution,
randomise: emitter.position.strIsRandomise,
},
velocity: {
value: [
emitter.velocity.vec3Value.x,
emitter.velocity.vec3Value.y,
emitter.velocity.vec3Value.z,
],
spread: [
emitter.velocity.vec3Spread.x,
emitter.velocity.vec3Spread.y,
emitter.velocity.vec3Spread.z,
],
distribution: emitter.velocity.iDistribution,
randomise: emitter.velocity.iDistribution,
},
acceleration: {
value: [
emitter.acceleration.vec3Value.x,
emitter.acceleration.vec3Value.y,
emitter.acceleration.vec3Value.z,
],
spread: [
emitter.acceleration.vec3Spread.x,
emitter.acceleration.vec3Spread.y,
emitter.acceleration.vec3Spread.z,
],
distribution: emitter.acceleration.iDistribution,
randomise: emitter.acceleration.strIsRandomise,
},
drag: {
randomise: emitter.drag.strIsRandomise,
value: emitter.drag.fValue,
spread: emitter.drag.fSpread,
},
wiggle: {
value: emitter.wiggle.fValue,
spread: emitter.wiggle.fSpread,
},
rotation: {
axis: [
emitter.rotation.vec3Axis.x,
emitter.rotation.vec3Axis.y,
emitter.rotation.vec3Axis.z,
],
axisSpread: [
emitter.rotation.vec3AxisSpread.x,
emitter.rotation.vec3AxisSpread.y,
emitter.rotation.vec3AxisSpread.z,
],
angle: emitter.rotation.fAngle,
angleSpread: emitter.rotation.fAngleSpread,
static: emitter.rotation.strIsStatic,
center: [
emitter.rotation.vec3Center.x,
emitter.rotation.vec3Center.y,
emitter.rotation.vec3Center.z,
],
randomise: emitter.rotation.strIsRandomise,
},
colors: clrArray,
opacities: opacityArray,
sizes: sizeArray,
angles: angleArray,
};
return emitterOptions;
}
function _getOffset(rootNode, item) {
const orientedBox = rootNode.orientedBox;
let orientSize;
if (rootNode.facadeSize) {
orientSize = rootNode.facadeSize;
}
else {
orientSize = orientedBox.size;
rootNode.facadeSize = JSON.parse(JSON.stringify(orientSize));
}
const min = MathUtils.min(...orientSize);
const diff = min * (item.scaleFactor - 1);
orientSize = MathUtils.divideVector(orientSize, rootNode.scale);
return { orientSize, diff };
}
function _createBox(rootNode, item, position, size, onCreateThemeObjects) {
const effectFacades = [];
const effectFacadeParams = {
name: 'effectFacade' + item.name,
localPosition: position,
width: size[0],
height: size[1],
depth: size[2],
style: {
color: item.color,
opacity: item.opacity,
sideType: SideType.Double
},
parent: rootNode,
// queryable: false
}
onCreateThemeObjects && onCreateThemeObjects(effectFacadeParams);
const box = new Box(effectFacadeParams);
box.renderOrder = item.renderOrder === undefined ? 100 : item.renderOrder;
box.pickable = false;
box.userData['_skipTheme_'] = true;
box.userData['_themeFacade_'] = true;
effectFacades.push(box);
if (item.wireframe.enable) {
const lines = _createBoxLine(rootNode, item, box);
effectFacades.push(...lines);
}
box.inherit.style = InheritType.Jump;
box.bounding.inheritType = InheritType.Jump;
box.level.config.ignoreStyle = true;
return effectFacades;
}
function _createBoxLine(rootNode, item, box) {
const lines = [];
const orientedBox = box.orientedBox;
const orientedPoints = orientedBox.getPoints();
const pointsArr = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]];
pointsArr.forEach(arr => {
const points = [];
arr.forEach(index => {
points.push(orientedPoints[index]);
});
const line = new PixelLine({
name: 'facadeLine' + item.name,
points: points,
style: {
color: item.wireframe.color,
opacity: item.wireframe.opacity,
sideType: SideType.Double
},
parent: rootNode,
// queryable: false,
tags: ['helper'] // use this can make this not add in Scene
});
line.renderOrder = item.wireframe.renderOrder === undefined ? 99 : item.wireframe.renderOrder;
line.pickable = false;
line.userData['_skipTheme_'] = true;
line.userData['_themeFacade_'] = true;
line.inherit.style = InheritType.Jump;
line.bounding.inheritType = InheritType.Jump;
line.level.config.ignoreStyle = true;
lines.push(line);
});
return lines;
}
function _boxParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const center = rootNode.orientedBox.center;
const size = [orientSize[0] + diff, orientSize[1] + diff / 2, orientSize[2] + diff];
const localPos = rootNode.worldToSelf(center);
const facadeEffects = _createBox(rootNode, item, localPos, size, onCreateThemeObjects);
return facadeEffects;
}
function _downParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const center = rootNode.orientedBox.center;
const size = [orientSize[0] + diff, item.thickness / rootNode.scale[1], orientSize[2] + diff];
const localPos = rootNode.worldToSelf(center);
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const y = item.interval / rootNode.scale[1] * i;
const position = [localPos[0], localPos[1] + y - orientSize[1] * 0.5, localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
}
}
else {
let y = 0;
while (y <= orientSize[1]) {
const position = [localPos[0], localPos[1] + y - orientSize[1] * 0.5, localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
y += item.interval / rootNode.scale[1];
}
}
return facadeEffects;
}
function _topParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const center = rootNode.orientedBox.center;
const size = [orientSize[0] + diff, item.thickness / rootNode.scale[1], orientSize[2] + diff];
const localPos = rootNode.worldToSelf(center);
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const y = orientSize[1] - item.interval / rootNode.scale[1] * i;
const position = [localPos[0], localPos[1] + y - orientSize[1] * 0.5, localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
}
}
else {
let y = orientSize[1];
while (y >= 0) {
const position = [localPos[0], localPos[1] + y - orientSize[1] * 0.5, localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
y -= item.interval / rootNode.scale[1];
}
}
return facadeEffects;
}
function _leftParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const size = [item.thickness / rootNode.scale[0], orientSize[1] + diff / 2, orientSize[2] + diff];
const center = rootNode.orientedBox.center;
const localPos = rootNode.worldToSelf(center);
let x = -orientSize[0] / 2;
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const position = [localPos[0] + x, localPos[1], localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
x += item.interval / rootNode.scale[0];
}
}
else {
while (x <= orientSize[0] / 2) {
const position = [localPos[0] + x, localPos[1], localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
x += item.interval / rootNode.scale[0];
}
}
return facadeEffects;
}
function _rightParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const size = [item.thickness / rootNode.scale[0], orientSize[1] + diff / 2, orientSize[2] + diff];
const center = rootNode.orientedBox.center;
const localPos = rootNode.worldToSelf(center);
let x = orientSize[0] / 2;
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const position = [localPos[0] + x, localPos[1], localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
x -= item.interval / rootNode.scale[0];
}
}
else {
while (x >= -orientSize[0] / 2) {
const position = [localPos[0] + x, localPos[1], localPos[2]];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
x -= item.interval / rootNode.scale[0];
}
}
return facadeEffects;
}
function _frontParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const size = [orientSize[0] + diff, orientSize[1] + diff / 2, item.thickness / rootNode.scale[2]];
const center = rootNode.orientedBox.center;
const localPos = rootNode.worldToSelf(center);
let z = orientSize[2] / 2;
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const position = [localPos[0], localPos[1], localPos[2] + z];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
z -= item.interval / rootNode.scale[2];
}
}
else {
while (z >= -orientSize[2] / 2) {
const position = [localPos[0], localPos[1], localPos[2] + z];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
z -= item.interval / rootNode.scale[2];
}
}
return facadeEffects;
}
function _backParam(rootNode, item, onCreateThemeObjects) {
const { orientSize, diff } = _getOffset(rootNode, item);
const size = [orientSize[0] + diff, orientSize[1] + diff / 2, item.thickness / rootNode.scale[2]];
const center = rootNode.orientedBox.center;
const localPos = rootNode.worldToSelf(center);
let z = -orientSize[2] / 2;
const facadeEffects = [];
if (item.repeat === 'norepeat') {
for (let i = 0; i < item.count; i++) {
const position = [localPos[0], localPos[1], localPos[2] + z];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
z += item.interval / rootNode.scale[2];
}
}
else {
while (z <= orientSize[2] / 2) {
const position = [localPos[0], localPos[1], localPos[2] + z];
facadeEffects.push(..._createBox(rootNode, item, position, size, onCreateThemeObjects));
z += item.interval / rootNode.scale[2];
}
}
return facadeEffects;
}
function _createEffectFacade(item, rootNode, onCreateThemeObjects) {
let effectFacade;
switch (item.type) {
case 'box':
effectFacade = _boxParam(rootNode, item, onCreateThemeObjects);
break;
case 'top':
effectFacade = _topParam(rootNode, item, onCreateThemeObjects);
break;
case 'down':
effectFacade = _downParam(rootNode, item, onCreateThemeObjects);
break;
case 'left':
effectFacade = _leftParam(rootNode, item, onCreateThemeObjects);
break;
case 'right':
effectFacade = _rightParam(rootNode, item, onCreateThemeObjects);
break;
case 'front':
effectFacade = _frontParam(rootNode, item, onCreateThemeObjects);
break;
case 'back':
effectFacade = _backParam(rootNode, item, onCreateThemeObjects);
break;
}
return effectFacade;
}