import {StateProperty, Trait} from '@avocado/traits'; import {Rectangle} from '@avocado/math'; import {compose} from '@latus/core'; import Container from '../container'; const { SIDE, } = process.env; // 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, }), ); export default class Visible extends decorate(Trait) { static behaviorTypes() { return { updateVisibleBoundingBox: { advanced: true, type: 'void', label: 'Update the visible bounding box.', }, }; } static defaultParams() { return { filter: undefined, trackPosition: true, }; } static defaultState() { return { isVisible: true, opacity: 1, rotation: 0, visibleScale: [1, 1], zIndex: AUTO_ZINDEX, }; } static describeParams() { return { filter: { type: 'object', label: 'Filter', }, trackPosition: { type: 'bool', label: 'Track position', }, }; } 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', }, }; } static type() { return 'visible'; } constructor(entity, params, state) { super(entity, params, state); if ('client' === SIDE) { this._container = new Container(); const {filter} = this.params; if (filter) { this._container.setFilter(filter); } this._container.isVisible = this.state.isVisible; } this._rawVisibleAabb = [0, 0, 0, 0]; this.scheduledBoundingBoxUpdate = true; this.trackPosition = this.params.trackPosition; const scale = this.state.visibleScale; this._visibleScale = [scale[0], scale[1]]; this.onZIndexChanged(this.state.zIndex); } destroy() { if (this._container) { this._container.destroy(); } } acceptPacket(packet) { if ('TraitUpdateVisiblePacket' === packet.constructor.name) { this.entity.isVisible = packet.data.isVisible; this.entity.opacity = packet.data.opacity; } } get container() { return this._container; } onZIndexChanged(zIndex) { this._usingAutoZIndex = AUTO_ZINDEX === zIndex; if (!this._usingAutoZIndex) { if (this._container) { this._container.zIndex = zIndex; } } } packets() { const {isVisible, opacity} = this.stateDifferences(); if (isVisible || opacity) { return [ 'TraitUpdateVisiblePacket', { isVisible: this.state.isVisible, opacity: this.state.opacity, }, ]; } return undefined; } get rawVisibleScale() { return this._visibleScale; } set rawVisibleScale(scale) { this.entity.visibleScale = scale; } synchronizePosition() { if (!this.entity.is('positioned')) { return; } if (!this._container) { return; } this._container.position = this.entity.position; if (this._usingAutoZIndex) { if (this._container) { this._container.zIndex = this.entity.y; } } } 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]; } 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; }, traitAdded: (type) => { if (-1 === [ 'visible', 'positioned', ].indexOf(type)) { return; } this.synchronizePosition(); }, visibleScaleChanged: () => { const scale = this.entity.visibleScale; this._visibleScale = [scale[0], scale[1]]; if (this._container) { this._container.scale = this._visibleScale; } }, zIndexChanged: (old, zIndex) => { this.onZIndexChanged(zIndex); }, }; } methods() { return { updateVisibleBoundingBox: () => { this.scheduledBoundingBoxUpdate = true; }, }; } renderTick() { this.synchronizePosition(); } 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'); } tick() { if (AVOCADO_SERVER) { if (this.scheduledBoundingBoxUpdate) { // 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; } } } }