diff --git a/samples/gui/Sample_TextBarrage.ts b/samples/gui/Sample_TextBarrage.ts new file mode 100644 index 00000000..2dec8dcd --- /dev/null +++ b/samples/gui/Sample_TextBarrage.ts @@ -0,0 +1,142 @@ +import { Engine3D, Scene3D, CameraUtil, HoverCameraController, Object3D, View3D, Color, Camera3D, ViewPanel, UITextField, TextAnchor, ComponentBase, AtmosphericComponent, webGPUContext, CResizeEvent } from "@orillusion/core"; + +class Sample_TextBarrage { + private scene: Scene3D; + private camera: Camera3D; + + async run() { + // init engine + await Engine3D.init(); + // create new Scene + let scene = new Scene3D(); + this.scene = scene; + + // load base font + await Engine3D.res.loadFont("https://cdn.orillusion.com/fnt/0.fnt"); + + // add an Atmospheric sky enviroment + let sky = scene.addComponent(AtmosphericComponent); + sky.sunY = 0.6; + + // init camera3D + let mainCamera = CameraUtil.createCamera3D(null, scene); + mainCamera.perspective(60, Engine3D.aspect, 1, 2000.0); + this.camera = mainCamera; + + // add a basic camera controller + const hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController); + hoverCameraController.setCamera(0, 0, 20); + + // create a view with target scene and camera + let view = new View3D(); + view.scene = scene; + view.camera = mainCamera; + + // create UIpanel root + let panelRoot: Object3D = new Object3D(); + const panel = panelRoot.addComponent(ViewPanel); + // resize panel radio + webGPUContext.addEventListener(CResizeEvent.RESIZE, () => panel.uiTransform.resize(Engine3D.width, Engine3D.height), this); + + // add to UIcanvas + let canvas = view.enableUICanvas(); + canvas.addChild(panel.object3D); + + // Create text barrage + const textCount = 100; + for (let i = 0; i < textCount; ++i) { + const textQuad = new Object3D(); + panelRoot.addChild(textQuad); + const text = textQuad.addComponent(UITextField); + text.color = new Color(1, 1, 1); + text.fontSize = 32; + text.alignment = TextAnchor.MiddleCenter; + text.uiTransform.resize(300, 12); + + // Init and reset text barrage animation + const barrage = textQuad.addComponent(TextBarrageAnimation); + barrage.camera = this.camera; + barrage.priorityOffset = textCount; + barrage.play(); + } + + // start render + Engine3D.startRenderView(view); + } +} + +class TextBarrageAnimation extends ComponentBase { + // prettier-ignore + static words = [ "GALACEAN", "galacean", "HELLO", "hello", "WORLD", "world", "TEXT", "text", "PEACE", "peace", "LOVE", "love", "abcdefg", "hijklmn", "opqrst", "uvwxyz", "ABCDEFG", "HIJKLMN", "OPQRST", "UVWXYZ", "~!@#$", "%^&*", "()_+" ]; + static colors = new Array(10).fill(1).map(() => Color.random()); + + public camera: Camera3D; + public priorityOffset: number = 0; + + private _speed: number = 0; + private _isPlaying: boolean = false; + private _text: UITextField; + private lastTime: number = 0; + + play() { + this._isPlaying = true; + } + + start(): void { + this._text = this.object3D.getComponent(UITextField); + this.lastTime = Date.now(); + this._reset(true); + } + + onUpdate(): void { + if (this._isPlaying) { + const now = Date.now(); + const dt = now - this.lastTime; + this.lastTime = now; + let halfWidth = Engine3D.width * 0.5, + halfHeight = Engine3D.height * 0.5; + let { x, y, width, height } = this._text.uiTransform; + + // move text to left + this._text.uiTransform.x += this._speed * dt; + // reset text position after pass the left side of the screen + if (x < -halfWidth - width) { + this._reset(false); + } + // limit y position in view port + if (y < -halfHeight + height) this._text.uiTransform.y = -halfHeight + height; + else if (y > halfHeight - height) this._text.uiTransform.y = halfHeight - height; + } + } + + private _reset(isFirst: boolean) { + const text = this._text; + const { words, colors } = TextBarrageAnimation; + + // Reset the text to render + const wordLastIndex = words.length - 1; + text.text = `${words[getRandomNum(0, wordLastIndex)]} ${words[getRandomNum(0, wordLastIndex)]}`; + // Reset color + text.color = colors[getRandomNum(0, colors.length - 1)]; + const halfWidth = Engine3D.width * 0.5; + const halfHeight = Engine3D.height * 0.5; + // Reset position + this._text.uiTransform.x = isFirst ? getRandomNum(halfWidth, halfWidth * 3) : halfWidth + this._text.uiTransform.width; + this._text.uiTransform.y = getRandomNum(-halfHeight + this._text.uiTransform.height, halfHeight - this._text.uiTransform.height); + // Reset speed + this._speed = getRandomNum(-500, -200) * 0.0003; + } +} + +/** + * Get a random number between min and max + * @param min + * @param max + */ +function getRandomNum(min: number, max: number): number { + const range = max - min; + const rand = Math.random(); + return min + Math.round(rand * range); +} + +new Sample_TextBarrage().run(); diff --git a/samples/gui/Sample_UIPerformance2.ts b/samples/gui/Sample_UIPerformance2.ts index 95315eed..424419e2 100644 --- a/samples/gui/Sample_UIPerformance2.ts +++ b/samples/gui/Sample_UIPerformance2.ts @@ -1,4 +1,4 @@ -import { BoundingBox, Color, Engine3D, GUIConfig, GUIQuad, Object3D, Scene3D, TextAnchor, UIImageGroup, UITextField, Vector2, Vector3, ViewPanel, clamp } from "@orillusion/core"; +import { BoundingBox, Color, Engine3D, GUIConfig, GUIQuad, Object3D, Scene3D, TextAnchor, UIImageGroup, UITextField, Vector2, Vector3, ViewPanel, clamp, webGPUContext } from "@orillusion/core"; import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { createExampleScene } from "@samples/utils/ExampleScene"; import { Stats } from "@orillusion/stats"; @@ -105,7 +105,7 @@ export class Sample_UIPerformance2 { //create UI root let panelRoot: Object3D = new Object3D(); //create panel - let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + let panel = panelRoot.addComponent(ViewPanel); canvas.addChild(panel.object3D); //create sprite sheet list this.createSpriteSheets(panelRoot); @@ -116,7 +116,7 @@ export class Sample_UIPerformance2 { //create UI root let panelRoot: Object3D = new Object3D(); //create panel - let panel = panelRoot.addComponent(ViewPanel, { billboard: true }); + let panel = panelRoot.addComponent(ViewPanel); panel.panelOrder = 10000; canvas.addChild(panel.object3D); let textQuad = new Object3D(); @@ -128,7 +128,6 @@ export class Sample_UIPerformance2 { text.alignment = TextAnchor.MiddleCenter; return text; - } spriteSheets: SpriteSheet[]; @@ -149,14 +148,16 @@ export class Sample_UIPerformance2 { let size = 64; let halfSize = size * 0.5; - let imgGroup = root.addComponent(UIImageGroup, { count: 5000 }); + let groupNode = new Object3D(); + root.addChild(groupNode); + let imgGroup = groupNode.addComponent(UIImageGroup, { count: 5000 }); for (let i = 0; i < 5000; i++) { imgGroup.setColor(i, color); imgGroup.setSprite(i, sprite); imgGroup.setSize(i, size, size); imgGroup.setXY(i, - (Math.random() - 0.5) * width * 0.7 - halfSize, - (Math.random() - 0.5) * height * 0.7 - halfSize); + (Math.random() - 0.5) * (width - size * 0.5) - halfSize, + (Math.random() - 0.5) * (height - size * 0.5) - halfSize); let sheet: SpriteSheet = new SpriteSheet(imgGroup, i, this.keyFrames, bound); this.spriteSheets.push(sheet); } diff --git a/src/Engine3D.ts b/src/Engine3D.ts index d04f92e6..5778ec85 100644 --- a/src/Engine3D.ts +++ b/src/Engine3D.ts @@ -454,16 +454,6 @@ export class Engine3D { this.resume(); } - private static updateGUIPixelRatio(screenWidth: number, screenHeight: number) { - let xyRatioSolution = GUIConfig.solution.x / GUIConfig.solution.y; - let xyRatioCurrent = screenWidth / screenHeight; - if (xyRatioSolution < xyRatioCurrent) { - GUIConfig.pixelRatio = screenHeight / GUIConfig.solution.y; - } else { - GUIConfig.pixelRatio = screenWidth / GUIConfig.solution.x; - } - } - private static updateFrame(time: number) { Time.delta = time - Time.time; Time.time = time; @@ -479,8 +469,6 @@ export class Engine3D { view.camera.resetPerspective(webGPUContext.aspect); } - this.updateGUIPixelRatio(webGPUContext.canvas.clientWidth, webGPUContext.canvas.clientHeight); - if (this._beforeRender) this._beforeRender(); /****** auto start with component list *****/ diff --git a/src/components/gui/GUIConfig.ts b/src/components/gui/GUIConfig.ts index a417226f..f1e47e44 100644 --- a/src/components/gui/GUIConfig.ts +++ b/src/components/gui/GUIConfig.ts @@ -1,8 +1,5 @@ -import { Vector2 } from "@orillusion/core"; - export class GUIConfig { - public static pixelRatio: number = 1.0; - public static readonly solution: Vector2 = new Vector2(1600, 1280); + public static panelRatio: number = 1.0; public static quadMaxCountForWorld: number = 256; public static quadMaxCountForView: number = 2048; public static readonly SortOrderStartWorld: number = 7000; diff --git a/src/components/gui/GUIPickHelper.ts b/src/components/gui/GUIPickHelper.ts index f678e84e..c51ca3b5 100644 --- a/src/components/gui/GUIPickHelper.ts +++ b/src/components/gui/GUIPickHelper.ts @@ -34,7 +34,7 @@ export class GUIPickHelper { this._worldMatrix = new Matrix4(); } - public static rayPick(ray: Ray, screenPos: Vector2, screenSize: Vector2, space: GUISpace, uiTransform: UITransform, worldMatrix: Matrix4): HitInfo { + public static rayPick(ray: Ray, screenPos: Vector2, screenSize: Vector2, space: GUISpace, panelRatio: number, uiTransform: UITransform, worldMatrix: Matrix4): HitInfo { if (!this._isInit) { this.init(); this._isInit = true; @@ -64,7 +64,7 @@ export class GUIPickHelper { }; } } else { - this.calculateHotArea_View(uiTransform, this._pt0, this._pt1, this._pt2, this._pt3); + this.calculateHotArea_View(uiTransform, panelRatio, this._pt0, this._pt1, this._pt2, this._pt3); // let screenSizeX: number = screenSize.x; @@ -87,7 +87,7 @@ export class GUIPickHelper { return null; } - private static calculateHotArea_View(transform: UITransform, pt0: Vector3, pt1: Vector3, pt2: Vector3, pt3: Vector3) { + private static calculateHotArea_View(transform: UITransform, panelRatio: number, pt0: Vector3, pt1: Vector3, pt2: Vector3, pt3: Vector3) { let uiMtx = transform.getWorldMatrix(); //2 3 //0 1 @@ -113,10 +113,10 @@ export class GUIPickHelper { pt2.y -= offset; pt3.y -= offset; - pt0.multiplyScalar(GUIConfig.pixelRatio); - pt1.multiplyScalar(GUIConfig.pixelRatio); - pt2.multiplyScalar(GUIConfig.pixelRatio); - pt3.multiplyScalar(GUIConfig.pixelRatio); + pt0.multiplyScalar(panelRatio); + pt1.multiplyScalar(panelRatio); + pt2.multiplyScalar(panelRatio); + pt3.multiplyScalar(panelRatio); } private static calculateHotArea_World(transform: UITransform, pt0: Vector3, pt1: Vector3, pt2: Vector3, pt3: Vector3) { diff --git a/src/components/gui/core/GUIMaterial.ts b/src/components/gui/core/GUIMaterial.ts index 24870765..a5bc8e78 100644 --- a/src/components/gui/core/GUIMaterial.ts +++ b/src/components/gui/core/GUIMaterial.ts @@ -1,12 +1,14 @@ -import { PassType, Shader } from "../../.."; import { Engine3D } from "../../../Engine3D"; import { ShaderLib } from "../../../assets/shader/ShaderLib"; import { GPUCompareFunction, GPUCullMode } from "../../../gfx/graphics/webGpu/WebGPUConst"; import { Texture } from "../../../gfx/graphics/webGpu/core/texture/Texture"; import { RenderShaderPass } from "../../../gfx/graphics/webGpu/shader/RenderShaderPass"; +import { Shader } from "../../../gfx/graphics/webGpu/shader/Shader"; +import { PassType } from "../../../gfx/renderJob/passRenderer/state/RendererType"; import { BlendMode } from "../../../materials/BlendMode"; import { Material } from "../../../materials/Material"; import { Vector2 } from "../../../math/Vector2"; +import { Vector3 } from "../../../math/Vector3"; import { Vector4 } from "../../../math/Vector4"; import { GUISpace } from "../GUIConfig"; import { GUIShader } from "./GUIShader"; @@ -32,13 +34,13 @@ export class GUIMaterial extends Material { colorPass.passType = PassType.COLOR; colorPass.setShaderEntry(`VertMain`, `FragMain`); - colorPass.setUniformVector2('screenSize', this._screenSize); - colorPass.setUniformVector2('guiSolution', this._screenSize); colorPass.setUniformVector4('scissorRect', new Vector4()); + colorPass.setUniformVector2('screenSize', this._screenSize); colorPass.setUniformFloat('scissorCornerRadius', 0.0); colorPass.setUniformFloat('scissorFadeOutSize', 0.0); + colorPass.setUniformFloat('pixelRatio', 1); - colorPass.setUniformFloat('empty', 0); + colorPass.setUniformVector3('v3', Vector3.ZERO); let shaderState = colorPass.shaderState; // shaderState.useZ = false; @@ -53,8 +55,7 @@ export class GUIMaterial extends Material { this.shader = newShader; } - public setGUISolution(value: Vector2, pixelRatio: number) { - this.shader.setUniformVector2('guiSolution', value); + public setPanelRatio(pixelRatio: number) { this.shader.setUniformFloat('pixelRatio', pixelRatio); } diff --git a/src/components/gui/core/GUIShader.ts b/src/components/gui/core/GUIShader.ts index 3d80d3d4..74567e13 100644 --- a/src/components/gui/core/GUIShader.ts +++ b/src/components/gui/core/GUIShader.ts @@ -176,13 +176,11 @@ export class GUIShader { scissorRect:vec4, screenSize:vec2, - guiSolution:vec2, - scissorCornerRadius:f32, scissorFadeOutSize:f32, pixelRatio:f32, - empty:f32, + v3:vec3 } struct VertexOutput { diff --git a/src/components/gui/uiComponents/UIInteractive.ts b/src/components/gui/uiComponents/UIInteractive.ts index d2fc9ae3..5fc4d58f 100644 --- a/src/components/gui/uiComponents/UIInteractive.ts +++ b/src/components/gui/uiComponents/UIInteractive.ts @@ -43,7 +43,7 @@ export class UIInteractive extends UIComponentBase implements IUIInteractive { } public rayPick(ray: Ray, panel: UIPanel, screenPos: Vector2, screenSize: Vector2): HitInfo { - return GUIPickHelper.rayPick(ray, screenPos, screenSize, panel.space, this._uiTransform, panel.transform.worldMatrix); + return GUIPickHelper.rayPick(ray, screenPos, screenSize, panel.space, panel.panelRatio, this._uiTransform, panel.transform.worldMatrix); } public cloneTo(obj: Object3D) { diff --git a/src/components/gui/uiComponents/UIPanel.ts b/src/components/gui/uiComponents/UIPanel.ts index 8f8f78c1..b3b4eda2 100644 --- a/src/components/gui/uiComponents/UIPanel.ts +++ b/src/components/gui/uiComponents/UIPanel.ts @@ -32,6 +32,7 @@ export class UIPanel extends UIImage { protected _geometry: GUIGeometry; protected _maxCount: number = 128; + public panelRatio: number = 1; public readonly isUIPanel = true; public cloneTo(obj: Object3D): void { @@ -55,6 +56,7 @@ export class UIPanel extends UIImage { init(param?: any) { super.init(param); + this._uiTransform.resize(webGPUContext.canvas.width, webGPUContext.canvas.height); this.create(this.space); this.visible = false; } @@ -153,9 +155,19 @@ export class UIPanel extends UIImage { panel._uiRenderer.needSortOnCameraZ = panel.needSortOnCameraZ; //update material + if (this.space == GUISpace.View) { + let sW = webGPUContext.canvas.clientWidth; + let sH = webGPUContext.canvas.clientHeight; + let pW = this._uiTransform.width; + let pH = this._uiTransform.height; + this.panelRatio = this.updateGUIPixelRatio(sW, sH, pW, pH); + } else { + this.panelRatio = 1; + } + for (let item of panel['_uiRenderer'].materials) { let material = item as GUIMaterial; - material.setGUISolution(GUIConfig.solution, GUIConfig.pixelRatio); + material.setPanelRatio(this.panelRatio); material.setScreenSize(webGPUContext.canvas.clientWidth, webGPUContext.canvas.clientHeight); material.setScissorEnable(panel.scissorEnable); if (panel.scissorEnable) { @@ -165,9 +177,21 @@ export class UIPanel extends UIImage { } } - //clear flag panel.needUpdateGeometry = false; } + private updateGUIPixelRatio(sW: number, sH: number, pW: number, pH: number) { + let xyRatioSolution = pW / pH; + let xyRatioCurrent = sW / sH; + let panelRatio: number = 1; + if (xyRatioSolution < xyRatioCurrent) { + panelRatio = sH / pH; + } else { + panelRatio = sW / pW; + } + + return panelRatio; + } + }