avocado/packages/graphics/src/traits/visible.js

311 lines
6.3 KiB
JavaScript
Raw Normal View History

2020-12-31 18:35:06 -06:00
import {StateProperty, Trait} from '@avocado/traits';
import {Rectangle, Vector} from '@avocado/math';
2020-12-31 18:35:06 -06:00
import {compose} from '@latus/core';
2020-12-28 21:18:54 -06:00
import Container from '../container';
// eslint-disable-next-line no-bitwise
const AUTO_ZINDEX = 1 << 16;
const decorate = compose(
StateProperty('isVisible', {
track: true,
}),
StateProperty('opacity', {
track: true,
}),
StateProperty('rotation', {
track: true,
}),
StateProperty('visibleScale', {
track: true,
}),
StateProperty('zIndex', {
track: true,
}),
);
2021-01-13 00:05:33 -06:00
export default () => class Visible extends decorate(Trait) {
2020-12-28 21:18:54 -06:00
#container;
#rawVisibleAabb = [0, 0, 0, 0];
#scheduledBoundingBoxUpdate = true;
#usingAutoZIndex = true;
#visibleScale = [0, 0];
constructor() {
super();
if ('client' === process.env.SIDE) {
this.#container = new Container();
}
({
visibleScale: this.#visibleScale,
} = this.constructor.defaultState());
}
acceptPacket(packet) {
if ('TraitUpdateVisible' === packet.constructor.type) {
this.entity.isVisible = packet.data.isVisible;
this.entity.opacity = packet.data.opacity;
}
}
2020-12-28 21:18:54 -06:00
static behaviorTypes() {
return {
updateVisibleBoundingBox: {
advanced: true,
type: 'void',
label: 'Update the visible bounding box.',
},
};
}
static defaultParams() {
return {
filter: undefined,
};
}
static defaultState() {
return {
isVisible: true,
opacity: 1,
rotation: 0,
visibleScale: [1, 1],
zIndex: AUTO_ZINDEX,
};
}
static describeParams() {
return {
filter: {
type: 'object',
label: 'Filter',
},
};
}
static describeState() {
return {
isVisible: {
type: 'bool',
label: 'Is visible',
},
opacity: {
type: 'number',
label: 'Opacity',
},
rotation: {
type: 'number',
label: 'Rotation',
},
visibleScale: {
type: 'vector',
label: 'Scale',
},
zIndex: {
type: 'number',
label: 'Z index',
},
};
}
destroy() {
if (this.#container) {
this.#container.destroy();
2020-12-28 21:18:54 -06:00
}
}
get container() {
return this.#container;
2020-12-28 21:18:54 -06:00
}
2021-01-17 11:07:55 -06:00
forceUpdateBoundingBox() {
// Collect all bounding boxes.
const visibleAabbs = this.entity.invokeHookFlat('visibleAabbs');
if (0 === visibleAabbs.length) {
this.#rawVisibleAabb = [0, 0, 0, 0];
}
else {
let unifiedBoundingBox = [0, 0, 0, 0];
for (let i = 0; i < visibleAabbs.length; ++i) {
const visibleAabb = visibleAabbs[i];
unifiedBoundingBox = Rectangle.united(
unifiedBoundingBox,
visibleAabb,
);
}
this.#rawVisibleAabb = unifiedBoundingBox;
}
this.translateVisibleAabb();
this.#scheduledBoundingBoxUpdate = false;
}
listeners() {
return {
isVisibleChanged: () => {
if (!this.#container) {
return;
}
this.#container.visible = this.entity.isVisible;
},
opacityChanged: () => {
if (!this.#container) {
return;
}
this.#container.alpha = this.entity.opacity;
},
positionChanged: () => {
this.translateVisibleAabb();
},
rotationChanged: () => {
if (!this.#container) {
return;
}
this.#container.rotation = this.entity.rotation;
},
2021-01-17 11:18:15 -06:00
traitAdded: () => {
this.synchronizePosition();
},
updateVisibleBoundingBox: () => {
this.#scheduledBoundingBoxUpdate = true;
},
visibleScaleChanged: () => {
this.#visibleScale = Vector.copy(this.entity.visibleScale);
if (this.#container) {
this.#container.scale = this.#visibleScale;
}
},
zIndexChanged: (old, zIndex) => {
this.onZIndexChanged(zIndex);
},
};
2020-12-28 21:18:54 -06:00
}
2021-01-19 14:59:51 -06:00
async load(json) {
await super.load(json);
if ('client' === process.env.SIDE) {
const {filter} = this.params;
if (filter) {
this.#container.setFilter(filter);
}
this.#container.isVisible = this.state.isVisible;
}
this.#visibleScale = Vector.copy(this.state.visibleScale);
this.onZIndexChanged(this.state.zIndex);
this.entity.emit('updateVisibleBoundingBox');
2020-12-28 21:18:54 -06:00
}
2021-01-17 11:07:55 -06:00
methods() {
return {
forceUpdateBoundingBox: () => {
this.forceUpdateBoundingBox();
},
};
}
2020-12-28 21:18:54 -06:00
onZIndexChanged(zIndex) {
this.#usingAutoZIndex = AUTO_ZINDEX === zIndex;
if (!this.#usingAutoZIndex) {
if (this.#container) {
this.#container.zIndex = zIndex;
2020-12-28 21:18:54 -06:00
}
}
}
packets() {
const {isVisible, opacity} = this.stateDifferences();
if (isVisible || opacity) {
2021-01-14 01:22:47 -06:00
return [[
'TraitUpdateVisible',
2020-12-28 21:18:54 -06:00
{
isVisible: this.state.isVisible,
opacity: this.state.opacity,
},
2021-01-14 01:22:47 -06:00
]];
2020-12-28 21:18:54 -06:00
}
return [];
2020-12-28 21:18:54 -06:00
}
get rawVisibleScale() {
return this.#visibleScale;
2020-12-28 21:18:54 -06:00
}
set rawVisibleScale(scale) {
this.entity.visibleScale = scale;
}
renderTick() {
this.synchronizePosition();
}
2020-12-28 21:18:54 -06:00
synchronizePosition() {
2021-01-17 11:18:15 -06:00
if (!this.entity.is('Positioned')) {
2020-12-28 21:18:54 -06:00
return;
}
if (!this.#container) {
2020-12-28 21:18:54 -06:00
return;
}
this.#container.position = this.entity.position;
if (this.#usingAutoZIndex) {
if (this.#container) {
this.#container.zIndex = this.entity.y;
2020-12-28 21:18:54 -06:00
}
}
}
get visibleScaleX() {
return this.state.visibleScale[0];
}
set visibleScaleX(x) {
this.rawVisibleScale = [x, this.visibleScaleY];
}
get visibleScaleY() {
return this.state.visibleScale[1];
}
set visibleScaleY(y) {
this.rawVisibleScale = [this.visibleScaleX, y];
}
tick() {
2021-01-14 01:22:47 -06:00
if ('client' !== process.env.SIDE) {
if (this.#scheduledBoundingBoxUpdate) {
2021-01-17 11:07:55 -06:00
this.forceUpdateBoundingBox();
2020-12-28 21:18:54 -06:00
}
}
}
translateVisibleAabb() {
const visibleAabb = Rectangle.translated(
this.#rawVisibleAabb,
this.entity.position,
);
/* eslint-disable prefer-destructuring */
this.entity.visibleAabb[0] = visibleAabb[0];
this.entity.visibleAabb[1] = visibleAabb[1];
this.entity.visibleAabb[2] = visibleAabb[2];
this.entity.visibleAabb[3] = visibleAabb[3];
/* eslint-enable prefer-destructuring */
this.entity.emit('visibleAabbChanged');
}
2021-01-13 00:05:33 -06:00
};