playcanvas
Version:
PlayCanvas WebGL game engine
1,401 lines (1,398 loc) • 40.7 kB
JavaScript
import { Mat4 } from '../../../core/math/mat4.js';
import { Vec2 } from '../../../core/math/vec2.js';
import { Vec3 } from '../../../core/math/vec3.js';
import { Vec4 } from '../../../core/math/vec4.js';
import { FUNC_EQUAL, STENCILOP_INCREMENT, STENCILOP_REPLACE, FUNC_ALWAYS } from '../../../platform/graphics/constants.js';
import { LAYERID_UI } from '../../../scene/constants.js';
import { BatchGroup } from '../../../scene/batching/batch-group.js';
import { StencilParameters } from '../../../platform/graphics/stencil-parameters.js';
import { Entity } from '../../entity.js';
import { Component } from '../component.js';
import { ELEMENTTYPE_GROUP, FITMODE_STRETCH, ELEMENTTYPE_IMAGE, ELEMENTTYPE_TEXT } from './constants.js';
import { ImageElement } from './image-element.js';
import { TextElement } from './text-element.js';
const position = new Vec3();
const invParentWtm = new Mat4();
const vecA = new Vec3();
const vecB = new Vec3();
const matA = new Mat4();
const matB = new Mat4();
const matC = new Mat4();
const matD = new Mat4();
class ElementComponent extends Component {
static{
this.EVENT_MOUSEDOWN = 'mousedown';
}
static{
this.EVENT_MOUSEUP = 'mouseup';
}
static{
this.EVENT_MOUSEENTER = 'mouseenter';
}
static{
this.EVENT_MOUSELEAVE = 'mouseleave';
}
static{
this.EVENT_MOUSEMOVE = 'mousemove';
}
static{
this.EVENT_MOUSEWHEEL = 'mousewheel';
}
static{
this.EVENT_CLICK = 'click';
}
static{
this.EVENT_TOUCHSTART = 'touchstart';
}
static{
this.EVENT_TOUCHEND = 'touchend';
}
static{
this.EVENT_TOUCHMOVE = 'touchmove';
}
static{
this.EVENT_TOUCHCANCEL = 'touchcancel';
}
constructor(system, entity){
super(system, entity), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;
this._beingInitialized = false;
this._anchor = new Vec4();
this._localAnchor = new Vec4();
this._pivot = new Vec2();
this._width = this._calculatedWidth = 32;
this._height = this._calculatedHeight = 32;
this._margin = new Vec4(0, 0, -32, -32);
this._modelTransform = new Mat4();
this._screenToWorld = new Mat4();
this._anchorTransform = new Mat4();
this._anchorDirty = true;
this._parentWorldTransform = new Mat4();
this._screenTransform = new Mat4();
this._screenCorners = [
new Vec3(),
new Vec3(),
new Vec3(),
new Vec3()
];
this._canvasCorners = [
new Vec2(),
new Vec2(),
new Vec2(),
new Vec2()
];
this._worldCorners = [
new Vec3(),
new Vec3(),
new Vec3(),
new Vec3()
];
this._cornersDirty = true;
this._canvasCornersDirty = true;
this._worldCornersDirty = true;
this.entity.on('insert', this._onInsert, this);
this._patch();
this.screen = null;
this._type = ELEMENTTYPE_GROUP;
this._image = null;
this._text = null;
this._group = null;
this._drawOrder = 0;
this._fitMode = FITMODE_STRETCH;
this._useInput = false;
this._layers = [
LAYERID_UI
];
this._addedModels = [];
this._batchGroupId = -1;
this._offsetReadAt = 0;
this._maskOffset = 0.5;
this._maskedBy = null;
}
get data() {
const record = this.system.store[this.entity.getGuid()];
return record ? record.data : null;
}
set enabled(value) {
const data = this.data;
const oldValue = data.enabled;
data.enabled = value;
this.fire('set', 'enabled', oldValue, value);
}
get enabled() {
return this.data.enabled;
}
get _absLeft() {
return this._localAnchor.x + this._margin.x;
}
get _absRight() {
return this._localAnchor.z - this._margin.z;
}
get _absTop() {
return this._localAnchor.w - this._margin.w;
}
get _absBottom() {
return this._localAnchor.y + this._margin.y;
}
get _hasSplitAnchorsX() {
return Math.abs(this._anchor.x - this._anchor.z) > 0.001;
}
get _hasSplitAnchorsY() {
return Math.abs(this._anchor.y - this._anchor.w) > 0.001;
}
get aabb() {
if (this._image) {
return this._image.aabb;
}
if (this._text) {
return this._text.aabb;
}
return null;
}
set anchor(value) {
if (value instanceof Vec4) {
this._anchor.copy(value);
} else {
this._anchor.set(...value);
}
if (!this.entity._parent && !this.screen) {
this._calculateLocalAnchors();
} else {
this._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);
}
this._anchorDirty = true;
if (!this.entity._dirtyLocal) {
this.entity._dirtifyLocal();
}
this.fire('set:anchor', this._anchor);
}
get anchor() {
return this._anchor;
}
set batchGroupId(value) {
if (this._batchGroupId === value) {
return;
}
if (this.entity.enabled && this._batchGroupId >= 0) {
this.system.app.batcher?.remove(BatchGroup.ELEMENT, this.batchGroupId, this.entity);
}
if (this.entity.enabled && value >= 0) {
this.system.app.batcher?.insert(BatchGroup.ELEMENT, value, this.entity);
}
if (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {
if (this._image && this._image._renderable.model) {
this.addModelToLayers(this._image._renderable.model);
} else if (this._text && this._text._model) {
this.addModelToLayers(this._text._model);
}
}
this._batchGroupId = value;
}
get batchGroupId() {
return this._batchGroupId;
}
set bottom(value) {
this._margin.y = value;
const p = this.entity.getLocalPosition();
const wt = this._absTop;
const wb = this._localAnchor.y + value;
this._setHeight(wt - wb);
p.y = value + this._calculatedHeight * this._pivot.y;
this.entity.setLocalPosition(p);
}
get bottom() {
return this._margin.y;
}
set calculatedWidth(value) {
this._setCalculatedWidth(value, true);
}
get calculatedWidth() {
return this._calculatedWidth;
}
set calculatedHeight(value) {
this._setCalculatedHeight(value, true);
}
get calculatedHeight() {
return this._calculatedHeight;
}
get canvasCorners() {
if (!this._canvasCornersDirty || !this.screen || !this.screen.screen.screenSpace) {
return this._canvasCorners;
}
const device = this.system.app.graphicsDevice;
const screenCorners = this.screenCorners;
const sx = device.canvas.clientWidth / device.width;
const sy = device.canvas.clientHeight / device.height;
for(let i = 0; i < 4; i++){
this._canvasCorners[i].set(screenCorners[i].x * sx, (device.height - screenCorners[i].y) * sy);
}
this._canvasCornersDirty = false;
return this._canvasCorners;
}
set drawOrder(value) {
let priority = 0;
if (this.screen) {
priority = this.screen.screen.priority;
}
if (value > 0xFFFFFF) {
value = 0xFFFFFF;
}
this._drawOrder = (priority << 24) + value;
this.fire('set:draworder', this._drawOrder);
}
get drawOrder() {
return this._drawOrder;
}
set height(value) {
this._height = value;
if (!this._hasSplitAnchorsY) {
this._setCalculatedHeight(value, true);
}
this.fire('set:height', this._height);
}
get height() {
return this._height;
}
set layers(value) {
if (this._addedModels.length) {
for(let i = 0; i < this._layers.length; i++){
const layer = this.system.app.scene.layers.getLayerById(this._layers[i]);
if (layer) {
for(let j = 0; j < this._addedModels.length; j++){
layer.removeMeshInstances(this._addedModels[j].meshInstances);
}
}
}
}
this._layers = value;
if (!this.enabled || !this.entity.enabled || !this._addedModels.length) {
return;
}
for(let i = 0; i < this._layers.length; i++){
const layer = this.system.app.scene.layers.getLayerById(this._layers[i]);
if (layer) {
for(let j = 0; j < this._addedModels.length; j++){
layer.addMeshInstances(this._addedModels[j].meshInstances);
}
}
}
}
get layers() {
return this._layers;
}
set left(value) {
this._margin.x = value;
const p = this.entity.getLocalPosition();
const wr = this._absRight;
const wl = this._localAnchor.x + value;
this._setWidth(wr - wl);
p.x = value + this._calculatedWidth * this._pivot.x;
this.entity.setLocalPosition(p);
}
get left() {
return this._margin.x;
}
set margin(value) {
this._margin.copy(value);
this._calculateSize(true, true);
this.fire('set:margin', this._margin);
}
get margin() {
return this._margin;
}
get maskedBy() {
return this._maskedBy;
}
set pivot(value) {
const { pivot, margin } = this;
const prevX = pivot.x;
const prevY = pivot.y;
if (value instanceof Vec2) {
pivot.copy(value);
} else {
pivot.set(...value);
}
const mx = margin.x + margin.z;
const dx = pivot.x - prevX;
margin.x += mx * dx;
margin.z -= mx * dx;
const my = margin.y + margin.w;
const dy = pivot.y - prevY;
margin.y += my * dy;
margin.w -= my * dy;
this._anchorDirty = true;
this._cornersDirty = true;
this._worldCornersDirty = true;
this._calculateSize(false, false);
this._flagChildrenAsDirty();
this.fire('set:pivot', pivot);
}
get pivot() {
return this._pivot;
}
set right(value) {
this._margin.z = value;
const p = this.entity.getLocalPosition();
const wl = this._absLeft;
const wr = this._localAnchor.z - value;
this._setWidth(wr - wl);
p.x = this._localAnchor.z - this._localAnchor.x - value - this._calculatedWidth * (1 - this._pivot.x);
this.entity.setLocalPosition(p);
}
get right() {
return this._margin.z;
}
get screenCorners() {
if (!this._cornersDirty || !this.screen) {
return this._screenCorners;
}
const parentBottomLeft = this.entity.parent && this.entity.parent.element && this.entity.parent.element.screenCorners[0];
this._screenCorners[0].set(this._absLeft, this._absBottom, 0);
this._screenCorners[1].set(this._absRight, this._absBottom, 0);
this._screenCorners[2].set(this._absRight, this._absTop, 0);
this._screenCorners[3].set(this._absLeft, this._absTop, 0);
const screenSpace = this.screen.screen.screenSpace;
for(let i = 0; i < 4; i++){
this._screenTransform.transformPoint(this._screenCorners[i], this._screenCorners[i]);
if (screenSpace) {
this._screenCorners[i].mulScalar(this.screen.screen.scale);
}
if (parentBottomLeft) {
this._screenCorners[i].add(parentBottomLeft);
}
}
this._cornersDirty = false;
this._canvasCornersDirty = true;
this._worldCornersDirty = true;
return this._screenCorners;
}
get textWidth() {
return this._text ? this._text.width : 0;
}
get textHeight() {
return this._text ? this._text.height : 0;
}
set top(value) {
this._margin.w = value;
const p = this.entity.getLocalPosition();
const wb = this._absBottom;
const wt = this._localAnchor.w - value;
this._setHeight(wt - wb);
p.y = this._localAnchor.w - this._localAnchor.y - value - this._calculatedHeight * (1 - this._pivot.y);
this.entity.setLocalPosition(p);
}
get top() {
return this._margin.w;
}
set type(value) {
if (value !== this._type) {
this._type = value;
if (this._image) {
this._image.destroy();
this._image = null;
}
if (this._text) {
this._text.destroy();
this._text = null;
}
if (value === ELEMENTTYPE_IMAGE) {
this._image = new ImageElement(this);
} else if (value === ELEMENTTYPE_TEXT) {
this._text = new TextElement(this);
}
}
}
get type() {
return this._type;
}
set useInput(value) {
if (this._useInput === value) {
return;
}
this._useInput = value;
if (this.system.app.elementInput) {
if (value) {
if (this.enabled && this.entity.enabled) {
this.system.app.elementInput.addElement(this);
}
} else {
this.system.app.elementInput.removeElement(this);
}
} else {
if (this._useInput === true) ;
}
this.fire('set:useInput', value);
}
get useInput() {
return this._useInput;
}
set fitMode(value) {
this._fitMode = value;
this._calculateSize(true, true);
if (this._image) {
this._image.refreshMesh();
}
}
get fitMode() {
return this._fitMode;
}
set width(value) {
this._width = value;
if (!this._hasSplitAnchorsX) {
this._setCalculatedWidth(value, true);
}
this.fire('set:width', this._width);
}
get width() {
return this._width;
}
get worldCorners() {
if (!this._worldCornersDirty) {
return this._worldCorners;
}
if (this.screen) {
const screenCorners = this.screenCorners;
if (!this.screen.screen.screenSpace) {
matA.copy(this.screen.screen._screenMatrix);
matA.data[13] = -matA.data[13];
matA.mul2(this.screen.getWorldTransform(), matA);
for(let i = 0; i < 4; i++){
matA.transformPoint(screenCorners[i], this._worldCorners[i]);
}
}
} else {
const localPos = this.entity.getLocalPosition();
matA.setTranslate(-localPos.x, -localPos.y, -localPos.z);
matB.setTRS(Vec3.ZERO, this.entity.getLocalRotation(), this.entity.getLocalScale());
matC.setTranslate(localPos.x, localPos.y, localPos.z);
const entity = this.entity.parent ? this.entity.parent : this.entity;
matD.copy(entity.getWorldTransform());
matD.mul(matC).mul(matB).mul(matA);
vecA.set(localPos.x - this.pivot.x * this.calculatedWidth, localPos.y - this.pivot.y * this.calculatedHeight, localPos.z);
matD.transformPoint(vecA, this._worldCorners[0]);
vecA.set(localPos.x + (1 - this.pivot.x) * this.calculatedWidth, localPos.y - this.pivot.y * this.calculatedHeight, localPos.z);
matD.transformPoint(vecA, this._worldCorners[1]);
vecA.set(localPos.x + (1 - this.pivot.x) * this.calculatedWidth, localPos.y + (1 - this.pivot.y) * this.calculatedHeight, localPos.z);
matD.transformPoint(vecA, this._worldCorners[2]);
vecA.set(localPos.x - this.pivot.x * this.calculatedWidth, localPos.y + (1 - this.pivot.y) * this.calculatedHeight, localPos.z);
matD.transformPoint(vecA, this._worldCorners[3]);
}
this._worldCornersDirty = false;
return this._worldCorners;
}
set fontSize(arg) {
this._setValue('fontSize', arg);
}
get fontSize() {
if (this._text) {
return this._text.fontSize;
}
return null;
}
set minFontSize(arg) {
this._setValue('minFontSize', arg);
}
get minFontSize() {
if (this._text) {
return this._text.minFontSize;
}
return null;
}
set maxFontSize(arg) {
this._setValue('maxFontSize', arg);
}
get maxFontSize() {
if (this._text) {
return this._text.maxFontSize;
}
return null;
}
set maxLines(arg) {
this._setValue('maxLines', arg);
}
get maxLines() {
if (this._text) {
return this._text.maxLines;
}
return null;
}
set autoFitWidth(arg) {
this._setValue('autoFitWidth', arg);
}
get autoFitWidth() {
if (this._text) {
return this._text.autoFitWidth;
}
return null;
}
set autoFitHeight(arg) {
this._setValue('autoFitHeight', arg);
}
get autoFitHeight() {
if (this._text) {
return this._text.autoFitHeight;
}
return null;
}
set color(arg) {
this._setValue('color', arg);
}
get color() {
if (this._text) {
return this._text.color;
}
if (this._image) {
return this._image.color;
}
return null;
}
set font(arg) {
this._setValue('font', arg);
}
get font() {
if (this._text) {
return this._text.font;
}
return null;
}
set fontAsset(arg) {
this._setValue('fontAsset', arg);
}
get fontAsset() {
if (this._text && typeof this._text.fontAsset === 'number') {
return this._text.fontAsset;
}
return null;
}
set spacing(arg) {
this._setValue('spacing', arg);
}
get spacing() {
if (this._text) {
return this._text.spacing;
}
return null;
}
set lineHeight(arg) {
this._setValue('lineHeight', arg);
}
get lineHeight() {
if (this._text) {
return this._text.lineHeight;
}
return null;
}
set wrapLines(arg) {
this._setValue('wrapLines', arg);
}
get wrapLines() {
if (this._text) {
return this._text.wrapLines;
}
return null;
}
set lines(arg) {
this._setValue('lines', arg);
}
get lines() {
if (this._text) {
return this._text.lines;
}
return null;
}
set alignment(arg) {
this._setValue('alignment', arg);
}
get alignment() {
if (this._text) {
return this._text.alignment;
}
return null;
}
set autoWidth(arg) {
this._setValue('autoWidth', arg);
}
get autoWidth() {
if (this._text) {
return this._text.autoWidth;
}
return null;
}
set autoHeight(arg) {
this._setValue('autoHeight', arg);
}
get autoHeight() {
if (this._text) {
return this._text.autoHeight;
}
return null;
}
set rtlReorder(arg) {
this._setValue('rtlReorder', arg);
}
get rtlReorder() {
if (this._text) {
return this._text.rtlReorder;
}
return null;
}
set unicodeConverter(arg) {
this._setValue('unicodeConverter', arg);
}
get unicodeConverter() {
if (this._text) {
return this._text.unicodeConverter;
}
return null;
}
set text(arg) {
this._setValue('text', arg);
}
get text() {
if (this._text) {
return this._text.text;
}
return null;
}
set key(arg) {
this._setValue('key', arg);
}
get key() {
if (this._text) {
return this._text.key;
}
return null;
}
set texture(arg) {
this._setValue('texture', arg);
}
get texture() {
if (this._image) {
return this._image.texture;
}
return null;
}
set textureAsset(arg) {
this._setValue('textureAsset', arg);
}
get textureAsset() {
if (this._image) {
return this._image.textureAsset;
}
return null;
}
set material(arg) {
this._setValue('material', arg);
}
get material() {
if (this._image) {
return this._image.material;
}
return null;
}
set materialAsset(arg) {
this._setValue('materialAsset', arg);
}
get materialAsset() {
if (this._image) {
return this._image.materialAsset;
}
return null;
}
set sprite(arg) {
this._setValue('sprite', arg);
}
get sprite() {
if (this._image) {
return this._image.sprite;
}
return null;
}
set spriteAsset(arg) {
this._setValue('spriteAsset', arg);
}
get spriteAsset() {
if (this._image) {
return this._image.spriteAsset;
}
return null;
}
set spriteFrame(arg) {
this._setValue('spriteFrame', arg);
}
get spriteFrame() {
if (this._image) {
return this._image.spriteFrame;
}
return null;
}
set pixelsPerUnit(arg) {
this._setValue('pixelsPerUnit', arg);
}
get pixelsPerUnit() {
if (this._image) {
return this._image.pixelsPerUnit;
}
return null;
}
set opacity(arg) {
this._setValue('opacity', arg);
}
get opacity() {
if (this._text) {
return this._text.opacity;
}
if (this._image) {
return this._image.opacity;
}
return null;
}
set rect(arg) {
this._setValue('rect', arg);
}
get rect() {
if (this._image) {
return this._image.rect;
}
return null;
}
set mask(arg) {
this._setValue('mask', arg);
}
get mask() {
if (this._image) {
return this._image.mask;
}
return null;
}
set outlineColor(arg) {
this._setValue('outlineColor', arg);
}
get outlineColor() {
if (this._text) {
return this._text.outlineColor;
}
return null;
}
set outlineThickness(arg) {
this._setValue('outlineThickness', arg);
}
get outlineThickness() {
if (this._text) {
return this._text.outlineThickness;
}
return null;
}
set shadowColor(arg) {
this._setValue('shadowColor', arg);
}
get shadowColor() {
if (this._text) {
return this._text.shadowColor;
}
return null;
}
set shadowOffset(arg) {
this._setValue('shadowOffset', arg);
}
get shadowOffset() {
if (this._text) {
return this._text.shadowOffset;
}
return null;
}
set enableMarkup(arg) {
this._setValue('enableMarkup', arg);
}
get enableMarkup() {
if (this._text) {
return this._text.enableMarkup;
}
return null;
}
set rangeStart(arg) {
this._setValue('rangeStart', arg);
}
get rangeStart() {
if (this._text) {
return this._text.rangeStart;
}
return null;
}
set rangeEnd(arg) {
this._setValue('rangeEnd', arg);
}
get rangeEnd() {
if (this._text) {
return this._text.rangeEnd;
}
return null;
}
_setValue(name, value) {
if (this._text) {
if (this._text[name] !== value) {
this._dirtyBatch();
}
this._text[name] = value;
} else if (this._image) {
if (this._image[name] !== value) {
this._dirtyBatch();
}
this._image[name] = value;
}
}
_patch() {
this.entity._sync = this._sync;
this.entity.setPosition = this._setPosition;
this.entity.setLocalPosition = this._setLocalPosition;
}
_unpatch() {
this.entity._sync = Entity.prototype._sync;
this.entity.setPosition = Entity.prototype.setPosition;
this.entity.setLocalPosition = Entity.prototype.setLocalPosition;
}
_setPosition(x, y, z) {
if (!this.element.screen) {
Entity.prototype.setPosition.call(this, x, y, z);
return;
}
if (x instanceof Vec3) {
position.copy(x);
} else {
position.set(x, y, z);
}
this.getWorldTransform();
invParentWtm.copy(this.element._screenToWorld).invert();
invParentWtm.transformPoint(position, this.localPosition);
if (!this._dirtyLocal) {
this._dirtifyLocal();
}
}
_setLocalPosition(x, y, z) {
if (x instanceof Vec3) {
this.localPosition.copy(x);
} else {
this.localPosition.set(x, y, z);
}
const element = this.element;
const p = this.localPosition;
const pvt = element._pivot;
element._margin.x = p.x - element._calculatedWidth * pvt.x;
element._margin.z = element._localAnchor.z - element._localAnchor.x - element._calculatedWidth - element._margin.x;
element._margin.y = p.y - element._calculatedHeight * pvt.y;
element._margin.w = element._localAnchor.w - element._localAnchor.y - element._calculatedHeight - element._margin.y;
if (!this._dirtyLocal) {
this._dirtifyLocal();
}
}
_sync() {
const element = this.element;
const screen = element.screen;
if (screen) {
if (element._anchorDirty) {
let resx = 0;
let resy = 0;
let px = 0;
let py = 1;
if (this._parent && this._parent.element) {
resx = this._parent.element.calculatedWidth;
resy = this._parent.element.calculatedHeight;
px = this._parent.element.pivot.x;
py = this._parent.element.pivot.y;
} else {
const resolution = screen.screen.resolution;
resx = resolution.x / screen.screen.scale;
resy = resolution.y / screen.screen.scale;
}
element._anchorTransform.setTranslate(resx * (element.anchor.x - px), -(resy * (py - element.anchor.y)), 0);
element._anchorDirty = false;
element._calculateLocalAnchors();
}
if (element._sizeDirty) {
element._calculateSize(false, false);
}
}
if (this._dirtyLocal) {
this.localTransform.setTRS(this.localPosition, this.localRotation, this.localScale);
const p = this.localPosition;
const pvt = element._pivot;
element._margin.x = p.x - element._calculatedWidth * pvt.x;
element._margin.z = element._localAnchor.z - element._localAnchor.x - element._calculatedWidth - element._margin.x;
element._margin.y = p.y - element._calculatedHeight * pvt.y;
element._margin.w = element._localAnchor.w - element._localAnchor.y - element._calculatedHeight - element._margin.y;
this._dirtyLocal = false;
}
if (!screen) {
if (this._dirtyWorld) {
element._cornersDirty = true;
element._canvasCornersDirty = true;
element._worldCornersDirty = true;
}
Entity.prototype._sync.call(this);
return;
}
if (this._dirtyWorld) {
if (this._parent === null) {
this.worldTransform.copy(this.localTransform);
} else {
if (this._parent.element) {
element._screenToWorld.mul2(this._parent.element._modelTransform, element._anchorTransform);
} else {
element._screenToWorld.copy(element._anchorTransform);
}
element._modelTransform.mul2(element._screenToWorld, this.localTransform);
if (screen) {
element._screenToWorld.mul2(screen.screen._screenMatrix, element._screenToWorld);
if (!screen.screen.screenSpace) {
element._screenToWorld.mul2(screen.worldTransform, element._screenToWorld);
}
this.worldTransform.mul2(element._screenToWorld, this.localTransform);
const parentWorldTransform = element._parentWorldTransform;
parentWorldTransform.setIdentity();
const parent = this._parent;
if (parent && parent.element && parent !== screen) {
matA.setTRS(Vec3.ZERO, parent.getLocalRotation(), parent.getLocalScale());
parentWorldTransform.mul2(parent.element._parentWorldTransform, matA);
}
const depthOffset = vecA;
depthOffset.set(0, 0, this.localPosition.z);
const pivotOffset = vecB;
pivotOffset.set(element._absLeft + element._pivot.x * element.calculatedWidth, element._absBottom + element._pivot.y * element.calculatedHeight, 0);
matA.setTranslate(-pivotOffset.x, -pivotOffset.y, -pivotOffset.z);
matB.setTRS(depthOffset, this.getLocalRotation(), this.getLocalScale());
matC.setTranslate(pivotOffset.x, pivotOffset.y, pivotOffset.z);
element._screenTransform.mul2(element._parentWorldTransform, matC).mul(matB).mul(matA);
element._cornersDirty = true;
element._canvasCornersDirty = true;
element._worldCornersDirty = true;
} else {
this.worldTransform.copy(element._modelTransform);
}
}
this._dirtyWorld = false;
}
}
_onInsert(parent) {
const result = this._parseUpToScreen();
this.entity._dirtifyWorld();
this._updateScreen(result.screen);
this._dirtifyMask();
}
_dirtifyMask() {
let current = this.entity;
while(current){
const next = current.parent;
if ((next === null || next.screen) && current.element) {
if (!this.system._prerender || !this.system._prerender.length) {
this.system._prerender = [];
this.system.app.once('prerender', this._onPrerender, this);
}
const i = this.system._prerender.indexOf(this.entity);
if (i >= 0) {
this.system._prerender.splice(i, 1);
}
const j = this.system._prerender.indexOf(current);
if (j < 0) {
this.system._prerender.push(current);
}
}
current = next;
}
}
_onPrerender() {
for(let i = 0; i < this.system._prerender.length; i++){
const mask = this.system._prerender[i];
if (mask.element) {
const depth = 1;
mask.element.syncMask(depth);
}
}
this.system._prerender.length = 0;
}
_bindScreen(screen) {
screen._bindElement(this);
}
_unbindScreen(screen) {
screen._unbindElement(this);
}
_updateScreen(screen) {
if (this.screen && this.screen !== screen) {
this._unbindScreen(this.screen.screen);
}
const previousScreen = this.screen;
this.screen = screen;
if (this.screen) {
this._bindScreen(this.screen.screen);
}
this._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);
this.fire('set:screen', this.screen, previousScreen);
this._anchorDirty = true;
const children = this.entity.children;
for(let i = 0, l = children.length; i < l; i++){
if (children[i].element) {
children[i].element._updateScreen(screen);
}
}
if (this.screen) {
this.screen.screen.syncDrawOrder();
}
}
syncMask(depth) {
const result = this._parseUpToScreen();
this._updateMask(result.mask, depth);
}
_setMaskedBy(mask) {
const renderableElement = this._image || this._text;
if (mask) {
const ref = mask.element._image._maskRef;
renderableElement?._setStencil(new StencilParameters({
ref: ref,
func: FUNC_EQUAL
}));
this._maskedBy = mask;
} else {
renderableElement?._setStencil(null);
this._maskedBy = null;
}
}
_updateMask(currentMask, depth) {
if (currentMask) {
this._setMaskedBy(currentMask);
if (this.mask) {
const ref = currentMask.element._image._maskRef;
const sp = new StencilParameters({
ref: ref,
func: FUNC_EQUAL,
zpass: STENCILOP_INCREMENT
});
this._image._setStencil(sp);
this._image._maskRef = depth;
depth++;
currentMask = this.entity;
}
const children = this.entity.children;
for(let i = 0, l = children.length; i < l; i++){
children[i].element?._updateMask(currentMask, depth);
}
if (this.mask) depth--;
} else {
this._setMaskedBy(null);
if (this.mask) {
const sp = new StencilParameters({
ref: depth,
func: FUNC_ALWAYS,
zpass: STENCILOP_REPLACE
});
this._image._setStencil(sp);
this._image._maskRef = depth;
depth++;
currentMask = this.entity;
}
const children = this.entity.children;
for(let i = 0, l = children.length; i < l; i++){
children[i].element?._updateMask(currentMask, depth);
}
if (this.mask) {
depth--;
}
}
}
_parseUpToScreen() {
const result = {
screen: null,
mask: null
};
let parent = this.entity._parent;
while(parent && !parent.screen){
if (parent.element && parent.element.mask) {
if (!result.mask) result.mask = parent;
}
parent = parent.parent;
}
if (parent && parent.screen) {
result.screen = parent;
}
return result;
}
_onScreenResize(res) {
this._anchorDirty = true;
this._cornersDirty = true;
this._worldCornersDirty = true;
this._calculateSize(this._hasSplitAnchorsX, this._hasSplitAnchorsY);
this.fire('screen:set:resolution', res);
}
_onScreenSpaceChange() {
this.fire('screen:set:screenspace', this.screen.screen.screenSpace);
}
_onScreenRemove() {
if (this.screen) {
if (this.screen._destroying) {
this.screen = null;
} else {
this._updateScreen(null);
}
}
}
_calculateLocalAnchors() {
let resx = 1000;
let resy = 1000;
const parent = this.entity._parent;
if (parent && parent.element) {
resx = parent.element.calculatedWidth;
resy = parent.element.calculatedHeight;
} else if (this.screen) {
const res = this.screen.screen.resolution;
const scale = this.screen.screen.scale;
resx = res.x / scale;
resy = res.y / scale;
}
this._localAnchor.set(this._anchor.x * resx, this._anchor.y * resy, this._anchor.z * resx, this._anchor.w * resy);
}
getOffsetPosition(x, y) {
const p = this.entity.getLocalPosition().clone();
p.x += x;
p.y += y;
this._screenToWorld.transformPoint(p, p);
return p;
}
onLayersChanged(oldComp, newComp) {
this.addModelToLayers(this._image ? this._image._renderable.model : this._text._model);
oldComp.off('add', this.onLayerAdded, this);
oldComp.off('remove', this.onLayerRemoved, this);
newComp.on('add', this.onLayerAdded, this);
newComp.on('remove', this.onLayerRemoved, this);
}
onLayerAdded(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this._image) {
layer.addMeshInstances(this._image._renderable.model.meshInstances);
} else if (this._text) {
layer.addMeshInstances(this._text._model.meshInstances);
}
}
onLayerRemoved(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this._image) {
layer.removeMeshInstances(this._image._renderable.model.meshInstances);
} else if (this._text) {
layer.removeMeshInstances(this._text._model.meshInstances);
}
}
onEnable() {
const scene = this.system.app.scene;
const layers = scene.layers;
if (this._image) {
this._image.onEnable();
}
if (this._text) {
this._text.onEnable();
}
if (this._group) {
this._group.onEnable();
}
if (this.useInput && this.system.app.elementInput) {
this.system.app.elementInput.addElement(this);
}
this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
if (layers) {
this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
}
if (this._batchGroupId >= 0) {
this.system.app.batcher?.insert(BatchGroup.ELEMENT, this.batchGroupId, this.entity);
}
this.fire('enableelement');
}
onDisable() {
const scene = this.system.app.scene;
const layers = scene.layers;
this._evtLayersChanged?.off();
this._evtLayersChanged = null;
if (layers) {
this._evtLayerAdded?.off();
this._evtLayerAdded = null;
this._evtLayerRemoved?.off();
this._evtLayerRemoved = null;
}
if (this._image) this._image.onDisable();
if (this._text) this._text.onDisable();
if (this._group) this._group.onDisable();
if (this.system.app.elementInput && this.useInput) {
this.system.app.elementInput.removeElement(this);
}
if (this._batchGroupId >= 0) {
this.system.app.batcher?.remove(BatchGroup.ELEMENT, this.batchGroupId, this.entity);
}
this.fire('disableelement');
}
onRemove() {
this.entity.off('insert', this._onInsert, this);
this._unpatch();
if (this._image) {
this._image.destroy();
}
if (this._text) {
this._text.destroy();
}
if (this.system.app.elementInput && this.useInput) {
this.system.app.elementInput.removeElement(this);
}
if (this.screen && this.screen.screen) {
this._unbindScreen(this.screen.screen);
this.screen.screen.syncDrawOrder();
}
this.off();
}
_calculateSize(propagateCalculatedWidth, propagateCalculatedHeight) {
if (!this.entity._parent && !this.screen) {
return;
}
this._calculateLocalAnchors();
const newWidth = this._absRight - this._absLeft;
const newHeight = this._absTop - this._absBottom;
if (propagateCalculatedWidth) {
this._setWidth(newWidth);
} else {
this._setCalculatedWidth(newWidth, false);
}
if (propagateCalculatedHeight) {
this._setHeight(newHeight);
} else {
this._setCalculatedHeight(newHeight, false);
}
const p = this.entity.getLocalPosition();
p.x = this._margin.x + this._calculatedWidth * this._pivot.x;
p.y = this._margin.y + this._calculatedHeight * this._pivot.y;
this.entity.setLocalPosition(p);
this._sizeDirty = false;
}
_setWidth(w) {
this._width = w;
this._setCalculatedWidth(w, false);
this.fire('set:width', this._width);
}
_setHeight(h) {
this._height = h;
this._setCalculatedHeight(h, false);
this.fire('set:height', this._height);
}
_setCalculatedWidth(value, updateMargins) {
if (Math.abs(value - this._calculatedWidth) <= 1e-4) {
return;
}
this._calculatedWidth = value;
this.entity._dirtifyLocal();
if (updateMargins) {
const p = this.entity.getLocalPosition();
const pvt = this._pivot;
this._margin.x = p.x - this._calculatedWidth * pvt.x;
this._margin.z = this._localAnchor.z - this._localAnchor.x - this._calculatedWidth - this._margin.x;
}
this._flagChildrenAsDirty();
this.fire('set:calculatedWidth', this._calculatedWidth);
this.fire('resize', this._calculatedWidth, this._calculatedHeight);
}
_setCalculatedHeight(value, updateMargins) {
if (Math.abs(value - this._calculatedHeight) <= 1e-4) {
return;
}
this._calculatedHeight = value;
this.entity._dirtifyLocal();
if (updateMargins) {
const p = this.entity.getLocalPosition();
const pvt = this._pivot;
this._margin.y = p.y - this._calculatedHeight * pvt.y;
this._margin.w = this._localAnchor.w - this._localAnchor.y - this._calculatedHeight - this._margin.y;
}
this._flagChildrenAsDirty();
this.fire('set:calculatedHeight', this._calculatedHeight);
this.fire('resize', this._calculatedWidth, this._calculatedHeight);
}
_flagChildrenAsDirty() {
const c = this.entity._children;
for(let i = 0, l = c.length; i < l; i++){
if (c[i].element) {
c[i].element._anchorDirty = true;
c[i].element._sizeDirty = true;
}
}
}
addModelToLayers(model) {
this._addedModels.push(model);
for(let i = 0; i < this.layers.length; i++){
const layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
if (!layer) continue;
layer.addMeshInstances(model.meshInstances);
}
}
removeModelFromLayers(model) {
const idx = this._addedModels.indexOf(model);
if (idx >= 0) {
this._addedModels.splice(idx, 1);
}
for(let i = 0; i < this.layers.length; i++){
const layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
if (!layer) {
continue;
}
layer.removeMeshInstances(model.meshInstances);
}
}
getMaskOffset() {
const frame = this.system.app.frame;
if (this._offsetReadAt !== frame) {
this._maskOffset = 0.5;
this._offsetReadAt = frame;
}
const mo = this._maskOffset;
this._maskOffset -= 0.001;
return mo;
}
isVisibleForCamera(camera) {
let clipL, clipR, clipT, clipB;
if (this.maskedBy) {
const corners = this.maskedBy.element.screenCorners;
clipL = Math.min(Math.min(corners[0].x, corners[1].x), Math.min(corners[2].x, corners[3].x));
clipR = Math.max(Math.max(corners[0].x, corners[1].x), Math.max(corners[2].x, corners[3].x));
clipB = Math.min(Math.min(corners[0].y, corners[1].y), Math.min(corners[2].y, corners[3].y));
clipT = Math.max(Math.max(corners[0].y, corners[1].y), Math.max(corners[2].y, corners[3].y));
} else {
const sw = this.system.app.graphicsDevice.width;
const sh = this.system.app.graphicsDevice.height;
const cameraWidth = camera._rect.z * sw;
const cameraHeight = camera._rect.w * sh;
clipL = camera._rect.x * sw;
clipR = clipL + cameraWidth;
clipT = (1 - camera._rect.y) * sh;
clipB = clipT - cameraHeight;
}
const hitCorners = this.screenCorners;
const left = Math.min(Math.min(hitCorners[0].x, hitCorners[1].x), Math.min(hitCorners[2].x, hitCorners[3].x));
const right = Math.max(Math.max(hitCorners[0].x, hitCorners[1].x), Math.max(hitCorners[2].x, hitCorners[3].x));
const bottom = Math.min(Math.min(hitCorners[0].y, hitCorners[1].y), Math.min(hitCorners[2].y, hitCorners[3].y));
const top = Math.max(Math.max(hitCorners[0].y, hitCorners[1].y), Math.max(hitCorners[2].y, hitCorners[3].y));
if (right < clipL || left > clipR || bottom > clipT || top < clipB) {
return false;
}
return true;
}
_isScreenSpace() {
if (this.screen && this.screen.screen) {
return this.screen.screen.screenSpace;
}
return false;
}
_isScreenCulled() {
if (this.screen && this.screen.screen) {
return this.screen.screen.cull;
}
return false;
}
_dirtyBatch() {
if (this.batchGroupId !== -1) {
this.system.app.batcher?.markGroupDirty(this.batchGroupId);
}
}
}
export { ElementComponent };