diff --git a/examples/tests/viewport-events.ts b/examples/tests/viewport-events.ts index 19da04a7..deb14550 100644 --- a/examples/tests/viewport-events.ts +++ b/examples/tests/viewport-events.ts @@ -29,6 +29,23 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: testRoot, }); + const yellowStatus = renderer.createTextNode({ + text: 'Yellow Status: ', + fontSize: 30, + x: 800, + y: 10, + parent: testRoot, + }); + + const clippingStatus = renderer.createTextNode({ + text: 'Clipping: ON', + fontSize: 30, + x: 800, + y: 50, + parent: testRoot, + color: 0x00ff00ff, + }); + const boundaryRect = renderer.createNode({ x: 1920 / 2 - (1920 * 0.75) / 2, y: 1080 / 2 - (1080 * 0.75) / 2, @@ -51,6 +68,50 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: boundaryRect, }); + const yellow1Rect = renderer.createNode({ + x: 20, + y: 20, + alpha: 1, + width: 20, + height: 20, + color: 0xffff00ff, + pivot: 0, + parent: redRect, + }); + + const yellow2Rect = renderer.createNode({ + x: 50, + y: 50, + alpha: 1, + width: 20, + height: 20, + color: 0xffff00ff, + pivot: 0, + parent: redRect, + }); + + const yellow3Rect = renderer.createNode({ + x: 80, + y: 80, + alpha: 1, + width: 20, + height: 20, + color: 0xffff00ff, + pivot: 0, + parent: redRect, + }); + + const yellow4Rect = renderer.createNode({ + x: 110, + y: 110, + alpha: 1, + width: 20, + height: 20, + color: 0xffff00ff, + pivot: 0, + parent: redRect, + }); + redRect.on('outOfBounds', () => { console.log('red rect out of bounds'); redStatus.text = 'Red Status: rect out of bounds'; @@ -69,6 +130,84 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { redStatus.color = 0xffff00ff; }); + // yellowstate + // 0 : out of bounds + // 1 : in bounds + // 2 : in viewport + const yellowRectState = [0, 0, 0, 0]; + const updateYellowState = (state: number, yellowIdx: number) => { + let stateString = ''; + yellowRectState[yellowIdx] = state; + + Array(4) + .fill(0) + .forEach((_, i) => { + stateString += `${yellowRectState[i]} `; + }); + + yellowStatus.text = `Yellow Status: ${stateString}`; + }; + + yellow1Rect.on('inBounds', () => { + console.log('yellow 1 rect inside render bounds'); + updateYellowState(1, 0); + }); + + yellow1Rect.on('inViewport', () => { + console.log('yellow 1 rect in view port'); + updateYellowState(2, 0); + }); + + yellow1Rect.on('outOfBounds', () => { + console.log('yellow 1 rect out of bounds'); + updateYellowState(0, 0); + }); + + yellow2Rect.on('inBounds', () => { + console.log('yellow 2 rect inside render bounds'); + updateYellowState(1, 1); + }); + + yellow2Rect.on('inViewport', () => { + console.log('yellow 2 rect in view port'); + updateYellowState(2, 1); + }); + + yellow2Rect.on('outOfBounds', () => { + console.log('yellow 2 rect out of bounds'); + updateYellowState(0, 1); + }); + + yellow3Rect.on('inBounds', () => { + console.log('yellow 3 rect inside render bounds'); + updateYellowState(1, 2); + }); + + yellow3Rect.on('inViewport', () => { + console.log('yellow 3 rect in view port'); + updateYellowState(2, 2); + }); + + yellow3Rect.on('outOfBounds', () => { + console.log('yellow 3 rect out of bounds'); + updateYellowState(0, 2); + }); + + yellow4Rect.on('inBounds', () => { + console.log('yellow 4 rect inside render bounds'); + updateYellowState(1, 3); + }); + + yellow4Rect.on('inViewport', () => { + console.log('yellow 4 rect in view port'); + updateYellowState(2, 3); + }); + + yellow4Rect.on('outOfBounds', () => { + console.log('yellow 4 rect out of bounds'); + updateYellowState(0, 3); + }); + const blueRect = renderer.createNode({ x: 1920 / 2 - 200, y: 100, @@ -181,5 +320,14 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { redRect.x = 520; blueRect.x = 1920 / 2 - 200; } + + if (e.key === 't') { + boundaryRect.clipping = !boundaryRect.clipping; + + clippingStatus.text = boundaryRect.clipping + ? 'Clipping: ON' + : 'Clipping: OFF'; + clippingStatus.color = boundaryRect.clipping ? 0x00ff00ff : 0xff0000ff; + } }; } diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index 3b115bc5..cc1de820 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -178,6 +178,11 @@ export enum UpdateType { */ ParentRenderTexture = 4096, + /** + * Render Bounds update + */ + RenderBounds = 8192, + /** * None */ @@ -738,6 +743,10 @@ export class CoreNode extends EventEmitter { this.rtt = props.rtt; this.updateScaleRotateTransform(); + + this.setUpdateType( + UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState, + ); } //#region Textures @@ -988,16 +997,42 @@ export class CoreNode extends EventEmitter { this.calculateRenderCoords(); this.updateBoundingRect(); - this.setUpdateType( - UpdateType.Clipping | UpdateType.RenderState | UpdateType.Children, - ); + + this.setUpdateType(UpdateType.RenderState | UpdateType.Children); + + if (this.clipping === true) { + this.setUpdateType(UpdateType.Clipping); + } + childUpdateType |= UpdateType.Global; } + if (this.updateType & UpdateType.RenderBounds) { + this.createRenderBounds(); + this.setUpdateType(UpdateType.RenderState); + this.setUpdateType(UpdateType.Children); + } + + if (this.updateType & UpdateType.RenderState) { + this.updateRenderState(); + this.setUpdateType(UpdateType.IsRenderable); + } + + if (this.updateType & UpdateType.IsRenderable) { + this.updateIsRenderable(); + } + + if (this.renderState === CoreNodeRenderState.OutOfBounds) { + this.updateType = 0; + return; + } + if (this.updateType & UpdateType.Clipping) { this.calculateClippingRect(parentClippingRect); this.setUpdateType(UpdateType.Children); + childUpdateType |= UpdateType.Clipping; + childUpdateType |= UpdateType.RenderBounds; } if (this.updateType & UpdateType.WorldAlpha) { @@ -1050,17 +1085,8 @@ export class CoreNode extends EventEmitter { } } - if (this.updateType & UpdateType.RenderState) { - this.updateRenderState(parentClippingRect); - this.setUpdateType(UpdateType.IsRenderable); - } - - if (this.updateType & UpdateType.IsRenderable) { - this.updateIsRenderable(); - } - // No need to update zIndex if there is no parent - if (parent && this.updateType & UpdateType.CalculatedZIndex) { + if (parent !== null && this.updateType & UpdateType.CalculatedZIndex) { this.calculateZIndex(); // Tell parent to re-sort children parent.setUpdateType(UpdateType.ZIndexSortedChildren); @@ -1068,8 +1094,8 @@ export class CoreNode extends EventEmitter { if ( this.updateType & UpdateType.Children && - this.children.length && - !this.rtt + this.children.length > 0 && + this.rtt === false ) { this.children.forEach((child) => { // Trigger the depenedent update types on the child @@ -1151,39 +1177,78 @@ export class CoreNode extends EventEmitter { return false; } - checkRenderBounds(parentClippingRect: RectWithValid): CoreNodeRenderState { + checkRenderBounds(): CoreNodeRenderState { assertTruthy(this.renderBound); - const rectW = parentClippingRect.width || this.stage.root.width; - const rectH = parentClippingRect.height || this.stage.root.height; - this.strictBound = createBound( - parentClippingRect.x, - parentClippingRect.y, - parentClippingRect.x + rectW, - parentClippingRect.y + rectH, - this.strictBound, - ); + assertTruthy(this.strictBound); + assertTruthy(this.preloadBound); if (boundInsideBound(this.renderBound, this.strictBound)) { return CoreNodeRenderState.InViewport; } + if (boundInsideBound(this.renderBound, this.preloadBound)) { + return CoreNodeRenderState.InBounds; + } + + return CoreNodeRenderState.OutOfBounds; + } + + createPreloadBounds(strictBound: Bound): Bound { const renderM = this.stage.boundsMargin; - this.preloadBound = createBound( - this.strictBound.x1 - renderM[3], - this.strictBound.y1 - renderM[0], - this.strictBound.x2 + renderM[1], - this.strictBound.y2 + renderM[2], + return createBound( + strictBound.x1 - renderM[3], + strictBound.y1 - renderM[0], + strictBound.x2 + renderM[1], + strictBound.y2 + renderM[2], this.preloadBound, ); + } - if (boundInsideBound(this.renderBound, this.preloadBound)) { - return CoreNodeRenderState.InBounds; + createRenderBounds(): void { + assertTruthy(this.stage); + + // no clipping, use parent's bounds + if (this.clipping === false) { + if (this.parent !== null) { + this.strictBound = + this.parent.strictBound ?? + createBound(0, 0, this.stage.root.width, this.stage.root.height); + + this.preloadBound = + this.parent.preloadBound ?? + this.createPreloadBounds(this.strictBound); + return; + } else { + this.strictBound = createBound( + 0, + 0, + this.stage.root.width, + this.stage.root.height, + ); + + this.preloadBound = this.createPreloadBounds(this.strictBound); + return; + } } - return CoreNodeRenderState.OutOfBounds; + + // clipping is enabled create our own bounds + const { x, y, width, height } = this.props; + const { tx, ty } = this.globalTransform || {}; + const _x = tx ?? x; + const _y = ty ?? y; + this.strictBound = createBound( + _x, + _y, + _x + width, + _y + height, + this.strictBound, + ); + + this.preloadBound = this.createPreloadBounds(this.strictBound); } - updateRenderState(parentClippingRect: RectWithValid) { - const renderState = this.checkRenderBounds(parentClippingRect); + updateRenderState() { + const renderState = this.checkRenderBounds(); if (renderState === this.renderState) { return; @@ -1300,7 +1365,7 @@ export class CoreNode extends EventEmitter { const isRotated = gt.tb !== 0 || gt.tc !== 0; - if (clipping && !isRotated) { + if (clipping === true && isRotated === false) { clippingRect.x = gt.tx; clippingRect.y = gt.ty; clippingRect.width = this.width * gt.ta; @@ -1310,10 +1375,10 @@ export class CoreNode extends EventEmitter { clippingRect.valid = false; } - if (parentClippingRect.valid && clippingRect.valid) { + if (parentClippingRect.valid === true && clippingRect.valid === true) { // Intersect parent clipping rect with node clipping rect intersectRect(parentClippingRect, clippingRect, clippingRect); - } else if (parentClippingRect.valid) { + } else if (parentClippingRect.valid === true) { // Copy parent clipping rect copyRect(parentClippingRect, clippingRect); clippingRect.valid = true; @@ -1659,7 +1724,9 @@ export class CoreNode extends EventEmitter { set clipping(value: boolean) { this.props.clipping = value; - this.setUpdateType(UpdateType.Clipping); + this.setUpdateType( + UpdateType.Clipping | UpdateType.RenderBounds | UpdateType.Children, + ); } get color(): number { @@ -1825,6 +1892,9 @@ export class CoreNode extends EventEmitter { } } this.updateScaleRotateTransform(); + + // fetch render bounds from parent + this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children); } get preventCleanup(): boolean { diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 135c5397..504406d0 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -381,20 +381,20 @@ export class Stage { } addQuads(node: CoreNode) { - assertTruthy(this.renderer && node.globalTransform); + assertTruthy(this.renderer); - if (node.isRenderable) { + if (node.isRenderable === true) { node.renderQuads(this.renderer); } for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; - if (!child) { + if (child === undefined) { continue; } - if (child?.worldAlpha === 0) { + if (child.worldAlpha === 0) { continue; }