import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
import { AmbientLight } from '../objects/AmbientLight';
import { DirectionalLight } from '../objects/DirectionalLight';
import { HemisphereLight } from '../objects/HemisphereLight';
import { ImageMappingType, EventType } from '../const'
import { CameraPostEffectComponent } from './CameraPostEffectComponent';
import { CameraEffectComponent } from './CameraEffectComponent';
const __ = {
private: Symbol('private'),
}
/**
* 全局渲染配置组件,用于管理应用的渲染设置。
* @class
* @public
* @memberof THING
* @extends THING.BaseComponent
*/
class AppRenderConfigComponent extends BaseComponent {
/**
* @constructor
* @public
* 全局渲染配置组件,用于管理应用的渲染设置。
*/
constructor() {
super();
this[__.private] = {};
this._initPrivateMembers();
}
onAdd(object) {
super.onAdd(object);
const _private = this[__.private];
const LightManager = Utils.getRegisteredClass('LightManager') || defaultLightManager;
_private.lightManager = new LightManager(this._object);
this.lightManager.initMainLight();
}
onRemove() {
super.onRemove();
this.lightManager.dispose();
}
/**
* @public
* 应用渲染配置的相关信息。
* @typedef {object} RenderSettingsInfo
* @property {Lights} lights 灯光配置
* @property {THING.CameraFogComponent} fog 雾效配置
* @property {THING.ImageTexture| THING.CubeTexture | Array<number>} background 背景
* @property {Environment} environment 场景的全局环境
* @property {THING.CameraPostEffectComponent} postEffects 后处理效果
* @property {THING.CameraEffectComponent} cameraEffects 摄像机效果
*/
/**
* @public
* 灯光配置
* @typedef {object} Lights
* @property {AmbientLight[]} ambientLights 场景的环境光列表。
* @property {DirectionalLight[]} directionalLights 场景的方向光列表。
* @property {HemisphereLight[]} hemisphereLights 场景的半球光列表。
*/
/**
* @public
* 环境配置。
* @typedef {object} Environment
* @property {THING.ImageTexture | THING.CubeTexture} envMap 场景的环境贴图。
* @property {number} diffuseIntensity 环境贴图的漫反射光照强度。
*/
/**
* @public
* 获取/设置全局渲染配置。(涉及到灯光参数时,只能控制 app.root.children 下的灯光)
* @type {RenderSettingsInfo}
*/
get renderSettings() {
const app = this._object;
// 集群光照
const { enable: clusteredLightingEnable, debug: clusteredLightingDebug, clipNear, clipFar, maxLightsPerCell, cells } = app.view.getAttribute('ClusteredLighting');
// 遮挡
const { enable: occlusionOptionsEnable, debug: occlusionOptionsDebug, interval } = app.view.getAttribute('OcclusionOptions');
const config = {
'background': app.background,
'environment': {
'envMap': app.envMap,
'diffuseIntensity': app.scene.envDiffuseIntensity,
'specularIntensity': app.scene.envSpecularIntensity
},
'clusteredLighting': {
'enable': clusteredLightingEnable,
'debug': clusteredLightingDebug,
'clipNear': clipNear,
'clipFar': clipFar,
'maxLightsPerCell': maxLightsPerCell,
'cells': cells,
},
'occlusionOptions': {
'enable': occlusionOptionsEnable,
'debug': occlusionOptionsDebug,
'interval': interval
}
};
// 灯光
config.lights = this.lightManager.getConfig();
// 雾效
config.fog = Utils.cloneObject(app.camera.fog.config, false);
config.postEffects = Utils.cloneObject(app.camera.postEffect.config, false);
config.cameraEffects = Utils.cloneObject(app.camera.effect.config, false);
return config;
}
set renderSettings(config) {
const app = this._object;
if (config.clusteredLighting) {
app.view.setAttribute('ClusteredLighting', config.clusteredLighting)
}
if (config.occlusionOptions) {
app.view.setAttribute('OcclusionOptions', config.occlusionOptions)
}
if (config.background !== undefined) {
app.background = config.background;
}
this._setEnvironment(config.environment)
// 雾效
const fog = config.fog;
if (fog) {
app.camera.fog.config = fog;
}
// 灯光
if (config.lights) {
this.lightManager.setLights(config.lights);
}
app.camera.postEffect.config = config.postEffects;
app.camera.effect.config = config.cameraEffects;
}
get config() {
let _private = this[__.private];
const config = this.renderSettings
// 处理背景和环境贴图 URL
const background = config.background
const environment = config.environment
config.background = _private.processBackground(background);
config.environment = _private.processEnvironment(environment);
return config
}
set config(config) {
let _private = this[__.private];
const background = config.background;
const environment = config.environment;
config.background = _private.loadBackground(background);
config.environment = _private.loadEnvMap(environment);
this.renderSettings = config;
}
get lightManager() {
const _private = this[__.private];
return _private.lightManager;
}
_setEnvironment(environment) {
if (!environment) {
return
}
const app = this._object;
if (Utils.isValid(environment.diffuseIntensity)) {
app.scene.envDiffuseIntensity = environment.diffuseIntensity;
}
if (Utils.isValid(environment.specularIntensity)) { // 兼容旧配置
app.scene.envSpecularIntensity = environment.specularIntensity;
}
if (environment.envMap === undefined) {
return
}
app.envMap = environment.envMap;
}
_initPrivateMembers() {
const _private = this[__.private];
const app = this._object || Utils.getCurrentApp();
_private.getWorkPath = () => {
let workPath = '';
if (app && app.workPath) {
workPath = app.workPath;
}
else {
workPath = Utils.workPath;
}
workPath = workPath.replaceAll('\\', '/');
if (!workPath.endsWith('/')) {
workPath = workPath + '/';
}
return workPath;
}
_private.resolveURL = (url) => {
// let baseURL = _private.getBaseURL();
// if (url.startsWith(baseURL)) {
// url = url.replace(baseURL, '');
// }
let workPath = _private.getWorkPath();
if (workPath && url.startsWith(workPath)) {
url = url.replace(workPath, '');
url = "/" + url;
}
return url;
}
_private.processURI = (oriURI) => {
let retURI = null;
if (Array.isArray(oriURI)) {
retURI = oriURI.map(uri => {
uri = _private.resolveURL(uri);
return uri;
});
}
else {
retURI = _private.resolveURL(oriURI);
}
return retURI;
}
_private.collectImage = (value) => {
if (Utils.isNull(value)) {
return null;
}
else if (Array.isArray(value)) {
return {
type: 'color',
value: value
}
}
let data = {};
// 值为图像
if (Utils.isNull(value.url)) {
return null;
}
else if (value.isImageTexture) {
if (value.mappingType === ImageMappingType.EquirectangularReflection) {
data.type = 'cube';
}
else {
data.type = 'image';
}
}
else if (value.isCubeTexture) {
data.type = 'cube'
}
// 是纹理
let url = _private.processURI(value.url);
data.value = url;
return data;
}
_private.processBackground = (value) => {
let background = _private.collectImage(value);
return background;
}
_private.processEnvironment = (value) => {
const environment = {
type: 'none',
value: ''
}
if (Utils.isNull(value)) {
return null;
}
if (value.envMap != null) {
let envMap = _private.collectImage(value.envMap);
if (Utils.isNull(envMap)) {
environment.value = null;
}
else {
environment.type = envMap.type;
environment.value = envMap.value;
}
}
if (Utils.isValid(value.diffuseIntensity)) {
environment.diffuseIntensity = value.diffuseIntensity;
}
if (Utils.isValid(value.specularIntensity)) {
environment.specularIntensity = value.specularIntensity;
}
return environment;
}
_private.resolveLoadURL = (url) => {
if (url._startsWith('http://') || url._startsWith('https://') || url._startsWith('file')) {
return url;
}
else if (url._startsWith('/') || url._startsWith('.')) {
return Utils.workPath._appendURL(url);
}
// else if (url._startsWith('/')) {
// if (_private.workPath) {
// return _private.workPath._appendURL(url);
// }
// else {
// return url;
// }
// }
// else if (url._startsWith('.')) {
// if (_private.basePath) {
// return _private.basePath._appendURL(url);
// }
// else {
// return url;
// }
// }
else {
return Utils.resolveURL(url);
}
}
_private.loadImage = (image, pmrem) => {
if (!image) {
return null;
}
if (image.type == 'color') {
return image.value;
}
else if (image.type == 'image' || (image.type == 'cube' && !Array.isArray(image.value))) {
let imageTexture = null;
// 需要 PMREM
if (image.type == 'cube') {
imageTexture = _private.loadImageTexture(image, true);
}
else {
imageTexture = _private.loadImageTexture(image, pmrem);
}
return imageTexture;
}
else if (image.type == 'cube') {
let cubeTexture = _private.loadCubeTexture(image);
return cubeTexture;
}
return null;
}
_private.loadCubeTexture = (image) => {
let urls = image.value;
if (!Array.isArray(urls)) {
urls = [image.value];
}
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
urls[i] = _private.resolveLoadURL(url)
}
let cubeTexture = app.loadCubeTexture(urls);
return cubeTexture;
}
_private.loadImageTexture = (image, pmrem = false) => {
let url = _private.resolveLoadURL(image.value);
let imageTexture;
if (pmrem) {
imageTexture = app.loadImageTexture(url, { mappingType: ImageMappingType.EquirectangularReflection });
}
else {
imageTexture = app.loadImageTexture(url);
}
return imageTexture;
}
_private.loadBackground = (background) => {
if (Utils.isNull(background)) {
return;
}
return _private.loadImage(background);
}
_private.loadEnvMap = (environment) => {
if (Utils.isNull(environment)) {
return;
}
let envMap = environment.value;
if (Utils.isNull(envMap)) {
environment.type = 'none';
environment.value = null;
}
environment.envMap = _private.loadImage(environment, true);
return environment;
}
}
}
class LightManager {
/**
* 灯光管理器,用于管理应用中的各种灯光。
* @param {THING.App} app 应用实例
*/
constructor(app) {
this.app = app;
this.lightMap = new Map([
['ambientLights', []],
['directionalLights', []],
['hemisphereLights', []]
]);
}
// 首次创建时初始化
initMainLight() {
const app = this.app;
if (app.scene.mainLight) {
this.lightMap.get('directionalLights').push(app.scene.mainLight);
}
if (app.scene.ambientLight) {
this.lightMap.get('ambientLights').push(app.scene.ambientLight);
}
}
setLights(config) {
const app = this.app;
this.clearLights();
// 不直接销毁所有灯光,而是尽量复用并赋值:移除多余灯光,按需添加新灯光。
const lightSettings = Utils.cloneObject(config, false);
const { ambientLights, directionalLights, hemisphereLights } = lightSettings;
// 处理主灯光
const mainAmbientLight = app.scene.ambientLight;
const mainDirectionalLight = app.scene.mainLight;
if (ambientLights.length > 0 && mainAmbientLight) {
const mainAmbientLightConfig = ambientLights.shift();
_setAmbientLight(mainAmbientLight, mainAmbientLightConfig);
this.lightMap.get('ambientLights').push(mainAmbientLight);
}
if (directionalLights.length > 0 && mainDirectionalLight) {
const mainDirectionalLightConfig = directionalLights.shift();
_setDirectionalLight(mainDirectionalLight, mainDirectionalLightConfig, app);
this.lightMap.get('directionalLights').push(mainDirectionalLight);
}
// 处理其他灯光
ambientLights.forEach(ambientLightConfig => {
this.addLight('ambientLights', ambientLightConfig);
});
directionalLights.forEach(directionalLightConfig => {
this.addLight('directionalLights', directionalLightConfig);
});
hemisphereLights.forEach(hemisphereLightConfig => {
this.addLight('hemisphereLights', hemisphereLightConfig);
});
}
addLight(type, lightConfig, options) {
const light = this._createLight(type, lightConfig, options);
this.lightMap.get(type).push(light);
return light;
}
_createLight(type, lightConfig, options) {
const app = this.app;
switch (type) {
case 'ambientLights':
return _createAmbientLight(lightConfig, app, options);
case 'directionalLights':
return _createDirectionalLight(lightConfig, app, options);
case 'hemisphereLights':
return _createHemisphereLight(lightConfig, app, options);
default:
break;
}
}
removeLight(light) {
const uuid = light.uuid;
const type = getLightType(light);
const lights = this.lightMap.get(type);
const index = lights.findIndex(light => light.uuid === uuid);
if (index > -1) {
light.destroy();
lights.splice(index, 1);
}
}
clearLights() {
this.lightMap.forEach((lights, type) => {
lights.forEach(light => {
!light.destroyed && light.destroy();
});
lights.length = 0;
});
}
dispose() {
this.lightMap.forEach((lights, type) => {
lights.forEach(light => {
!light.destroyed && light.destroy();
});
});
this.lightMap.clear();
}
getConfig() {
const data = {
ambientLights: [],
directionalLights: [],
hemisphereLights: []
};
this.lightMap.forEach((lights, type) => {
lights.forEach(light => {
data[type].push(_getLightConfig(type, light));
});
});
return data;
}
}
let defaultLightManager = LightManager;
const _getLightConfig = (type, light) => {
switch (type) {
case 'ambientLights':
return _getAmbientLightConfig(light);
case 'directionalLights':
return _getDirectionalLightConfig(light);
case 'hemisphereLights':
return _getHemisphereLightConfig(light);
default:
break;
}
}
const getLightType = (light) => {
switch (light.type) {
case 'AmbientLight':
return 'ambientLights';
case 'DirectionalLight':
return 'directionalLights';
case 'HemisphereLight':
return 'hemisphereLights';
default:
break;
}
}
const _createAmbientLight = (lightConfig, app, options) => {
const light = new AmbientLight(Object.assign({
app,
internalData: {
'renderSettingsLight': true
}
}, options));
_setAmbientLight(light, lightConfig);
return light;
}
const _getAmbientLightConfig = (light) => {
return {
'name': light.name,
'color': light.color,
'intensity': light.intensity,
'groups': light.groups
};
}
const _setAmbientLight = (light, config) => {
if (config.name) {
light.name = config.name;
}
light.color = config.color;
light.intensity = config.intensity;
if (Utils.isValid(config.groups)) {
light.groups = config.groups;
}
_triggerLightChangeEvent(light);
}
const _createDirectionalLight = (lightConfig, app, options) => {
const light = new DirectionalLight(Object.assign({
app,
internalData: {
'renderSettingsLight': true
}
}, options));
light.adapter.bind(app.root);
_setDirectionalLight(light, lightConfig, app);
return light;
}
const _getDirectionalLightConfig = (light) => {
return {
name: light.name,
enableShadow: light.castShadow,
intensity: light.intensity,
color: light.color,
shadowBias: light.shadowBias,
shadowQuality: light.shadowQuality,
horzAngle: light.adapter.horzAngle,
vertAngle: light.adapter.vertAngle,
visible: light.visible,
bindCamera: !!light.adapter.bindingCamera,
distance: light.adapter.distance,
farFactor: light.adapter.farFactor,
groups: light.groups
};
}
const _setDirectionalLight = (light, config, app) => {
if (config.name) {
light.name = config.name;
}
light.intensity = config.intensity;
light.color = config.color;
light.shadowQuality = config.shadowQuality;
light.adapter.horzAngle = config.horzAngle;
light.adapter.vertAngle = config.vertAngle;
if (Utils.isValid(config.enableShadow)) {
light.castShadow = config.enableShadow
}
if (Utils.isValid(config.castShadow)) {
light.castShadow = config.castShadow
}
if (Utils.isValid(config.shadowBias)) {
light.shadowBias = config.shadowBias;
}
if (Utils.isValid(config.visible)) {
light.visible = config.visible;
}
if (Utils.isValid(config.distance)) {
light.adapter.distance = config.distance;
}
if (Utils.isValid(config.farFactor)) {
light.adapter.farFactor = config.farFactor;
}
if (Utils.isValid(config.groups)) {
light.groups = config.groups;
}
if (Utils.isValid(config.bindCamera) && config.bindCamera) {
light.adapter.bindCamera(app.camera);
}
if (config.enableShadow) {
light.adapter.refresh();
}
_triggerLightChangeEvent(light);
}
const _createHemisphereLight = (lightConfig, app, options) => {
const light = new HemisphereLight(Object.assign({
app,
internalData: {
'renderSettingsLight': true
}
}, options));
_setHemisphereLight(light, lightConfig);
return light;
}
const _getHemisphereLightConfig = (light) => {
return {
name: light.name,
intensity: light.intensity,
color: light.color,
groundColor: light.groundColor,
groups: light.groups
};
}
const _setHemisphereLight = (light, config) => {
if (config.name) {
light.name = config.name;
}
light.intensity = config.intensity;
light.color = config.color;
light.groundColor = config.groundColor;
if (Utils.isValid(config.groups)) {
light.groups = config.groups;
}
_triggerLightChangeEvent(light);
}
const _triggerLightChangeEvent = (light) => {
light.app.trigger(EventType.RenderSettingsLightChange, { object: light });
}
AppRenderConfigComponent.exportProperties = [
'renderSettings'
];
export { AppRenderConfigComponent, LightManager }