import { StringEncoder, Callbacks, ObjectProxy } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { Object3D } from '../objects/Object3D';
import { RootObject } from '../objects/RootObject';
import { AmbientLight } from '../objects/AmbientLight';
import { DirectionalLight } from '../objects/DirectionalLight';
import { ImageTexture } from '../resources/ImageTexture';
import { ImageMappingType } from '../const';
import { CubeTexture } from '../resources/CubeTexture';
const __ = {
private: Symbol('private'),
}
const _renderOverlayFuncName = StringEncoder.toText("<@secret renderOverlay>");
const _ClearColorBuffer = StringEncoder.toText("<@secret ClearColorBuffer>");
/**
* @public
* @class Scene
* 场景对象,负责维护底层渲染场景、根节点、相机、灯光、环境贴图和渲染生命周期。
* @memberof THING
*/
class Scene {
/**
* 创建场景管理对象。
* @param {object} param 初始化参数
* @param {THING.App} param.app App 实例。
* @param {object} param.scene 底层渲染场景对象。
* @param {boolean} [param.initLights=true] 是否创建默认环境光和主方向光。
* @param {string|Array<string>|THING.CubeTexture|THING.ImageTexture} [param.envMap] 环境贴图。字符串可为全景图地址或立方体贴图目录,数组表示立方体贴图地址列表。
* @param {object} param.defaultSettings 默认设置。
*/
constructor(param = {}) {
this[__.private] = {};
let _private = this[__.private];
_private.app = param['app'];
_private.visible = true;
_private.isDisposed = false;
// Root objects
_private.rootObjects = {};
_private.scene = null;
// Lights
_private.ambientLight = null;
_private.mainLight = null;
// Environment
_private.envMap = null;
// Render options
_private.instancedOffset = null;
_private.renderOptions = null;
// Camera
_private.curCameraIndex = -1; // The current logic camera index
_private.cameras = []; // [0]: main render camera
// Callbacks
_private.beforeRenderCallbacks = new Callbacks();
_private.afterRenderCallbacks = new Callbacks();
this._initScene(param);
this._initLight(param);
this._initRenderOptions();
this._initEnvMap(param);
// Enable static mode as default
this.staticMode = true;
}
// #region Private
/**
* 触发渲染前生命周期回调,并同步通知 App delegate 与底层场景。
* @param {number} deltaTime 距离上一帧的时间,单位为秒。
* @param {object} camera 当前用于主渲染的底层相机节点。
* @private
*/
_onBeforeRender(deltaTime, camera) {
let _private = this[__.private];
_private.beforeRenderCallbacks.invoke(deltaTime, camera);
_private.app.delegate.onBeforeRender(camera);
_private.scene.beforeRender(deltaTime);
}
/**
* 触发渲染后生命周期回调,并同步通知底层场景与 App delegate。
* @param {number} deltaTime 距离上一帧的时间,单位为秒。
* @param {object} camera 当前用于主渲染的底层相机节点。
* @private
*/
_onAfterRender(deltaTime, camera) {
let _private = this[__.private];
_private.scene.afterRender(deltaTime);
_private.app.delegate.onAfterRender(camera);
_private.afterRenderCallbacks.invoke(deltaTime, camera);
}
/**
* 初始化底层渲染场景、逻辑根对象以及 Overlay 专用根节点。
* @param {object} param 初始化参数。
* @param {object} param.scene 底层渲染场景对象。
* @private
*/
_initScene(param) {
let _private = this[__.private];
// Create scene
_private.scene = param['scene'];
// The scene root object
_private.rootObjects['scene'] = new RootObject({
name: 'scene-root',
loaded: true,
queryable: false
});
// Create logical root nodes
let rootNodes = {
'attachedPoint': Utils.createObject('Node', { app: _private.app }),
'debug': Utils.createObject('Node', { app: _private.app }),
'bodyBridge': Utils.createObject('Node', { app: _private.app })
};
// Create other logical root objects
for (let key in rootNodes) {
let rootName = key + '-root';
let rootObject = new Object3D({
rootNode: rootNodes[key],
name: rootName,
queryable: false,
loaded: true,
castShadow: false,
receiveShadow: false
});
_private.rootObjects[key] = rootObject;
}
// Bind root objects
let root = _private.scene.getRoot();
for (let key in _private.rootObjects) {
root.add(_private.rootObjects[key].node);
}
// Get overlay root node
let getOverlayRootFunc = StringEncoder.toText("<@secret getOverlayRoot>");
let overlayRoot = _private.scene[getOverlayRootFunc]();
overlayRoot.setVisible(true);
// Create overlay root node
_private.overlayRootNode = Utils.createObject('Node', { app: _private.app });
_private.overlayRootNode.setName(StringEncoder.toText("<@secret OverlayRoot>"));
overlayRoot.add(_private.overlayRootNode);
// Create overlay logo root node
_private.overlayMarkerRootNode = Utils.createObject('Node', { app: _private.app });
_private.overlayMarkerRootNode.setName(StringEncoder.toText("<@secret OverlayMarkerRoot>"));
overlayRoot.add(_private.overlayMarkerRootNode);
// Update accessors for internal usage
_private.accessors = {};
_private.accessors[StringEncoder.toText("<@secret overlayRootNode>")] = _private.overlayRootNode;
_private.accessors[StringEncoder.toText("<@secret overlayMarkerRootNode>")] = _private.overlayMarkerRootNode;
}
/**
* 初始化场景默认灯光。
* @param {object} param 初始化参数。
* @param {boolean} [param.initLights=true] 是否创建默认灯光。
* @private
*/
_initLight(param) {
let initLights = Utils.parseValue(param['initLights'], true);
if (!initLights) {
return;
}
let _private = this[__.private];
let root = _private.rootObjects['scene'];
// Create ambient light
let ambientLight = new AmbientLight({
parent: root,
name: 'mainAmbientLight',
tags: ['MainAmbientLight']
});
// Create main light
let mainLight = new DirectionalLight({
parent: root,
name: 'mainDirectionalLight',
target: [0, 0, 0],
tags: ['MainDirectionalLight']
});
// Initialize adapater for main light as default
let adapter = mainLight.adapter;
if (_private.app.view._compatibleRender) {
adapter.horzAngle = 30;
}
else {
adapter.horzAngle = 40;
}
adapter.vertAngle = 30;
adapter.bind(root);
// Create lights finished
this.ambientLight = ambientLight;
this.mainLight = mainLight;
}
/**
* 初始化渲染配置代理,配置变更后会自动同步到底层场景。
* @private
*/
_initRenderOptions() {
let _private = this[__.private];
_private.renderOptions = new ObjectProxy({
data: {
environment: {
quaternion: null
},
},
onChange: function (ev) {
if (_private.scene) {
_private.scene.setRenderOptions(ev.data);
}
}
});
}
/**
* 初始化环境贴图。未传入贴图且默认设置启用时,会加载视图提供的默认环境贴图。
* @param {object} param 初始化参数。
* @param {string|Array<string>|THING.CubeTexture|THING.ImageTexture} [param.envMap] 环境贴图配置。
* @private
*/
_initEnvMap(param) {
const envMap = Utils.parseValue(param['envMap'], null);
let _private = this[__.private];
const app = _private.app;
if (Utils.isArray(envMap)) {
const imageTexture = new CubeTexture(envMap);
this.envMap = imageTexture;
}
else if (Utils.isString(envMap)) {
if ((envMap.indexOf('.png') > -1 || envMap.indexOf('.jpg') > -1)) {
const imageTexture = new ImageTexture({
url: envMap,
mappingType: ImageMappingType.EquirectangularReflection
});
this.envMap = imageTexture;
}
else {
const urls = Utils.parseCubeTextureUrlsByPath(envMap);
const imageTexture = new ImageTexture(urls);
this.envMap = imageTexture;
}
}
else if (param.defaultSettings.envMap) {
app.view.getDefaultEnvImage().then((envMap) => {
if (envMap) {
this.envMap = new CubeTexture({ data: envMap });
}
});
}
}
// #endregion
/**
* 销毁场景管理对象并释放根对象、Overlay 节点、渲染配置、底层场景和回调。
* @private
*/
dispose() {
let _private = this[__.private];
if (_private.overlayRootObject) {
_private.overlayRootObject.destroy();
_private.overlayRootObject = null;
}
_private.overlayRootNode.dispose();
_private.overlayMarkerRootNode.dispose();
_private.overlayRootNode = null;
_private.overlayMarkerRootNode = null;
_private.accessors = null;
_private.app.objectManager.clearKeepAliveObjects();
for (let key in _private.rootObjects) {
_private.rootObjects[key].destroy(true);
}
_private.rootObjects = {};
_private.renderOptions.dispose();
_private.renderOptions = null;
_private.scene.dispose();
_private.scene = null;
_private.ambientLight = null;
_private.mainLight = null;
_private.envMap = null;
_private.curCameraIndex = -1;
_private.cameras = [];
_private.beforeRenderCallbacks.clear();
_private.afterRenderCallbacks.clear();
_private.isDisposed = true;
}
/**
* 调整场景内所有渲染相机和 Overlay 根对象尺寸。
* @param {number} width 视口宽度,单位为像素。
* @param {number} height 视口高度,单位为像素。
* @private
*/
resize(width, height) {
let _private = this[__.private];
_private.cameras.forEach(camera => {
camera.resize(width, height);
});
if (_private.overlayRootObject) {
_private.overlayRootObject.width = width;
_private.overlayRootObject.height = height;
}
}
/**
* 执行一帧渲染。
*
* 渲染顺序为:主相机、附加渲染相机、Overlay;无主相机时直接跳过。
* @param {number} deltaTime 距离上一帧的时间,单位为秒。
* @private
*/
render(deltaTime) {
let _private = this[__.private];
let cameras = _private.cameras;
let mainCamera = cameras[0];
if (!mainCamera) {
return;
}
let mainCameraNode = mainCamera.node;
// Prepare for rendering
this._onBeforeRender(deltaTime, mainCameraNode);
if (_private.visible) {
// Render main camera
if (mainCameraNode.getVisible()) {
mainCameraNode.render();
}
// Render scene with other cameras
for (let i = 1, l = cameras.length; i < l; i++) {
let camera = cameras[i];
if (!camera.visible) {
continue;
}
// Render to texture
if (camera.renderTexture) {
camera.node.render(camera.renderTexture.getTextureResource());
}
// Render to viewport
else if (camera.enableViewport && camera.viewport) {
camera.node.render();
}
}
// Render overlay
if (_private.overlayRootNode && _private.overlayRootNode.getVisible()) {
// We can not clear color buffer due to context use the same color buffer ...
let clearColorBuffer = mainCameraNode.getAttribute(_ClearColorBuffer);
mainCameraNode.setAttribute(_ClearColorBuffer, false);
mainCameraNode[_renderOverlayFuncName]();
mainCameraNode.setAttribute(_ClearColorBuffer, clearColorBuffer);
}
}
// Finished for rendering
this._onAfterRender(deltaTime, mainCameraNode);
}
/**
* 添加一个附加渲染相机。附加相机默认不清除颜色缓冲,便于渲染到纹理或指定视口。
* @param {THING.Camera} camera 相机对象。
* @private
*/
addRenderCamera(camera) {
let _private = this[__.private];
let cameras = _private.cameras;
// For other render camera, we are not going to clear color buffer as default
let cameraNode = camera.node;
cameraNode.setAttribute(_ClearColorBuffer, false);
let mainCamera = cameras[_private.curCameraIndex];
Utils.pushToArray(cameras, camera);
_private.curCameraIndex = cameras.indexOf(mainCamera);
}
/**
* 移除一个附加渲染相机,并保持当前逻辑相机索引不变。
* @param {THING.Camera} camera 相机对象。
* @private
*/
removeRenderCamera(camera) {
let _private = this[__.private];
let cameras = _private.cameras;
let mainCamera = cameras[_private.curCameraIndex];
Utils.removeFromArray(cameras, camera);
_private.curCameraIndex = cameras.indexOf(mainCamera);
}
/**
* 渲染生命周期回调。
* @callback OnRenderCallback
* @param {number} deltaTime 距离上一帧的时间,单位为秒。
* @param {object} camera 当前用于主渲染的底层相机节点。
* @private
*/
/**
* 添加渲染前回调。
* @param {OnRenderCallback} callback 回调函数。
* @private
*/
addBeforeRenderCallback(callback) {
this[__.private].beforeRenderCallbacks.add(callback);
}
/**
* 移除渲染前回调。
* @param {OnRenderCallback} callback 回调函数。
* @private
*/
removeBeforeRenderCallback(callback) {
this[__.private].beforeRenderCallbacks.remove(callback);
}
/**
* 添加渲染后回调。
* @param {OnRenderCallback} callback 回调函数。
* @private
*/
addAfterRenderCallback(callback) {
this[__.private].afterRenderCallbacks.add(callback);
}
/**
* 移除渲染后回调。
* @param {OnRenderCallback} callback 回调函数。
* @private
*/
removeAfterRenderCallback(callback) {
this[__.private].afterRenderCallbacks.remove(callback);
}
/**
* 根据内部类型标识获取场景属性。
* @param {string} type 内部类型标识。
* @returns {*} 对应的内部对象;无法匹配时返回 null。
* @private
*/
getAttribute(type) {
const overlayRootObject = StringEncoder.toText("<@secret OverlayRootObject>");
const accessors = StringEncoder.toText("<@secret Accessors>");
let _private = this[__.private];
switch (type) {
case overlayRootObject:
if (!_private.overlayRootObject) {
let app = _private.app;
_private.overlayRootObject = new Object3D({
name: StringEncoder.toText("<@secret overlayRoot>"),
rootNode: _private.overlayRootNode,
parent: null,
queryable: false,
});
_private.overlayRootObject.x = 0;
_private.overlayRootObject.y = 0;
_private.overlayRootObject.width = app.size[0];
_private.overlayRootObject.height = app.size[1];
}
return _private.overlayRootObject;
case accessors:
return _private.accessors;
default:
return null;
}
}
// #region Accessor
/**
* 获取底层场景根节点。
* @type {object}
* @private
*/
get root() {
return this[__.private].scene.getRoot();
}
/**
* 获取场景逻辑根对象集合。
* @type {object}
* @private
*/
get rootObjects() {
return this[__.private].rootObjects;
}
/**
* 获取场景渲染相机列表。数组第 0 项为主渲染相机。
* @type {Array<THING.Camera>}
* @public
*/
get cameras() {
return this[__.private].cameras;
}
/**
* 获取/设置当前逻辑相机。
* 设置后会同时将该相机设为主渲染相机。
* @type {THING.Camera}
* @public
*/
get camera() {
let _private = this[__.private];
return _private.cameras[_private.curCameraIndex];
}
set camera(value) {
let _private = this[__.private];
let index = _private.cameras.indexOf(value);
if (index === -1) {
_private.cameras.push(value);
index = _private.cameras.length - 1;
}
_private.curCameraIndex = index;
// Use camera as render camera
this.renderCamera = value;
// Keep it alive
_private.app.objectManager.addKeepAliveObject(value);
}
/**
* 获取/设置主渲染相机。
*
* 主渲染相机固定在相机列表第 0 项,并负责渲染到主视口。
* @type {THING.Camera}
* @private
*/
get renderCamera() {
let _private = this[__.private];
return _private.cameras[0];
}
set renderCamera(value) {
let _private = this[__.private];
let cameras = _private.cameras;
let mainCamera = cameras[_private.curCameraIndex];
// Disable previous main camera render to viewport
let mainRenderCamera = cameras[0];
if (mainRenderCamera && mainRenderCamera != value) {
mainRenderCamera.enableViewport = false;
}
let index = cameras.indexOf(value);
if (index === -1) {
cameras._insert(0, value);
}
else if (index !== 0) {
cameras._swap(0, index);
}
_private.curCameraIndex = cameras.indexOf(mainCamera);
// Enable render to viewport
value.enableViewport = true;
}
/**
* 获取/设置实例化绘制偏移量,会影响所有实例化绘制对象的相对位置。
* @type {Array<number>}
* @example
* app.scene.instancedOffset = [10000, 10000, 10000];
* @public
*/
get instancedOffset() {
let _private = this[__.private];
return _private.instancedOffset;
}
set instancedOffset(value) {
let _private = this[__.private];
if (value) {
_private.instancedOffset = value.slice(0);
this.root.setInstancedOffset(_private.instancedOffset);
}
else {
_private.instancedOffset = null;
this.root.setInstancedOffset([0, 0, 0]);
}
}
/**
* 获取/设置静态模式,用于开启或关闭变换矩阵缓存以优化矩阵计算。
*
* 加载资源时通常关闭,预览渲染时再开启。
* @type {boolean}
* @public
*/
get staticMode() {
let _private = this[__.private];
return _private.scene.isEnabled('TransformCache');
}
set staticMode(value) {
let _private = this[__.private];
_private.scene.enable('TransformCache', value);
}
/**
* 获取/设置 环境贴图资源。
* @type {THING.CubeTexture|THING.ImageTexture}
* @public
*/
get envMap() {
let _private = this[__.private];
return _private.envMap;
}
set envMap(value) {
let _private = this[__.private];
let scene = _private.scene;
if (_private.envMap) {
_private.envMap.release();
}
if (value) {
if (value.isImageTexture || value.isCubeTexture) {
_private.envMap = value;
_private.envMap.addRef();
_private.envMap.waitForComplete().then((image) => {
if (_private.isDisposed) {
return;
}
scene.setEnvironment(image.getTextureResource());
});
}
// Image url of String type
else if (Utils.isString(value)) {
_private.envMap = new ImageTexture(value);
_private.envMap.mappingType = ImageMappingType.EquirectangularReflection;
_private.envMap.waitForComplete().then((image) => {
if (_private.isDisposed) {
return;
}
scene.setEnvironment(image.getTextureResource());
});
}
else {
_private.envMap = null;
scene.setEnvironment(null);
}
}
else {
_private.envMap = null;
scene.setEnvironment(null);
}
}
/**
* 获取/设置环境光照强度,取值范围为0到1。
* @type {number}
* @public
* @example
* app.scene.envMapLightIntensity = 0.5;
* // @expect(app.scene.envMapLightIntensity == 0.5);
*/
get envMapLightIntensity() {
Utils.warn('Please use envDiffuseIntensity instead');
let _private = this[__.private];
return _private.scene.getEnvDiffuseIntensity();
}
set envMapLightIntensity(value) {
Utils.warn('Please use envDiffuseIntensity instead');
let _private = this[__.private];
_private.scene.setEnvDiffuseIntensity(value);
}
/**
* 获取/设置环境漫反射强度,取值范围为0到1。
* @type {number}
* @public
* @example
* app.scene.envDiffuseIntensity = 0.5;
* // @expect(app.scene.envDiffuseIntensity == 0.5);
*/
get envDiffuseIntensity() {
let _private = this[__.private];
return _private.scene.getEnvDiffuseIntensity();
}
set envDiffuseIntensity(value) {
let _private = this[__.private];
_private.scene.setEnvDiffuseIntensity(value);
}
/**
* 获取/设置环境镜面反射强度,取值范围为0到1。
* @type {number}
* @public
* @example
* app.scene.envSpecularIntensity = 0.5;
* // @expect(app.scene.envSpecularIntensity == 0.5);
*/
get envSpecularIntensity() {
let _private = this[__.private];
return _private.scene.getEnvSpecularIntensity();
}
set envSpecularIntensity(value) {
let _private = this[__.private];
_private.scene.setEnvSpecularIntensity(value);
}
/**
* 获取/设置场景默认环境光。
* @type {THING.AmbientLight}
* @public
* @example
* let app = THING.App.current;
* app.scene.ambientLight.color = 'blue';
* app.scene.ambientLight.intensity = 0.1;
* let ret1 = app.scene.ambientLight.color == 'blue';
* let ret2 = app.scene.ambientLight.intensity == 0.1;
* // @expect(ret1 == true && ret2 == true);
*/
get ambientLight() {
return this[__.private].ambientLight;
}
set ambientLight(value) {
let _private = this[__.private];
_private.ambientLight = value;
// Keep it alive to prevent destroy action
_private.app.objectManager.addKeepAliveObject(value);
}
/**
* 获取/设置场景主平行光。
* @type {THING.DirectionalLight}
* @public
*/
get mainLight() {
return this[__.private].mainLight;
}
set mainLight(value) {
let _private = this[__.private];
_private.mainLight = value;
// Keep it alive to prevent destroy action
_private.app.objectManager.addKeepAliveObject(value);
}
/**
* @typedef {object} SceneRenderEnvironmentOptions
* @property {Array<number>} quaternion 环境贴图旋转四元数。
* @public
*/
/**
* @typedef {object} SceneRenderOptions
* @property {SceneRenderEnvironmentOptions} environment 环境渲染配置。
* @public
*/
/**
* 获取/设置渲染配置。
* @type {SceneRenderOptions}
* @public
*/
get renderOptions() {
let _private = this[__.private];
return _private.renderOptions.dataProxy;
}
set renderOptions(value) {
let _private = this[__.private];
let scene = _private.scene;
let dataProxy = _private.renderOptions.dataProxy;
if (value) {
Utils.mergeObject(dataProxy, value, true);
scene.setRenderOptions(dataProxy);
}
else {
dataProxy.environment.quaternion = null;
scene.setRenderOptions(dataProxy);
}
}
/**
* 获取当前渲染状态。
* @type {RenderStateResult}
* @private
*/
get renderState() {
let _private = this[__.private];
return _private.scene.getRenderState();
}
/**
* @public
* 获取/设置输出颜色编码类型。
* @type {TexelEncodingType}
*/
get outputEncodingType() {
let _private = this[__.private];
return _private.scene.getOutputEncodingType();
}
set outputEncodingType(value) {
let _private = this[__.private];
_private.scene.setOutputEncodingType(value);
}
/**
* @public
* 获取/设置最大灯光分组数量。
* @type {number}
*/
get maxLightingGroupCount() {
let _private = this[__.private];
return _private.scene.getMaxLightingGroupCount();
}
set maxLightingGroupCount(value) {
let _private = this[__.private];
_private.scene.setMaxLightingGroupCount(value);
}
/**
* 获取/设置场景是否参与主渲染。
* @type {boolean}
* @public
*/
get visible() {
let _private = this[__.private];
return _private.visible;
}
set visible(value) {
let _private = this[__.private];
_private.visible = value;
}
/**
* 获取底层渲染场景对象。
* @type {object}
* @private
*/
get context() {
let _private = this[__.private];
return _private.scene;
}
// #endregion
/**
* 标识当前对象为 Scene 实例。
* @type {boolean}
* @readonly
*/
get isScene() {
return true;
}
}
export { Scene }