From 2df50f9822350d6f0f505d86bdf2eb53fca07149 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 12 Jun 2024 11:28:26 +0800 Subject: [PATCH 01/71] feat: ui transform --- .../core/src/2d/assembler/ISpriteAssembler.ts | 10 +- .../src/2d/assembler/SimpleSpriteAssembler.ts | 22 +- .../src/2d/assembler/SlicedSpriteAssembler.ts | 24 +- .../src/2d/assembler/TiledSpriteAssembler.ts | 26 +- packages/core/src/2d/sprite/SpriteMask.ts | 22 +- packages/core/src/2d/sprite/SpriteRenderer.ts | 22 +- packages/core/src/Camera.ts | 50 ++- packages/core/src/Component.ts | 23 +- packages/core/src/Engine.ts | 27 +- packages/core/src/Entity.ts | 36 +- .../src/RenderPipeline/BasicRenderPipeline.ts | 29 +- packages/core/src/Transform.ts | 8 +- packages/core/src/index.ts | 73 +++-- packages/core/src/shader/ShaderPool.ts | 3 + .../src/shaderlib/extra/uiDefault.fs.glsl | 9 + .../src/shaderlib/extra/uiDefault.vs.glsl | 15 + packages/core/src/ui/Image.ts | 309 ++++++++++++++++++ packages/core/src/ui/UICanvas.ts | 272 +++++++++++++-- packages/core/src/ui/UIRenderer.ts | 92 +++++- packages/core/src/ui/UITransform.ts | 58 ++++ packages/core/src/ui/index.ts | 2 + 21 files changed, 954 insertions(+), 178 deletions(-) create mode 100644 packages/core/src/shaderlib/extra/uiDefault.fs.glsl create mode 100644 packages/core/src/shaderlib/extra/uiDefault.vs.glsl create mode 100644 packages/core/src/ui/Image.ts create mode 100644 packages/core/src/ui/UITransform.ts diff --git a/packages/core/src/2d/assembler/ISpriteAssembler.ts b/packages/core/src/2d/assembler/ISpriteAssembler.ts index b481fb0d08..18eac4cfaa 100644 --- a/packages/core/src/2d/assembler/ISpriteAssembler.ts +++ b/packages/core/src/2d/assembler/ISpriteAssembler.ts @@ -1,3 +1,4 @@ +import { Vector2 } from "@galacean/engine-math"; import { Renderer } from "../../Renderer"; /** @@ -5,7 +6,14 @@ import { Renderer } from "../../Renderer"; */ export interface ISpriteAssembler { resetData(renderer: Renderer, vertexCount?: number): void; - updatePositions?(renderer: Renderer): void; + updatePositions?( + renderer: Renderer, + width: number, + height: number, + pivot: Vector2, + flipX?: boolean, + flipY?: boolean + ): void; updateUVs?(renderer: Renderer): void; updateColor?(renderer: Renderer): void; } diff --git a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts index 1e64fa33bd..dc5d539999 100644 --- a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts @@ -1,5 +1,6 @@ -import { BoundingBox, Matrix } from "@galacean/engine-math"; +import { BoundingBox, Matrix, Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { Image } from "../../ui"; import { SpriteMask } from "../sprite"; import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; @@ -12,7 +13,7 @@ export class SimpleSpriteAssembler { static _rectangleTriangles = [0, 1, 2, 2, 1, 3]; static _worldMatrix = new Matrix(); - static resetData(renderer: SpriteRenderer | SpriteMask): void { + static resetData(renderer: SpriteRenderer | SpriteMask | Image): void { const manager = renderer._getChunkManager(); const lastChunk = renderer._chunk; lastChunk && manager.freeChunk(lastChunk); @@ -21,16 +22,23 @@ export class SimpleSpriteAssembler { renderer._chunk = chunk; } - static updatePositions(renderer: SpriteRenderer | SpriteMask): void { - const { width, height, sprite } = renderer; - const { x: pivotX, y: pivotY } = sprite.pivot; + static updatePositions( + renderer: SpriteRenderer | SpriteMask | Image, + width: number, + height: number, + pivot: Vector2, + flipX: boolean = false, + flipY: boolean = false + ): void { + const { sprite } = renderer; + const { x: pivotX, y: pivotY } = pivot; // Renderer's worldMatrix const worldMatrix = SimpleSpriteAssembler._worldMatrix; const { elements: wE } = worldMatrix; // Parent's worldMatrix const { elements: pWE } = renderer.entity.transform.worldMatrix; - const sx = renderer.flipX ? -width : width; - const sy = renderer.flipY ? -height : height; + const sx = flipX ? -width : width; + const sy = flipY ? -height : height; (wE[0] = pWE[0] * sx), (wE[1] = pWE[1] * sx), (wE[2] = pWE[2] * sx); (wE[4] = pWE[4] * sy), (wE[5] = pWE[5] * sy), (wE[6] = pWE[6] * sy); (wE[8] = pWE[8]), (wE[9] = pWE[9]), (wE[10] = pWE[10]); diff --git a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts index 9af9014780..183fc3f371 100644 --- a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts @@ -1,5 +1,6 @@ -import { Matrix } from "@galacean/engine-math"; +import { Matrix, Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { SpriteMask } from "../sprite"; import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; @@ -23,8 +24,15 @@ export class SlicedSpriteAssembler { renderer._chunk = chunk; } - static updatePositions(renderer: SpriteRenderer): void { - const { width, height, sprite } = renderer; + static updatePositions( + renderer: SpriteRenderer | SpriteMask, + width: number, + height: number, + pivot: Vector2, + flipX: boolean = false, + flipY: boolean = false + ): void { + const { sprite } = renderer; const { border } = sprite; // Update local positions. const spritePositions = sprite._getPositions(); @@ -73,16 +81,16 @@ export class SlicedSpriteAssembler { } // Update renderer's worldMatrix. - const { x: pivotX, y: pivotY } = renderer.sprite.pivot; - const localTransX = renderer.width * pivotX; - const localTransY = renderer.height * pivotY; + const { x: pivotX, y: pivotY } = pivot; + const localTransX = width * pivotX; + const localTransY = height * pivotY; // Renderer's worldMatrix. const worldMatrix = SlicedSpriteAssembler._worldMatrix; const { elements: wE } = worldMatrix; // Parent's worldMatrix. const { elements: pWE } = renderer.entity.transform.worldMatrix; - const sx = renderer.flipX ? -1 : 1; - const sy = renderer.flipY ? -1 : 1; + const sx = flipX ? -1 : 1; + const sy = flipY ? -1 : 1; (wE[0] = pWE[0] * sx), (wE[1] = pWE[1] * sx), (wE[2] = pWE[2] * sx); (wE[4] = pWE[4] * sy), (wE[5] = pWE[5] * sy), (wE[6] = pWE[6] * sy); (wE[8] = pWE[8]), (wE[9] = pWE[9]), (wE[10] = pWE[10]); diff --git a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts index 59962cdcb5..3ece338b28 100644 --- a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts @@ -1,4 +1,4 @@ -import { MathUtil, Matrix } from "@galacean/engine-math"; +import { MathUtil, Matrix, Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; import { DisorderedArray } from "../../DisorderedArray"; import { SpriteTileMode } from "../enums/SpriteTileMode"; @@ -7,6 +7,7 @@ import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; import { Logger } from "../../base"; import { DynamicGeometryDataManager } from "../../RenderPipeline/DynamicGeometryDataManager"; +import { Image, UIRenderer } from "../../ui"; /** * @internal @@ -19,7 +20,7 @@ export class TiledSpriteAssembler { static _uvRow = new DisorderedArray(); static _uvColumn = new DisorderedArray(); - static resetData(renderer: SpriteRenderer, vertexCount: number): void { + static resetData(renderer: SpriteRenderer | Image, vertexCount: number): void { if (vertexCount) { const manager = renderer._getChunkManager(); const chunk = renderer._chunk; @@ -38,8 +39,15 @@ export class TiledSpriteAssembler { } } - static updatePositions(renderer: SpriteRenderer): void { - const { width, height, sprite, tileMode, tiledAdaptiveThreshold: threshold } = renderer; + static updatePositions( + renderer: SpriteRenderer | Image, + width: number, + height: number, + pivot: Vector2, + flipX: boolean = false, + flipY: boolean = false + ): void { + const { sprite, tileMode, tiledAdaptiveThreshold: threshold } = renderer; // Calculate row and column const { _posRow: posRow, _posColumn: posColumn, _uvRow: uvRow, _uvColumn: uvColumn } = TiledSpriteAssembler; posRow.length = posColumn.length = uvRow.length = uvColumn.length = 0; @@ -58,16 +66,16 @@ export class TiledSpriteAssembler { : TiledSpriteAssembler._calculateContinuousDividing(sprite, width, height, posRow, posColumn, uvRow, uvColumn); TiledSpriteAssembler.resetData(renderer, vertexCount); // Update renderer's worldMatrix - const { x: pivotX, y: pivotY } = renderer.sprite.pivot; - const localTransX = renderer.width * pivotX; - const localTransY = renderer.height * pivotY; + const { x: pivotX, y: pivotY } = pivot; + const localTransX = width * pivotX; + const localTransY = height * pivotY; // Renderer's worldMatrix const { _worldMatrix: worldMatrix } = TiledSpriteAssembler; const { elements: wE } = worldMatrix; // Parent's worldMatrix const { elements: pWE } = renderer.entity.transform.worldMatrix; - const sx = renderer.flipX ? -1 : 1; - const sy = renderer.flipY ? -1 : 1; + const sx = flipX ? -1 : 1; + const sy = flipY ? -1 : 1; let wE0: number, wE1: number, wE2: number; let wE4: number, wE5: number, wE6: number; (wE0 = wE[0] = pWE[0] * sx), (wE1 = wE[1] = pWE[1] * sx), (wE2 = wE[2] = pWE[2] * sx); diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 963c53cfa3..9d199096b4 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -1,7 +1,12 @@ import { BoundingBox, Matrix } from "@galacean/engine-math"; import { Entity } from "../../Entity"; +import { Chunk } from "../../RenderPipeline/DynamicGeometryData"; +import { DynamicGeometryDataManager } from "../../RenderPipeline/DynamicGeometryDataManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; +import { RenderData2D } from "../../RenderPipeline/RenderData2D"; import { RenderElement } from "../../RenderPipeline/RenderElement"; +import { ForceUploadShaderDataFlag } from "../../RenderPipeline/enums/ForceUploadShaderDataFlag"; +import { RenderDataUsage } from "../../RenderPipeline/enums/RenderDataUsage"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderProperty } from "../../shader/ShaderProperty"; @@ -9,11 +14,6 @@ import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; import { Sprite } from "./Sprite"; -import { RenderDataUsage } from "../../RenderPipeline/enums/RenderDataUsage"; -import { Chunk } from "../../RenderPipeline/DynamicGeometryData"; -import { RenderData2D } from "../../RenderPipeline/RenderData2D"; -import { ForceUploadShaderDataFlag } from "../../RenderPipeline/enums/ForceUploadShaderDataFlag"; -import { DynamicGeometryDataManager } from "../../RenderPipeline/DynamicGeometryDataManager"; /** * A component for masking Sprites. @@ -207,8 +207,9 @@ export class SpriteMask extends Renderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - if (this.sprite) { - SimpleSpriteAssembler.updatePositions(this); + const { sprite } = this; + if (sprite) { + SimpleSpriteAssembler.updatePositions(this, this.width, this.height, sprite.pivot, this._flipX, this._flipY); } else { worldBounds.min.set(0, 0, 0); worldBounds.max.set(0, 0, 0); @@ -219,7 +220,8 @@ export class SpriteMask extends Renderer { * @inheritdoc */ protected override _render(context: RenderContext): void { - if (!this.sprite?.texture || !this.width || !this.height) { + const { _sprite: sprite } = this; + if (!sprite?.texture || !this.width || !this.height) { return; } @@ -235,7 +237,7 @@ export class SpriteMask extends Renderer { // Update position if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { - SimpleSpriteAssembler.updatePositions(this); + SimpleSpriteAssembler.updatePositions(this, this.width, this.height, sprite.pivot, this._flipX, this._flipY); this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; } @@ -248,7 +250,7 @@ export class SpriteMask extends Renderer { engine._spriteMaskManager.addMask(this); const { _chunk: chunk } = this; const renderData = engine._renderData2DPool.getFromPool(); - renderData.set(this, material, chunk._primitive, chunk._subMesh, this.sprite.texture, chunk); + renderData.set(this, material, chunk._primitive, chunk._subMesh, sprite.texture, chunk); renderData.usage = RenderDataUsage.SpriteMask; renderData.uploadFlag = ForceUploadShaderDataFlag.None; renderData.preRender = null; diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index 0b1c41d95c..e577f3b25e 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -1,6 +1,12 @@ import { BoundingBox, Color, MathUtil, Matrix } from "@galacean/engine-math"; import { Entity } from "../../Entity"; +import { Chunk } from "../../RenderPipeline/DynamicGeometryData"; +import { DynamicGeometryDataManager } from "../../RenderPipeline/DynamicGeometryDataManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; +import { RenderData2D } from "../../RenderPipeline/RenderData2D"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; +import { ForceUploadShaderDataFlag } from "../../RenderPipeline/enums/ForceUploadShaderDataFlag"; +import { RenderDataUsage } from "../../RenderPipeline/enums/RenderDataUsage"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderProperty } from "../../shader/ShaderProperty"; @@ -15,12 +21,6 @@ import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; import { SpriteTileMode } from "../enums/SpriteTileMode"; import { Sprite } from "./Sprite"; -import { RenderDataUsage } from "../../RenderPipeline/enums/RenderDataUsage"; -import { Chunk, DynamicGeometryData } from "../../RenderPipeline/DynamicGeometryData"; -import { RenderElement } from "../../RenderPipeline/RenderElement"; -import { RenderData2D } from "../../RenderPipeline/RenderData2D"; -import { ForceUploadShaderDataFlag } from "../../RenderPipeline/enums/ForceUploadShaderDataFlag"; -import { DynamicGeometryDataManager } from "../../RenderPipeline/DynamicGeometryDataManager"; /** * Renders a Sprite for 2D graphics. @@ -315,8 +315,9 @@ export class SpriteRenderer extends Renderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - if (this.sprite) { - this._assembler.updatePositions(this); + const { _sprite: sprite } = this; + if (sprite) { + this._assembler.updatePositions(this, this.width, this.height, sprite.pivot); } else { worldBounds.min.set(0, 0, 0); worldBounds.max.set(0, 0, 0); @@ -324,7 +325,8 @@ export class SpriteRenderer extends Renderer { } protected override _render(context: RenderContext): void { - if (!this.sprite?.texture || !this.width || !this.height) { + const { _sprite: sprite } = this; + if (!sprite?.texture || !this.width || !this.height) { return; } @@ -339,7 +341,7 @@ export class SpriteRenderer extends Renderer { // Update position if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { - this._assembler.updatePositions(this); + this._assembler.updatePositions(this, this.width, this.height, sprite.pivot); this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; } diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 1ff9b08e3f..9ab47d6ceb 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -7,6 +7,7 @@ import { Layer } from "./Layer"; import { BasicRenderPipeline } from "./RenderPipeline/BasicRenderPipeline"; import { PipelineUtils } from "./RenderPipeline/PipelineUtils"; import { Transform } from "./Transform"; +import { UpdateFlagManager } from "./UpdateFlagManager"; import { VirtualCamera } from "./VirtualCamera"; import { Logger } from "./base"; import { deepClone, ignoreClone } from "./clone/CloneManager"; @@ -119,6 +120,9 @@ export class Camera extends Component { private _depthBufferParams: Vector4 = new Vector4(); private _opaqueTextureEnabled: boolean = false; + /** @internal */ + @ignoreClone + _updateFlagManager: UpdateFlagManager = new UpdateFlagManager(); @ignoreClone private _frustumChangeFlag: BoolUpdateFlag; @ignoreClone @@ -207,8 +211,11 @@ export class Camera extends Component { } set fieldOfView(value: number) { - this._fieldOfView = value; - this._projectionMatrixChange(); + if (this._fieldOfView !== value) { + this._fieldOfView = value; + this._projectionMatrixChange(); + this._updateFlagManager.dispatch(CameraModifyFlags.FOV); + } } /** @@ -272,13 +279,16 @@ export class Camera extends Component { } set isOrthographic(value: boolean) { - this._virtualCamera.isOrthographic = value; - this._projectionMatrixChange(); - - if (value) { - this.shaderData.enableMacro("CAMERA_ORTHOGRAPHIC"); - } else { - this.shaderData.disableMacro("CAMERA_ORTHOGRAPHIC"); + const { _virtualCamera: virtualCamera } = this; + if (virtualCamera.isOrthographic !== value) { + virtualCamera.isOrthographic = value; + this._projectionMatrixChange(); + if (value) { + this.shaderData.enableMacro("CAMERA_ORTHOGRAPHIC"); + } else { + this.shaderData.disableMacro("CAMERA_ORTHOGRAPHIC"); + } + this._updateFlagManager.dispatch(CameraModifyFlags.Type); } } @@ -290,8 +300,11 @@ export class Camera extends Component { } set orthographicSize(value: number) { - this._orthographicSize = value; - this._projectionMatrixChange(); + if (this._orthographicSize !== value) { + this._orthographicSize = value; + this._projectionMatrixChange(); + this._updateFlagManager.dispatch(CameraModifyFlags.OrthographicSize); + } } /** @@ -381,6 +394,7 @@ export class Camera extends Component { this._renderTarget = value; this._onPixelViewportChanged(); this._checkMainCanvasAntialiasWaste(); + this._updateFlagManager.dispatch(CameraModifyFlags.RenderTarget); } } @@ -777,6 +791,7 @@ export class Camera extends Component { this._updatePixelViewport(); this._customAspectRatio ?? this._projectionMatrixChange(); this._checkMainCanvasAntialiasWaste(); + this._updateFlagManager.dispatch(CameraModifyFlags.ViewPort); } private _checkMainCanvasAntialiasWaste(): void { @@ -787,3 +802,16 @@ export class Camera extends Component { } } } + +/** + * @internal + */ +export enum CameraModifyFlags { + Type = 0x1, + NearPlane = 0x2, + FarPlane = 0x4, + FOV = 0x8, + ViewPort = 0x10, + OrthographicSize = 0x20, + RenderTarget = 0x40 +} diff --git a/packages/core/src/Component.ts b/packages/core/src/Component.ts index 0615805153..c95d0f3d59 100644 --- a/packages/core/src/Component.ts +++ b/packages/core/src/Component.ts @@ -19,6 +19,10 @@ export class Component extends EngineObject { @ignoreClone protected _phasedActiveInScene: boolean = false; + /** @internal */ + @ignoreClone + _onParentChangeIndex: number = -1; + @ignoreClone private _phasedActive: boolean = false; @assignmentClone @@ -100,12 +104,27 @@ export class Component extends EngineObject { /** * @internal */ - _onEnableInScene(): void {} + _onEnableInScene(): void { + const { prototype } = Component; + if (this._onParentChange !== prototype._onParentChange) { + this._entity._addOnParentChanges(this); + } + } + + /** + * @internal + */ + _onDisableInScene(): void { + const { prototype } = Component; + if (this._onParentChange !== prototype._onParentChange) { + this._entity._removeOnParentChanges(this); + } + } /** * @internal */ - _onDisableInScene(): void {} + _onParentChange(seniority: number): void {} /** * @internal diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index e992ceedba..5b3cebc531 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -14,11 +14,13 @@ import { Camera } from "./Camera"; import { Canvas } from "./Canvas"; import { EngineSettings } from "./EngineSettings"; import { Entity } from "./Entity"; +import { BatcherManager } from "./RenderPipeline/BatcherManager"; import { ClassPool } from "./RenderPipeline/ClassPool"; import { RenderContext } from "./RenderPipeline/RenderContext"; import { RenderData } from "./RenderPipeline/RenderData"; -import { RenderElement } from "./RenderPipeline/RenderElement"; import { RenderData2D } from "./RenderPipeline/RenderData2D"; +import { RenderElement } from "./RenderPipeline/RenderElement"; +import { SpriteMaskManager } from "./RenderPipeline/SpriteMaskManager"; import { Scene } from "./Scene"; import { SceneManager } from "./SceneManager"; import { ContentRestorer } from "./asset/ContentRestorer"; @@ -45,8 +47,6 @@ import { CullMode } from "./shader/enums/CullMode"; import { RenderQueueType } from "./shader/enums/RenderQueueType"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, Texture2DArray, TextureCube, TextureCubeFace, TextureFormat } from "./texture"; -import { BatcherManager } from "./RenderPipeline/BatcherManager"; -import { SpriteMaskManager } from "./RenderPipeline/SpriteMaskManager"; import { XRManager } from "./xr/XRManager"; ShaderPool.init(); @@ -100,6 +100,8 @@ export class Engine extends EventDispatcher { /* @internal */ _spriteMaskDefaultMaterial: Material; /* @internal */ + _uiDefaultMaterial: Material; + /* @internal */ _textDefaultFont: Font; /* @internal */ _renderContext: RenderContext = new RenderContext(); @@ -260,6 +262,8 @@ export class Engine extends EventDispatcher { spriteDefaultMaterials[SpriteMaskInteraction.VisibleOutsideMask] = this._createSpriteMaterial( SpriteMaskInteraction.VisibleOutsideMask ); + + this._uiDefaultMaterial = this._createUIMaterial(); this._spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(); this._textDefaultFont = Font.createFromOS(this, "Arial"); this._textDefaultFont.isGCIgnored = true; @@ -720,6 +724,23 @@ export class Engine extends EventDispatcher { return material; } + private _createUIMaterial(): Material { + const material = new Material(this, Shader.find("ui")); + const renderState = material.renderState; + const target = renderState.blendState.targetBlendState; + target.enabled = true; + target.sourceColorBlendFactor = BlendFactor.SourceAlpha; + target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.sourceAlphaBlendFactor = BlendFactor.One; + target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; + renderState.depthState.writeEnabled = false; + renderState.rasterState.cullMode = CullMode.Off; + renderState.renderQueueType = RenderQueueType.Transparent; + material.isGCIgnored = true; + return material; + } + private _onDeviceLost(): void { this._isDeviceLost = true; // Lose graphic resources diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 80bc5ff2c0..fc7e231381 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -101,6 +101,7 @@ export class Entity extends EngineObject { private _templateResource: ReferResource; private _parent: Entity = null; private _activeChangedComponents: Component[]; + private _onParentChanges: DisorderedArray = new DisorderedArray(); /** * Whether to activate locally. @@ -321,7 +322,7 @@ export class Entity extends EngineObject { } activeChangeFlag && child._processActive(activeChangeFlag); - child._setTransformDirty(); + child._setParentChange(); } else { child._setParent(this, index); } @@ -582,6 +583,19 @@ export class Entity extends EngineObject { this._setActiveComponents(false, activeChangeFlag); } + /** @internal */ + _addOnParentChanges(component: Component) { + component._onParentChangeIndex = this._onParentChanges.length; + this._onParentChanges.add(component); + } + + /** @internal */ + _removeOnParentChanges(component: Component) { + const replaced = this._onParentChanges.deleteByIndex(component._onParentChangeIndex); + replaced && (replaced._onParentChangeIndex = component._onParentChangeIndex); + component._onParentChangeIndex = -1; + } + private _addToChildrenList(index: number, child: Entity): void { const children = this._children; const childCount = children.length; @@ -648,7 +662,7 @@ export class Entity extends EngineObject { Entity._traverseSetOwnerScene(this, null); } } - this._setTransformDirty(); + this._setParentChange(); } } @@ -703,13 +717,19 @@ export class Entity extends EngineObject { } } - private _setTransformDirty() { - if (this.transform) { - this.transform._parentChange(); - } else { - for (let i = 0, len = this._children.length; i < len; i++) { - this._children[i]._setTransformDirty(); + private _setParentChange(seniority: number = 1) { + const { _children: children } = this; + this._onParentChanges.forEach( + (component: Component) => { + component._onParentChange(seniority); + }, + (element: Component, index: number) => { + element._onParentChangeIndex = index; } + ); + seniority = seniority + 1; + for (let i = 0, n = children.length; i < n; i++) { + children[i]._setParentChange(seniority); } } diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 559c3e240c..8026556286 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -298,22 +298,20 @@ export class BasicRenderPipeline { private _prepareRender(context: RenderContext): void { const camera = context.camera; - const engine = camera.engine; - const componentsManager = camera.scene._componentsManager; - const renderers = componentsManager._renderers; + const { engine, enableFrustumCulling, cullingMask, _frustum: frustum } = camera; + const { _renderers: renderers, _uiCanvases: uiCanvases } = camera.scene._componentsManager; - const elements = renderers._elements; + let rendererElements = renderers._elements; for (let i = renderers.length - 1; i >= 0; --i) { - const renderer = elements[i]; - + const renderer = rendererElements[i]; // Filter by camera culling mask - if (!(camera.cullingMask & renderer._entity.layer)) { + if (!(cullingMask & renderer._entity.layer)) { continue; } // Filter by camera frustum - if (camera.enableFrustumCulling) { - if (!camera._frustum.intersectsBox(renderer.bounds)) { + if (enableFrustumCulling) { + if (!frustum.intersectsBox(renderer.bounds)) { continue; } } @@ -322,12 +320,15 @@ export class BasicRenderPipeline { } // Prepare ui - const uiCanvases = componentsManager._uiCanvases; - for (let i = 0, l = uiCanvases.length; i < l; ++i) { - const uiCanvas = uiCanvases[i]; - if (uiCanvas.renderCamera === camera) { - uiCanvas._prepareRender(context); + const canvasElements = uiCanvases._elements; + for (let i = uiCanvases.length - 1; i >= 0; i--) { + const canvas = canvasElements[i]; + if (canvas.renderCamera !== camera) continue; + // Filter by camera culling mask + if (!(cullingMask & canvas._entity.layer)) { + continue; } + canvas._prepareRender(context); } } diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index d2ce909d32..acb9ff2dc9 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -561,8 +561,8 @@ export class Transform extends Component { /** * @internal */ - _parentChange(): void { - this._isParentDirty = true; + override _onParentChange(seniority: number): void { + this._isParentDirty = seniority === 1; this._updateAllWorldFlag(); } @@ -682,10 +682,6 @@ export class Transform extends Component { private _updateAllWorldFlag(): void { if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWeWqWs)) { this._worldAssociatedChange(TransformModifyFlags.WmWpWeWqWs); - const nodeChildren = this._entity._children; - for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) { - nodeChildren[i].transform?._updateAllWorldFlag(); - } } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 94dbfbf082..b0d26c8bca 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,66 +1,67 @@ -export { Platform } from "./Platform"; +export { Canvas } from "./Canvas"; export { Engine } from "./Engine"; +export { Platform } from "./Platform"; export { SystemInfo } from "./SystemInfo"; -export { Canvas } from "./Canvas"; export { Scene } from "./Scene"; export { SceneManager } from "./SceneManager"; -export { Entity } from "./Entity"; +export { BoolUpdateFlag } from "./BoolUpdateFlag"; +export { Camera } from "./Camera"; export { Component } from "./Component"; -export { Script } from "./Script"; +export { DependentMode, dependentComponents } from "./ComponentsDependencies"; +export type { EngineConfiguration } from "./Engine"; +export type { EngineSettings } from "./EngineSettings"; +export { Entity } from "./Entity"; export { Renderer } from "./Renderer"; -export { dependentComponents, DependentMode } from "./ComponentsDependencies"; -export { Camera } from "./Camera"; +export { Script } from "./Script"; export { Transform } from "./Transform"; -export { BoolUpdateFlag } from "./BoolUpdateFlag"; -export type { EngineSettings } from "./EngineSettings"; -export type { EngineConfiguration } from "./Engine"; -export { request } from "./asset/request"; -export { Loader } from "./asset/Loader"; -export { ContentRestorer } from "./asset/ContentRestorer"; -export { ResourceManager, resourceLoader } from "./asset/ResourceManager"; export { AssetPromise } from "./asset/AssetPromise"; -export type { LoadItem } from "./asset/LoadItem"; export { AssetType } from "./asset/AssetType"; +export { ContentRestorer } from "./asset/ContentRestorer"; +export type { LoadItem } from "./asset/LoadItem"; +export { Loader } from "./asset/Loader"; export { ReferResource } from "./asset/ReferResource"; +export { ResourceManager, resourceLoader } from "./asset/ResourceManager"; +export { request } from "./asset/request"; export * from "./RenderPipeline/index"; export * from "./base"; +export * from "./2d/index"; export { Background } from "./Background"; +export * from "./Layer"; +export * from "./Utils"; +export * from "./animation/index"; +export * from "./clone/CloneManager"; export { BackgroundMode } from "./enums/BackgroundMode"; -export { DepthTextureMode } from "./enums/DepthTextureMode"; -export { FogMode } from "./enums/FogMode"; +export { BackgroundTextureFillMode } from "./enums/BackgroundTextureFillMode"; export { CameraClearFlags } from "./enums/CameraClearFlags"; export { CameraType } from "./enums/CameraType"; -export { MSAASamples } from "./enums/MSAASamples"; -export { Downsampling } from "./enums/Downsampling"; export { ColorSpace } from "./enums/ColorSpace"; -export { BackgroundTextureFillMode } from "./enums/BackgroundTextureFillMode"; -export { XRManager } from "./xr/XRManager"; -export { Pool } from "./utils/Pool"; -export type { IPoolElement } from "./utils/Pool"; +export { DepthTextureMode } from "./enums/DepthTextureMode"; +export { Downsampling } from "./enums/Downsampling"; +export { FogMode } from "./enums/FogMode"; +export { MSAASamples } from "./enums/MSAASamples"; +export * from "./env-probe/index"; +export * from "./graphic/index"; export * from "./input/index"; export * from "./lighting/index"; -export * from "./shadow/index"; export * from "./material/index"; -export * from "./texture/index"; -export * from "./graphic/index"; -export * from "./2d/index"; -export * from "./shaderlib/index"; -export * from "./animation/index"; export * from "./mesh/index"; -export * from "./sky/index"; export * from "./particle/index"; -export * from "./trail/index"; -export * from "./env-probe/index"; -export * from "./shader/index"; -export * from "./Layer"; -export * from "./clone/CloneManager"; -export * from "./renderingHardwareInterface/index"; export * from "./physics/index"; -export * from "./Utils"; +export * from "./renderingHardwareInterface/index"; +export * from "./shader/index"; +export * from "./shaderlib/index"; +export * from "./shadow/index"; +export * from "./sky/index"; +export * from "./texture/index"; +export * from "./trail/index"; +export * from "./ui"; +export { Pool } from "./utils/Pool"; +export type { IPoolElement } from "./utils/Pool"; +export { XRManager } from "./xr/XRManager"; export { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; diff --git a/packages/core/src/shader/ShaderPool.ts b/packages/core/src/shader/ShaderPool.ts index ad20aa8744..604c6a2ae8 100644 --- a/packages/core/src/shader/ShaderPool.ts +++ b/packages/core/src/shader/ShaderPool.ts @@ -23,6 +23,8 @@ import spriteMaskFs from "../shaderlib/extra/sprite-mask.fs.glsl"; import spriteMaskVs from "../shaderlib/extra/sprite-mask.vs.glsl"; import spriteFs from "../shaderlib/extra/sprite.fs.glsl"; import spriteVs from "../shaderlib/extra/sprite.vs.glsl"; +import uiDefaultFs from "../shaderlib/extra/uiDefault.fs.glsl"; +import uiDefaultVs from "../shaderlib/extra/uiDefault.vs.glsl"; import unlitFs from "../shaderlib/extra/unlit.fs.glsl"; import unlitVs from "../shaderlib/extra/unlit.vs.glsl"; import { Shader } from "./Shader"; @@ -67,6 +69,7 @@ export class ShaderPool { Shader.create("particle-shader", [new ShaderPass("Forward", particleVs, particleFs, forwardPassTags)]); Shader.create("SpriteMask", [new ShaderPass("Forward", spriteMaskVs, spriteMaskFs, forwardPassTags)]); Shader.create("Sprite", [new ShaderPass("Forward", spriteVs, spriteFs, forwardPassTags)]); + Shader.create("ui", [new ShaderPass("Forward", uiDefaultVs, uiDefaultFs, forwardPassTags)]); Shader.create("background-texture", [ new ShaderPass("Forward", backgroundTextureVs, backgroundTextureFs, forwardPassTags) ]); diff --git a/packages/core/src/shaderlib/extra/uiDefault.fs.glsl b/packages/core/src/shaderlib/extra/uiDefault.fs.glsl new file mode 100644 index 0000000000..8e48c0cde8 --- /dev/null +++ b/packages/core/src/shaderlib/extra/uiDefault.fs.glsl @@ -0,0 +1,9 @@ +uniform sampler2D renderer_UITexture; + +varying vec2 v_uv; +varying vec4 v_color; + +void main() { + vec4 baseColor = texture2D(renderer_UITexture, v_uv); + gl_FragColor = baseColor * v_color; +} diff --git a/packages/core/src/shaderlib/extra/uiDefault.vs.glsl b/packages/core/src/shaderlib/extra/uiDefault.vs.glsl new file mode 100644 index 0000000000..2a6b45be4e --- /dev/null +++ b/packages/core/src/shaderlib/extra/uiDefault.vs.glsl @@ -0,0 +1,15 @@ +uniform mat4 renderer_MVPMat; + +attribute vec3 POSITION; +attribute vec2 TEXCOORD_0; +attribute vec4 COLOR_0; + +varying vec2 v_uv; +varying vec4 v_color; + +void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + + v_uv = TEXCOORD_0; + v_color = COLOR_0; +} diff --git a/packages/core/src/ui/Image.ts b/packages/core/src/ui/Image.ts new file mode 100644 index 0000000000..0bd41d6aa9 --- /dev/null +++ b/packages/core/src/ui/Image.ts @@ -0,0 +1,309 @@ +import { Color, MathUtil } from "@galacean/engine-math"; +import { Sprite, SpriteDrawMode, SpriteRenderer, SpriteTileMode } from "../2d"; +import { ISpriteAssembler } from "../2d/assembler/ISpriteAssembler"; +import { SimpleSpriteAssembler } from "../2d/assembler/SimpleSpriteAssembler"; +import { SlicedSpriteAssembler } from "../2d/assembler/SlicedSpriteAssembler"; +import { TiledSpriteAssembler } from "../2d/assembler/TiledSpriteAssembler"; +import { SpriteModifyFlags } from "../2d/enums/SpriteModifyFlags"; +import { Entity } from "../Entity"; +import { Chunk } from "../RenderPipeline/DynamicGeometryData"; +import { RenderContext } from "../RenderPipeline/RenderContext"; +import { RenderData2D } from "../RenderPipeline/RenderData2D"; +import { RenderElement } from "../RenderPipeline/RenderElement"; +import { ForceUploadShaderDataFlag } from "../RenderPipeline/enums/ForceUploadShaderDataFlag"; +import { RenderDataUsage } from "../RenderPipeline/enums/RenderDataUsage"; +import { RendererUpdateFlags } from "../Renderer"; +import { assignmentClone, deepClone, ignoreClone } from "../clone/CloneManager"; +import { ShaderProperty } from "../shader"; +import { UIRenderer } from "./UIRenderer"; + +export class Image extends UIRenderer { + /** @internal */ + static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_SpriteTexture"); + @deepClone + private _color: Color = new Color(1, 1, 1, 1); + @ignoreClone + private _sprite: Sprite = null; + @ignoreClone + private _drawMode: SpriteDrawMode; + @assignmentClone + private _assembler: ISpriteAssembler; + @assignmentClone + private _tileMode: SpriteTileMode = SpriteTileMode.Continuous; + @assignmentClone + private _tiledAdaptiveThreshold: number = 0.5; + /** @internal */ + @ignoreClone + _chunk: Chunk; + + /** + * The draw mode of the sprite renderer. + */ + get drawMode(): SpriteDrawMode { + return this._drawMode; + } + + set drawMode(value: SpriteDrawMode) { + if (this._drawMode !== value) { + this._drawMode = value; + switch (value) { + case SpriteDrawMode.Simple: + this._assembler = SimpleSpriteAssembler; + break; + case SpriteDrawMode.Sliced: + this._assembler = SlicedSpriteAssembler; + break; + case SpriteDrawMode.Tiled: + this._assembler = TiledSpriteAssembler; + break; + default: + break; + } + this._assembler.resetData(this); + this._dirtyUpdateFlag |= ImageUpdateFlags.VertexData; + } + } + + /** + * The tiling mode of the sprite renderer. (Only works in tiled mode.) + */ + get tileMode(): SpriteTileMode { + return this._tileMode; + } + + set tileMode(value: SpriteTileMode) { + if (this._tileMode !== value) { + this._tileMode = value; + if (this.drawMode === SpriteDrawMode.Tiled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.VertexData; + } + } + } + + /** + * Stretch Threshold in Tile Adaptive Mode, specified in normalized. (Only works in tiled adaptive mode.) + */ + get tiledAdaptiveThreshold(): number { + return this._tiledAdaptiveThreshold; + } + + set tiledAdaptiveThreshold(value: number) { + if (value !== this._tiledAdaptiveThreshold) { + value = MathUtil.clamp(value, 0, 1); + this._tiledAdaptiveThreshold = value; + if (this.drawMode === SpriteDrawMode.Tiled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.VertexData; + } + } + } + + /** + * The Sprite to render. + */ + get sprite(): Sprite { + return this._sprite; + } + + set sprite(value: Sprite | null) { + const lastSprite = this._sprite; + if (lastSprite !== value) { + if (lastSprite) { + this._addResourceReferCount(lastSprite, -1); + lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + } + this._dirtyUpdateFlag |= ImageUpdateFlags.All; + if (value) { + this._addResourceReferCount(value, 1); + value._updateFlagManager.addListener(this._onSpriteChange); + this.shaderData.setTexture(SpriteRenderer._textureProperty, value.texture); + } else { + this.shaderData.setTexture(SpriteRenderer._textureProperty, null); + } + this._sprite = value; + } + } + + /** + * Rendering color for the Sprite graphic. + */ + get color(): Color { + return this._color; + } + + set color(value: Color) { + if (this._color !== value) { + this._color.copyFrom(value); + this._dirtyUpdateFlag |= ImageUpdateFlags.Color; + } + } + + /** + * @internal + */ + constructor(entity: Entity) { + super(entity); + + this.drawMode = SpriteDrawMode.Simple; + this._dirtyUpdateFlag |= ImageUpdateFlags.Color; + this.setMaterial(this._engine._spriteDefaultMaterial); + this._onSpriteChange = this._onSpriteChange.bind(this); + } + + /** + * @internal + */ + protected override _render(context: RenderContext): void { + const { _sprite: sprite } = this; + const { _uiTransform: uiTransform } = this; + const { x: width, y: height } = uiTransform.rect; + if (!sprite?.texture || !width || !height) { + return; + } + + let material = this.getMaterial(); + if (!material) { + return; + } + // @todo: This question needs to be raised rather than hidden. + if (material.destroyed) { + material = this._engine._uiDefaultMaterial; + } + + // Update position + if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { + this._assembler.updatePositions(this, width, height, uiTransform.pivot); + this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; + } + + // Update uv + if (this._dirtyUpdateFlag & ImageUpdateFlags.UV) { + this._assembler.updateUVs(this); + this._dirtyUpdateFlag &= ~ImageUpdateFlags.UV; + } + + // Update color + if (this._dirtyUpdateFlag & ImageUpdateFlags.Color) { + this._assembler.updateColor(this); + this._dirtyUpdateFlag &= ~ImageUpdateFlags.Color; + } + + // Push primitive + const { engine } = context.camera; + + const renderData = engine._renderData2DPool.getFromPool(); + const chunk = this._chunk; + renderData.set(this, material, chunk._primitive, chunk._subMesh, this.sprite.texture, chunk); + renderData.usage = RenderDataUsage.Sprite; + renderData.uploadFlag = ForceUploadShaderDataFlag.None; + engine._batcherManager.commitRenderData(context, renderData); + } + + protected override _canBatch(elementA: RenderElement, elementB: RenderElement): boolean { + const renderDataA = elementA.data; + const renderDataB = elementB.data; + if (renderDataA.chunk._data !== renderDataB.chunk._data) { + return false; + } + + // Compare texture and material + return renderDataA.texture === renderDataB.texture && renderDataA.material === renderDataB.material; + } + + protected override _batchRenderElement(elementA: RenderElement, elementB?: RenderElement): void { + const renderDataA = elementA.data; + const chunk = elementB ? (elementB.data).chunk : renderDataA.chunk; + const { _data: meshBuffer, _indices: tempIndices } = chunk; + const { offset, size, stride } = chunk._primitive.vertexBufferBindings[0]; + const indices = meshBuffer._indices; + const vertexStartIndex = offset / stride; + const len = tempIndices.length; + let startIndex = meshBuffer._indexLen; + if (elementB) { + const subMesh = renderDataA.chunk._subMesh; + subMesh.count += len; + } else { + const subMesh = chunk._subMesh; + subMesh.start = startIndex; + subMesh.count = len; + } + for (let i = 0; i < len; ++i) { + indices[startIndex++] = vertexStartIndex + tempIndices[i]; + } + meshBuffer._indexLen += len; + meshBuffer._vertexLen = Math.max(meshBuffer._vertexLen, offset / 4 + size / 4); + } + + protected override _onDestroy(): void { + const sprite = this._sprite; + if (sprite) { + this._addResourceReferCount(sprite, -1); + sprite._updateFlagManager.removeListener(this._onSpriteChange); + } + + super._onDestroy(); + this._entity = null; + this._color = null; + this._sprite = null; + this._assembler = null; + if (this._chunk) { + this.engine._batcherManager._dynamicGeometryDataManager2D.freeChunk(this._chunk); + this._chunk = null; + } + } + + @ignoreClone + private _onSpriteChange(type: SpriteModifyFlags): void { + switch (type) { + case SpriteModifyFlags.texture: + this.shaderData.setTexture(SpriteRenderer._textureProperty, this.sprite.texture); + break; + case SpriteModifyFlags.size: + const { _drawMode: drawMode } = this; + switch (drawMode) { + case SpriteDrawMode.Sliced: + this._dirtyUpdateFlag |= ImageUpdateFlags.Position; + break; + case SpriteDrawMode.Tiled: + this._dirtyUpdateFlag |= ImageUpdateFlags.VertexData; + break; + default: + break; + } + break; + case SpriteModifyFlags.border: + this._drawMode === SpriteDrawMode.Sliced && (this._dirtyUpdateFlag |= ImageUpdateFlags.VertexData); + break; + case SpriteModifyFlags.region: + case SpriteModifyFlags.atlasRegionOffset: + this._dirtyUpdateFlag |= ImageUpdateFlags.PositionAndUV; + break; + case SpriteModifyFlags.atlasRegion: + this._dirtyUpdateFlag |= ImageUpdateFlags.UV; + break; + case SpriteModifyFlags.pivot: + this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; + break; + case SpriteModifyFlags.destroy: + this.sprite = null; + break; + } + } +} + +/** + * @remarks Extends `RendererUpdateFlag`. + */ +enum ImageUpdateFlags { + /** Position. */ + Position = 0x1, + /** UV. */ + UV = 0x2, + /** Position and UV. */ + PositionAndUV = 0x3, + /** Color. */ + Color = 0x4, + /** Vertex data. */ + VertexData = 0x7, + /** All. */ + All = 0x7 +} diff --git a/packages/core/src/ui/UICanvas.ts b/packages/core/src/ui/UICanvas.ts index af246d11dc..a4d8995431 100644 --- a/packages/core/src/ui/UICanvas.ts +++ b/packages/core/src/ui/UICanvas.ts @@ -1,17 +1,19 @@ -import { Camera } from "../Camera"; +import { MathUtil, Vector2 } from "@galacean/engine-math"; +import { Camera, CameraModifyFlags } from "../Camera"; import { Component } from "../Component"; +import { DependentMode, dependentComponents } from "../ComponentsDependencies"; +import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { Transform } from "../Transform"; +import { Logger } from "../base"; import { ignoreClone } from "../clone/CloneManager"; +import { UIRenderer } from "./UIRenderer"; +import { UITransform } from "./UITransform"; import { CanvasRenderMode } from "./enums/CanvasRenderMode"; import { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrategy"; +@dependentComponents(UITransform, DependentMode.AutoAdd) export class UICanvas extends Component { - /** @internal */ - static _overlayCamera: Camera; - - /** @internal */ - _camera: Camera; - /** @internal */ @ignoreClone _uiCanvasIndex: number = -1; @@ -20,37 +22,73 @@ export class UICanvas extends Component { private _renderCamera: Camera; private _resolutionAdaptationStrategy: ResolutionAdaptationStrategy = ResolutionAdaptationStrategy.BothAdaptation; private _sortOrder: number = 0; - private _distance: number = 0; + private _distance: number = 10; + private _renderers: UIRenderer[] = []; + private _transform: Transform; + private _uiTransform: UITransform; + private _referenceResolution: Vector2 = new Vector2(750, 1624); + private _isRootCanvas: boolean = false; + + get referenceResolution(): Vector2 { + return this._referenceResolution; + } + + set referenceResolution(val: Vector2) { + const { _referenceResolution: referenceResolution } = this; + if (referenceResolution === val) return; + (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val); + } get renderMode(): CanvasRenderMode { return this._renderMode; } - set renderMode(val: CanvasRenderMode) { - if (this._renderMode !== val) { - this._renderMode = val; - this._renderCamera && this._updateCameraProiroty(this._renderCamera); + set renderMode(mode: CanvasRenderMode) { + let preMode = this._renderMode; + if (preMode !== mode) { + this._renderMode = mode; + if (this._isRootCanvas) { + const camera = this._renderCamera; + preMode = + preMode === CanvasRenderMode.ScreenSpaceCamera && !camera ? CanvasRenderMode.ScreenSpaceOverlay : preMode; + mode = mode === CanvasRenderMode.ScreenSpaceCamera && !camera ? CanvasRenderMode.ScreenSpaceOverlay : mode; + if (preMode !== mode) { + if (preMode === CanvasRenderMode.ScreenSpaceCamera) { + this._removeCameraListener(camera); + } else if (preMode === CanvasRenderMode.ScreenSpaceOverlay) { + this._removeCanvasListener(); + } + if (mode === CanvasRenderMode.ScreenSpaceCamera) { + this._addCameraListener(camera); + } else if (mode === CanvasRenderMode.ScreenSpaceOverlay) { + this._addCanvasListener(); + } + } + } else { + Logger.error("「根画布」"); + } } } get renderCamera(): Camera { - if (this._renderMode === CanvasRenderMode.ScreenSpaceOverlay) { - return UICanvas._overlayCamera; - } else if (this._renderMode === CanvasRenderMode.ScreenSpaceCamera) { - return this._renderCamera || UICanvas._overlayCamera; - } else { - return this._renderCamera; - } + return this._renderCamera; } set renderCamera(val: Camera) { - if (this._renderCamera !== val) { - if (val) { - this._renderCamera = val; - this._updateCameraProiroty(val); + const preCamera = this._renderCamera; + if (preCamera !== val) { + this._renderCamera = val; + if (this._isRootCanvas && this._renderMode === CanvasRenderMode.ScreenSpaceCamera) { + preCamera ? this._removeCameraListener(preCamera) : this._removeCanvasListener(); + if (val) { + this._addCameraListener(val); + } else { + this._addCanvasListener(); + } + this._adapterPoseInScreenSpace(); + this._adapterSizeInScreenSpace(); } else { - this._updateCameraProiroty(this._renderCamera); - this._renderCamera = null; + Logger.error("「根画布」「渲染模式 ScreenSpaceCamera 」"); } } } @@ -59,7 +97,16 @@ export class UICanvas extends Component { return this._resolutionAdaptationStrategy; } - set resolutionAdaptationStrategy(val: ResolutionAdaptationStrategy) {} + set resolutionAdaptationStrategy(val: ResolutionAdaptationStrategy) { + if (this._resolutionAdaptationStrategy !== val) { + this._resolutionAdaptationStrategy = val; + if (this._isRootCanvas && this._renderMode !== CanvasRenderMode.WorldSpace) { + this._adapterSizeInScreenSpace(); + } else { + Logger.error("「根画布」「渲染模式 ScreenSpaceXXX 」"); + } + } + } get sortOrder(): number { return this._sortOrder; @@ -77,32 +124,185 @@ export class UICanvas extends Component { set distance(val: number) { if (this._distance !== val) { + const { _isRootCanvas: isRootCanvas, _renderMode: renderMode } = this; this._distance = val; + if (isRootCanvas && renderMode === CanvasRenderMode.ScreenSpaceCamera && this._renderCamera) { + this._adapterPoseInScreenSpace(); + } else { + Logger.error("「根画布」 「渲染模式 ScreenSpaceCamera 」 「设置相机」"); + } } } - /** - * @internal - */ - _prepareRender(context: RenderContext): void {} + constructor(entity: Entity) { + super(entity); + this._transform = entity.getComponent(Transform); + this._uiTransform = entity.getComponent(UITransform); + this._onCanvasSizeChange = this._onCanvasSizeChange.bind(this); + this._onCameraPropertyChange = this._onCameraPropertyChange.bind(this); + this._onCameraTransformChange = this._onCameraTransformChange.bind(this); + this._onReferenceResolutionChanged = this._onReferenceResolutionChanged.bind(this); + // @ts-ignore + this._referenceResolution._onValueChanged = this._onReferenceResolutionChanged.bind(this); + } + + _prepareRender(context: RenderContext): void { + const { _renderers: renderers } = this; + // 先清空,后续需要设置 dirty + renderers.length = 0; + this._walk(this.entity, renderers); + const distanceForSort = this._distance; + for (let i = 0, n = renderers.length; i < n; i++) { + const renderer = renderers[i]; + renderer._distanceForSort = distanceForSort; + renderer._prepareRender(context); + } + } + + _setIsRootCanvas(value: boolean): void { + if (this._isRootCanvas !== value) { + this._isRootCanvas = value; + if (value) { + this.scene._componentsManager.addUICanvas(this); + } else { + this.scene._componentsManager.removeUICanvas(this); + } + } + } /** * @internal */ override _onEnableInScene(): void { - this.scene._componentsManager.addUICanvas(this); + this._setIsRootCanvas(this.entity._isRoot); } /** * @internal */ override _onDisableInScene(): void { - this.scene._componentsManager.removeUICanvas(this); + this._isRootCanvas && this.scene._componentsManager.removeUICanvas(this); + } + + private _onReferenceResolutionChanged(): void { + if (this._isRootCanvas && this.renderMode !== CanvasRenderMode.WorldSpace) { + this._adapterSizeInScreenSpace(); + } else { + Logger.error("「根画布」「渲染模式 ScreenSpaceXXX 」"); + } + } + + private _onCameraTransformChange(): void { + this._adapterPoseInScreenSpace(); + } + + private _onCameraPropertyChange(flag: CameraModifyFlags): void { + switch (flag) { + case CameraModifyFlags.NearPlane: + case CameraModifyFlags.FarPlane: + break; + default: + this._adapterSizeInScreenSpace(); + break; + } + } + + private _onCanvasSizeChange(): void { + const { canvas } = this.engine; + this._transform.setWorldPosition(canvas.width / 2, canvas.height / 2, 0); + this._adapterSizeInScreenSpace(); + } + + private _adapterPoseInScreenSpace(): void { + const { _renderCamera: renderCamera, _transform: transform } = this; + if (renderCamera) { + const { transform: cameraTransform } = renderCamera.entity; + const { worldPosition: cameraWorldPosition, worldForward: cameraWorldForward } = cameraTransform; + const { _distance: distance } = this; + transform.setWorldPosition( + cameraWorldPosition.x + cameraWorldForward.x * distance, + cameraWorldPosition.y + cameraWorldForward.y * distance, + cameraWorldPosition.z + cameraWorldForward.z * distance + ); + transform.worldRotationQuaternion.copyFrom(cameraTransform.worldRotationQuaternion); + } else { + const { canvas } = this.engine; + transform.setWorldPosition(canvas.width / 2, canvas.height / 2, 0); + transform.worldRotationQuaternion.set(0, 0, 0, 1); + } + } + + private _adapterSizeInScreenSpace(): void { + const { _renderCamera: renderCamera } = this; + const { x: width, y: height } = this._referenceResolution; + let curWidth: number; + let curHeight: number; + if (renderCamera) { + curHeight = renderCamera.isOrthographic + ? renderCamera.orthographicSize * 2 + : 2 * (Math.tan(MathUtil.degreeToRadian(renderCamera.fieldOfView / 2)) * this._distance); + curWidth = renderCamera.aspectRatio * curHeight; + } else { + const canvas = this.engine.canvas; + curHeight = canvas.height; + curWidth = canvas.width; + } + let expectX: number, expectY: number, expectZ: number; + switch (this._resolutionAdaptationStrategy) { + case ResolutionAdaptationStrategy.WidthAdaptation: + expectX = expectY = expectZ = curWidth / width; + break; + case ResolutionAdaptationStrategy.HeightAdaptation: + expectX = expectY = expectZ = curHeight / height; + break; + case ResolutionAdaptationStrategy.BothAdaptation: + expectX = curWidth / width; + expectY = curHeight / height; + expectZ = (expectX + expectY) / 2; + break; + case ResolutionAdaptationStrategy.ExpandAdaptation: + expectX = expectY = expectZ = Math.min(curWidth / width, curHeight / height); + break; + case ResolutionAdaptationStrategy.ShrinkAdaptation: + expectX = expectY = expectZ = Math.max(curWidth / width, curHeight / height); + break; + default: + break; + } + this.entity.transform.setScale(expectX, expectY, expectZ); + this._uiTransform.rect.set(curWidth / expectX, curHeight / expectY); + } + + private _walk(entity: Entity, out: UIRenderer[]): void { + const { _children: children } = entity; + for (let i = 0, n = children.length; i < n; i++) { + const { _components: components } = children[i]; + for (let j = 0, m = components.length; j < m; j++) { + const component = components[j]; + component instanceof UIRenderer && out.push(component); + } + } + } + + private _addCameraListener(camera: Camera): void { + camera.entity.transform._updateFlagManager.addListener(this._onCameraTransformChange); + camera._updateFlagManager.addListener(this._onCameraPropertyChange); + } + + private _addCanvasListener(): void { + this.engine.canvas._sizeUpdateFlagManager.addListener(this._onCanvasSizeChange); + } + + private _removeCameraListener(camera: Camera): void { + camera.entity.transform._updateFlagManager.removeListener(this._onCameraTransformChange); + camera._updateFlagManager.removeListener(this._onCameraPropertyChange); + } + + private _removeCanvasListener(): void { + this.engine.canvas._sizeUpdateFlagManager.removeListener(this._onCanvasSizeChange); } - private _updateCameraProiroty(camera: Camera): void { - if (this._renderMode === CanvasRenderMode.ScreenSpaceOverlay) return; - const priority = camera.priority; - camera.priority = this._renderMode === CanvasRenderMode.WorldSpace ? priority & ~(1 << 30) : priority | (1 << 30); + override _onParentChange(seniority: number): void { + this._setIsRootCanvas(this.entity._isRoot); } } diff --git a/packages/core/src/ui/UIRenderer.ts b/packages/core/src/ui/UIRenderer.ts index e177cd4bf4..9a9142a067 100644 --- a/packages/core/src/ui/UIRenderer.ts +++ b/packages/core/src/ui/UIRenderer.ts @@ -1,46 +1,104 @@ +import { Matrix } from "@galacean/engine-math"; +import { DependentMode, dependentComponents } from "../ComponentsDependencies"; import { Entity } from "../Entity"; +import { DynamicGeometryDataManager } from "../RenderPipeline/DynamicGeometryDataManager"; import { RenderContext } from "../RenderPipeline/RenderContext"; import { Renderer } from "../Renderer"; +import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { UICanvas } from "./UICanvas"; +import { UITransform, UITransformModifyFlags } from "./UITransform"; +@dependentComponents(UITransform, DependentMode.AutoAdd) export class UIRenderer extends Renderer { + protected _canvas: UICanvas; + protected _uiTransform: UITransform; + + get canvas(): UICanvas { + return this._canvas; + } + + set canvas(val: UICanvas) { + if (this._canvas !== val) { + this._canvas = val; + } + } + /** * @internal */ constructor(entity: Entity) { super(entity); + this._uiTransform = entity.getComponent(UITransform); } /** * @internal */ - override _prepareRender(context: RenderContext): void {} + override _updateShaderData(context: RenderContext, onlyMVP: boolean): void { + if (onlyMVP) { + // @ts-ignore + this._updateMVPShaderData(context, Matrix._identity); + } else { + // @ts-ignore + this._updateTransformShaderData(context, Matrix._identity); + } + } - // /** - // * @internal - // */ - // protected override _render(context: RenderContext): void { - // console.log(`render ui ${this.entity.name}`); - // } + /** + * @internal + */ + override _prepareRender(context: RenderContext): void { + this._updateShaderData(context, true); + this._render(context); + // union camera global macro and renderer macro. + ShaderMacroCollection.unionCollection( + context.camera._globalShaderMacro, + this.shaderData._macroCollection, + this._globalShaderMacro + ); + } /** * @internal */ override _onEnableInScene(): void { - // const componentsManager = this.scene._componentsManager; - // if (this._overrideUpdate) { - // componentsManager.addOnUpdateRenderers(this); - // } - // componentsManager.addRenderer(this); + const componentsManager = this.scene._componentsManager; + if (this._overrideUpdate) { + componentsManager.addOnUpdateRenderers(this); + } + this._uiTransform._updateFlagManager.addListener(this._onTransformChanged); + let { _entity: entity } = this; + while (entity.parent) { + entity = entity.parent; + } + this._canvas = entity._isRoot ? entity.getComponent(UICanvas) : null; } /** * @internal */ override _onDisableInScene(): void { - // const componentsManager = this.scene._componentsManager; - // if (this._overrideUpdate) { - // componentsManager.removeOnUpdateRenderers(this); - // } - // componentsManager.removeRenderer(this); + const componentsManager = this.scene._componentsManager; + if (this._overrideUpdate) { + componentsManager.removeOnUpdateRenderers(this); + } + this._uiTransform._updateFlagManager.removeListener(this._onTransformChanged); + } + + override _onParentChange(seniority: number): void { + let { _entity: entity } = this; + while (entity.parent) { + entity = entity.parent; + } + this._canvas = entity._isRoot ? entity.getComponent(UICanvas) : null; } + + /** + * @internal + */ + _getChunkManager(): DynamicGeometryDataManager { + return this.engine._batcherManager._dynamicGeometryDataManager2D; + } + + protected _onUITransformChanged(flag: UITransformModifyFlags): void {} } diff --git a/packages/core/src/ui/UITransform.ts b/packages/core/src/ui/UITransform.ts new file mode 100644 index 0000000000..3549c949f0 --- /dev/null +++ b/packages/core/src/ui/UITransform.ts @@ -0,0 +1,58 @@ +import { Vector2 } from "@galacean/engine-math"; +import { Component } from "../Component"; +import { Entity } from "../Entity"; +import { UpdateFlagManager } from "../UpdateFlagManager"; +import { deepClone, ignoreClone } from "../clone/CloneManager"; + +export class UITransform extends Component { + @deepClone + private _rect: Vector2 = new Vector2(100, 100); + @deepClone + private _pivot: Vector2 = new Vector2(0.5, 0.5); + /** @internal */ + @ignoreClone + _updateFlagManager: UpdateFlagManager = new UpdateFlagManager(); + + get rect(): Vector2 { + return this._rect; + } + + set rect(val: Vector2) { + const { _rect: rect } = this; + if (rect === val) return; + (rect.x !== val.x || rect.y !== val.y) && rect.copyFrom(val); + } + + get pivot(): Vector2 { + return this._pivot; + } + + set pivot(val: Vector2) { + const { _pivot: pivot } = this; + if (pivot === val) return; + (pivot.x !== val.x || pivot.y !== val.y) && pivot.copyFrom(val); + } + + constructor(entity: Entity) { + super(entity); + // @ts-ignore + this._rect._onValueChanged = this._onRectChange.bind(this); + // @ts-ignore + this._pivot._onValueChanged = this._onPivotChange.bind(this); + } + + private _onRectChange(): void { + this._updateFlagManager.dispatch(UITransformModifyFlags.Rect); + } + + private _onPivotChange(): void { + this._updateFlagManager.dispatch(UITransformModifyFlags.Pivot); + } +} + +export enum UITransformModifyFlags { + /** Rect. */ + Rect = 1, + /** Pivot. */ + Pivot = 2 +} diff --git a/packages/core/src/ui/index.ts b/packages/core/src/ui/index.ts index 7c762c860e..b5306ee5dd 100644 --- a/packages/core/src/ui/index.ts +++ b/packages/core/src/ui/index.ts @@ -3,3 +3,5 @@ export { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrate export { UICanvas } from "./UICanvas"; export { UIRenderer } from "./UIRenderer"; +export { Image } from "./Image"; +export { UITransform } from "./UITransform"; From cf0643d8715a51d100988c09e047a9f09c28dad5 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 17 Jun 2024 18:04:42 +0800 Subject: [PATCH 02/71] feat: update code --- packages/core/src/ComponentsManager.ts | 57 +++++---- packages/core/src/Engine.ts | 14 ++- packages/core/src/Entity.ts | 50 +++++++- .../src/RenderPipeline/BasicRenderPipeline.ts | 21 +++- packages/core/src/Script.ts | 34 +++++- .../core/src/input/pointer/PointerEvent.ts | 5 + .../src/input/pointer/PointerEventType.ts | 7 ++ .../core/src/input/pointer/PointerManager.ts | 108 +++++++++++++----- packages/core/src/ui/RedBlackTree.ts | 1 + packages/core/src/ui/UICanvas.ts | 54 ++++++++- packages/core/src/ui/UIRenderer.ts | 57 +++++++-- packages/core/src/ui/enums/BlockingObjects.ts | 6 + 12 files changed, 326 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/input/pointer/PointerEvent.ts create mode 100644 packages/core/src/input/pointer/PointerEventType.ts create mode 100644 packages/core/src/ui/RedBlackTree.ts create mode 100644 packages/core/src/ui/enums/BlockingObjects.ts diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts index cdaa09b57f..6d288f984c 100644 --- a/packages/core/src/ComponentsManager.ts +++ b/packages/core/src/ComponentsManager.ts @@ -17,9 +17,9 @@ export class ComponentsManager { /** @internal */ _renderers: DisorderedArray = new DisorderedArray(); /* @internal */ - _uiCanvasNeedSorting: boolean = false; + _uiCanvasSortingFlag: number = 0; /** @internal */ - _uiCanvases: DisorderedArray = new DisorderedArray(); + _uiCanvasesArray: DisorderedArray[]; // Script private _onStartScripts: DisorderedArray