import { Flags, ObjectAttributes } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { EventType, ImageSlotType, PlayStateType, Empty } from '../const';
const __ = {
private: Symbol('private'),
}
// #region Internal Variables
const cAnimationTagName = '__Style_AnimationTagName__';
const Flag = {
// #region Diff
// Type
BlendingType: 1 << 0,
SideType: 1 << 1,
// Number
Opacity: 1 << 2,
Metalness: 1 << 3,
Roughness: 1 << 4,
// Color
Color: 1 << 5,
Emissive: 1 << 6,
OutlineColor: 1 << 7,
Transparent: 1 << 8,
// Object
ClippingPlanes: 1 << 9,
Edge: 1 << 10,
Effect: 1 << 11,
Images: 1 << 12,
UVMatrix: 1 << 13,
// Boolean
Wireframe: 1 << 14,
DepthTest: 1 << 15,
DepthWrite: 1 << 16,
EnvMapping: 1 << 17,
// External
Animation: 1 << 18,
Attributes: 1 << 19,
Uniforms: 1 << 20,
Macros: 1 << 21,
// #endregion
// #region State
DelayBegin: 1 << 22,
HookAnimationEvent: 1 << 23,
ChangingDefaultValues: 1 << 24
// #endregion
};
const cBaseValueFlags =
Flag.BlendingType |
Flag.SideType |
Flag.Opacity |
Flag.Metalness |
Flag.Roughness |
Flag.Color |
Flag.Emissive |
Flag.OutlineColor |
Flag.Transparent |
Flag.ClippingPlanes |
Flag.Wireframe |
Flag.DepthTest |
Flag.DepthWrite |
Flag.EnvMapping;
const cDiffValueFlags =
cBaseValueFlags |
Flag.Edge |
Flag.Effect |
Flag.Images |
Flag.UVMatrix |
Flag.Animation |
Flag.Attributes |
Flag.Uniforms |
Flag.Macros;
const cDefaultValues = {
// Type
blendingType: null,
sideType: null,
// Number
opacity: null,
metalness: null,
roughness: null,
// Color
color: null,
emissive: null,
outlineColor: null,
transparent: null,
// Object
clippingPlanes: null,
// Boolean
wireframe: null,
depthTest: null,
depthWrite: null,
envMapping: null
};
const cDefaultValueKeys = Object.keys(cDefaultValues);
/**
* @typedef {Object} StyleEdgeResult
* @property {Boolean} enable True indicates enable it.
* @property {Array<Number>} color The color.
* @property {Number} opacity The opacity.
* @property {Boolean} glow True indicates enable glow effect.
*/
const cEdgeDefaultValues = {
enable: false,
color: [1, 1, 1],
opacity: 1,
glow: false,
}
const cEdgeDefaultValueKeys = Object.keys(cEdgeDefaultValues);
/**
* @typedef {Object} StyleEffectResult
* @property {Number} glow The glow intensity (null indicates resume original effect).
* @property {Boolean} innerGlow The inner glow effect (null indicates resume original effect).
* @property {Boolean} lineBloom The line bloom effect (null indicates resume original effect).
* @property {Boolean} tailing The tailing effect (null indicates resume original effect).
* @property {Boolean} radial The radial effect (null indicates resume original effect).
* @property {Boolean} ghosting The ghosting effect (null indicates resume original effect).
* @public
*/
const cEffectDefaultValues = {
glow: null,
innerGlow: null,
lineBloom: null,
tailing: null,
radial: null,
ghosting: null
};
const cEffectDefaultValueKeys = Object.keys(cEffectDefaultValues);
/**
* @typedef {Object} StyleImagesResult
* @property {Object} map
* @property {Object} envMap
* @property {Object} alphaMap
* @property {Object} emissiveMap
* @property {Object} normalMap
* @property {Object} colorMapping
* @property {Object} aoMap
* @public
*/
const cImagesDefaultValues = {
map: null,
envMap: null,
alphaMap: null,
emissiveMap: null,
normalMap: null,
colorMapping: null,
aoMap: null,
};
const cImagesDefaultValueKeys = Object.keys(cImagesDefaultValues);
function _createUVMatrixDefaultValues() {
return {
offset: [0, 0],
repeat: [1, 1],
center: [0, 0],
rotation: 0
};
}
/**
* @typedef {Object} StyleUVMatrixResult
* @property {Array<Number>} offset The offset of UV matrix, default: [0, 0]
* @property {Array<Number>} repeat The repeat of UV matrix, default: [1, 1]
* @property {Array<Number>} center The center of UV matrix, default: [0, 0]
* @property {Number} rotation The rotation in degree.
* @public
*/
const cUVMatrixDefaultValues = _createUVMatrixDefaultValues();
const cValueKeys = [
'attributes',
'uniforms',
'macros'
];
// #endregion
// #region Private Functions
// Fast to compare values.
function _equals(v1, v2) {
if (v1 && v2 && Utils.isArray(v1) && Utils.isArray(v2)) {
if (v1.length != v2.length) {
return false;
}
switch (v1.length) {
case 1: return v1[0] == v2[0];
case 2: return v1[0] == v2[0] && v1[1] == v2[1];
case 3: return v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2];
case 4: return v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2] && v1[3] == v2[3];
default:
for (let i = 0; i < v1.length; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
break;
}
}
else if (v1 !== v2) {
return false;
}
return true;
}
function _assignArray(value) {
return value;
}
function _toImageType(type) {
switch (type) {
case 'Map': return 'map';
case 'EnvMap': return 'envMap';
case 'AlphaMap': return 'alphaMap';
case 'EmissiveMap': return 'emissiveMap';
case 'NormalMap': return 'normalMap';
case 'ColorMapping': return 'colorMapping';
case 'AOMap': return 'aoMap';
default:
return type;
}
}
function _toUImageType(type) {
switch (type) {
case 'map': return 'Map';
case 'envMap': return 'EnvMap';
case 'alphaMap': return 'AlphaMap';
case 'emissiveMap': return 'EmissiveMap';
case 'normalMap': return 'NormalMap';
case 'colorMapping': return 'ColorMapping';
case 'aoMap': return 'AOMap';
default:
return type;
}
}
function _needProxy(prop, value) {
if (value && value.isBaseObject) {
return false;
}
return true;
}
function _assignObject(data, target) {
target = target || {};
return Object.assign(target, data);
}
function _cloneObject(data, target) {
target = target || {};
for (let key in data) {
let value = data[key];
if (value !== null && value !== undefined) {
if (Utils.isArray(value)) {
target[key] = value.slice(0);
}
else {
target[key] = value;
}
}
else {
target[key] = value;
}
}
return target;
}
// #endregion
/**
* @class Style
* The style resource of objects.
* @memberof THING
* @public
*/
class Style {
static cDefaultValues = cDefaultValues;
static cEdgeDefaultValues = cEdgeDefaultValues;
static cEffectDefaultValues = cEffectDefaultValues;
static cImagesDefaultValues = cImagesDefaultValues;
static cUVMatrixDefaultValues = cUVMatrixDefaultValues;
/**
* The object style, it can change object's color, outlineColor etc.
* @param {Object} modifier The modifier.
*/
constructor(modifier) {
this[__.private] = {};
let _private = this[__.private];
_private.modifier = modifier;
// The current image slot type
_private.curImageSlotType = ImageSlotType.Map;
// Attributes
_private.edgeData = null;
_private.effectData = null;
_private.imagesData = null;
_private.uvData = null; // Use image type as key
_private.data = new ObjectAttributes({
data: cDefaultValues,
keys: cDefaultValueKeys,
onNeedProxy: _needProxy,
onCloneObject: _cloneObject,
onEquals: _equals
});
// If reach to 0 then indicates use the default original style in Diff flags
_private.state = new Flags();
// The values for attributes/uniforms/macros
_private.values = {
attributes: null,
uniforms: null,
macros: null,
};
// Watch GC to check memory leak
if (_DEBUG) {
this._uuid = MathUtils.generateUUID();
let app = Utils.getCurrentApp();
if (!Style.gcObjects) {
Style.gcObjects = new Set();
app.global.gc.registerDestroyObjectCallback((key) => {
Style.gcObjects.delete(key);
});
}
let key = `type: 'Style', uuid: ${this._uuid}`;
Style.gcObjects.add(key);
app.global.gc.register(this, key);
}
}
// #region Private
// #region Private-Attributes
/**
* Get proxy values or original values.
* @param {*} valueKey
* @returns {Object}
* @private
*/
_getValues(valueKey) {
let _private = this[__.private];
let values = {
data: _private.data[valueKey]
};
if (_private.edgeData) {
values['edge'] = _private.edgeData[valueKey];
}
if (_private.effectData) {
values['effect'] = _private.effectData[valueKey];
}
if (_private.imagesData) {
values['images'] = _private.imagesData[valueKey];
}
if (_private.uvData) {
values['uvData'] = _private.uvData[valueKey];
}
return values;
}
_updateDiffSlots(flag, value) {
let _private = this[__.private];
let diffFlags = _private.state;
// Update flags
let oldValues = diffFlags.values & cDiffValueFlags;
diffFlags.enable(flag, value);
// Begin to change body node style
if (oldValues === 0 && diffFlags.values) {
_private.modifier.onBegin();
if (_private.state.has(Flag.DelayBegin)) {
_private.modifier.resource.begin();
_private.state.enable(Flag.DelayBegin, false);
}
}
// Restore the default original style
else if (oldValues && ((diffFlags.values & cDiffValueFlags) === 0)) {
_private.modifier.onEnd();
}
}
_createColorOpCallback(callback) {
if (!callback) {
return null;
}
let opCallback = function (baseValue) {
let color = callback(baseValue, this);
return Utils.parseColor(color, baseValue);
}
if (callback.isConstantValue) {
opCallback.isConstantValue = true;
}
return opCallback;
}
_createOpCallback(callback) {
if (!callback) {
return null;
}
let opCallback = function (baseValue) {
return callback(baseValue, this);
}
if (callback.isConstantValue) {
opCallback.isConstantValue = true;
}
return opCallback;
}
_createColorCallbackWrapper(key) {
let _private = this[__.private];
return this._createColorOpCallback(baseValue => {
return _private.data.getValueByDefault(key);
});
}
_createOpCallbackWrapper(key, callback) {
let _private = this[__.private];
if (callback) {
return this._createOpCallback(baseValue => {
return callback(baseValue, _private.data.getValueByDefault(key));
});
}
else {
return this._createOpCallback(baseValue => {
return _private.data.getValueByDefault(key);
});
}
}
_updateAttributeValue(flag, key, value, onFormatValue, onChange) {
let _private = this[__.private];
// Clear value
if (Utils.isNull(value)) {
if (!_private.data.setValue(key, null, _equals)) {
return;
}
}
// Set value
else {
// Try to format/convert value
if (onFormatValue) {
value = onFormatValue(value);
}
if (!_private.data.setValue(key, value, _equals, null, _assignArray)) {
return;
}
}
// Update the diff slots of current and base values
let isBaseValue = _private.data.isBaseValue(key, _equals);
this._updateDiffSlots(flag, !isBaseValue);
// Get the value again(the default value could be returned here)
value = _private.data.getValue(key);
// Update renderer style attribute value
onChange(key, value);
}
_updateAttributeOpFunc(flag, key, value, onFormatValue, onCreateOpCallback, onCreateCallbackWrapper) {
let _private = this[__.private];
// Clear it
if (Utils.isNull(value)) {
if (!_private.data.setValue(key, null, _equals)) {
return;
}
// Get the default value to set as latest attribute
if (_private.data.hasValue(key)) {
value = onCreateCallbackWrapper(key);
}
}
// Set it by function
else if (Utils.isFunction(value)) {
value = onCreateOpCallback(value);
if (!_private.data.setValue(key, value, _equals)) {
return;
}
// If we are changing default values and have value already then do not change attribute of style
if (_private.state.has(Flag.ChangingDefaultValues)) {
if (_private.data.hasValue(key, true)) {
return;
}
}
}
// Set it by value(need to wrap function)
else {
// Try to format/convert value
if (onFormatValue) {
value = onFormatValue(value);
if (!value) {
return;
}
}
if (!_private.data.setValue(key, value, _equals, null, _assignArray)) {
return;
}
// If we are changing default values and have value already then do not change attribute of style
if (_private.state.has(Flag.ChangingDefaultValues)) {
if (_private.data.hasValue(key, true)) {
return;
}
}
// Create callback function wrapper due to renderer use callback function as argument
value = onCreateCallbackWrapper(key);
}
// Update the diff slots of current and base values
let isBaseValue = _private.data.isBaseValue(key, _equals);
this._updateDiffSlots(flag, !isBaseValue);
// Update renderer callback function
_private.modifier.onChangeOpFunc(key, value);
}
_updateColor(flag, key, value) {
this._updateAttributeValue(flag, key, value,
(value) => {
return Utils.parseColor(value, null);
},
(key, value) => {
let _private = this[__.private];
_private.modifier.onChangeColor(key, value);
}
);
}
_updateValue(flag, key, value) {
this._updateAttributeValue(flag, key, value, null,
(key, value) => {
let _private = this[__.private];
_private.modifier.onChangeValue(key, value);
}
);
}
_updateColorOpFunc(flag, key, value) {
this._updateAttributeOpFunc(flag, key, value,
(v) => {
return Utils.parseColor(v, null);
},
// By function
(v) => {
return this._createColorOpCallback(v);
},
// By value(we need to wrap function)
(key) => {
return this._createColorCallbackWrapper(key);
}
);
}
_updateValueOpFunc(flag, key, value, callback) {
this._updateAttributeOpFunc(flag, key, value, null,
// By function
(v) => {
return this._createOpCallback(v);
},
// By value(we need to wrap function)
(key) => {
return this._createOpCallbackWrapper(key, callback);
}
);
}
_updateValues(flag, key, value, values) {
values.setValue(key, value, _equals, (key, value) => {
let isBaseValue = _equals(values.getValue(key), value);
this._updateDiffSlots(flag, !isBaseValue);
});
}
_changeValue(flag, funcName, ev) {
let _private = this[__.private];
let key = ev.key;
let value = Utils.parseValue(ev.value, null);
// Update the diff slots of current and base values
let isBaseValue = _private.data.isBaseValue(key, _equals);
this._updateDiffSlots(flag, !isBaseValue);
_private.modifier[funcName](key, value);
}
_createAttributes() {
let _private = this[__.private];
if (!_private.values['attributes']) {
_private.values['attributes'] = new ObjectAttributes({
onChange: (ev) => {
this._changeValue(Flag.Attributes, 'onChangeAttribute', ev);
}
});
}
return _private.values['attributes'];
}
_createUniforms() {
let _private = this[__.private];
if (!_private.values['uniforms']) {
_private.values['uniforms'] = new ObjectAttributes({
onNeedProxy: function (prop, value) {
if (value && value.isImageTexture) {
return false;
}
return true;
},
onChange: (ev) => {
this._changeValue(Flag.Uniforms, 'onChangeUniformValue', ev);
}
});
}
return _private.values['uniforms'];
}
_createMacros() {
let _private = this[__.private];
if (!_private.values['macros']) {
_private.values['macros'] = new ObjectAttributes({
onChange: (ev) => {
this._changeValue(Flag.Macros, 'onChangeMacroValue', ev);
}
});
}
return _private.values['macros'];
}
// #endregion
// #region Private-Image
_updateImage(type, image, isChangingDefaultValues) {
let _private = this[__.private];
// If we had destroyed object then modifier would be null
let modifier = _private.modifier;
if (!modifier) {
return;
}
// If it's default values then need to check the current image state, if it's existing then do not update resource image now
if (isChangingDefaultValues) {
let values = _private.imagesData.getValues().data;
if (values[type]) {
return;
}
}
// We must check the image here again to prevent the image loading order problem
else {
if (_private.imagesData.data[type] != image) {
return;
}
}
// Update diff slots
this._updateDiffSlots(Flag.Images, true);
// Get the U-Image type
let uImageType = _toUImageType(type);
// Update image resource
if (image) {
modifier.onChangeImage(uImageType, image.getTextureResource());
}
// Check whether need to use default image
else {
let defaultImage = _private.imagesData.defaultValues[type];
if (defaultImage) {
modifier.onChangeImage(uImageType, defaultImage.getTextureResource());
}
else {
modifier.onChangeImage(uImageType, null);
}
}
}
_createImagesData() {
let _private = this[__.private];
if (!_private.imagesData) {
_private.imagesData = new ObjectAttributes({
data: cImagesDefaultValues,
keys: cImagesDefaultValueKeys,
onCloneObject: (data) => {
return {
map: data.map,
envMap: data.envMap,
alphaMap: data.alphaMap,
emissiveMap: data.emissiveMap,
normalMap: data.normalMap,
colorMapping: data.colorMapping,
aoMap: data.aoMap
};
},
onFilterProperty: () => {
// We skip to proxy images here
return false;
},
onChange: (ev) => {
},
onNeedProxy: _needProxy,
onCloneObject: _assignObject,
onEquals: _equals
});
}
return _private.imagesData;
}
// #endregion
// #region Private-UV
_updateImageUVMatrix(type, uv) {
let _private = this[__.private];
let offset = uv.offset;
let repeat = uv.repeat;
let center = uv.center;
let rotation = MathUtils.degToRad(uv.rotation);
const c = Math.cos(rotation);
const s = Math.sin(rotation);
let uvMatrix = [
repeat[0] * c, -repeat[1] * s, 0,
repeat[0] * s, repeat[1] * c, 0,
-repeat[0] * (c * center[0] + s * center[1]) + center[0] + offset[0], -repeat[1] * (-s * center[0] + c * center[1]) + center[1] + offset[1], 1
]
this._updateDiffSlots(Flag.UVMatrix, true);
_private.modifier.onChangeUVMatrix(type, uvMatrix);
}
_createUVMatrixData() {
let _private = this[__.private];
if (!_private.uvData) {
_private.uvData = new ObjectAttributes({
data: {
map: cUVMatrixDefaultValues,
envMap: cUVMatrixDefaultValues,
alphaMap: cUVMatrixDefaultValues,
emissiveMap: cUVMatrixDefaultValues,
normalMap: cUVMatrixDefaultValues,
colorMapping: cUVMatrixDefaultValues,
aoMap: cUVMatrixDefaultValues,
},
keys: cImagesDefaultValueKeys,
onCloneObject: (data) => {
return {
map: _createUVMatrixDefaultValues(),
envMap: _createUVMatrixDefaultValues(),
alphaMap: _createUVMatrixDefaultValues(),
emissiveMap: _createUVMatrixDefaultValues(),
normalMap: _createUVMatrixDefaultValues(),
colorMapping: _createUVMatrixDefaultValues(),
aoMap: _createUVMatrixDefaultValues()
};
},
onChange: (ev) => {
let type = _private.curImageSlotType;
let data = ev.data[_toImageType(type)];
if (data) {
this._updateImageUVMatrix(type, data);
}
},
onEquals: _equals
});
}
return _private.uvData;
}
_getImageUVMatrixByType(type) {
let _private = this[__.private];
type = _toImageType(type);
this._createUVMatrixData();
let uvData = _private.uvData.getValueByDefault(type);
if (!uvData) {
_private.uvData.setValue(type, {
'offset': [0, 0],
'repeat': [1, 1],
'center': [0, 0],
'rotation': 0
}, _equals);
uvData = _private.uvData.getValueByDefault(type);
}
return uvData;
}
// #endregion
// #region Private-Edge
_createEdgeData() {
let _private = this[__.private];
if (!_private.edgeData) {
_private.edgeData = new ObjectAttributes({
data: cEdgeDefaultValues,
keys: cEdgeDefaultValueKeys,
onCloneObject: (data) => {
return {
enable: data.enable,
color: data.color.slice(0),
opacity: data.opacity,
glow: data.glow
};
},
onConvertValue: (type, value) => {
if (type == 'color') {
return Utils.parseColor(value, null);
}
},
onChange: (ev) => {
this._updateDiffSlots(Flag.Edge, true);
_private.modifier.onChangeEdge(ev.data);
},
onEquals: _equals
});
}
return _private.edgeData;
}
// #endregion
// #region Private-Effect
_createEffectData() {
let _private = this[__.private];
if (!_private.effectData) {
_private.effectData = new ObjectAttributes({
data: cEffectDefaultValues,
keys: cEffectDefaultValueKeys,
onCloneObject: (data) => {
return {
glow: data.glow,
innerGlow: data.innerGlow,
lineBloom: data.lineBloom,
tailing: data.tailing,
radial: data.radial,
ghosting: data.ghosting
};
},
onChange: (ev) => {
this._updateDiffSlots(Flag.Effect, true);
_private.modifier.onChangeEffect(ev.data);
},
onCloneObject: _assignObject,
onEquals: _equals
});
}
return _private.effectData;
}
// #endregion
// #endregion
dispose() {
let _private = this[__.private];
if (_private.edgeData) {
_private.edgeData.dispose();
_private.edgeData = null;
}
if (_private.effectData) {
_private.effectData.dispose();
_private.effectData = null;
}
if (_private.imagesData) {
_private.imagesData.dispose();
_private.imagesData = null;
}
if (_private.uvData) {
_private.uvData.dispose();
_private.uvData = null;
}
// TODO ? Really need this ?!
// Remember: we should not clear any values here, due to style use the callback functions what reply on it
// if (_private.data) {
// _private.data.dispose();
// _private.data = null;
// }
if (_private.modifier) {
let object = _private.modifier.object;
object.off(EventType.PlayAnimation, cAnimationTagName);
object.off(EventType.StopAnimation, cAnimationTagName);
_private.modifier.dispose();
_private.modifier = null;
}
cValueKeys.forEach(key => {
if (_private.values[key]) {
_private.values[key].dispose();
_private.values[key] = null;
}
});
}
updateBaseValues(values) {
let _private = this[__.private];
let diffValues = _private.state.values & cDiffValueFlags;
if (!diffValues) {
return;
}
if (diffValues & cBaseValueFlags) {
_private.data.updateBaseValues(values.data);
}
if (values.edge) {
this._createEdgeData();
_private.edgeData.updateBaseValues(values.edge);
}
if (values.effect) {
this._createEffectData();
_private.effectData.updateBaseValues(values.effect);
}
if (values.images) {
this._createImagesData();
_private.imagesData.updateBaseValues(values.images);
}
if (values.uv) {
this._createUVMatrixData();
_private.uvData.updateBaseValues(values.uv);
}
cValueKeys.forEach(key => {
if (_private.values[key]) {
_private.values[key].makeCurrentAsBaseValue();
}
});
_private.state.clear(cDiffValueFlags);
}
initResource(resource) {
let _private = this[__.private];
_private.modifier.initResource(resource);
}
updateResource(resource) {
let _private = this[__.private];
_private.modifier.updateResource(resource);
}
getOringialValues() {
return this._getValues('data');
}
getValuesWithDefaults() {
let _private = this[__.private];
let values = {
data: _private.data.getValuesWithDefaults(_equals)
};
if (_private.edgeData) {
values['edge'] = _private.edgeData.getValuesWithDefaults(_equals);
}
if (_private.effectData) {
values['effect'] = _private.effectData.getValuesWithDefaults(_equals);
}
if (_private.imagesData) {
values['images'] = _private.imagesData.getValuesWithDefaults(_equals);
}
if (_private.uvData) {
values['uvData'] = _private.uvData.getValuesWithDefaults(_equals);
}
return values;
}
getData(key) {
if (key == 'edge') {
return this._createEdgeData().data;
}
else if (key == 'effect') {
return this._createEffectData().data;
}
else if (key == 'images') {
return this._createImagesData().data;
}
else if (key == 'uv') {
return this._createUVMatrixData().data;
}
else if (key == 'attributes') {
return this._createAttributes().data;
}
else if (key == 'macros') {
return this._createMacros().data;
}
else if (key == 'uniforms') {
return this._createUniforms().data;
}
return null;
}
copyFromValues(values) {
let data = values['data'];
for (let key in data) {
this[key] = data[key];
}
let edge = values['edge'];
if (edge) {
this.edge = edge;
}
let effect = values['effect'];
if (effect) {
this.effect = effect;
}
let images = values['images'];
if (images) {
for (let key in images) {
let image = images[key];
if (image) {
this.setImage(key, image);
}
}
}
let uvData = values['uvData'];
if (uvData) {
let prevImageSlotType = this.imageSlotType;
for (let key in uvData) {
this.imageSlotType = key;
let uv = uvData[key];
this.uv.offset = uv.offset;
this.uv.repeat = uv.repeat;
this.uv.center = uv.center;
this.uv.rotation = uv.rotation;
}
this.imageSlotType = prevImageSlotType;
}
}
copyFromProperties(values) {
for (let key in values) {
if (key === 'edge') {
let edge = values['edge'];
if (edge) {
this.edge = edge;
}
}
else if (key === 'effect') {
let effect = values['effect'];
if (effect) {
this.effect = effect;
}
}
else if (key === 'images') {
let images = values['images'];
if (images) {
for (let key in images) {
let image = images[key];
if (image) {
this.setImage(key, image);
}
}
}
}
else if (key === 'uvData') {
let uvData = values['uvData'];
if (uvData) {
let prevImageSlotType = this.imageSlotType;
for (let key in uvData) {
this.imageSlotType = key;
let uv = uvData[key];
this.uv.offset = uv.offset;
this.uv.repeat = uv.repeat;
this.uv.center = uv.center;
this.uv.rotation = uv.rotation;
}
this.imageSlotType = prevImageSlotType;
}
}
else {
this[key] = values[key];
}
}
}
/**
* Copy from another style or object.
* @param {Object|THING.Style} style The style or object.
* @returns {THING.Style}
* @private
*/
copy(style) {
if (!style) {
return this;
}
if (style.isStyle) {
this.copyFromValues(style.getOringialValues());
}
else {
if (Utils.isObject(style)) {
for (let key in style) {
this[key] = style[key];
}
}
}
return this;
}
/**
* Get image.
* @param {ImageSlotType} type The image slot type.
* @returns {THING.ImageTexture}
* @private
*/
getImage(type) {
let _private = this[__.private];
if (!_private.imagesData) {
return null;
}
return _private.imagesData.data[_toImageType(type)];
}
/**
* Set image.
* @param {ImageSlotType} type The image slot type.
* @param {THING.ImageTexture} value The image url or resource.
* @private
*/
setImage(type, value) {
// Check value
if (value) {
// Check whether need to clear all textures.
if (value == Empty.ImageTexture) {
value = Utils.getCurrentApp().global.cache.images['empty_image_texture'];
console.warn('THING.Empty.ImageTexture will not be supported soon. Use THING.Empty.Texture instead.')
}
if (value == Empty.Texture) {
value = Utils.getCurrentApp().global.cache.images['empty_image_texture'];
}
if (!Utils.isValidTexture(value)) {
Utils.error(`Set style image failed, due to image is unknown`, value);
return;
}
}
// Convert image type
type = _toImageType(type);
// Create images data
let imagesData = this._createImagesData();
// Use the right image resource to release
let isChangingDefaultValues = imagesData.isChangingDefaultValues();
// Check whether it's same image resource
let imageResource = isChangingDefaultValues ? imagesData.defaultValues[type] : imagesData.data[type];
if (imageResource == value) {
return;
}
// Release previous image
if (imageResource) {
imageResource.release();
}
// Update image data
imagesData.updateValue(type, value);
if (value) {
this._updateImage(type, value, isChangingDefaultValues);
// Add image resource referenced count to keep it alive
value.addRef();
}
else {
this._updateImage(type, null);
}
}
/**
* Get image UV.
* @param {ImageSlotType} type The image slot type.
* @returns {StyleUVMatrixResult}
* @private
*/
getUV(type) {
let uvData = this._getImageUVMatrixByType(type);
if (!uvData) {
return null;
}
return uvData;
}
/**
* Set image UV.
* @param {ImageSlotType} type The image slot type.
* @param {String} key The UV key.
* @param {StyleUVMatrixResult} value The UV value.
* @private
*/
setUV(type, key, value) {
let uvData = this._getImageUVMatrixByType(type);
if (!uvData) {
return;
}
uvData[key] = value;
}
/**
* Begin to set default values.
*/
beginDefaultValues() {
let _private = this[__.private];
_private.state.enable(Flag.ChangingDefaultValues, true);
this._createEdgeData();
this._createEffectData();
this._createImagesData();
this._createUVMatrixData();
_private.edgeData.beginDefaultValues();
_private.effectData.beginDefaultValues();
_private.imagesData.beginDefaultValues();
_private.uvData.beginDefaultValues();
_private.data.beginDefaultValues();
cValueKeys.forEach(key => {
if (_private.values[key]) {
_private.values[key].beginDefaultValues();
}
});
}
/**
* End to set default values.
*/
endDefaultValues() {
let _private = this[__.private];
_private.edgeData.endDefaultValues();
_private.effectData.endDefaultValues();
_private.imagesData.endDefaultValues();
_private.uvData.endDefaultValues();
_private.data.endDefaultValues();
cValueKeys.forEach(key => {
if (_private.values[key]) {
_private.values[key].endDefaultValues();
}
});
_private.state.enable(Flag.ChangingDefaultValues, false);
}
getDiffValues() {
let _private = this[__.private];
let values = _private.data.getDiffValues();
if (_private.edgeData) {
values['edge'] = _private.edgeData.getDiffValues();
}
if (_private.effectData) {
values['effect'] = _private.effectData.getDiffValues();
}
if (_private.imagesData) {
values['images'] = _private.imagesData.getDiffValues();
}
if (_private.uvData) {
values['uvData'] = _private.uvData.getDiffValues();
}
return values;
}
bindAnimationEvents(object) {
let _private = this[__.private];
if (_private.state.has(Flag.HookAnimationEvent)) {
return;
}
_private.state.enable(Flag.HookAnimationEvent, true);
// Hook animation events
let animationNames = object.animationNames;
if (animationNames && animationNames.length) {
object.on(EventType.PlayAnimation, (ev) => {
this._updateDiffSlots(Flag.Animation, true);
}, cAnimationTagName);
object.on(EventType.StopAnimation, (ev) => {
// let animations = object.animations;
// for (let i = 0; i < animations.length; i++) {
// let animation = animations[i];
// if (animation.state != PlayStateType.Ready || animation.state != PlayStateType.Stopped) {
// return;
// }
// }
this._updateDiffSlots(Flag.Animation, false);
}, cAnimationTagName);
}
}
begin() {
if (this.resource) {
this.resource.begin();
}
else {
let _private = this[__.private];
_private.state.enable(Flag.DelayBegin, true);
}
}
end() {
if (this.resource) {
this.resource.end();
}
else {
let _private = this[__.private];
_private.state.enable(Flag.DelayBegin, false);
}
}
/**
* Set attribute.
* @param {String} type The type.
* @param {*} value The value.
* @private
*/
setAttribute(type, value) {
let attributes = this._createAttributes();
// Try to sync base value with resource default attribute value
let currentValue = attributes.getValue(type);
if (currentValue === null && this.resource) {
currentValue = this.resource.getAttribute(type);
if (Utils.isValid(currentValue)) {
attributes.updateBaseValue(type, currentValue);
}
}
this._updateValues(Flag.Attributes, type, value, attributes);
}
/**
* Get attribute.
* @param {String} type The type.
* @returns {*}
* @private
*/
getAttribute(type) {
let attributes = this._createAttributes();
return attributes.getValue(type);
}
getProperties() {
let _private = this[__.private];
let values = _private.data['data'];
if (_private.edgeData) {
values['edge'] = _private.edgeData['data'];
}
if (_private.effectData) {
values['effect'] = _private.effectData['data'];
}
if (_private.imagesData) {
values['images'] = _private.imagesData['data'];
}
if (_private.uvData) {
values['uvData'] = _private.uvData['data'];
}
return values;
}
/**
* Set uniform value.
* @param {String} name The name.
* @param {*} value The value.
* @private
*/
setUniform(name, value) {
let uniforms = this._createUniforms();
this._updateValues(Flag.Uniforms, name, value, uniforms);
}
/**
* Get uniform value.
* @param {String} name The name.
* @returns {*}
* @private
*/
getUniform(name) {
let uniforms = this._createUniforms();
return uniforms.getValue(name);
}
/**
* Set macro value.
* @param {String} name The name.
* @param {*} value The value.
* @private
*/
setMacro(name, value) {
let macros = this._createMacros();
this._updateValues(Flag.Macros, name, value, macros);
}
/**
* Get macro value.
* @param {String} name The name.
* @returns {*}
* @private
*/
getMacro(name) {
let macros = this._createMacros();
return macros.getValue(name);
}
toJSON() {
let style = {};
function _export(values, keys, callback) {
keys.forEach(key => {
let value = values[key];
if (Utils.isNull(value)) {
return;
}
if (Utils.isFunction(value)) {
return;
}
callback(key, value)
});
}
_export(this, cDefaultValueKeys, (key, value) => {
style[key] = value;
});
_export(this.edge, cEdgeDefaultValueKeys, (key, value) => {
style.edge = style.edge || {};
style.edge[key] = value;
});
_export(this.effect, cEffectDefaultValueKeys, (key, value) => {
style.effect = style.effect || {};
style.effect[key] = value;
});
return style;
}
// #region Accessor
get originalResource() {
return this[__.private].modifier.originalResource;
}
get resource() {
return this[__.private].modifier.resource;
}
get modifier() {
return this[__.private].modifier;
}
set modifier(value) {
this[__.private].modifier = value;
}
get values() {
return this._getValues('values');
}
get data() {
return this._getValues('data');
}
get isInstancedDrawing() {
let _private = this[__.private];
let style = _private.modifier.object.bodyNode.getStyle();
return style.isEnabled('InstancedDrawing');
}
get isChanged() {
let _private = this[__.private];
if (_private.data.isChanged()) {
return true;
}
if (_private.edgeData && _private.edgeData.isChanged()) {
return true;
}
if (_private.effectData && _private.effectData.isChanged()) {
return true;
}
if (_private.imagesData && _private.imagesData.isChanged()) {
return true;
}
if (_private.uvData && _private.uvData.isChanged()) {
return true;
}
return false;
}
// #region Common
/**
* Get/Set the blending type.
* @type {BlendingType}
* @public
*/
get blendingType() {
return this.values.data.blendingType;
}
set blendingType(value) {
this._updateValue(Flag.BlendingType, 'blendingType', value);
}
/**
* Get/Set the side type.
* @type {SideType}
* @public
*/
get sideType() {
return this.values.data.sideType;
}
set sideType(value) {
this._updateValue(Flag.SideType, 'sideType', value);
}
/**
* Enable/Disable transparent mode.
* @type {Boolean}
* @public
*/
get transparent() {
return this.values.data.transparent;
}
set transparent(value) {
this._updateValue(Flag.Transparent, 'transparent', value);
}
/**
* Enable/Disable wireframe.
* @type {Boolean}
* @public
*/
get wireframe() {
return this.values.data.wireframe;
}
set wireframe(value) {
this._updateValue(Flag.Wireframe, 'wireframe', value);
}
/**
* Enable/Disable depth test.
* @type {Boolean}
* @public
*/
get depthTest() {
return this.values.data.depthTest;
}
set depthTest(value) {
this._updateValue(Flag.DepthTest, 'depthTest', value);
}
/**
* Enable/Disable depth write.
* @type {Boolean}
* @public
*/
get depthWrite() {
return this.values.data.depthWrite;
}
set depthWrite(value) {
this._updateValue(Flag.DepthWrite, 'depthWrite', value);
}
/**
* Enable/Disable the env mapping.
* @type {Boolean}
* @public
*/
get envMapping() {
return this.values.data.envMapping;
}
set envMapping(value) {
this._updateValue(Flag.EnvMapping, 'envMapping', value);
}
// #endregion
// #region Image
/**
* Get/Set the current image slot type.
* @type {ImageSlotType}
* @public
*/
get imageSlotType() {
let _private = this[__.private];
return _private.curImageSlotType;
}
set imageSlotType(value) {
let _private = this[__.private];
_private.curImageSlotType = _toUImageType(value);
}
/**
* Get/Set the (canvas/image/url) image resource.
* @type {*}
* @public
*/
get image() {
let _private = this[__.private];
return this.getImage(_private.curImageSlotType);
}
set image(value) {
let _private = this[__.private];
this.setImage(_private.curImageSlotType, value);
}
/**
* Get/Set the image resource of map.
* @type {THING.ImageTexture}
* @public
*/
get map() {
return this.getImage(ImageSlotType.Map);
}
set map(value) {
this.setImage(ImageSlotType.Map, value);
}
/**
* Get/Set the image resource of env map.
* @type {THING.ImageTexture}
* @public
*/
get envMap() {
return this.getImage(ImageSlotType.EnvMap);
}
set envMap(value) {
this.setImage(ImageSlotType.EnvMap, value);
}
/**
* Get/Set the image resource of alpha map.
* @type {THING.ImageTexture}
* @public
*/
get alphaMap() {
return this.getImage(ImageSlotType.AlphaMap);
}
set alphaMap(value) {
this.setImage(ImageSlotType.AlphaMap, value);
}
/**
* Get/Set the image resource of emissive map.
* @type {THING.ImageTexture}
* @public
*/
get emissiveMap() {
return this.getImage(ImageSlotType.EmissiveMap);
}
set emissiveMap(value) {
this.setImage(ImageSlotType.EmissiveMap, value);
}
/**
* Get/Set the image resource of normal map.
* @type {THING.ImageTexture}
* @public
*/
get normalMap() {
return this.getImage(ImageSlotType.NormalMap);
}
set normalMap(value) {
this.setImage(ImageSlotType.NormalMap, value);
}
/**
* Get/Set the image resource of color mapping.
* @type {THING.ImageTexture}
* @public
*/
get colorMapping() {
return this.getImage(ImageSlotType.ColorMapping);
}
set colorMapping(value) {
this.setImage(ImageSlotType.ColorMapping, value);
}
/**
* Get/Set the image resource of ao map.
* @type {THING.ImageTexture}
* @public
*/
get aoMap() {
return this.getImage(ImageSlotType.AOMap);
}
set aoMap(value) {
this.setImage(ImageSlotType.AOMap, value);
}
// #endregion
// #region UV
/**
* Get/Set the uv(matrix).
* @type {StyleUVMatrixResult}
* @public
*/
get uv() {
let _private = this[__.private];
return this.getUV(_private.curImageSlotType);
}
set uv(value) {
let _private = this[__.private];
let uvData = this._getImageUVMatrixByType(_private.curImageSlotType);
if (!uvData) {
return;
}
for (let key in value) {
uvData[key] = value[key];
}
}
// #endregion
// #region Color
/**
* The function to call when update value.
* @callback StyleValueOperationCallback
* @param {Number} baseValue The base value.
* @param {THING.Style} style The style.
*/
/**
* The function to call when update color.
* @callback StyleColorOperationCallback
* @param {Array<Number>} baseValue The base color.
* @param {THING.Style} style The style.
*/
/**
* Get/Set the opacity.
* @type {Number|StyleValueOperationCallback}
* @public
*/
get opacity() {
return this.values.data.opacity;
}
set opacity(value) {
this._updateValueOpFunc(Flag.Opacity, 'opacity', value, (baseValue, v) => {
return baseValue * v;
});
}
/**
* Get/Set the color.
* @type {Number|String|Array<Number>|StyleColorOperationCallback}
* @public
*/
get color() {
let _private = this[__.private];
return _private.data.values['color'];
}
set color(value) {
this._updateColorOpFunc(Flag.Color, 'color', value);
}
/**
* Get/Set the metalness.
* @type {Number|StyleValueOperationCallback}
* @public
*/
get metalness() {
return this.values.data.metalness;
}
set metalness(value) {
this._updateValueOpFunc(Flag.Metalness, 'metalness', value);
}
/**
* Get/Set the roughness.
* @type {Number|StyleValueOperationCallback}
* @public
*/
get roughness() {
return this.values.data.roughness;
}
set roughness(value) {
this._updateValueOpFunc(Flag.Roughness, 'roughness', value);
}
/**
* Get/Set the emissive.
* @type {Number|String|Array<Number>|StyleColorOperationCallback}
* @public
*/
get emissive() {
let _private = this[__.private];
return _private.data.values['emissive'];
}
set emissive(value) {
this._updateColorOpFunc(Flag.Emissive, 'emissive', value);
}
/**
* Get/Set the outline color.
* @type {Number|String|Array<Number>}
* @public
*/
get outlineColor() {
let _private = this[__.private];
return _private.data.values['outlineColor'];
}
set outlineColor(value) {
this._updateColor(Flag.OutlineColor, 'outlineColor', value);
}
// #endregion
// #region Object
/**
* Get/Set the clipping planes.
* @type {Array<THING.ClippingPlanes>}
* @example
* let clippingPlanes = [
* { direction: [0, -1, 0], height: 10 }, // top
* { direction: [0, 1, 0], height: 10 }, // bottom
* { direction: [-1, 0, 0], height: 10 }, // right
* { direction: [1, 0, 0], height: 10 }, // left
* { direction: [0, 0, -1], height: 10 }, // front
* { direction: [0, 0, 1], height: 10 } // back
* ];
*
* let clippingPlanesObject = new THING.ClippingPlanes({
* parent: object,
* planes: clippingPlanes,
* });
*
* object.query('*', { includeSelf: true }).style.clippingPlanes = clippingPlanesObject;
* @public
*
*/
get clippingPlanes() {
return this.values.data.clippingPlanes;
}
set clippingPlanes(value) {
this._updateValue(Flag.ClippingPlanes, 'clippingPlanes', value);
}
/**
* Get/Set the edge.
* @type {StyleEdgeResult}
* @public
*/
get edge() {
let _private = this[__.private];
this._createEdgeData();
return _private.edgeData.values;
}
set edge(value) {
if (!value) {
return;
}
let _private = this[__.private];
this._createEdgeData();
_private.edgeData.mergeValues(value);
}
/**
* Get/Set the effect.
* @type {StyleEffectResult}
* @public
*/
get effect() {
let _private = this[__.private];
this._createEffectData();
return _private.effectData.values;
}
set effect(value) {
if (!value) {
return;
}
let _private = this[__.private];
this._createEffectData();
_private.effectData.mergeValues(value);
}
// #endregion
// #region Attributes
/**
* Get/Set attributes.
* @type {Object}
* @private
*/
get attributes() {
let attributes = this._createAttributes();
return attributes.values;
}
set attributes(value) {
for (let key in value) {
this.setAttribute(key, value);
}
}
/**
* Get/Set uniforms.
* @type {Object}
* @private
*/
get uniforms() {
let uniforms = this._createUniforms();
return uniforms.values;
}
set uniforms(value) {
for (let key in value) {
this.setUniform(key, value);
}
}
/**
* Get/Set macros.
* @type {Object}
* @private
*/
get macros() {
let macros = this._createMacros();
return macros.values;
}
set macros(value) {
for (let key in value) {
this.setMacro(key, value);
}
}
// #endregion
/**
* Check class type.
* @type {Boolean}
* @private
*/
get isStyle() {
return true;
}
// #endregion
}
export { Style }