diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54df16e60d..448246f846 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,6 +76,8 @@ jobs: cp -r ${{ github.workspace }}/packages/galacean/dist ${{ github.workspace }}/temp/@galacean/engine mkdir -p ${{ github.workspace }}/temp/@galacean/engine-xr cp -r ${{ github.workspace }}/packages/xr/dist ${{ github.workspace }}/temp/@galacean/engine-xr + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-ui + cp -r ${{ github.workspace }}/packages/ui/dist ${{ github.workspace }}/temp/@galacean/engine-ui mkdir -p ${{ github.workspace }}/temp/@galacean/engine-shaderlab cp -r ${{ github.workspace }}/packages/shader-lab/dist ${{ github.workspace }}/temp/@galacean/engine-shaderlab mkdir -p ${{ github.workspace }}/temp/@galacean/engine-shader-shaderlab @@ -113,6 +115,7 @@ jobs: "engine": [ "${{ github.workspace }}/temp/@galacean/engine/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-xr/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-ui/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-shaderlab/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-shader-shaderlab/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-physics-lite/dist/browser.js", diff --git a/packages/core/src/2d/assembler/ISpriteAssembler.ts b/packages/core/src/2d/assembler/ISpriteAssembler.ts index b481fb0d08..876c6f87f7 100644 --- a/packages/core/src/2d/assembler/ISpriteAssembler.ts +++ b/packages/core/src/2d/assembler/ISpriteAssembler.ts @@ -1,11 +1,21 @@ -import { Renderer } from "../../Renderer"; +import { Matrix, Vector2 } from "@galacean/engine-math"; +import { ISpriteRenderer } from "./ISpriteRenderer"; /** - * @internal + * Interface for sprite assembler. */ export interface ISpriteAssembler { - resetData(renderer: Renderer, vertexCount?: number): void; - updatePositions?(renderer: Renderer): void; - updateUVs?(renderer: Renderer): void; - updateColor?(renderer: Renderer): void; + resetData(renderer: ISpriteRenderer, vertexCount?: number): void; + updatePositions( + renderer: ISpriteRenderer, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean, + referenceResolutionPerUnit?: number + ): void; + updateUVs(renderer: ISpriteRenderer): void; + updateColor(renderer: ISpriteRenderer, alpha: number): void; } diff --git a/packages/core/src/2d/assembler/ISpriteRenderer.ts b/packages/core/src/2d/assembler/ISpriteRenderer.ts new file mode 100644 index 0000000000..a72f4e9436 --- /dev/null +++ b/packages/core/src/2d/assembler/ISpriteRenderer.ts @@ -0,0 +1,17 @@ +import { Color } from "@galacean/engine-math"; +import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; +import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; +import { SpriteTileMode } from "../enums/SpriteTileMode"; +import { Sprite } from "../sprite"; + +/** + * Interface for sprite renderer. + */ +export interface ISpriteRenderer { + sprite: Sprite; + color?: Color; + tileMode?: SpriteTileMode; + tiledAdaptiveThreshold?: number; + _subChunk: SubPrimitiveChunk; + _getChunkManager(): PrimitiveChunkManager; +} diff --git a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts index 7af247d480..563f812106 100644 --- a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts @@ -1,18 +1,17 @@ -import { BoundingBox, Matrix } from "@galacean/engine-math"; +import { BoundingBox, Matrix, Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; -import { SpriteMask } from "../sprite"; -import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; +import { ISpriteRenderer } from "./ISpriteRenderer"; /** - * @internal + * Assemble vertex data for the sprite renderer in simple mode. */ @StaticInterfaceImplement() export class SimpleSpriteAssembler { - static _rectangleTriangles = [0, 1, 2, 2, 1, 3]; - static _worldMatrix = new Matrix(); + private static _rectangleTriangles = [0, 1, 2, 2, 1, 3]; + private static _matrix = new Matrix(); - static resetData(renderer: SpriteRenderer | SpriteMask): void { + static resetData(renderer: ISpriteRenderer): void { const manager = renderer._getChunkManager(); const lastSubChunk = renderer._subChunk; lastSubChunk && manager.freeSubChunk(lastSubChunk); @@ -21,16 +20,24 @@ export class SimpleSpriteAssembler { renderer._subChunk = subChunk; } - static updatePositions(renderer: SpriteRenderer | SpriteMask): void { - const { width, height, sprite } = renderer; - const { x: pivotX, y: pivotY } = sprite.pivot; + static updatePositions( + renderer: ISpriteRenderer, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean + ): void { + const { sprite } = renderer; + const { x: pivotX, y: pivotY } = pivot; + // Position to World + const modelMatrix = SimpleSpriteAssembler._matrix; + const { elements: wE } = modelMatrix; // 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 { elements: pWE } = worldMatrix; + 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]); @@ -54,10 +61,11 @@ export class SimpleSpriteAssembler { vertices[o + 2] = wE[2] * x + wE[6] * y + wE[14]; } - BoundingBox.transform(sprite._getBounds(), worldMatrix, renderer._bounds); + // @ts-ignore + BoundingBox.transform(sprite._getBounds(), modelMatrix, renderer._bounds); } - static updateUVs(renderer: SpriteRenderer | SpriteMask): void { + static updateUVs(renderer: ISpriteRenderer): void { const spriteUVs = renderer.sprite._getUVs(); const { x: left, y: bottom } = spriteUVs[0]; const { x: right, y: top } = spriteUVs[3]; @@ -74,15 +82,16 @@ export class SimpleSpriteAssembler { vertices[offset + 28] = top; } - static updateColor(renderer: SpriteRenderer): void { + static updateColor(renderer: ISpriteRenderer, alpha: number): void { const subChunk = renderer._subChunk; const { r, g, b, a } = renderer.color; + const finalAlpha = a * alpha; const vertices = subChunk.chunk.vertices; for (let i = 0, o = subChunk.vertexArea.start + 5; i < 4; ++i, o += 9) { vertices[o] = r; vertices[o + 1] = g; vertices[o + 2] = b; - vertices[o + 3] = a; + vertices[o + 3] = finalAlpha; } } } diff --git a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts index 70d77a5541..31d19d149c 100644 --- a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts @@ -1,20 +1,22 @@ -import { Matrix } from "@galacean/engine-math"; +import { Matrix, Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; -import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; +import { ISpriteRenderer } from "./ISpriteRenderer"; /** - * @internal + * Assemble vertex data for the sprite renderer in sliced mode. */ @StaticInterfaceImplement() export class SlicedSpriteAssembler { - static _rectangleTriangles = [ + private static _rectangleTriangles = [ 0, 1, 4, 1, 5, 4, 1, 2, 5, 2, 6, 5, 2, 3, 6, 3, 7, 6, 4, 5, 8, 5, 9, 8, 5, 6, 9, 6, 10, 9, 6, 7, 10, 7, 11, 10, 8, 9, 12, 9, 13, 12, 9, 10, 13, 10, 14, 13, 10, 11, 14, 11, 15, 14 ]; - static _worldMatrix = new Matrix(); + private static _matrix = new Matrix(); + private static _row = new Array(4); + private static _column = new Array(4); - static resetData(renderer: SpriteRenderer): void { + static resetData(renderer: ISpriteRenderer): void { const manager = renderer._getChunkManager(); const lastSubChunk = renderer._subChunk; lastSubChunk && manager.freeSubChunk(lastSubChunk); @@ -23,14 +25,24 @@ export class SlicedSpriteAssembler { renderer._subChunk = subChunk; } - static updatePositions(renderer: SpriteRenderer): void { - const { width, height, sprite } = renderer; + static updatePositions( + renderer: ISpriteRenderer, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean, + referenceResolutionPerUnit: number = 1 + ): void { + const { sprite } = renderer; const { border } = sprite; // Update local positions. const spritePositions = sprite._getPositions(); const { x: left, y: bottom } = spritePositions[0]; const { x: right, y: top } = spritePositions[3]; - const { width: expectWidth, height: expectHeight } = sprite; + const expectWidth = sprite.width * referenceResolutionPerUnit; + const expectHeight = sprite.height * referenceResolutionPerUnit; const fixedLeft = expectWidth * border.x; const fixedBottom = expectHeight * border.y; const fixedRight = expectWidth * border.z; @@ -47,42 +59,36 @@ export class SlicedSpriteAssembler { // column // ------------------------ // Calculate row and column. - let row: number[], column: number[]; + const { _row: row, _column: column } = SlicedSpriteAssembler; if (fixedLeft + fixedRight > width) { const widthScale = width / (fixedLeft + fixedRight); - row = [ - expectWidth * left * widthScale, - fixedLeft * widthScale, - fixedLeft * widthScale, - width - expectWidth * (1 - right) * widthScale - ]; + (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale); + row[3] = width - expectWidth * (1 - right) * widthScale; } else { - row = [expectWidth * left, fixedLeft, width - fixedRight, width - expectWidth * (1 - right)]; + (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight); + row[3] = width - expectWidth * (1 - right); } if (fixedTop + fixedBottom > height) { const heightScale = height / (fixedTop + fixedBottom); - column = [ - expectHeight * bottom * heightScale, - fixedBottom * heightScale, - fixedBottom * heightScale, - height - expectHeight * (1 - top) * heightScale - ]; + (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale); + column[3] = height - expectHeight * (1 - top) * heightScale; } else { - column = [expectHeight * bottom, fixedBottom, height - fixedTop, height - expectHeight * (1 - top)]; + (column[0] = expectHeight * bottom), (column[1] = fixedBottom), (column[2] = height - fixedTop); + column[3] = height - expectHeight * (1 - top); } // 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; + // Position to World + const modelMatrix = SlicedSpriteAssembler._matrix; + const { elements: wE } = modelMatrix; // 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 { elements: pWE } = worldMatrix; + 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]); @@ -112,13 +118,14 @@ export class SlicedSpriteAssembler { } } - const { min, max } = renderer._bounds; - min.set(row[0], column[0], 0); - max.set(row[3], column[3], 0); - renderer._bounds.transform(worldMatrix); + // @ts-ignore + const bounds = renderer._bounds; + bounds.min.set(row[0], column[0], 0); + bounds.max.set(row[3], column[3], 0); + bounds.transform(modelMatrix); } - static updateUVs(renderer: SpriteRenderer): void { + static updateUVs(renderer: ISpriteRenderer): void { const subChunk = renderer._subChunk; const vertices = subChunk.chunk.vertices; const spriteUVs = renderer.sprite._getUVs(); @@ -131,15 +138,16 @@ export class SlicedSpriteAssembler { } } - static updateColor(renderer: SpriteRenderer): void { + static updateColor(renderer: ISpriteRenderer, alpha: number): void { const subChunk = renderer._subChunk; const { r, g, b, a } = renderer.color; + const finalAlpha = a * alpha; const vertices = subChunk.chunk.vertices; for (let i = 0, o = subChunk.vertexArea.start + 5; i < 16; ++i, o += 9) { vertices[o] = r; vertices[o + 1] = g; vertices[o + 2] = b; - vertices[o + 3] = a; + vertices[o + 3] = finalAlpha; } } } diff --git a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts index db62925041..bc765c45f9 100644 --- a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts @@ -1,24 +1,23 @@ -import { MathUtil, Matrix } from "@galacean/engine-math"; +import { MathUtil, Matrix, Vector2 } from "@galacean/engine-math"; import { Logger } from "../../base"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { DisorderedArray } from "../../utils/DisorderedArray"; import { SpriteTileMode } from "../enums/SpriteTileMode"; -import { Sprite } from "../sprite"; -import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { ISpriteAssembler } from "./ISpriteAssembler"; -import { DisorderedArray } from "../../utils/DisorderedArray"; +import { ISpriteRenderer } from "./ISpriteRenderer"; /** - * @internal + * Assemble vertex data for the sprite renderer in tiled mode. */ @StaticInterfaceImplement() export class TiledSpriteAssembler { - static _worldMatrix = new Matrix(); - static _posRow = new DisorderedArray(); - static _posColumn = new DisorderedArray(); - static _uvRow = new DisorderedArray(); - static _uvColumn = new DisorderedArray(); + private static _matrix = new Matrix(); + private static _posRow = new DisorderedArray(); + private static _posColumn = new DisorderedArray(); + private static _uvRow = new DisorderedArray(); + private static _uvColumn = new DisorderedArray(); - static resetData(renderer: SpriteRenderer, vertexCount: number): void { + static resetData(renderer: ISpriteRenderer, vertexCount: number): void { if (vertexCount) { const manager = renderer._getChunkManager(); const lastSubChunk = renderer._subChunk; @@ -33,58 +32,44 @@ export class TiledSpriteAssembler { } } - static updatePositions(renderer: SpriteRenderer): void { - const { width, height, sprite, tileMode, tiledAdaptiveThreshold: threshold } = renderer; + static updatePositions( + renderer: ISpriteRenderer, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean, + referenceResolutionPerUnit: number = 1 + ): void { // Calculate row and column - const { _posRow: posRow, _posColumn: posColumn, _uvRow: uvRow, _uvColumn: uvColumn } = TiledSpriteAssembler; - const maxVertexCount = renderer._getChunkManager().maxVertexCount; - posRow.length = posColumn.length = uvRow.length = uvColumn.length = 0; - const vertexCount = - tileMode === SpriteTileMode.Adaptive - ? TiledSpriteAssembler._calculateAdaptiveDividing( - sprite, - width, - height, - threshold, - posRow, - posColumn, - uvRow, - uvColumn, - maxVertexCount - ) - : TiledSpriteAssembler._calculateContinuousDividing( - sprite, - width, - height, - posRow, - posColumn, - uvRow, - uvColumn, - maxVertexCount - ); - TiledSpriteAssembler.resetData(renderer, vertexCount); + const { _posRow: rPos, _posColumn: cPos, _uvRow: rUV, _uvColumn: cUV } = TiledSpriteAssembler; + TiledSpriteAssembler.resetData( + renderer, + TiledSpriteAssembler._calculateDividing(renderer, width, height, rPos, cPos, rUV, cUV, referenceResolutionPerUnit) + ); // 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; + // Position to World + const modelMatrix = TiledSpriteAssembler._matrix; + const { elements: wE } = modelMatrix; // 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 { elements: pWE } = worldMatrix; + 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); (wE4 = wE[4] = pWE[4] * sy), (wE5 = wE[5] = pWE[5] * sy), (wE6 = wE[6] = pWE[6] * sy); (wE[8] = pWE[8]), (wE[9] = pWE[9]), (wE[10] = pWE[10]); - const wE12 = (wE[12] = pWE[12] - localTransX * wE[0] - localTransY * wE[4]); - const wE13 = (wE[13] = pWE[13] - localTransX * wE[1] - localTransY * wE[5]); - const wE14 = (wE[14] = pWE[14] - localTransX * wE[2] - localTransY * wE[6]); + const wE12 = (wE[12] = pWE[12] - localTransX * wE0 - localTransY * wE4); + const wE13 = (wE[13] = pWE[13] - localTransX * wE1 - localTransY * wE5); + const wE14 = (wE[14] = pWE[14] - localTransX * wE2 - localTransY * wE6); // Assemble position and uv - const rowLength = posRow.length - 1; - const columnLength = posColumn.length - 1; + const rowLength = rPos.length - 1; + const columnLength = cPos.length - 1; const subChunk = renderer._subChunk; const vertices = subChunk.chunk.vertices; @@ -92,15 +77,15 @@ export class TiledSpriteAssembler { let count = 0; let trianglesOffset = 0; for (let j = 0, o = subChunk.vertexArea.start; j < columnLength; j++) { - const doubleJ = 2 * j; + const doubleJ = j << 1; + if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) { + continue; + } for (let i = 0; i < rowLength; i++) { - const uvL = uvRow.get(2 * i); - const uvR = uvRow.get(2 * i + 1); - const uvT = uvColumn.get(doubleJ + 1); - if (isNaN(uvL) || isNaN(uvR) || isNaN(uvT)) { + const doubleI = i << 1; + if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) { continue; } - indices[trianglesOffset++] = count; indices[trianglesOffset++] = count + 1; indices[trianglesOffset++] = count + 2; @@ -108,10 +93,10 @@ export class TiledSpriteAssembler { indices[trianglesOffset++] = count + 1; indices[trianglesOffset++] = count + 3; count += 4; - const l = posRow.get(i); - const b = posColumn.get(j); - const r = posRow.get(i + 1); - const t = posColumn.get(j + 1); + const l = rPos.get(i); + const b = cPos.get(j); + const r = rPos.get(i + 1); + const t = cPos.get(j + 1); // left and bottom vertices[o] = wE0 * l + wE4 * b + wE12; @@ -133,13 +118,14 @@ export class TiledSpriteAssembler { } } - const { min, max } = renderer._bounds; - min.set(posRow.get(0), posColumn.get(0), 0); - max.set(posRow.get(rowLength), posColumn.get(columnLength), 0); - renderer._bounds.transform(worldMatrix); + // @ts-ignore + const bounds = renderer._bounds; + bounds.min.set(rPos.get(0), cPos.get(0), 0); + bounds.max.set(rPos.get(rowLength), cPos.get(columnLength), 0); + bounds.transform(modelMatrix); } - static updateUVs(renderer: SpriteRenderer): void { + static updateUVs(renderer: ISpriteRenderer): void { const { _posRow: posRow, _posColumn: posColumn, _uvRow: uvRow, _uvColumn: uvColumn } = TiledSpriteAssembler; const rowLength = posRow.length - 1; const columnLength = posColumn.length - 1; @@ -152,7 +138,7 @@ export class TiledSpriteAssembler { const uvB = uvColumn.get(doubleJ); const uvR = uvRow.get(2 * i + 1); const uvT = uvColumn.get(doubleJ + 1); - if (isNaN(uvL) || isNaN(uvB) || isNaN(uvR) || isNaN(uvT)) { + if (Number.isNaN(uvL) || Number.isNaN(uvB) || Number.isNaN(uvR) || Number.isNaN(uvT)) { continue; } @@ -173,36 +159,38 @@ export class TiledSpriteAssembler { } } - static updateColor(renderer: SpriteRenderer): void { + static updateColor(renderer: ISpriteRenderer, alpha: number): void { const subChunk = renderer._subChunk; const { r, g, b, a } = renderer.color; + const finalAlpha = a * alpha; const vertices = subChunk.chunk.vertices; const vertexArea = subChunk.vertexArea; for (let i = 0, o = vertexArea.start + 5, n = vertexArea.size / 9; i < n; ++i, o += 9) { vertices[o] = r; vertices[o + 1] = g; vertices[o + 2] = b; - vertices[o + 3] = a; + vertices[o + 3] = finalAlpha; } } - private static _calculateAdaptiveDividing( - sprite: Sprite, + private static _calculateDividing( + renderer: ISpriteRenderer, width: number, height: number, - threshold: number, - posRow: DisorderedArray, - posColumn: DisorderedArray, - uvRow: DisorderedArray, - uvColumn: DisorderedArray, - maxVertexCount: number + rPos: DisorderedArray, + cPos: DisorderedArray, + rUV: DisorderedArray, + cUV: DisorderedArray, + referenceResolutionPerUnit: number ): number { + const { sprite, tiledAdaptiveThreshold: threshold } = renderer; const { border } = sprite; const spritePositions = sprite._getPositions(); const { x: left, y: bottom } = spritePositions[0]; const { x: right, y: top } = spritePositions[3]; const [spriteUV0, spriteUV1, spriteUV2, spriteUV3] = sprite._getUVs(); - const { width: expectWidth, height: expectHeight } = sprite; + const expectWidth = sprite.width * referenceResolutionPerUnit; + const expectHeight = sprite.height * referenceResolutionPerUnit; const fixedL = expectWidth * border.x; const fixedR = expectWidth * border.z; const fixedLR = fixedL + fixedR; @@ -211,221 +199,83 @@ export class TiledSpriteAssembler { const fixedB = expectHeight * border.y; const fixedTB = fixedT + fixedB; const fixedCH = expectHeight - fixedTB; - let scale: number; - let rType: TiledType, cType: TiledType; - let rVertCount: number, cVertCount: number; - let rRepeatCount: number, cRepeatCount: number; + const isAdaptive = renderer.tileMode === SpriteTileMode.Adaptive; + let rType: TiledType, rBlocksCount: number, rTiledCount: number; + let cType: TiledType, cBlocksCount: number, cTiledCount: number; if (fixedLR >= width) { - rVertCount = 3; + rBlocksCount = 2; rType = TiledType.Compressed; } else { if (fixedCW > MathUtil.zeroTolerance) { - rRepeatCount = (width - fixedLR) / fixedCW; - rRepeatCount = rRepeatCount % 1 >= threshold ? Math.ceil(rRepeatCount) : Math.floor(rRepeatCount); - rVertCount = 4 + rRepeatCount - 1; - rType = TiledType.WithTiled; - } else { - rVertCount = 4; - rType = TiledType.WithoutTiled; - } - } - - if (fixedTB >= height) { - cVertCount = 3; - cType = TiledType.Compressed; - } else { - if (fixedCH > MathUtil.zeroTolerance) { - cRepeatCount = (height - fixedTB) / fixedCH; - cRepeatCount = cRepeatCount % 1 >= threshold ? Math.ceil(cRepeatCount) : Math.floor(cRepeatCount); - cVertCount = 4 + cRepeatCount - 1; - cType = TiledType.WithTiled; - } else { - cVertCount = 4; - cType = TiledType.WithoutTiled; - } - } - - let rowCount = 0; - let columnCount = 0; - - if ((rVertCount - 1) * (cVertCount - 1) * 4 > maxVertexCount) { - posRow.add(width * left), posRow.add(width * right); - posColumn.add(height * bottom), posColumn.add(height * top); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV3.x); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV3.y); - rowCount += 2; - columnCount += 2; - Logger.warn(`The number of vertices exceeds the upper limit(${maxVertexCount}).`); - return rowCount * columnCount; - } - - switch (rType) { - case TiledType.Compressed: - scale = width / fixedLR; - posRow.add(expectWidth * left * scale), posRow.add(fixedL * scale); - posRow.add(width - expectWidth * (1 - right) * scale); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 4; - break; - case TiledType.WithoutTiled: - posRow.add(expectWidth * left), posRow.add(fixedL), posRow.add(width - fixedR); - posRow.add(width - expectWidth * (1 - right)); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(NaN), uvRow.add(NaN); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 4; - break; - case TiledType.WithTiled: - scale = width / (fixedLR + rRepeatCount * fixedCW); - posRow.add(expectWidth * left * scale), posRow.add(fixedL * scale); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(spriteUV1.x); - rowCount += 3; - for (let i = 0, l = rRepeatCount - 1; i < l; i++) { - posRow.add(fixedL + (i + 1) * fixedCW * scale); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV1.x); - rowCount += 2; + rTiledCount = (width - fixedLR) / fixedCW; + if (isAdaptive) { + rTiledCount = rTiledCount % 1 >= threshold ? Math.ceil(rTiledCount) : Math.floor(rTiledCount); + rBlocksCount = 2 + rTiledCount; + } else { + rBlocksCount = 2 + Math.ceil(rTiledCount); } - posRow.add(width - fixedR * scale), posRow.add(width - expectWidth * (1 - right) * scale); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 3; - break; - default: - break; - } - - switch (cType) { - case TiledType.Compressed: - scale = height / fixedTB; - posColumn.add(expectHeight * bottom * scale), posColumn.add(fixedB * scale); - posColumn.add(height - expectHeight * (1 - top) * scale); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 4; - break; - case TiledType.WithoutTiled: - posColumn.add(expectHeight * bottom), posColumn.add(fixedB), posColumn.add(height - fixedT); - posColumn.add(height - expectHeight * (1 - top)); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(NaN), uvColumn.add(NaN); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 4; - break; - case TiledType.WithTiled: - scale = height / (fixedTB + cRepeatCount * fixedCH); - posColumn.add(expectHeight * bottom * scale), posColumn.add(fixedB * scale); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(spriteUV1.y); - columnCount += 3; - for (let i = 0, l = cRepeatCount - 1; i < l; i++) { - posColumn.add(fixedB + (i + 1) * fixedCH * scale); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV1.y); - columnCount += 2; - } - posColumn.add(height - fixedT * scale), posColumn.add(height - expectHeight * (1 - top) * scale); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 3; - break; - default: - break; - } - - return rowCount * columnCount; - } - - private static _calculateContinuousDividing( - sprite: Sprite, - width: number, - height: number, - posRow: DisorderedArray, - posColumn: DisorderedArray, - uvRow: DisorderedArray, - uvColumn: DisorderedArray, - maxVertexCount: number - ): number { - const { border } = sprite; - const spritePositions = sprite._getPositions(); - const { x: left, y: bottom } = spritePositions[0]; - const { x: right, y: top } = spritePositions[3]; - const [spriteUV0, spriteUV1, spriteUV2, spriteUV3] = sprite._getUVs(); - const { width: expectWidth, height: expectHeight } = sprite; - const fixedL = expectWidth * border.x; - const fixedR = expectWidth * border.z; - const fixedLR = fixedL + fixedR; - const fixedCW = expectWidth - fixedLR; - const fixedT = expectHeight * border.w; - const fixedB = expectHeight * border.y; - const fixedTB = fixedT + fixedB; - const fixedCH = expectHeight - fixedTB; - let rType: TiledType, cType: TiledType; - let rVertCount: number, cVertCount: number; - let rRepeatCount: number, cRepeatCount: number; - if (fixedLR >= width) { - rVertCount = 3; - rType = TiledType.Compressed; - } else { - if (fixedCW > MathUtil.zeroTolerance) { - rRepeatCount = (width - fixedLR) / fixedCW; - rVertCount = 4 + (rRepeatCount | 0); rType = TiledType.WithTiled; } else { - rVertCount = 4; + rBlocksCount = 2; rType = TiledType.WithoutTiled; } } - if (fixedTB >= height) { - cVertCount = 3; + cBlocksCount = 2; cType = TiledType.Compressed; } else { if (fixedCH > MathUtil.zeroTolerance) { - cRepeatCount = (height - fixedTB) / fixedCH; - cVertCount = 4 + (cRepeatCount | 0); + cTiledCount = (height - fixedTB) / fixedCH; + if (isAdaptive) { + cTiledCount = cTiledCount % 1 >= threshold ? Math.ceil(cTiledCount) : Math.floor(cTiledCount); + cBlocksCount = 2 + cTiledCount; + } else { + cBlocksCount = 2 + Math.ceil(cTiledCount); + } cType = TiledType.WithTiled; } else { - cVertCount = 4; + cBlocksCount = 2; cType = TiledType.WithoutTiled; } } - let rowCount = 0; - let columnCount = 0; - - if ((rVertCount - 1) * (cVertCount - 1) * 4 > maxVertexCount) { - posRow.add(width * left), posRow.add(width * right); - posColumn.add(height * bottom), posColumn.add(height * top); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV3.x); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV3.y); - rowCount += 2; - columnCount += 2; + rPos.length = cPos.length = rUV.length = cUV.length = 0; + const vertexCount = rBlocksCount * cBlocksCount * 4; + const maxVertexCount = renderer._getChunkManager().maxVertexCount; + if (vertexCount > maxVertexCount) { + rPos.add(width * left), rPos.add(width * right); + cPos.add(height * bottom), cPos.add(height * top); + rUV.add(spriteUV0.x), rUV.add(spriteUV3.x); + cUV.add(spriteUV0.y), cUV.add(spriteUV3.y); Logger.warn(`The number of vertices exceeds the upper limit(${maxVertexCount}).`); - return rowCount * columnCount; + return 4; } switch (rType) { case TiledType.Compressed: const scale = width / fixedLR; - posRow.add(expectWidth * left * scale), posRow.add(fixedL * scale); - posRow.add(width - expectWidth * (1 - right) * scale); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 4; + rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale); + rPos.add(width - expectWidth * (1 - right) * scale); + rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x); break; case TiledType.WithoutTiled: - posRow.add(expectWidth * left), posRow.add(fixedL), posRow.add(width - fixedR); - posRow.add(width - expectWidth * (1 - right)); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(NaN), uvRow.add(NaN); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 4; + rPos.add(expectWidth * left), rPos.add(fixedL), rPos.add(width - fixedR); + rPos.add(width - expectWidth * (1 - right)); + rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(NaN), rUV.add(NaN); + rUV.add(spriteUV2.x), rUV.add(spriteUV3.x); break; case TiledType.WithTiled: - posRow.add(expectWidth * left), posRow.add(fixedL); - uvRow.add(spriteUV0.x), uvRow.add(spriteUV1.x), uvRow.add(spriteUV1.x); - rowCount += 3; - const countInteger = rRepeatCount | 0; - for (let i = 0; i < countInteger; i++) { - posRow.add(fixedL + (i + 1) * fixedCW); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV1.x); - rowCount += 2; + const uv1 = spriteUV1.x; + const uv2 = spriteUV2.x; + const repeatWidth = (width - fixedLR) / rTiledCount; + rPos.add(expectWidth * left), rPos.add(fixedL); + rUV.add(spriteUV0.x), rUV.add(uv1), rUV.add(uv1); + for (let i = 1, l = rBlocksCount - 2; i < l; i++) { + rPos.add(fixedL + i * repeatWidth), rUV.add(uv2), rUV.add(uv1); } - posRow.add(width - fixedR), posRow.add(width - expectWidth * (1 - right)); - uvRow.add((spriteUV2.x - spriteUV1.x) * (rRepeatCount - countInteger) + spriteUV1.x); - uvRow.add(spriteUV2.x), uvRow.add(spriteUV3.x); - rowCount += 3; + rPos.add(width - fixedR), rPos.add(width - expectWidth * (1 - right)); + isAdaptive ? rUV.add(uv2) : rUV.add((rTiledCount - (Math.ceil(rTiledCount) - 1)) * (uv2 - uv1) + uv1); + rUV.add(uv2), rUV.add(spriteUV3.x); break; default: break; @@ -434,37 +284,33 @@ export class TiledSpriteAssembler { switch (cType) { case TiledType.Compressed: const scale = height / fixedTB; - posColumn.add(expectHeight * bottom * scale), posColumn.add(fixedB * scale); - posColumn.add(height - expectHeight * (1 - top) * scale); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 4; + cPos.add(expectHeight * bottom * scale), cPos.add(fixedB * scale); + cPos.add(height - expectHeight * (1 - top) * scale); + cUV.add(spriteUV0.y), cUV.add(spriteUV1.y), cUV.add(spriteUV2.y), cUV.add(spriteUV3.y); break; case TiledType.WithoutTiled: - posColumn.add(expectHeight * bottom), posColumn.add(fixedB), posColumn.add(height - fixedT); - posColumn.add(height - expectHeight * (1 - top)); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(NaN), uvColumn.add(NaN); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 4; + cPos.add(expectHeight * bottom), cPos.add(fixedB), cPos.add(height - fixedT); + cPos.add(height - expectHeight * (1 - top)); + cUV.add(spriteUV0.y), cUV.add(spriteUV1.y), cUV.add(NaN), cUV.add(NaN); + cUV.add(spriteUV2.y), cUV.add(spriteUV3.y); break; case TiledType.WithTiled: - posColumn.add(expectHeight * bottom), posColumn.add(fixedB); - uvColumn.add(spriteUV0.y), uvColumn.add(spriteUV1.y), uvColumn.add(spriteUV1.y); - columnCount += 3; - const countInteger = cRepeatCount | 0; - for (let i = 0; i < countInteger; i++) { - posColumn.add(fixedB + (i + 1) * fixedCH); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV1.y); - columnCount += 2; + const uv1 = spriteUV1.y; + const uv2 = spriteUV2.y; + const repeatHeight = (height - fixedTB) / cTiledCount; + cPos.add(expectHeight * bottom), cPos.add(fixedB); + cUV.add(spriteUV0.y), cUV.add(uv1), cUV.add(uv1); + for (let i = 1, l = cBlocksCount - 2; i < l; i++) { + cPos.add(fixedB + i * repeatHeight), cUV.add(uv2), cUV.add(uv1); } - posColumn.add(height - fixedT), posColumn.add(height - expectHeight * (1 - top)); - uvColumn.add((spriteUV2.y - spriteUV1.y) * (cRepeatCount - countInteger) + spriteUV1.y); - uvColumn.add(spriteUV2.y), uvColumn.add(spriteUV3.y); - columnCount += 3; + cPos.add(height - fixedT), cPos.add(height - expectHeight * (1 - top)); + isAdaptive ? cUV.add(uv2) : cUV.add((cTiledCount - (Math.ceil(cTiledCount) - 1)) * (uv2 - uv1) + uv1); + cUV.add(uv2), cUV.add(spriteUV3.y); break; default: break; } - return rowCount * columnCount; + return vertexCount; } } diff --git a/packages/core/src/2d/enums/SpriteModifyFlags.ts b/packages/core/src/2d/enums/SpriteModifyFlags.ts index 14772de837..3654997f85 100644 --- a/packages/core/src/2d/enums/SpriteModifyFlags.ts +++ b/packages/core/src/2d/enums/SpriteModifyFlags.ts @@ -2,13 +2,22 @@ * Sprite Property Dirty Flag. */ export enum SpriteModifyFlags { + /** The texture of sprite changes. */ texture = 0x1, + /** The size of sprite changes. */ size = 0x2, + /** The rotation of sprite changes. */ atlasRotate = 0x4, + /** The atlasRegion of sprite changes. */ atlasRegion = 0x8, + /** The atlasRegionOffset of sprite changes. */ atlasRegionOffset = 0x10, + /** The region of sprite changes. */ region = 0x20, + /** The pivot of sprite changes. */ pivot = 0x40, + /** The border of sprite changes. */ border = 0x80, + /** The sprite destroyed. */ destroy = 0x100 } diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index d8fccbd267..47be64ccfc 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -1,9 +1,16 @@ -export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction"; -export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlignment"; -export { OverflowMode } from "./enums/TextOverflow"; -export { FontStyle } from "./enums/FontStyle"; +export type { ISpriteAssembler } from "./assembler/ISpriteAssembler"; +export type { ISpriteRenderer } from "./assembler/ISpriteRenderer"; +export { SimpleSpriteAssembler } from "./assembler/SimpleSpriteAssembler"; +export { SlicedSpriteAssembler } from "./assembler/SlicedSpriteAssembler"; +export { TiledSpriteAssembler } from "./assembler/TiledSpriteAssembler"; export { SpriteAtlas } from "./atlas/SpriteAtlas"; +export { FontStyle } from "./enums/FontStyle"; export { SpriteDrawMode } from "./enums/SpriteDrawMode"; +export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction"; +export { SpriteModifyFlags } from "./enums/SpriteModifyFlags"; export { SpriteTileMode } from "./enums/SpriteTileMode"; +export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlignment"; +export { OverflowMode } from "./enums/TextOverflow"; export * from "./sprite/index"; export * from "./text/index"; +export type { ITextRenderer } from "./text/ITextRenderer"; diff --git a/packages/core/src/2d/sprite/Sprite.ts b/packages/core/src/2d/sprite/Sprite.ts index 9b2257227e..d89fed6443 100644 --- a/packages/core/src/2d/sprite/Sprite.ts +++ b/packages/core/src/2d/sprite/Sprite.ts @@ -2,9 +2,10 @@ import { BoundingBox, MathUtil, Rect, Vector2, Vector4 } from "@galacean/engine- import { Engine } from "../../Engine"; import { UpdateFlagManager } from "../../UpdateFlagManager"; import { ReferResource } from "../../asset/ReferResource"; +import { ignoreClone } from "../../clone/CloneManager"; import { Texture2D } from "../../texture/Texture2D"; -import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; import { SpriteAtlas } from "../atlas/SpriteAtlas"; +import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; /** * 2D sprite. @@ -60,7 +61,7 @@ export class Sprite extends ReferResource { * * @remarks * If width is set, return the set value, - * otherwise return the width calculated according to `Texture.width`, `Sprite.region`, `Sprite.atlasRegion`, `Sprite.atlasRegionOffset` and `Engine._pixelsPerUnit`. + * otherwise return the width calculated according to `Texture.width`, `Sprite.region`, `Sprite.atlasRegion` and `Sprite.atlasRegionOffset`. */ get width(): number { if (this._customWidth !== undefined) { @@ -83,7 +84,7 @@ export class Sprite extends ReferResource { * * @remarks * If height is set, return the set value, - * otherwise return the height calculated according to `Texture.height`, `Sprite.region`, `Sprite.atlasRegion`, `Sprite.atlasRegionOffset` and `Engine._pixelsPerUnit`. + * otherwise return the height calculated according to `Texture.height`, `Sprite.region`, `Sprite.atlasRegion` and `Sprite.atlasRegionOffset`. */ get height(): number { if (this._customHeight !== undefined) { @@ -325,9 +326,9 @@ export class Sprite extends ReferResource { positions[2].set(left, top); positions[3].set(right, top); - const { min, max } = this._bounds; - min.set(left, bottom, 0); - max.set(right, top, 0); + const bounds = this._bounds; + bounds.min.set(left, bottom, 0); + bounds.max.set(right, top, 0); this._dirtyUpdateFlag &= ~SpriteUpdateFlags.positions; } @@ -382,6 +383,7 @@ export class Sprite extends ReferResource { this._updateFlagManager.dispatch(type); } + @ignoreClone private _onRegionChange(): void { const { _region: region } = this; // @ts-ignore @@ -397,10 +399,12 @@ export class Sprite extends ReferResource { region._onValueChanged = this._onRegionChange; } + @ignoreClone private _onPivotChange(): void { this._dispatchSpriteChange(SpriteModifyFlags.pivot); } + @ignoreClone private _onBorderChange(): void { const { _border: border } = this; // @ts-ignore diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 8c50eaf04a..b179899b8e 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -11,6 +11,7 @@ import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; import { ShaderProperty } from "../../shader/ShaderProperty"; +import { ISpriteRenderer } from "../assembler/ISpriteRenderer"; import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; import { Sprite } from "./Sprite"; @@ -18,7 +19,7 @@ import { Sprite } from "./Sprite"; /** * A component for masking Sprites. */ -export class SpriteMask extends Renderer { +export class SpriteMask extends Renderer implements ISpriteRenderer { /** @internal */ static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_MaskTexture"); /** @internal */ @@ -238,11 +239,21 @@ export class SpriteMask extends Renderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - if (this.sprite) { - SimpleSpriteAssembler.updatePositions(this); + const sprite = this._sprite; + if (sprite) { + SimpleSpriteAssembler.updatePositions( + this, + this._transformEntity.transform.worldMatrix, + this.width, + this.height, + sprite.pivot, + this._flipX, + this._flipY + ); } else { - worldBounds.min.set(0, 0, 0); - worldBounds.max.set(0, 0, 0); + const { worldPosition } = this._transformEntity.transform; + worldBounds.min.copyFrom(worldPosition); + worldBounds.max.copyFrom(worldPosition); } } @@ -250,7 +261,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; } @@ -266,7 +278,15 @@ export class SpriteMask extends Renderer { // Update position if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { - SimpleSpriteAssembler.updatePositions(this); + SimpleSpriteAssembler.updatePositions( + this, + this._transformEntity.transform.worldMatrix, + this.width, + this.height, + sprite.pivot, + this._flipX, + this._flipY + ); this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; } @@ -333,7 +353,7 @@ export class SpriteMask extends Renderer { break; case SpriteModifyFlags.region: case SpriteModifyFlags.atlasRegionOffset: - this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.RenderData; + this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.WorldVolumeAndUV; break; case SpriteModifyFlags.atlasRegion: this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.UV; @@ -351,15 +371,15 @@ export class SpriteMask extends Renderer { } /** - * @remarks Extends `RendererUpdateFlag`. + * @remarks Extends `RendererUpdateFlags`. */ enum SpriteMaskUpdateFlags { /** UV. */ UV = 0x2, - /** WorldVolume and UV . */ - RenderData = 0x3, /** Automatic Size. */ AutomaticSize = 0x4, + /** WorldVolume and UV. */ + WorldVolumeAndUV = 0x3, /** All. */ All = 0x7 } diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index 7327bded52..654cda0677 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -9,6 +9,7 @@ import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderProperty } from "../../shader/ShaderProperty"; import { ISpriteAssembler } from "../assembler/ISpriteAssembler"; +import { ISpriteRenderer } from "../assembler/ISpriteRenderer"; import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; import { SlicedSpriteAssembler } from "../assembler/SlicedSpriteAssembler"; import { TiledSpriteAssembler } from "../assembler/TiledSpriteAssembler"; @@ -21,7 +22,7 @@ import { Sprite } from "./Sprite"; /** * Renders a Sprite for 2D graphics. */ -export class SpriteRenderer extends Renderer { +export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** @internal */ static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_SpriteTexture"); @@ -80,7 +81,7 @@ export class SpriteRenderer extends Renderer { break; } this._assembler.resetData(this); - this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.VertexData; + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; } } @@ -95,7 +96,7 @@ export class SpriteRenderer extends Renderer { if (this._tileMode !== value) { this._tileMode = value; if (this.drawMode === SpriteDrawMode.Tiled) { - this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.VertexData; + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; } } } @@ -112,7 +113,7 @@ export class SpriteRenderer extends Renderer { value = MathUtil.clamp(value, 0, 1); this._tiledAdaptiveThreshold = value; if (this.drawMode === SpriteDrawMode.Tiled) { - this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.VertexData; + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; } } } @@ -177,7 +178,7 @@ export class SpriteRenderer extends Renderer { this._customWidth = value; this._dirtyUpdateFlag |= this._drawMode === SpriteDrawMode.Tiled - ? SpriteRendererUpdateFlags.VertexData + ? SpriteRendererUpdateFlags.WorldVolumeUVAndColor : RendererUpdateFlags.WorldVolume; } } @@ -203,7 +204,7 @@ export class SpriteRenderer extends Renderer { this._customHeight = value; this._dirtyUpdateFlag |= this._drawMode === SpriteDrawMode.Tiled - ? SpriteRendererUpdateFlags.VertexData + ? SpriteRendererUpdateFlags.WorldVolumeUVAndColor : RendererUpdateFlags.WorldVolume; } } @@ -313,16 +314,27 @@ export class SpriteRenderer extends Renderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - if (this.sprite) { - this._assembler.updatePositions(this); + const sprite = this._sprite; + if (sprite) { + this._assembler.updatePositions( + this, + this._transformEntity.transform.worldMatrix, + this.width, + this.height, + sprite.pivot, + this._flipX, + this._flipY + ); } else { - worldBounds.min.set(0, 0, 0); - worldBounds.max.set(0, 0, 0); + const { worldPosition } = this._transformEntity.transform; + worldBounds.min.copyFrom(worldPosition); + worldBounds.max.copyFrom(worldPosition); } } 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; } @@ -337,7 +349,15 @@ export class SpriteRenderer extends Renderer { // Update position if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { - this._assembler.updatePositions(this); + this._assembler.updatePositions( + this, + this._transformEntity.transform.worldMatrix, + this.width, + this.height, + sprite.pivot, + this._flipX, + this._flipY + ); this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; } @@ -349,7 +369,7 @@ export class SpriteRenderer extends Renderer { // Update color if (this._dirtyUpdateFlag & SpriteRendererUpdateFlags.Color) { - this._assembler.updateColor(this); + this._assembler.updateColor(this, 1); this._dirtyUpdateFlag &= ~SpriteRendererUpdateFlags.Color; } @@ -400,23 +420,37 @@ export class SpriteRenderer extends Renderer { this.shaderData.setTexture(SpriteRenderer._textureProperty, this.sprite.texture); break; case SpriteModifyFlags.size: - const { _drawMode: drawMode } = this; this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.AutomaticSize; - if (this._drawMode === SpriteDrawMode.Sliced) { + if (this._customWidth === undefined || this._customHeight === undefined) { this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; - } else if (drawMode === SpriteDrawMode.Tiled) { - this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; - } else { - // When the width and height of `SpriteRenderer` are `undefined`, - // the `size` of `Sprite` will affect the position of `SpriteRenderer`. - if (this._customWidth === undefined || this._customHeight === undefined) { + } + switch (this._drawMode) { + case SpriteDrawMode.Simple: + // When the width and height of `SpriteRenderer` are `undefined`, + // the `size` of `Sprite` will affect the position of `SpriteRenderer`. + if (this._customWidth === undefined || this._customHeight === undefined) { + this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; + } + break; + case SpriteDrawMode.Sliced: this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; - } + break; + case SpriteDrawMode.Tiled: + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; + break; } break; case SpriteModifyFlags.border: - this._drawMode === SpriteDrawMode.Sliced && - (this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV); + switch (this._drawMode) { + case SpriteDrawMode.Sliced: + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; + break; + case SpriteDrawMode.Tiled: + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; + break; + default: + break; + } break; case SpriteModifyFlags.region: case SpriteModifyFlags.atlasRegionOffset: @@ -441,7 +475,7 @@ export class SpriteRenderer extends Renderer { } /** - * @remarks Extends `RendererUpdateFlag`. + * @remarks Extends `RendererUpdateFlags`. */ enum SpriteRendererUpdateFlags { /** UV. */ @@ -450,10 +484,11 @@ enum SpriteRendererUpdateFlags { Color = 0x4, /** Automatic Size. */ AutomaticSize = 0x8, + /** WorldVolume and UV. */ - WorldVolumeAndUV = RendererUpdateFlags.WorldVolume | SpriteRendererUpdateFlags.UV, - /** Vertex data.*/ - VertexData = SpriteRendererUpdateFlags.WorldVolumeAndUV | SpriteRendererUpdateFlags.Color, + WorldVolumeAndUV = 0x3, + /** WorldVolume, UV and Color. */ + WorldVolumeUVAndColor = 0x7, /** All. */ All = 0xf } diff --git a/packages/core/src/2d/sprite/index.ts b/packages/core/src/2d/sprite/index.ts index 7dd3e15d52..162d016472 100644 --- a/packages/core/src/2d/sprite/index.ts +++ b/packages/core/src/2d/sprite/index.ts @@ -1,3 +1,3 @@ export { Sprite } from "./Sprite"; -export { SpriteRenderer } from "./SpriteRenderer"; export { SpriteMask } from "./SpriteMask"; +export { SpriteRenderer } from "./SpriteRenderer"; diff --git a/packages/core/src/2d/text/ITextRenderer.ts b/packages/core/src/2d/text/ITextRenderer.ts new file mode 100644 index 0000000000..a7c885448a --- /dev/null +++ b/packages/core/src/2d/text/ITextRenderer.ts @@ -0,0 +1,9 @@ +import { OverflowMode } from "../enums/TextOverflow"; +import { SubFont } from "./SubFont"; + +export interface ITextRenderer { + text: string; + overflowMode: OverflowMode; + lineSpacing: number; + _getSubFont(): SubFont; +} diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index eaa5f82ca4..4596bf8ac4 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -18,29 +18,28 @@ import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAli import { OverflowMode } from "../enums/TextOverflow"; import { CharRenderInfo } from "./CharRenderInfo"; import { Font } from "./Font"; +import { ITextRenderer } from "./ITextRenderer"; import { SubFont } from "./SubFont"; import { TextUtils } from "./TextUtils"; /** * Renders a text for 2D graphics. */ -export class TextRenderer extends Renderer { +export class TextRenderer extends Renderer implements ITextRenderer { private static _textureProperty = ShaderProperty.getByName("renderElement_TextTexture"); private static _tempVec30 = new Vector3(); private static _tempVec31 = new Vector3(); private static _worldPositions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; private static _charRenderInfos: CharRenderInfo[] = []; - /** @internal */ @ignoreClone - _textChunks = Array(); + private _textChunks = Array(); /** @internal */ @assignmentClone _subFont: SubFont = null; /** @internal */ @ignoreClone _dirtyFlag: number = DirtyFlag.Font; - @deepClone private _color: Color = new Color(1, 1, 1, 1); @assignmentClone @@ -471,13 +470,10 @@ export class TextRenderer extends Renderer { // Right offset Vector3.scale(right, localPositions.z - topLeftX, worldPosition1); - // Top-Right Vector3.add(worldPosition0, worldPosition1, worldPosition1); - // Up offset Vector3.scale(up, localPositions.w - topLeftY, worldPosition2); - // Bottom-Left Vector3.add(worldPosition0, worldPosition2, worldPosition3); // Bottom-Right @@ -509,19 +505,24 @@ export class TextRenderer extends Renderer { } private _updateLocalData(): void { + const { _pixelsPerUnit } = Engine; const { min, max } = this._localBounds; const charRenderInfos = TextRenderer._charRenderInfos; - const charFont = this._subFont; + const charFont = this._getSubFont(); const textMetrics = this.enableWrapping - ? TextUtils.measureTextWithWrap(this) - : TextUtils.measureTextWithoutWrap(this); + ? TextUtils.measureTextWithWrap( + this, + this.width * _pixelsPerUnit, + this.height * _pixelsPerUnit, + this._lineSpacing * _pixelsPerUnit + ) + : TextUtils.measureTextWithoutWrap(this, this.height * _pixelsPerUnit, this._lineSpacing * _pixelsPerUnit); const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; const charRenderInfoPool = this.engine._charRenderInfoPool; const linesLen = lines.length; let renderElementCount = 0; if (linesLen > 0) { - const { _pixelsPerUnit } = Engine; const { horizontalAlignment } = this; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; const rendererWidth = this.width * _pixelsPerUnit; @@ -649,9 +650,7 @@ export class TextRenderer extends Renderer { charRenderInfos.length = 0; } - /** - * @internal - */ + @ignoreClone protected override _onTransformChanged(bit: TransformModifyFlags): void { super._onTransformChanged(bit); this._setDirtyFlagTrue(DirtyFlag.WorldPosition | DirtyFlag.WorldBounds); diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index ff9a0f2a89..a1a4079d7b 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -1,10 +1,9 @@ import { Vector2 } from "@galacean/engine-math"; -import { Engine } from "../../Engine"; import { FontStyle } from "../enums/FontStyle"; import { OverflowMode } from "../enums/TextOverflow"; import { CharInfo } from "./CharInfo"; +import { ITextRenderer } from "./ITextRenderer"; import { SubFont } from "./SubFont"; -import { TextRenderer } from "./TextRenderer"; /** * @internal @@ -96,7 +95,12 @@ export class TextUtils { return TextUtils._measureFontOrChar(fontString, char, true); } - static measureTextWithWrap(renderer: TextRenderer): TextMetrics { + static measureTextWithWrap( + renderer: ITextRenderer, + rendererWidth: number, + rendererHeight: number, + lineSpacing: number + ): TextMetrics { const subFont = renderer._getSubFont(); const fontString = subFont.nativeFontString; const fontSizeInfo = TextUtils.measureFont(fontString); @@ -106,9 +110,7 @@ export class TextUtils { const lineWidths = new Array(); const lineMaxSizes = new Array(); - const pixelsPerUnit = Engine._pixelsPerUnit; - const lineHeight = fontSizeInfo.size + renderer.lineSpacing * pixelsPerUnit; - const wrapWidth = renderer.width * pixelsPerUnit; + const lineHeight = fontSizeInfo.size + lineSpacing; let textWidth = 0; subFont.nativeFontString = fontString; @@ -152,7 +154,7 @@ export class TextUtils { if (unableFromWord) { // If it is a word before, need to handle the previous word and line if (word.length > 0) { - if (lineWidth + wordWidth > wrapWidth) { + if (lineWidth + wordWidth > rendererWidth) { // Push if before line is not empty if (lineWidth > 0) { this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); @@ -177,7 +179,7 @@ export class TextUtils { // Handle char // At least one char in a line - if (lineWidth + w > wrapWidth && lineWidth > 0) { + if (lineWidth + w > rendererWidth && lineWidth > 0) { this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); textWidth = Math.max(textWidth, lineWidth); notFirstLine = true; @@ -197,7 +199,7 @@ export class TextUtils { lineMaxDescent = Math.max(lineMaxDescent, descent); } } else { - if (wordWidth + charInfo.w > wrapWidth) { + if (wordWidth + charInfo.w > rendererWidth) { if (lineWidth > 0) { this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); textWidth = Math.max(textWidth, lineWidth); @@ -227,7 +229,7 @@ export class TextUtils { if (wordWidth > 0) { // If the total width from line and word exceed wrap width - if (lineWidth + wordWidth > wrapWidth) { + if (lineWidth + wordWidth > rendererWidth) { // Push chars to a single line if (lineWidth > 0) { this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); @@ -255,7 +257,7 @@ export class TextUtils { } } - let height = renderer.height * pixelsPerUnit; + let height = rendererHeight; if (renderer.overflowMode === OverflowMode.Overflow) { height = lineHeight * lines.length; } @@ -270,7 +272,7 @@ export class TextUtils { }; } - static measureTextWithoutWrap(renderer: TextRenderer): TextMetrics { + static measureTextWithoutWrap(renderer: ITextRenderer, rendererHeight: number, lineSpacing: number): TextMetrics { const subFont = renderer._getSubFont(); const fontString = subFont.nativeFontString; const fontSizeInfo = TextUtils.measureFont(fontString); @@ -279,8 +281,7 @@ export class TextUtils { const lines = new Array(); const lineWidths = new Array(); const lineMaxSizes = new Array(); - const { _pixelsPerUnit } = Engine; - const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; + const lineHeight = fontSizeInfo.size + lineSpacing; let width = 0; subFont.nativeFontString = fontString; @@ -307,7 +308,7 @@ export class TextUtils { } } - let height = renderer.height * _pixelsPerUnit; + let height = rendererHeight; if (renderer.overflowMode === OverflowMode.Overflow) { height = lineHeight * lines.length; } diff --git a/packages/core/src/2d/text/index.ts b/packages/core/src/2d/text/index.ts index 03cdda093b..91a084757d 100644 --- a/packages/core/src/2d/text/index.ts +++ b/packages/core/src/2d/text/index.ts @@ -1,4 +1,6 @@ export { Font } from "./Font"; export { TextRenderer } from "./TextRenderer"; // For set TextUtils._extendHeight used to extend the height of canvas, because in miniprogram performance is different from h5 +export { CharRenderInfo } from "./CharRenderInfo"; +export { SubFont } from "./SubFont"; export { TextUtils } from "./TextUtils"; diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 6069f6c44e..a03638d27d 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -7,10 +7,12 @@ 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 { GLCapabilityType, Logger } from "./base"; import { deepClone, ignoreClone } from "./clone/CloneManager"; import { CameraClearFlags } from "./enums/CameraClearFlags"; +import { CameraModifyFlags } from "./enums/CameraModifyFlags"; import { CameraType } from "./enums/CameraType"; import { DepthTextureMode } from "./enums/DepthTextureMode"; import { Downsampling } from "./enums/Downsampling"; @@ -130,6 +132,8 @@ export class Camera extends Component { private _enableHDR = false; private _enablePostProcess = false; + @ignoreClone + private _updateFlagManager: UpdateFlagManager; @ignoreClone private _frustumChangeFlag: BoolUpdateFlag; @ignoreClone @@ -219,8 +223,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._dispatchModify(CameraModifyFlags.FieldOfView); + } } /** @@ -235,6 +242,7 @@ export class Camera extends Component { set aspectRatio(value: number) { this._customAspectRatio = value; this._projectionMatrixChange(); + this._dispatchModify(CameraModifyFlags.AspectRatio); } /** @@ -284,13 +292,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._dispatchModify(CameraModifyFlags.ProjectionType); } } @@ -302,8 +313,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._dispatchModify(CameraModifyFlags.OrthographicSize); + } } /** @@ -461,6 +475,7 @@ export class Camera extends Component { resetAspectRatio(): void { this._customAspectRatio = undefined; this._projectionMatrixChange(); + this._dispatchModify(CameraModifyFlags.AspectRatio); } /** @@ -697,6 +712,7 @@ export class Camera extends Component { */ override _onEnableInScene(): void { this.scene._componentsManager.addCamera(this); + this._dispatchModify(CameraModifyFlags.EnableInScene); } /** @@ -704,6 +720,7 @@ export class Camera extends Component { */ override _onDisableInScene(): void { this.scene._componentsManager.removeCamera(this); + this._dispatchModify(CameraModifyFlags.DisableInScene); } /** @@ -717,6 +734,20 @@ export class Camera extends Component { : TextureFormat.R8G8B8A8; } + /** + * @internal + */ + _registerModifyListener(onChange: (flag: CameraModifyFlags) => void): void { + (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange); + } + + /** + * @internal + */ + _unRegisterModifyListener(onChange: (flag: CameraModifyFlags) => void): void { + this._updateFlagManager?.removeListener(onChange); + } + /** * @internal * @inheritdoc @@ -731,6 +762,7 @@ export class Camera extends Component { //@ts-ignore this._viewport._onValueChanged = null; this.engine.canvas._sizeUpdateFlagManager.removeListener(this._onPixelViewportChanged); + this._updateFlagManager = null; this._entity = null; this._globalShaderMacro = null; @@ -761,6 +793,7 @@ export class Camera extends Component { const viewport = this._viewport; this._pixelViewport.set(viewport.x * width, viewport.y * height, viewport.z * width, viewport.w * height); + !this._customAspectRatio && this._dispatchModify(CameraModifyFlags.AspectRatio); } private _viewMatrixChange(): void { @@ -840,4 +873,8 @@ export class Camera extends Component { ); } } + + private _dispatchModify(flag: CameraModifyFlags): void { + this._updateFlagManager?.dispatch(flag); + } } diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts index cd3d39809c..1921cc1f0e 100644 --- a/packages/core/src/ComponentsManager.ts +++ b/packages/core/src/ComponentsManager.ts @@ -3,6 +3,7 @@ import { Component } from "./Component"; import { Renderer } from "./Renderer"; import { Script } from "./Script"; import { Animator } from "./animation"; +import { IUICanvas } from "./ui/IUICanvas"; import { DisorderedArray } from "./utils/DisorderedArray"; /** @@ -16,6 +17,13 @@ export class ComponentsManager { /** @internal */ _renderers: DisorderedArray = new DisorderedArray(); + /** @internal */ + _overlayCanvases: DisorderedArray = new DisorderedArray(); + /* @internal */ + _overlayCanvasesSortingDirty: boolean = false; + /** @internal */ + _canvases: DisorderedArray = new DisorderedArray(); + // Script private _onStartScripts: DisorderedArray