From 58ad3441d4ce6bb2a472a8232b37360d18b34f3d Mon Sep 17 00:00:00 2001 From: hellmor Date: Mon, 29 May 2023 17:21:02 +0800 Subject: [PATCH] perf(GUI): performance (#174) Improve GUI. Add performance sample. revert a sample. fix some errors of rebuild gui When the imageType changes, the position attribute needs to be changed simplified code See Sample_UIPerformance2.ts fix the erro of layout sprites. --- samples/gui/Sample_UIPerformance.ts | 168 +++++++++++++++++ samples/gui/Sample_UIPerformance2.ts | 170 ++++++++++++++++++ samples/gui/Sample_UISpriteSheet.ts | 2 +- samples/gui/Sample_UITextField.ts | 15 +- src/components/gui/core/GUICanvas.ts | 16 +- src/components/gui/core/GUIGeometry.ts | 151 ++++++++++------ src/components/gui/core/GUIGeometryRebuild.ts | 36 ++-- src/components/gui/core/GUIMaterial.ts | 11 +- src/components/gui/core/GUIMesh.ts | 1 + src/components/gui/core/GUIQuad.ts | 85 +++++++-- src/components/gui/core/GUIRenderer.ts | 10 +- src/components/gui/core/GUIShader.ts | 44 ++--- .../gui/uiComponents/TextFieldLayout.ts | 8 +- .../gui/uiComponents/UIComponentBase.ts | 10 +- src/components/gui/uiComponents/UIImage.ts | 56 ++++-- .../gui/uiComponents/UIImageGroup.ts | 86 +++++++++ src/components/gui/uiComponents/UIPanel.ts | 2 +- .../gui/uiComponents/UITextField.ts | 8 +- .../gui/uiComponents/UITransform.ts | 22 ++- src/components/gui/uiComponents/ViewPanel.ts | 2 + src/components/gui/uiComponents/WorldPanel.ts | 1 + src/core/entities/Object3D.ts | 27 ++- src/index.ts | 1 + 23 files changed, 761 insertions(+), 171 deletions(-) create mode 100644 samples/gui/Sample_UIPerformance.ts create mode 100644 samples/gui/Sample_UIPerformance2.ts create mode 100644 src/components/gui/uiComponents/UIImageGroup.ts diff --git a/samples/gui/Sample_UIPerformance.ts b/samples/gui/Sample_UIPerformance.ts new file mode 100644 index 00000000..38f0d976 --- /dev/null +++ b/samples/gui/Sample_UIPerformance.ts @@ -0,0 +1,168 @@ +import { BoundingBox, Color, Engine3D, GUIConfig, Object3D, Scene3D, UIImage, TextAnchor, UITextField, Vector2, Vector3, ViewPanel, clamp } from "@orillusion/core"; +import { GUIHelp } from "@orillusion/debug/GUIHelp"; +import { createExampleScene } from "@samples/utils/ExampleScene"; +import { Stats } from "@orillusion/stats"; + +class SpriteSheet { + public static toggleMove: boolean = false; + public static toggleAnim: boolean = true; + + private img: UIImage; + private lastIndex: number = -1; + private frame: number = 100 * Math.random(); + private frameSpeed: number = 0.5 + Math.random(); + private frameCount = 13; + private keyFrames: string[]; + private moveSpeed: Vector2; + private bound: BoundingBox; + constructor(img: UIImage, keyFrames: string[], bound: BoundingBox) { + this.img = img; + this.bound = bound; + this.keyFrames = keyFrames; + this.moveSpeed = new Vector2(Math.random() - 0.5, Math.random() - 0.5); + } + + updateFrame(): void { + if (SpriteSheet.toggleAnim) { + this.frame += this.frameSpeed; + let newIndex = Math.floor(this.frame * 0.1) % this.frameCount; + if (newIndex != this.lastIndex) { + this.lastIndex = newIndex; + this.img.sprite = Engine3D.res.getGUISprite(this.keyFrames[newIndex]); + } + } + + if (SpriteSheet.toggleMove) { + let x = this.img.uiTransform.x; + let y = this.img.uiTransform.y; + x += this.moveSpeed.x; + y += this.moveSpeed.y; + if (x < this.bound.min.x || x > this.bound.max.x) { + this.moveSpeed.x *= -1; + } + if (y < this.bound.min.y || y > this.bound.max.y) { + this.moveSpeed.y *= -1; + } + + this.img.uiTransform.setXY(x, y); + } + + } +} + +export class Sample_UISpriteSheet { + text: UITextField; + scene: Scene3D; + keyFrames: string[]; + + async run() { + Engine3D.setting.shadow.autoUpdate = true; + Engine3D.setting.shadow.shadowBias = 0.002; + + GUIConfig.quadMaxCountForView = 5001; + + GUIHelp.init(); + this.spriteSheets = []; + this.keyFrames = []; + let frameStart = 65;//65~77 + + for (let i = 0; i < 13; i++) { + this.keyFrames.push((frameStart + i).toString().padStart(5, '0')); + } + + await Engine3D.init({ renderLoop: () => { this.renderUpdate(); } }); + let exampleScene = createExampleScene(); + this.scene = exampleScene.scene; + this.scene.addComponent(Stats); + Engine3D.startRenderView(exampleScene.view); + await Engine3D.res.loadAtlas('atlas/Sheet_atlas.json'); + await Engine3D.res.loadFont('fnt/0.fnt'); + + this.text = this.createText(); + + + GUIHelp.add(SpriteSheet, 'toggleMove'); + GUIHelp.add(SpriteSheet, 'toggleAnim'); + + GUIHelp.addButton('Add Sprites', () => { + if (this.spriteSheets.length < 99999) { + this.addLotOfSprite(); + } + }); + + GUIHelp.open(); + GUIHelp.endFolder(); + + this.addLotOfSprite(); + } + + addLotOfSprite() { + // enable ui canvas i + let canvas = this.scene.view.enableUICanvas(0); + //create UI root + let panelRoot: Object3D = new Object3D(); + //create panel + let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + canvas.addChild(panel.object3D); + //create sprite sheet list + this.createSpriteSheets(panelRoot); + } + + createText(): UITextField { + let canvas = this.scene.view.enableUICanvas(0); + //create UI root + let panelRoot: Object3D = new Object3D(); + //create panel + let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + panel.panelOrder = 10000; + canvas.addChild(panel.object3D); + let textQuad = new Object3D(); + panelRoot.addChild(textQuad); + let text = textQuad.addComponent(UITextField); + text.uiTransform.resize(400, 60); + + text.fontSize = 24; + text.alignment = TextAnchor.MiddleCenter; + + return text; + + } + + spriteSheets: SpriteSheet[]; + + private createSpriteSheets(root: Object3D) { + let width = Engine3D.width; + let height = Engine3D.height; + let bound = new BoundingBox(new Vector3(0, 0, 0), new Vector3(width, height)); + //color + let color: Color = Color.random(); + color.a = 1; + + color.r = clamp(color.r * 1.5, 0.5, 1); + color.g = clamp(color.g * 1.5, 0.5, 1); + color.b = clamp(color.b * 1.5, 0.5, 1); + + for (let i = 0; i < 5000; i++) { + let quad = new Object3D(); + root.addChild(quad); + // + let img = quad.addComponent(UIImage); + img.color = color; + img.sprite = Engine3D.res.getGUISprite('00065'); + img.uiTransform.resize(64, 64); + img.uiTransform.x = (Math.random() - 0.5) * width * 0.7; + img.uiTransform.y = (Math.random() - 0.5) * height * 0.7; + let sheet: SpriteSheet = new SpriteSheet(img, this.keyFrames, bound); + this.spriteSheets.push(sheet); + } + + this.text.text = this.spriteSheets.length.toString() + ' Sprites'; + } + + renderUpdate() { + for (const item of this.spriteSheets) { + item.updateFrame(); + } + } + +} diff --git a/samples/gui/Sample_UIPerformance2.ts b/samples/gui/Sample_UIPerformance2.ts new file mode 100644 index 00000000..23646356 --- /dev/null +++ b/samples/gui/Sample_UIPerformance2.ts @@ -0,0 +1,170 @@ +import { BoundingBox, Color, Engine3D, GUIConfig, GUIQuad, Object3D, Scene3D, TextAnchor, UIImageGroup, UITextField, Vector2, Vector3, ViewPanel, clamp } from "@orillusion/core"; +import { GUIHelp } from "@orillusion/debug/GUIHelp"; +import { createExampleScene } from "@samples/utils/ExampleScene"; +import { Stats } from "@orillusion/stats"; + +class SpriteSheet { + public static toggleMove: boolean = false; + public static toggleAnim: boolean = true; + + private imgGroup: UIImageGroup; + private lastIndex: number = -1; + private frame: number = 100 * Math.random(); + private frameSpeed: number = 0.5 + Math.random(); + private frameCount = 13; + private keyFrames: string[]; + private moveSpeed: Vector2; + private bound: BoundingBox; + private index: number; + + private quad: GUIQuad; + constructor(img: UIImageGroup, index: number, keyFrames: string[], bound: BoundingBox) { + this.imgGroup = img; + this.index = index; + this.bound = bound; + this.keyFrames = keyFrames; + this.moveSpeed = new Vector2(Math.random() - 0.5, Math.random() - 0.5); + this.quad = img.getQuad(index); + } + + updateFrame(): void { + if (SpriteSheet.toggleAnim) { + this.frame += this.frameSpeed; + let newIndex = Math.floor(this.frame * 0.1) % this.frameCount; + if (newIndex != this.lastIndex) { + this.lastIndex = newIndex; + this.imgGroup.setSprite(this.index, Engine3D.res.getGUISprite(this.keyFrames[newIndex])); + } + } + + if (SpriteSheet.toggleMove) { + let x = this.quad.x; + let y = this.quad.y; + x += this.moveSpeed.x; + y += this.moveSpeed.y; + if (x < this.bound.min.x || x > this.bound.max.x) { + this.moveSpeed.x *= -1; + } + if (y < this.bound.min.y || y > this.bound.max.y) { + this.moveSpeed.y *= -1; + } + this.imgGroup.setXY(this.index, x, y); + } + + } +} + +export class Sample_UIPerformance2 { + text: UITextField; + scene: Scene3D; + keyFrames: string[]; + + async run() { + Engine3D.setting.shadow.autoUpdate = true; + Engine3D.setting.shadow.shadowBias = 0.002; + + GUIConfig.quadMaxCountForView = 5001; + + GUIHelp.init(); + this.spriteSheets = []; + this.keyFrames = []; + let frameStart = 65;//65~77 + + for (let i = 0; i < 13; i++) { + this.keyFrames.push((frameStart + i).toString().padStart(5, '0')); + } + + await Engine3D.init({ renderLoop: () => { this.renderUpdate(); } }); + let exampleScene = createExampleScene(); + this.scene = exampleScene.scene; + this.scene.addComponent(Stats); + Engine3D.startRenderView(exampleScene.view); + await Engine3D.res.loadAtlas('atlas/Sheet_atlas.json'); + await Engine3D.res.loadFont('fnt/0.fnt'); + + this.text = this.createText(); + + + GUIHelp.add(SpriteSheet, 'toggleMove'); + GUIHelp.add(SpriteSheet, 'toggleAnim'); + + GUIHelp.addButton('Add Sprites', () => { + if (this.spriteSheets.length < 99999) { + this.addLotOfSprite(); + } + }); + + GUIHelp.open(); + GUIHelp.endFolder(); + + this.addLotOfSprite(); + } + + addLotOfSprite() { + // enable ui canvas i + let canvas = this.scene.view.enableUICanvas(0); + //create UI root + let panelRoot: Object3D = new Object3D(); + //create panel + let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + canvas.addChild(panel.object3D); + //create sprite sheet list + this.createSpriteSheets(panelRoot); + } + + createText(): UITextField { + let canvas = this.scene.view.enableUICanvas(0); + //create UI root + let panelRoot: Object3D = new Object3D(); + //create panel + let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + panel.panelOrder = 10000; + canvas.addChild(panel.object3D); + let textQuad = new Object3D(); + panelRoot.addChild(textQuad); + let text = textQuad.addComponent(UITextField); + text.uiTransform.resize(400, 60); + + text.fontSize = 24; + text.alignment = TextAnchor.MiddleCenter; + + return text; + + } + + spriteSheets: SpriteSheet[]; + + private createSpriteSheets(root: Object3D) { + let width = Engine3D.width; + let height = Engine3D.height; + let bound = new BoundingBox(new Vector3(0, 0, 0), new Vector3(width, height)); + //color + let color: Color = Color.random(); + color.a = 1; + + color.r = clamp(color.r * 1.5, 0.5, 1); + color.g = clamp(color.g * 1.5, 0.5, 1); + color.b = clamp(color.b * 1.5, 0.5, 1); + + let sprite = Engine3D.res.getGUISprite('00065'); + + let imgGroup = root.addComponent(UIImageGroup, { count: 5000 }); + for (let i = 0; i < 5000; i++) { + imgGroup.setColor(i, color); + imgGroup.setSprite(i, sprite); + imgGroup.setSize(i, 64, 64); + imgGroup.setXY(i, (Math.random() - 0.5) * width * 0.7, (Math.random() - 0.5) * width * 0.7); + let sheet: SpriteSheet = new SpriteSheet(imgGroup, i, this.keyFrames, bound); + this.spriteSheets.push(sheet); + } + + this.text.text = this.spriteSheets.length.toString() + ' Sprites'; + } + + renderUpdate() { + for (const item of this.spriteSheets) { + item.updateFrame(); + } + } + +} diff --git a/samples/gui/Sample_UISpriteSheet.ts b/samples/gui/Sample_UISpriteSheet.ts index 27a5683e..73d7e77d 100644 --- a/samples/gui/Sample_UISpriteSheet.ts +++ b/samples/gui/Sample_UISpriteSheet.ts @@ -57,7 +57,7 @@ export class Sample_UISpriteSheet { private frameStart = 65;//65~77 private frameCount = 13; loopTextureSheet(): void { - let newIndex = Math.floor(this.frame * 0.05) % this.frameCount; + let newIndex = Math.floor(this.frame * 0.2) % this.frameCount; if (newIndex != this.lastIndex) { this.lastIndex = newIndex; let frameKey = (this.lastIndex + this.frameStart).toString().padStart(5, '0'); diff --git a/samples/gui/Sample_UITextField.ts b/samples/gui/Sample_UITextField.ts index 9b50ee19..42323297 100644 --- a/samples/gui/Sample_UITextField.ts +++ b/samples/gui/Sample_UITextField.ts @@ -1,6 +1,6 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { createExampleScene } from "@samples/utils/ExampleScene"; -import { Object3D, Engine3D, GUISpace, WorldPanel, ViewPanel, UITextField, TextAnchor, Object3DUtil, UIPanel } from "@orillusion/core"; +import { Object3D, Engine3D, GUISpace, WorldPanel, ViewPanel, UITextField, TextAnchor, Object3DUtil, UIPanel, UIImage } from "@orillusion/core"; export class Sample_UITextField { @@ -42,13 +42,26 @@ export class Sample_UITextField { let textQuad = new Object3D(); panelRoot.addChild(textQuad); this.text = textQuad.addComponent(UITextField); + textQuad.addComponent(UIImage).color.a = 0.2; this.text.uiTransform.resize(400, 60); this.text.uiTransform.y = 100; this.text.text = 'Hello,Orillusion!'; this.text.fontSize = 32; this.text.alignment = TextAnchor.MiddleCenter; + let size = { width: this.text.uiTransform.width, height: this.text.uiTransform.height }; + let changeSize = () => { + this.text.uiTransform.resize(size.width, size.height); + } + GUIHelp.add(size, 'width', 100, 200, 1).onChange(() => { + changeSize(); + }); + GUIHelp.open(); + GUIHelp.endFolder(); + } + + } private text: UITextField; diff --git a/src/components/gui/core/GUICanvas.ts b/src/components/gui/core/GUICanvas.ts index 1bc6451a..504c9ff7 100644 --- a/src/components/gui/core/GUICanvas.ts +++ b/src/components/gui/core/GUICanvas.ts @@ -1,13 +1,11 @@ import { Object3D } from "../../../core/entities/Object3D"; import { webGPUContext } from "../../../gfx/graphics/webGpu/Context3D"; -import { Ctor } from "../../../util/Global"; import { ComponentBase } from "../../ComponentBase"; import { GUIConfig } from "../GUIConfig"; import { UIPanel } from "../uiComponents/UIPanel"; import { UITransform } from "../uiComponents/UITransform"; -import { ViewPanel } from "../uiComponents/ViewPanel"; -import { WorldPanel } from "../uiComponents/WorldPanel"; import { GUIGeometryRebuild } from "./GUIGeometryRebuild"; +import { GUIMaterial } from "./GUIMaterial"; import { GUIMesh } from "./GUIMesh"; export class GUICanvas extends ComponentBase { @@ -44,18 +42,13 @@ export class GUICanvas extends ComponentBase { } private rebuildGUIMesh() { - this.buildGUIMesh(WorldPanel); - this.buildGUIMesh(ViewPanel); - } + let panelList: UIPanel[] = this.object3D.getComponentsByProperty('isUIPanel', true, true); - private buildGUIMesh(ctor: Ctor): void { let camera = this.object3D?.transform?.view3D?.camera; let screenWidth = webGPUContext.canvas.clientWidth; let screenHeight = webGPUContext.canvas.clientHeight; - let panelList = this.object3D.getComponentsExt(ctor); - - panelList.forEach(panel => { + for (let panel of panelList) { let guiMesh: GUIMesh = panel.guiMesh; let transforms: UITransform[] = panel.object3D.getComponents(UITransform); if (transforms.length > 0) { @@ -69,8 +62,9 @@ export class GUICanvas extends ComponentBase { guiMesh.uiRenderer.enable = transforms.length > 0; guiMesh.uiRenderer.renderOrder = GUIConfig.SortOrderStart + panel.panelOrder; guiMesh.uiRenderer.needSortOnCameraZ = panel.needSortOnCameraZ; + (guiMesh.uiRenderer.material as GUIMaterial).setLimitVertex(guiMesh.limitVertexCount); panel.needUpdateGeometry = false; - }); + } } public cloneTo(obj: Object3D) { diff --git a/src/components/gui/core/GUIGeometry.ts b/src/components/gui/core/GUIGeometry.ts index d0607fda..e378c2e3 100644 --- a/src/components/gui/core/GUIGeometry.ts +++ b/src/components/gui/core/GUIGeometry.ts @@ -6,6 +6,23 @@ import { ImageType } from '../GUIConfig'; import { UITransform } from '../uiComponents/UITransform'; import { GUIQuad } from './GUIQuad'; +export enum GUIQuadAttrEnum { + NONE = 0, + POSITION = 1 << 0, + SPRITE = 1 << 1, + COLOR = 1 << 2, + MAX = POSITION + COLOR + SPRITE +} + +class GUIAttribute { + public array: Float32Array; + public buffer: StorageGPUBuffer; + constructor(count: number) { + this.buffer = new StorageGPUBuffer(count, 0); + this.array = new Float32Array(this.buffer.memory.shareDataBuffer); + } +} + export class GUIGeometry extends GeometryBase { private _attributeUV: Float32Array; private _attributeVIndex: Float32Array; @@ -14,14 +31,12 @@ export class GUIGeometry extends GeometryBase { private _uvSize: number = 2; private _vIndexSize: number = 1; - private _vPosition: StorageGPUBuffer;//Position data per vertex - private _vUniform: StorageGPUBuffer;//data per quad: Color, and texture id... - - private _positionArray: Float32Array; - private _uniformArray: Float32Array; - + private _posAttribute: GUIAttribute;//Position data per vertex + private _spriteAttribute: GUIAttribute;//data per quad,texture id... + private _colorAttribute: GUIAttribute;//data per quad: Color private _onPositionChange: boolean = true; - private _onUniformChange: boolean = true; + private _onSpriteChange: boolean = true; + private _onColorChange: boolean = true; public readonly maxQuadCount: number; @@ -49,18 +64,26 @@ export class GUIGeometry extends GeometryBase { public get vPositionBuffer(): StorageGPUBuffer { if (this._onPositionChange) { - this._vPosition.apply(); + this._posAttribute.buffer.apply(); this._onPositionChange = false; } - return this._vPosition; + return this._posAttribute.buffer; + } + + public get vSpriteBuffer(): StorageGPUBuffer { + if (this._onSpriteChange) { + this._spriteAttribute.buffer.apply(); + this._onSpriteChange = false; + } + return this._spriteAttribute.buffer; } - public get vUniformBuffer(): StorageGPUBuffer { - if (this._onUniformChange) { - this._vUniform.apply(); - this._onUniformChange = false; + public get vColorBuffer(): StorageGPUBuffer { + if (this._onColorChange) { + this._colorAttribute.buffer.apply(); + this._onColorChange = false; } - return this._vUniform; + return this._colorAttribute.buffer; } public create(): this { @@ -114,39 +137,55 @@ export class GUIGeometry extends GeometryBase { let quadNum: number = this.maxQuadCount; //Each quad has 4 vertices, and each vertex has 2 data points let sizePositionArray = quadNum * 4 * 2; - this._vPosition = new StorageGPUBuffer(sizePositionArray, 0); - this._positionArray = new Float32Array(this._vPosition.memory.shareDataBuffer); - //Each quad has : color/uvRec_size/uvBorder_size/uvSlice_size/textureID/visible - let sizeUniformArray = quadNum * (4 + 4 + 4 + 2 + 2); - this._vUniform = new StorageGPUBuffer(sizeUniformArray, 0); - this._uniformArray = new Float32Array(this._vUniform.memory.shareDataBuffer); - } + this._posAttribute = new GUIAttribute(sizePositionArray); + + //Each quad has : uvRec_size/uvBorder_size/uvSlice_size/textureID/visible + let sizeSpriteArray = quadNum * (4 + 4 + 2 + 2); + this._spriteAttribute = new GUIAttribute(sizeSpriteArray); - public updateQuad(quad: GUIQuad, transform: UITransform) { - this.updateQuadVertex(quad, transform); + this._colorAttribute = new GUIAttribute(quadNum * 4); + } - this.updateQuadUniform(quad, transform); + public fillQuad(quad: GUIQuad, transform: UITransform) { + if (quad.dirtyAttributes & GUIQuadAttrEnum.POSITION) { + this.fillQuadPosition(quad, transform); + } + if (quad.dirtyAttributes & GUIQuadAttrEnum.COLOR) { + this.fillQuadColor(quad, transform); + } + if (quad.dirtyAttributes & GUIQuadAttrEnum.SPRITE) { + this.fillQuadSprite(quad, transform); + } } - private updateQuadVertex(quad: GUIQuad, transform: UITransform): void { + private fillQuadPosition(quad: GUIQuad, transform: UITransform): void { let qi = quad.z * QuadStruct.vertexCount; + let array = this._posAttribute.array; let vi = 0; - SetBufferDataV2.setXY(this._positionArray, qi + vi, quad.left, quad.top); + SetBufferDataV2.setXY(array, qi + vi, quad.left, quad.top); vi = 1; - SetBufferDataV2.setXY(this._positionArray, qi + vi, quad.right, quad.top); + SetBufferDataV2.setXY(array, qi + vi, quad.right, quad.top); vi = 2; - SetBufferDataV2.setXY(this._positionArray, qi + vi, quad.right, quad.bottom); + SetBufferDataV2.setXY(array, qi + vi, quad.right, quad.bottom); vi = 3; - SetBufferDataV2.setXY(this._positionArray, qi + vi, quad.left, quad.bottom); + SetBufferDataV2.setXY(array, qi + vi, quad.left, quad.bottom); this._onPositionChange = true; } - private updateQuadUniform(quad: GUIQuad, transform: UITransform) { + private fillQuadColor(quad: GUIQuad, transform: UITransform): void { + let color = quad.color; + let array = this._colorAttribute.array; + SetBufferDataV4.setXYZW(array, quad.z, color.r, color.g, color.b, color.a); + + this._onColorChange = true; + } + + private fillQuadSprite(quad: GUIQuad, transform: UITransform) { let texture = quad.sprite; let uvSliceWidth: number = 0; @@ -162,43 +201,39 @@ export class GUIGeometry extends GeometryBase { let i = quad.z; let textureID = texture.guiTexture.dynamicId; - let color = quad.color; let uvRec = texture.uvRec; let uvBorder = texture.uvBorder; - //Each quad has: color/uvRec_size/uvBorder_size/uvSlice_size/textureID/visible + //Each quad has: uvRec_size/uvBorder_size/uvSlice_size/textureID/visible - let offset = (4 + 4 + 4 + 2 + 2) * i; - this._uniformArray[offset + 0] = color.r; - this._uniformArray[offset + 1] = color.g; - this._uniformArray[offset + 2] = color.b; - this._uniformArray[offset + 3] = color.a; + let spriteArray = this._spriteAttribute.array; + let offset = (4 + 4 + 2 + 2) * i; - this._uniformArray[offset + 4] = uvRec.x; - this._uniformArray[offset + 5] = uvRec.y; - this._uniformArray[offset + 6] = uvRec.z; - this._uniformArray[offset + 7] = uvRec.w; + spriteArray[offset + 0] = uvRec.x; + spriteArray[offset + 1] = uvRec.y; + spriteArray[offset + 2] = uvRec.z; + spriteArray[offset + 3] = uvRec.w; - this._uniformArray[offset + 8] = uvBorder.w; - this._uniformArray[offset + 9] = uvBorder.y; - this._uniformArray[offset + 10] = uvBorder.z; - this._uniformArray[offset + 11] = uvBorder.w; + spriteArray[offset + 4] = uvBorder.w; + spriteArray[offset + 5] = uvBorder.y; + spriteArray[offset + 6] = uvBorder.z; + spriteArray[offset + 7] = uvBorder.w; - this._uniformArray[offset + 12] = uvSliceWidth; - this._uniformArray[offset + 13] = uvSliceHeight; - this._uniformArray[offset + 14] = textureID; - this._uniformArray[offset + 15] = quad.visible ? 1 : 0; + spriteArray[offset + 8] = uvSliceWidth; + spriteArray[offset + 9] = uvSliceHeight; + spriteArray[offset + 10] = textureID; + spriteArray[offset + 11] = quad.visible ? 1 : 0; - this._onUniformChange = true; + this._onSpriteChange = true; } - reset(z: number) { - let qi = z * QuadStruct.vertexCount; - let max = this.maxQuadCount * QuadStruct.vertexCount; - for (let i = qi; i < max; i++) { - SetBufferDataV2.setXY(this._positionArray, i, 0, 0); - } - this._onPositionChange = true; - } + // cutOff(z: number) { + // let qi = z * QuadStruct.vertexCount; + // let max = this.maxQuadCount * QuadStruct.vertexCount; + // for (let i = qi; i < max; i++) { + // SetBufferDataV2.setXY(this._posAttribute.array, i, 0, 0); + // } + // this._onPositionChange = true; + // } } diff --git a/src/components/gui/core/GUIGeometryRebuild.ts b/src/components/gui/core/GUIGeometryRebuild.ts index 2f7ac2db..44b4610e 100644 --- a/src/components/gui/core/GUIGeometryRebuild.ts +++ b/src/components/gui/core/GUIGeometryRebuild.ts @@ -1,5 +1,6 @@ -import { Texture } from "../../.."; +import { Texture } from "../../../gfx/graphics/webGpu/core/texture/Texture"; import { UITransform } from "../uiComponents/UITransform"; +import { GUIQuadAttrEnum } from "./GUIGeometry"; import { GUIMesh } from "./GUIMesh"; import { GUITexture } from "./GUITexture"; @@ -8,7 +9,6 @@ import { GUITexture } from "./GUITexture"; * @group GUI */ export class GUIGeometryRebuild { - private _textureMap: Map = new Map(); private _textureList: Texture[] = []; @@ -21,7 +21,6 @@ export class GUIGeometryRebuild { * @returns Return the build result (the maximum number of textures supported by GUIMaterials for a single UIPanel is limited and cannot exceed the limit) */ public build(transforms: UITransform[], guiMesh: GUIMesh, forceUpdate: boolean): boolean { - // let quadIndex = -1; let texIndex = -1; @@ -29,8 +28,7 @@ export class GUIGeometryRebuild { this._textureList.length = 0; let zMax: number = guiMesh.quadMaxCount - 1; - let isGeometryDirty = forceUpdate; - + let needBreak: boolean; for (let transform of transforms) { transform.guiMesh = guiMesh; let needUpdateQuads = transform.needUpdateQuads; @@ -52,23 +50,31 @@ export class GUIGeometryRebuild { } } - if (needUpdateQuads || quad.onChange || isGeometryDirty) { - quad.transformQuad(transform); + let updateAllAttr = needUpdateQuads || forceUpdate; + if (updateAllAttr) { + quad.dirtyAttributes = GUIQuadAttrEnum.MAX; + } + if (quad.dirtyAttributes & GUIQuadAttrEnum.POSITION) { + quad.applyTransform(transform); } - if (quad.onChange) { - quad.updateGeometryBuffer(guiMesh.geometry, transform); - isGeometryDirty = true; + if (quad.dirtyAttributes) { + quad.writeToGeometry(guiMesh.geometry, transform); } if (quadIndex == zMax) { - return false; + needBreak = true; + break; } } + if (needBreak) { + break; + } } guiMesh['_setTextures'](this._textureList); - if (isGeometryDirty) { - guiMesh.geometry.reset(quadIndex + 1); - } - return true; + guiMesh.limitVertexCount = (quadIndex + 1) * 4; + // if (isGeometryDirty) { + // guiMesh.geometry.cutOff(quadIndex + 1); + // } + return !needBreak; } } diff --git a/src/components/gui/core/GUIMaterial.ts b/src/components/gui/core/GUIMaterial.ts index bc900f8c..c447da8c 100644 --- a/src/components/gui/core/GUIMaterial.ts +++ b/src/components/gui/core/GUIMaterial.ts @@ -19,6 +19,7 @@ export class GUIMaterial extends MaterialBase { shader.setUniformVector2('screen', new Vector2(1024, 1024)); shader.setUniformVector2('mipmapRange', new Vector2(0, 10)); + shader.setUniformFloat('limitVertex', 0);//count: (quadCount + 1) * QuadStruct.vertexCount let shaderState = shader.shaderState; // shaderState.useZ = false; @@ -31,14 +32,22 @@ export class GUIMaterial extends MaterialBase { this.receiveEnv = false; } + /** + * Write effective vertex count (vertex index < vertexCount) + */ + public setLimitVertex(vertexCount: number) { + this.renderShader.setUniformFloat('limitVertex', vertexCount); + } + private _screenSizeVec2: Vector2 = new Vector2(); /** * Write screen size to the shader */ - public setScreenSize(width: number, height: number) { + public setScreenSize(width: number, height: number): this { this._screenSizeVec2.set(width, height); this.renderShader.setUniformVector2('screen', this._screenSizeVec2); + return this; } /** diff --git a/src/components/gui/core/GUIMesh.ts b/src/components/gui/core/GUIMesh.ts index 01f109ed..5abc3a35 100644 --- a/src/components/gui/core/GUIMesh.ts +++ b/src/components/gui/core/GUIMesh.ts @@ -13,6 +13,7 @@ export class GUIMesh extends Object3D { public geometry: GUIGeometry; public readonly space: GUISpace; + public limitVertexCount: number = 0; private readonly _maxCount: number = 128; private _billboard: BillboardComponent; diff --git a/src/components/gui/core/GUIQuad.ts b/src/components/gui/core/GUIQuad.ts index e10f4c79..0ae1f8a4 100644 --- a/src/components/gui/core/GUIQuad.ts +++ b/src/components/gui/core/GUIQuad.ts @@ -1,5 +1,5 @@ import { UITransform } from "../uiComponents/UITransform"; -import { GUIGeometry } from "./GUIGeometry"; +import { GUIGeometry, GUIQuadAttrEnum } from "./GUIGeometry"; import { GUISprite } from "./GUISprite"; import { ImageType } from "../GUIConfig"; import { Engine3D } from "../../../Engine3D"; @@ -25,19 +25,50 @@ export class GUIQuad { private _offsetX: number = 0; private _offsetY: number = 0; protected _sprite: GUISprite = Engine3D.res.defaultGUISprite; - public readonly color: Color = new Color(1, 1, 1, 1); - public imageType: ImageType = ImageType.Simple; - public onChange: boolean = true; + private _color: Color = new Color(1, 1, 1, 1); + private _imageType: ImageType = ImageType.Simple; + public dirtyAttributes: GUIQuadAttrEnum = GUIQuadAttrEnum.MAX; private static textPool: PoolNode; static get quadPool(): PoolNode { - if (this.textPool == null) { - this.textPool = new PoolNode(); - } + this.textPool ||= new PoolNode(); return this.textPool; } + static recycleQuad(quad: GUIQuad): void { + quad.sprite = null; + quad.dirtyAttributes = GUIQuadAttrEnum.MAX; + quad.x = 0; + quad.y = 0; + quad.z = -1; + GUIQuad.quadPool.pushBack(quad); + } + + static spawnQuad(): GUIQuad { + let quad = GUIQuad.quadPool.getOne(GUIQuad); + return quad; + } + + public get imageType(): ImageType { + return this._imageType; + } + + public set imageType(value: ImageType) { + this._imageType = value; + this.setAttrChange(GUIQuadAttrEnum.SPRITE | GUIQuadAttrEnum.POSITION); + } + + public get color(): Color { + return this._color; + } + + public set color(value: Color) { + this._color.copyFrom(value); + this.setAttrChange(GUIQuadAttrEnum.COLOR); + } + + public get visible(): boolean { return this._visible; } @@ -45,7 +76,7 @@ export class GUIQuad { public set visible(value: boolean) { if (value != this._visible) { this._visible = value; - this.onChange = true; + this.setAttrChange(GUIQuadAttrEnum.SPRITE); } } @@ -56,7 +87,7 @@ export class GUIQuad { public set sprite(value: GUISprite) { if (this._sprite != value) { this._sprite = value; - this.onChange = true; + this.setAttrChange(GUIQuadAttrEnum.SPRITE | GUIQuadAttrEnum.POSITION); } } @@ -76,8 +107,24 @@ export class GUIQuad { return this.top + this._globalHeight; } - public transformQuad(transform: UITransform): this { - this.onChange = true; + public setSize(width: number, height: number) { + this.width = width; + this.height = height; + this.setAttrChange(GUIQuadAttrEnum.POSITION); + } + + public setXY(x: number, y: number) { + this.x = x; + this.y = y; + this.setAttrChange(GUIQuadAttrEnum.POSITION); + } + + public setAttrChange(attr: GUIQuadAttrEnum) { + this.dirtyAttributes = this.dirtyAttributes | attr; + } + public applyTransform(transform: UITransform): this { + this.setAttrChange(GUIQuadAttrEnum.POSITION); + let item: GUISprite = this._sprite; let _worldMatrix = transform.getWorldMatrix(); if (this.x != 0 || this.y != 0) { @@ -85,7 +132,7 @@ export class GUIQuad { } let matrixScaleX = _worldMatrix.getScaleX(); let matrixScaleY = _worldMatrix.getScaleY(); - let isSliced = item.isSliced && this.imageType == ImageType.Sliced; + let isSliced = item.isSliced && this._imageType == ImageType.Sliced; //计算trim图偏移量 this._offsetX = transform.width * 0.5 * matrixScaleX; this._offsetY = transform.height * 0.5 * matrixScaleY; @@ -98,11 +145,11 @@ export class GUIQuad { this._globalX = _worldMatrix.tx + item.offsetSize.x * matrixScaleX; this._globalY = _worldMatrix.ty + item.offsetSize.y * matrixScaleY; } else { - let transformScaleX = transform.width / item.offsetSize.z; - let transformScaleY = transform.height / item.offsetSize.w; + let transformScaleX = this.width / item.offsetSize.z; + let transformScaleY = this.height / item.offsetSize.w; - this._globalWidth = matrixScaleX * item.trimSize.x * transformScaleX * this.width; - this._globalHeight = matrixScaleY * item.trimSize.y * transformScaleY * this.height; + this._globalWidth = matrixScaleX * item.trimSize.x * transformScaleX; + this._globalHeight = matrixScaleY * item.trimSize.y * transformScaleY; this._globalX = _worldMatrix.tx + item.offsetSize.x * transformScaleX * matrixScaleX; this._globalY = _worldMatrix.ty + item.offsetSize.y * transformScaleY * matrixScaleY; @@ -120,9 +167,9 @@ export class GUIQuad { return gui_help_mtx3; } - public updateGeometryBuffer(guiGeometry: GUIGeometry, transform: UITransform): this { - this.onChange = false; - guiGeometry.updateQuad(this, transform); + public writeToGeometry(guiGeometry: GUIGeometry, transform: UITransform): this { + guiGeometry.fillQuad(this, transform); + this.dirtyAttributes = GUIQuadAttrEnum.NONE; return this; } } diff --git a/src/components/gui/core/GUIRenderer.ts b/src/components/gui/core/GUIRenderer.ts index ee55b181..d7d1bdd6 100644 --- a/src/components/gui/core/GUIRenderer.ts +++ b/src/components/gui/core/GUIRenderer.ts @@ -49,14 +49,16 @@ export class GUIRenderer extends MeshRenderer { for (let i = 0; i < this.materials.length; i++) { const material = this.materials[i]; let passes = material.renderPasses.get(rendererType); - let vp = this._guiGeometry.vPositionBuffer; - let vu = this._guiGeometry.vUniformBuffer; + let vPosition = this._guiGeometry.vPositionBuffer; + let vSprite = this._guiGeometry.vSpriteBuffer; + let vColor = this._guiGeometry.vColorBuffer; if (passes) { for (let j = 0; j < passes.length; j++) { const renderShader = passes[j].renderShader; if (!renderShader.pipeline) { - renderShader.setStorageBuffer('vPositionBuffer', vp); - renderShader.setStorageBuffer('vUniformBuffer', vu); + renderShader.setStorageBuffer('vPositionBuffer', vPosition); + renderShader.setStorageBuffer('vSpriteBuffer', vSprite); + renderShader.setStorageBuffer('vColorBuffer', vColor); } } } diff --git a/src/components/gui/core/GUIShader.ts b/src/components/gui/core/GUIShader.ts index b8a423ba..148dbcdb 100644 --- a/src/components/gui/core/GUIShader.ts +++ b/src/components/gui/core/GUIShader.ts @@ -107,8 +107,7 @@ export class GUIShader { ${WorldMatrixUniform} ${GlobalUniform} - struct VertexUniformBuffer { - vColor4: vec4, + struct VertexSpriteBuffer { vUvRec: vec4, vUvBorder: vec4, vUvSlice: vec2, @@ -119,6 +118,7 @@ export class GUIShader { struct MaterialUniform{ screen:vec2, mipmapRange:vec2, + limitVertex:f32, } struct VertexOutput { @@ -143,8 +143,10 @@ export class GUIShader { @group(3) @binding(1) var vPositionBuffer: array>; @group(3) @binding(2) - var vUniformBuffer: array; - + var vSpriteBuffer: array; + @group(3) @binding(3) + var vColorBuffer: array>; + var vertexOut: VertexOutput ; `; @@ -158,22 +160,23 @@ export class GUIShader { let vertexIndex = vertex.vIndex; let quadIndex = u32(vertex.vIndex * 0.25); - var vUniformData = vUniformBuffer[quadIndex]; + var vSpriteData = vSpriteBuffer[quadIndex]; var op = vec2(0.0001); - if(vUniformData.vVisible > 0.5){ - op = 2.0 * vec2(vPositionBuffer[u32(vertexIndex)]) / materialUniform.screen; + if(vSpriteData.vVisible > 0.5 && vertexIndex < materialUniform.limitVertex){ + op = 2.0 * vPositionBuffer[u32(vertexIndex)] / materialUniform.screen; } vertexOut.member = vec4(op.x, op.y, vertexIndex * 0.0001, 1.0); vertexOut.vUV = vec2(vertex.uv); - vertexOut.vUvRec = vUniformData.vUvRec; - vertexOut.vColor4 = vUniformData.vColor4; - vertexOut.vUvBorder = vUniformData.vUvBorder; - vertexOut.vUvSlice = vUniformData.vUvSlice; - vertexOut.vTextureID = vUniformData.vTextureID; + vertexOut.vUvRec = vSpriteData.vUvRec; + vertexOut.vUvBorder = vSpriteData.vUvBorder; + vertexOut.vUvSlice = vSpriteData.vUvSlice; + vertexOut.vTextureID = vSpriteData.vTextureID; + vertexOut.vColor4 = vColorBuffer[quadIndex]; + return vertexOut; } @@ -188,22 +191,23 @@ export class GUIShader { let vertexIndex = vertex.vIndex; let quadIndex = u32(vertex.vIndex * 0.25); - var vUniformData = vUniformBuffer[quadIndex]; - + var vSpriteData = vSpriteBuffer[quadIndex]; + var localPos = vec4(vPositionBuffer[u32(vertexIndex)], vertexIndex * 0.0001, 1.0) ; var op = vec4(0.0001); - if(vUniformData.vVisible > 0.5){ + if(vSpriteData.vVisible > 0.5 && vertexIndex < materialUniform.limitVertex){ op = globalUniform.projMat * globalUniform.viewMat * modelMatrix * localPos ; } vertexOut.member = op; vertexOut.vUV = vec2(vertex.uv); - vertexOut.vUvRec = vUniformData.vUvRec; - vertexOut.vColor4 = vUniformData.vColor4; - vertexOut.vUvBorder = vUniformData.vUvBorder; - vertexOut.vUvSlice = vUniformData.vUvSlice; - vertexOut.vTextureID = vUniformData.vTextureID; + vertexOut.vUvRec = vSpriteData.vUvRec; + vertexOut.vUvBorder = vSpriteData.vUvBorder; + vertexOut.vUvSlice = vSpriteData.vUvSlice; + vertexOut.vTextureID = vSpriteData.vTextureID; + vertexOut.vColor4 = vColorBuffer[quadIndex]; + return vertexOut; } diff --git a/src/components/gui/uiComponents/TextFieldLayout.ts b/src/components/gui/uiComponents/TextFieldLayout.ts index 1b30b172..50b307ab 100644 --- a/src/components/gui/uiComponents/TextFieldLayout.ts +++ b/src/components/gui/uiComponents/TextFieldLayout.ts @@ -98,8 +98,6 @@ export class TextFieldLayout { let maxTextWidthReal = transform.width / realSize; let maxTextHeightReal = transform.height / realSize; - let transformScaleX = 1 / transform.width; - let transformScaleY = 1 / transform.height; let transformOffsetX = 0; let transformOffsetY = transform.height; @@ -119,12 +117,12 @@ export class TextFieldLayout { let charSprite = fonts.getFnt(fontName, originSize, code); let quad: GUIQuad = null; if (charSprite) { - quad = GUIQuad.quadPool.getOne(GUIQuad); + quad = GUIQuad.spawnQuad(); quad.sprite = charSprite; quad.x = (offsetX + charSprite.xoffset) * realSize - transformOffsetX; quad.y = (fontData.base - charSprite.height - charSprite.yoffset - fontData.base) * realSize + transformOffsetY; - quad.width = charSprite.offsetSize.width * realSize * transformScaleX; - quad.height = charSprite.offsetSize.height * realSize * transformScaleY; + quad.width = charSprite.offsetSize.width * realSize; + quad.height = charSprite.offsetSize.height * realSize; offsetX += charSprite.xadvance; } else { if (char == '\n') { diff --git a/src/components/gui/uiComponents/UIComponentBase.ts b/src/components/gui/uiComponents/UIComponentBase.ts index e804e756..b8070b42 100644 --- a/src/components/gui/uiComponents/UIComponentBase.ts +++ b/src/components/gui/uiComponents/UIComponentBase.ts @@ -45,17 +45,9 @@ export class UIComponentBase extends ComponentBase { } protected detachQuads(): this { - let allQuads = this._uiTransform.quads; while (this._exlusiveQuads.length > 0) { let quad = this._exlusiveQuads.shift(); - if (quad) { - quad.sprite = null; - GUIQuad.quadPool.pushBack(quad); - let index = allQuads.indexOf(quad); - if (index >= 0) { - allQuads.splice(index, 1); - } - } + this._uiTransform.recycleQuad(quad); } return this; } diff --git a/src/components/gui/uiComponents/UIImage.ts b/src/components/gui/uiComponents/UIImage.ts index c1686be9..4be43f7a 100644 --- a/src/components/gui/uiComponents/UIImage.ts +++ b/src/components/gui/uiComponents/UIImage.ts @@ -6,18 +6,25 @@ import { UIComponentBase } from './UIComponentBase'; import { ImageType } from '../GUIConfig'; import { Engine3D } from '../../../Engine3D'; +export enum UIImageShadow { + NONE = '', + LOW = 'LOW', + MEDIUM = 'MEDIUM', + HIGH = 'HIGH' +} + // A UI component to display image/sprite/video export class UIImage extends UIComponentBase { - private _quad: GUIQuad; - constructor() { super(); } - public init() { + private _shadow: UIImageShadow; + + init(param?: any): void { super.init(); - this._quad = GUIQuad.quadPool.getOne(GUIQuad); - this.attachQuad(this._quad); + let quad = GUIQuad.spawnQuad(); + this.attachQuad(quad); } public cloneTo(obj: Object3D) { @@ -28,12 +35,31 @@ export class UIImage extends UIComponentBase { component.imageType = this.imageType; } + public set shadow(value: UIImageShadow) { + if (this._shadow != value) { + this._shadow = value; + } + } + public get shadow() { + return this._shadow; + } + public set sprite(value: GUISprite) { - this._quad.sprite = value || Engine3D.res.defaultGUISprite; + value ||= Engine3D.res.defaultGUISprite; + for (let quad of this._exlusiveQuads) { + quad.sprite = value; + quad.setSize(this._uiTransform.width, this._uiTransform.height); + } + } + + protected onTransformResize(): void { + for (let quad of this._exlusiveQuads) { + quad.setSize(this._uiTransform.width, this._uiTransform.height); + } } public get sprite(): GUISprite { - return this._quad.sprite; + return this._exlusiveQuads[0].sprite; } protected onUIComponentVisible(visible: boolean): void { @@ -46,26 +72,26 @@ export class UIImage extends UIComponentBase { private applyComponentVisible(): void { let isHidden = !this._visible || !this._uiTransform.globalVisible; - this._quad.visible = !isHidden; + for (let item of this._exlusiveQuads) { + item.visible = !isHidden; + } } public get color() { - return this._quad.color; + return this._exlusiveQuads[0].color; } public set color(value: Color) { - this._quad.color.copyFrom(value); - this._quad.onChange = true; + this._exlusiveQuads[0].color = value; } public get imageType() { - return this._quad.imageType; + return this._exlusiveQuads[0].imageType; } public set imageType(value: ImageType) { - if (this._quad.imageType != value) { - this._quad.imageType = value; - this._quad.onChange = true; + for (let item of this._exlusiveQuads) { + item.imageType = value; } } diff --git a/src/components/gui/uiComponents/UIImageGroup.ts b/src/components/gui/uiComponents/UIImageGroup.ts new file mode 100644 index 00000000..b483a96c --- /dev/null +++ b/src/components/gui/uiComponents/UIImageGroup.ts @@ -0,0 +1,86 @@ +import { Object3D } from '../../../core/entities/Object3D'; +import { Color } from '../../../math/Color'; +import { GUIQuad } from '../core/GUIQuad'; +import { GUISprite } from '../core/GUISprite'; +import { UIComponentBase } from './UIComponentBase'; +import { ImageType } from '../GUIConfig'; +import { Engine3D } from '../../../Engine3D'; + +// A UI component to display a group images/sprites/videos +export class UIImageGroup extends UIComponentBase { + private _count: number = 0; + constructor() { + super(); + } + + init(param?: any): void { + super.init(); + this._count = param ? param.count : 1; + for (let i = 0; i < this._count; i++) { + this.attachQuad(GUIQuad.spawnQuad()); + } + } + + public getQuad(index: number) { + return this._exlusiveQuads[index]; + } + + public cloneTo(obj: Object3D) { + let component = obj.addComponent(UIImageGroup, { count: this._count }); + component.copyComponent(this); + for (let i = 0; i < this._count; i++) { + component.setSprite(i, this.getSprite(i)); + component.setColor(i, this.getColor(i)); + component.setImageType(this.getImageType(i), i); + } + } + + public setSprite(index: number, value: GUISprite) { + this._exlusiveQuads[index].sprite = value || Engine3D.res.defaultGUISprite; + } + + public getSprite(index: number): GUISprite { + return this._exlusiveQuads[index].sprite; + } + + protected onUIComponentVisible(visible: boolean): void { + this.applyComponentVisible(); + } + + protected onUITransformVisible(visible: boolean): void { + this.applyComponentVisible(); + } + + private applyComponentVisible(): void { + let isHidden = !this._visible || !this._uiTransform.globalVisible; + for (let item of this._exlusiveQuads) { + item.visible = !isHidden; + } + } + + public getColor(index: number) { + return this._exlusiveQuads[index].color; + } + + public setColor(index: number, value: Color) { + this._exlusiveQuads[index].color = value; + } + + public getImageType(index: number) { + return this._exlusiveQuads[index].imageType; + } + + public setImageType(index: number, value: ImageType) { + this._exlusiveQuads[index].imageType = value; + } + + public setSize(index: number, width: number, height: number) { + this._exlusiveQuads[index].setSize(width, height); + } + + public setXY(index: number, x: number, y: number) { + this._exlusiveQuads[index].setXY(x, y); + } + +} + diff --git a/src/components/gui/uiComponents/UIPanel.ts b/src/components/gui/uiComponents/UIPanel.ts index d365be2c..2ab137ef 100644 --- a/src/components/gui/uiComponents/UIPanel.ts +++ b/src/components/gui/uiComponents/UIPanel.ts @@ -11,7 +11,7 @@ export class UIPanel extends UIComponentBase { public panelOrder: number = 0; public needSortOnCameraZ?: boolean; protected _mesh: GUIMesh; - + public readonly isUIPanel = true; public get guiMesh(): GUIMesh { return this._mesh; } diff --git a/src/components/gui/uiComponents/UITextField.ts b/src/components/gui/uiComponents/UITextField.ts index cbe7017e..4d7687aa 100644 --- a/src/components/gui/uiComponents/UITextField.ts +++ b/src/components/gui/uiComponents/UITextField.ts @@ -110,8 +110,7 @@ export class UITextField extends UIComponentBase { public set color(value: Color) { this._color.copyFrom(value); for (let quad of this._exlusiveQuads) { - quad.color.copyFrom(value); - quad.onChange = true; + quad.color = value; } } @@ -136,11 +135,6 @@ export class UITextField extends UIComponentBase { this.layoutText(); } } - - public destroy(): void { - this.detachQuads(); - super.destroy(); - } } diff --git a/src/components/gui/uiComponents/UITransform.ts b/src/components/gui/uiComponents/UITransform.ts index 2b2b08f5..8922342f 100644 --- a/src/components/gui/uiComponents/UITransform.ts +++ b/src/components/gui/uiComponents/UITransform.ts @@ -107,7 +107,7 @@ export class UITransform extends ComponentBase { this._width = width; this._height = height; this.onChange = true; - for (let component of this.object3D.components) { + for (let component of this.object3D.components.values()) { component['onTransformResize']?.(); } return true; @@ -137,6 +137,13 @@ export class UITransform extends ComponentBase { } } + public setXY(x: number, y: number) { + let pos = this.object3D.localPosition; + pos.set(x, y, pos.z); + this.object3D.localPosition = pos; + this.onChange = true; + } + public get z() { return this.object3D.z; } @@ -174,6 +181,19 @@ export class UITransform extends ComponentBase { private _onChange: boolean = true; public needUpdateQuads = true; + public recycleQuad(quad?: GUIQuad): GUIQuad { + if (quad) { + let index = this.quads.indexOf(quad); + if (index >= 0) { + this.quads.splice(index, 1); + GUIQuad.recycleQuad(quad); + } else { + quad = null; + } + } + return quad; + } + public get onChange() { return this._onChange; } diff --git a/src/components/gui/uiComponents/ViewPanel.ts b/src/components/gui/uiComponents/ViewPanel.ts index 2ccb122b..dc444cb1 100644 --- a/src/components/gui/uiComponents/ViewPanel.ts +++ b/src/components/gui/uiComponents/ViewPanel.ts @@ -4,6 +4,8 @@ import { UIPanel } from './UIPanel'; // UI component container for view/screen space export class ViewPanel extends UIPanel { + public readonly isViewPanel = true; + constructor() { super(); this.space = GUISpace.View; diff --git a/src/components/gui/uiComponents/WorldPanel.ts b/src/components/gui/uiComponents/WorldPanel.ts index 07e0f813..db650f78 100644 --- a/src/components/gui/uiComponents/WorldPanel.ts +++ b/src/components/gui/uiComponents/WorldPanel.ts @@ -5,6 +5,7 @@ import { UIPanel } from "./UIPanel"; // UI component container for world space export class WorldPanel extends UIPanel { public depthTest: boolean = false; + public readonly isWorldPanel = true; constructor() { super(); diff --git a/src/core/entities/Object3D.ts b/src/core/entities/Object3D.ts index 8fa8cf24..01b972aa 100644 --- a/src/core/entities/Object3D.ts +++ b/src/core/entities/Object3D.ts @@ -192,9 +192,8 @@ export class Object3D extends Entity { * @memberof ELPObject3D */ public getComponentsExt(c: Ctor, ret?: T[], includeInactive?: boolean): T[] { - if (!ret) ret = []; - let className = c.name; - let component = this.components.get(className); + ret ||= []; + let component = this.components.get(c.name); if (component && (component.enable || includeInactive)) { ret.push(component as T); } else { @@ -207,6 +206,28 @@ export class Object3D extends Entity { return ret; } + public getComponentsByProperty(key: string, value: any, findedAndBreak: boolean = true, ret?: T[], includeInactive?: boolean): T[] { + ret ||= []; + let findComponent; + for (const component of this.components.values()) { + if (component && (component.enable || includeInactive)) { + if (component[key] == value) { + ret.push(component as T); + findComponent = true; + } + } + } + if (!(findComponent && findedAndBreak)) { + //keep find in child + for (const node of this.entityChildren) { + if (node instanceof Object3D) { + node.getComponentsByProperty(key, value, findedAndBreak, ret, includeInactive); + } + } + } + return ret; + } + /** * * clone a Object3D diff --git a/src/index.ts b/src/index.ts index 6363ced6..59e63cbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -146,6 +146,7 @@ export * from "./components/gui/uiComponents/TextFieldLayout" export * from "./components/gui/uiComponents/UIButton" export * from "./components/gui/uiComponents/UIComponentBase" export * from "./components/gui/uiComponents/UIImage" +export * from "./components/gui/uiComponents/UIImageGroup" export * from "./components/gui/uiComponents/UIInteractive" export * from "./components/gui/uiComponents/UIPanel" export * from "./components/gui/uiComponents/UITextField"