import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
import { Flags } from '@uino/base-thing';
import { ImageWrapType } from '../const';
const __ = {
private: Symbol('private'),
}
const Flag = {
CastShadow: 1 << 0,
ReceiveShadow: 1 << 1
}
// Instanced drawing mode
const InstancedDrawingMode = {
None: 1,
Doing: 2,
Done: 3,
};
const _callbackKeys = [
'opacity',
'color',
'metalness',
'roughness',
'emissive'
];
const _defaultOptions = {};
const _alwaysOnTopLayerNumber = 2;
const _defaultTextureParam = { wrapS: ImageWrapType.Repeat, wrapT: ImageWrapType.Repeat };
// #region Private Functions
// Check whether need to create instanced drawing style from style group.
function _canCreateInstancedDrawingStyle(object, styleGroupPool) {
let style = object.bodyNode.getStyle();
// If we have already used instanced drawing mode then skip to recreate it again
if (style.isEnabled('InstancedDrawing')) {
return false;
}
// Check whether it's already using style from group
if (styleGroupPool.hasStyle(style)) {
return false;
}
return true;
}
// Convert 2d array matrix to 1d array matrix.
function _convertMatrices(matrices) {
let _matrices = [];
for (let i = 0, il = matrices.length; i < il; i++) {
let m = matrices[i];
for (let j = 0, jl = m.length; j < jl; j++) {
_matrices.push(m[j]);
}
}
return _matrices;
}
// #endregion
/**
* @class RenderComponent
* The render component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class RenderComponent extends BaseComponent {
static mustCopyWithInstance = true;
/**
* The render control of object, like using instanced drawing mode.
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
// Render attributes
_private.renderOrder = 0;
_private.initRenderLayer = null;
_private.renderLayer = null;
_private.flags = new Flags();
_private.flags.set(Flag.CastShadow | Flag.ReceiveShadow);
// Instanced drawing - Style
_private.instancedDrawingMode = InstancedDrawingMode.None;
_private.instanceGroupName = '';
// Instanced drawing - Node
_private.instancingNodeInfo = null;
_private.instancingNode = null;
// The callbacks
_private.callbacks = null;
_private.imageTexture = null;
_private.modelInfo = null;
_private.unloadImageTexture = () => {
if (_private.imageTexture) {
_private.imageTexture.release();
_private.imageTexture = null;
}
}
}
// #region Private
_setRenderAttribute(key, funcName, value, recursive) {
let _private = this[__.private];
_private[key] = value;
// Get the object
let object = this.object;
// Update body node's render order
let bodyNode = object.bodyNode;
if (bodyNode[funcName]) {
bodyNode[funcName](value);
}
// Change all children if needed
if (recursive) {
if (object.hasChildren()) {
let children = object.children;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
child[funcName](value, true);
}
}
}
}
_canMakeInstancedDrawing() {
let _private = this[__.private];
// Get the object
let object = this.object;
// Let other module to determine whether to process instanced drawing
if (_private.callbacks) {
let beforeMakeInstancedDrawingFunc = _private.callbacks.beforeMakeInstancedDrawing;
if (beforeMakeInstancedDrawingFunc && beforeMakeInstancedDrawingFunc(object) === false) {
return false;
}
}
// Make sure instancing(node) is disable
if (_private.instancingNodeInfo) {
return false;
}
// Make sure it has instance group name or resource url
if (!object.instanceGroupName && !object.resource.url) {
return false;
}
// Make sure style is existing
let style = object.body.style;
if (!style) {
return false;
}
// If some attribute use callback function then we can not enable instanced drawing
for (let i = 0; i < _callbackKeys.length; i++) {
let func = style[_callbackKeys[i]];
// If it's function without returning constant value then skip to make instanced drawing
if (Utils.isFunction(func) && !func.isConstantValue) {
return false;
}
}
// Make sure all animations are stopped
if (object.hasComponent('animation')) {
if (object.animation.hasInited()) {
if (!object.animation.isAllAnimationsReady()) {
return false;
}
}
}
return true;
}
_makeInstancedDrawing(style, styleGroupPool, renderMode) {
let object = this.object;
let instancedDrawing = renderMode == 'InstancedRendering' ? true : false;
let instancedDrawingStyle = styleGroupPool.createStyleFromObject(object, { instancedDrawing });
if (!instancedDrawingStyle) {
return;
}
let _private = this[__.private];
style.initResource(instancedDrawingStyle);
style.updateBaseValues(object.body.style.values);
_private.instancedDrawingMode = InstancedDrawingMode.Done;
}
_tryMakeInstancedDrawing(style, renderMode) {
let _private = this[__.private];
// If it can not make instanced drawing
if (!this._canMakeInstancedDrawing()) {
// Enable instanced drawing failed, resume the state
_private.instancedDrawingMode = InstancedDrawingMode.None;
return false;
}
let object = this.object;
let styleGroupPool = object.app.resourceManager.getStyleGroupPool();
// Check whether need to re-create style from group
if (_canCreateInstancedDrawingStyle(object, styleGroupPool)) {
// Wait copy operation finished first
if (_private.copyPromise) {
_private.copyPromise.then(() => {
this._makeInstancedDrawing(style, styleGroupPool, renderMode);
});
}
else {
this._makeInstancedDrawing(style, styleGroupPool, renderMode);
}
}
_private.instancedDrawingMode = InstancedDrawingMode.Done;
return true;
}
// #endregion
// #region Common
getRenderOrder() {
let _private = this[__.private];
return _private.renderOrder;
}
setRenderOrder(value, recursive) {
this._setRenderAttribute('renderOrder', 'setRenderOrder', value, recursive);
}
getCastShadow() {
let _private = this[__.private];
return _private.flags.has(Flag.CastShadow);
}
setCastShadow(value, recursive) {
let _private = this[__.private];
this._setRenderAttribute('castShadow', 'setCastShadow', value, recursive);
_private.flags.enable(Flag.CastShadow, value);
}
getReceiveShadow() {
let _private = this[__.private];
return _private.flags.has(Flag.ReceiveShadow);
}
setReceiveShadow(value, recursive) {
let _private = this[__.private];
this._setRenderAttribute('receiveShadow', 'setReceiveShadow', value, recursive);
_private.flags.enable(Flag.ReceiveShadow, value);
}
syncAttributes() {
let bodyNode = this.object.bodyNode;
if (bodyNode.isRenderableNode) {
let _private = this[__.private];
if (bodyNode.getCastShadow() != this.castShadow) {
bodyNode.setCastShadow(this.castShadow);
}
if (bodyNode.getReceiveShadow() != this.receiveShadow) {
bodyNode.setReceiveShadow(this.receiveShadow);
}
if (bodyNode.getRenderOrder() != _private.renderOrder) {
bodyNode.setRenderOrder(_private.renderOrder);
}
let renderLayer = bodyNode.getRenderLayer();
_private.initRenderLayer = renderLayer;
if (renderLayer != _private.renderLayer) {
bodyNode.setRenderLayer(_private.renderLayer);
}
}
}
getGeometryInfo() {
return this.object.node.getGeometryInfo();
}
get callbacks() {
let _private = this[__.private];
_private.callbacks = _private.callbacks || {
beforeMakeInstancedDrawing: null
};
return _private.callbacks;
}
get renderOrder() {
return this.getRenderOrder();
}
set renderOrder(value) {
this.setRenderOrder(value, true);
}
get castShadow() {
return this.getCastShadow();
}
set castShadow(value) {
this.setCastShadow(value, true);
}
get receiveShadow() {
return this.getReceiveShadow();
}
set receiveShadow(value) {
this.setReceiveShadow(value, true);
}
get alwaysOnTop() {
let _private = this[__.private];
return _private.renderLayer == _alwaysOnTopLayerNumber;
}
set alwaysOnTop(value) {
let _private = this[__.private];
let bodyNode = this.object.bodyNode;
if (!bodyNode.isRenderableNode) {
return;
}
if (value) {
_private.renderLayer = _alwaysOnTopLayerNumber;
bodyNode.setRenderLayer(_alwaysOnTopLayerNumber);
}
else {
_private.renderLayer = null;
bodyNode.setRenderLayer(_private.initRenderLayer);
}
}
// #endregion
// #region Instanced Drawing - Style
makeInstancedDrawing(value = true, options = _defaultOptions) {
let _private = this[__.private];
if (_private.instancedDrawingMode == InstancedDrawingMode.Doing) {
return false;
}
// Get the object
let object = this.object;
// Get the current values of style, in order to update base values later
let style = object.body.style;
if (!style) {
return false;
}
// If style instance equal value, return
if (style.isInstancedDrawing == value) {
_private.instancedDrawingMode = value ? InstancedDrawingMode.Done : InstancedDrawingMode.None;
return true;
}
if (value) {
// Get the render mode
let renderMode = Utils.parseValue(options['renderMode'], 'InstancedRendering');
if (object.loaded) {
if (!this._tryMakeInstancedDrawing(style, renderMode)) {
return false;
}
}
else {
// Wait for complete
object.waitForComplete().then(
// OK
() => {
this._tryMakeInstancedDrawing(style, renderMode);
},
// Error
() => {
// Enable instanced drawing mode failed due to object had been destroyed
_private.instancedDrawingMode = InstancedDrawingMode.None;
}
);
_private.instancedDrawingMode = InstancedDrawingMode.Doing;
}
}
else {
// Get the body node
let bodyNode = object.bodyNode;
// Disable instanced drawing
_private.instancedDrawingMode = InstancedDrawingMode.None;
// Create style without enable instanced drawing
let resource = Utils.createObject('Style');
resource.copy(bodyNode.getStyle());
resource.enable('InstancedDrawing', false);
// Update style base values
let values = Object.assign({}, style.values);
style.initResource(resource);
style.updateBaseValues(values);
}
return true;
}
get isInstancedDrawing() {
let _private = this[__.private];
return _private.instancedDrawingMode != InstancedDrawingMode.None;
}
get instanceGroupName() {
return this[__.private].instanceGroupName;
}
set instanceGroupName(value) {
this[__.private].instanceGroupName = value;
}
// #endregion
// #region Instanced Drawing - Node
enableInstancing(options) {
let _private = this[__.private];
if (!_private.instancingNodeInfo) {
// Make sure object had been loaded
this.object.waitForComplete().then(() => {
// We can not use style instanced drawing at the same time
if (this.isInstancedDrawing) {
return false;
}
let instancingNode = this.object.bodyNode.getAttribute('Instancing');
if (!instancingNode) {
return false;
}
_private.instancingNode = instancingNode;
_private.instancingNodeInfo = {
maxNumber: 0,
number: 0,
matrices: [],
pickedIds: []
};
if (options) {
if (!this.setInstancing(options)) {
return false;
}
}
});
}
return true;
}
disableInstancing() {
let _private = this[__.private];
if (!_private.instancingNodeInfo) {
return;
}
this.object.bodyNode.setAttribute('Instancing', null);
_private.instancingNodeInfo = null;
_private.instancingNode = null;
}
setInstancing(options) {
let _private = this[__.private];
let instancingNodeInfo = _private.instancingNodeInfo;
if (!instancingNodeInfo) {
return false;
}
let maxNumber = Utils.parseValue(options['maxNumber'], instancingNodeInfo.maxNumber);
let number = Utils.parseValue(options['number'], instancingNodeInfo.number);
let matrices = Utils.parseArray(options['matrices'], instancingNodeInfo.matrices);
let pickedIds = Utils.parseArray(options['pickedIds'], instancingNodeInfo.pickedIds);
if (number > maxNumber) {
return false;
}
if (matrices.length != maxNumber) {
return false;
}
if (pickedIds.length != maxNumber) {
return false;
}
instancingNodeInfo.maxNumber = maxNumber;
instancingNodeInfo.number = number;
instancingNodeInfo.matrices = matrices;
instancingNodeInfo.pickedIds = pickedIds;
let instancingNode = _private.instancingNode;
instancingNode.begin();
instancingNode.setMaxCount(instancingNodeInfo.maxNumber);
instancingNode.setCount(instancingNodeInfo.number);
instancingNode.setMatrices(_convertMatrices(instancingNodeInfo.matrices));
instancingNode.setPickedIds(instancingNodeInfo.pickedIds);
instancingNode.end();
return true;
}
getInstancing() {
let _private = this[__.private];
return _private.instancingNodeInfo;
}
// #endregion
load(options, resolve, reject) {
let object = this.object;
if (object.destroyed) {
reject(`The object had been destroyed, skip to load resources`);
return;
}
let _private = this[__.private];
let modelOptions = {
lights: true
};
let objResource = object.resource;
if (objResource.nodeName) {
modelOptions.nodeName = objResource.nodeName;
}
if (objResource.excludeNodeNames) {
modelOptions.excludeNodeNames = objResource.excludeNodeNames;
}
if (objResource.inverseRotationMode) {
modelOptions.inverseRotationMode = objResource.inverseRotationMode;
}
if (objResource.instanceStyle) {
for (const key in objResource.instanceStyle) {
modelOptions[key] = objResource.instanceStyle[key];
}
}
const mapData = modelOptions.map;
if (mapData) {
_private.imageTexture = this.app.resourceManager.getTextureManager().load(mapData.url, _defaultTextureParam, {
flipY: mapData.flipY
});
modelOptions['image'] = _private.imageTexture.getTextureResource();
delete modelOptions['texture'];
}
const envapData = modelOptions.envMap;
if (envapData) {
modelOptions['envMap'] = envapData.getTextureResource();
}
if (objResource.instanceId) {
modelOptions['id'] = objResource.instanceId;
modelOptions['useInstance'] = true;
}
if (objResource.instanceCount) {
modelOptions['instanceCount'] = objResource.instanceCount;
}
if (options.indexData) {
modelOptions.indexData = options.indexData;
}
if (options.blobMap) {
modelOptions.blobMap = options.blobMap;
}
const onBeforeSetup = modelOptions.onBeforeSetup;
object.app.resourceManager.loadObjectResource(object, modelOptions, {
onBeforeSetup: (ev) => {
onBeforeSetup && onBeforeSetup(ev);
},
onSetup: (ev) => {
let info = ev.info;
_private.modelInfo = info;
},
onLoad: () => {
if (_private.imageTexture) {
_private.imageTexture.waitForComplete().then(resolve, reject);
}
else {
resolve();
}
},
onError: (ev) => {
reject(ev);
}
});
}
onBeforeRemove() {
let _private = this[__.private];
_private.unloadImageTexture();
}
onUnloadResource() {
let _private = this[__.private];
_private.unloadImageTexture();
}
get modelInfo() {
let _private = this[__.private];
return _private.modelInfo;
}
}
export { RenderComponent }