Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bound calculation when clipping is enabled #423

Merged
merged 4 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions examples/tests/viewport-largebound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { ExampleSettings } from '../common/ExampleSettings.js';
import type { CoreTextNode } from '../../dist/src/core/CoreTextNode.js';
import type { INode } from '../../dist/exports/index.js';

export async function automation(settings: ExampleSettings) {
const page = await test(settings);
page(1);
await settings.snapshot();

page(2);
await settings.snapshot();

page(3);
await settings.snapshot();
}

export default async function test({ renderer, testRoot }: ExampleSettings) {
// Create a container node
const containerNode = renderer.createNode({
x: -100,
y: -100,
width: 2040,
height: 1280,
color: 0xff0000ff, // Red
parent: testRoot,
clipping: true,
});

const nodeStatus = renderer.createTextNode({
text: '',
fontSize: 30,
x: 10,
y: 580,
parent: testRoot,
});

const amountOfNodes = 11;
const childNodeWidth = 1700 / amountOfNodes;

// Create 11 child nodes
for (let i = 0; i < amountOfNodes; i++) {
const childNode = renderer.createNode({
x: i * childNodeWidth + i * 100,
y: 300,
width: childNodeWidth,
height: 300,
color: 0x00ff00ff, // Green
parent: containerNode,
});

const nodeTest = renderer.createTextNode({
x: 10,
y: 130,
text: `Node ${i}`,
color: 0x000000ff,
parent: childNode,
});
}

function processAllNodes(containerNode: INode) {
let status =
'Container bound: ' + JSON.stringify(containerNode.renderState) + '\n';

for (const node of containerNode.children) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
status += `${
(node.children[0] as CoreTextNode)?.text
} bound: ${JSON.stringify(node.renderState)} \n`;
}

nodeStatus.text = status;
console.log(status);
}

renderer.on('idle', () => {
processAllNodes(containerNode);
});

let curPage = 1;
window.onkeydown = (e) => {
if (e.key === 'ArrowRight') {
containerNode.x -= 100;
}

if (e.key === 'ArrowLeft') {
containerNode.x += 100;
}

if (e.key === ' ') {
containerNode.strictBounds = !containerNode.strictBounds;
}

if (e.key === 'ArrowUp') {
curPage = Math.min(3, curPage + 1);
page(curPage);
}

if (e.key === 'ArrowDown') {
curPage = Math.max(1, curPage - 1);
page(curPage);
}

processAllNodes(containerNode);
};

const page = (i = 1) => {
switch (i) {
case 1:
containerNode.x = -100;
break;

case 2:
containerNode.x = -1390;
break;

case 3:
containerNode.x = -4000;
break;
}

processAllNodes(containerNode);
};

return page;
}
122 changes: 122 additions & 0 deletions examples/tests/viewport-memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { ExampleSettings } from '../common/ExampleSettings.js';

function getRandomTitle(length = 10) {
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}

export default async function test(settings: ExampleSettings) {
const { renderer, testRoot } = settings;

const containerNode = renderer.createNode({
x: 200, // Offset from the left
y: 200,
width: 2000,
height: 2000,
color: 0x000000ff, // Black
parent: testRoot,
clipping: true,
});

const redCircle = renderer.createNode({
x: 0,
y: 0,
width: 100,
height: 100,
color: 0xff0000ff,
shader: renderer.createShader('RoundedRectangle', {
radius: 50,
}),
parent: testRoot,
});

const blueCircle = renderer.createNode({
x: 150,
y: 0,
width: 100,
height: 100,
color: 0x0000ffff,
shader: renderer.createShader('RoundedRectangle', {
radius: 50,
}),
parent: testRoot,
});

const yellowCircle = renderer.createNode({
x: 300,
y: 0,
width: 100,
height: 100,
color: 0xffff00ff,
shader: renderer.createShader('RoundedRectangle', {
radius: 50,
}),
parent: testRoot,
});

const spawnRow = function (rowIndex = 0, amount = 20) {
let totalWidth = 0; // Track the total width used in the current row
const gap = 10; // Define the gap between items

// Create the row indicator (channel number)
const channelIndicator = renderer.createTextNode({
x: -60, // Position the indicator to the left of the row
y: rowIndex * 100 + 40, // Center vertically in the row
text: `Row ${rowIndex + 1}`, // Display channel number
color: 0xffffffff, // White color for the indicator
parent: containerNode,
});

for (let i = 0; i < amount; i++) {
const randomWidth = Math.floor(Math.random() * 401) + 100; // Unique width between 100 and 500
totalWidth += randomWidth + gap; // Include gap in total width calculation

// Generate a random title with a random length between 5 and 15 characters
const title = getRandomTitle(Math.floor(Math.random() * 11) + 10); // Random length between 5 and 15

// Create a black rectangle to serve as the border
const borderNode = renderer.createNode({
x: totalWidth - randomWidth - gap, // Adjust position by subtracting the gap
y: rowIndex * 100,
width: randomWidth + gap, // Width of the black rectangle to include the gap
height: 100, // Height of the border
color: 0x000000ff, // Black
parent: containerNode,
clipping: true,
});

// Create the green node slightly smaller than the black rectangle
const childNode = renderer.createNode({
x: 5, // Offset for the green node to mimic a border
y: 5, // Offset for the green node to mimic a border
width: randomWidth, // Width of the green node
height: 90, // Slightly smaller height
color: 0x00ff00ff, // Green
parent: borderNode,
shader: renderer.createShader('RoundedRectangle', {
radius: 10,
}),
});

// Create text node inside the green node
const nodeTest = renderer.createTextNode({
x: 10,
y: 10, // Center text vertically in the green node
text: title,
color: 0x000000ff,
parent: childNode,
});
}
};

// Generate up to 200 rows
for (let rowIndex = 0; rowIndex < 200; rowIndex++) {
console.log(`Spawning row ${rowIndex + 1}`);
spawnRow(rowIndex);
}
}
1 change: 1 addition & 0 deletions exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type {
export type { WebGlCoreRenderer } from '../src/core/renderers/webgl/WebGlCoreRenderer.js';
export type { WebGlCoreCtxTexture } from '../src/core/renderers/webgl/WebGlCoreCtxTexture.js';
export type { Inspector } from '../src/main-api/Inspector.js';
export type { CoreNodeRenderState } from '../src/core/CoreNode.js';

// Shaders
export * from '../src/core/renderers/webgl/WebGlCoreShader.js';
Expand Down
78 changes: 41 additions & 37 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
createBound,
boundInsideBound,
boundLargeThanBound,
createPreloadBounds,
} from './lib/utils.js';
import { Matrix3d } from './lib/Matrix3d.js';
import { RenderCoords } from './lib/RenderCoords.js';
Expand Down Expand Up @@ -1154,6 +1155,7 @@ export class CoreNode extends EventEmitter {
// being marked as out of bounds
if (renderState === CoreNodeRenderState.OutOfBounds) {
this.updateRenderState(renderState);
this.updateIsRenderable();
}

// reset update type
Expand All @@ -1162,7 +1164,7 @@ export class CoreNode extends EventEmitter {
}

//check if CoreNode is renderable based on props
checkRenderProps(): boolean {
hasRenderableProperties(): boolean {
if (this.props.texture) {
return true;
}
Expand Down Expand Up @@ -1248,16 +1250,6 @@ export class CoreNode extends EventEmitter {
return CoreNodeRenderState.OutOfBounds;
}

createPreloadBounds(strictBound: Bound): Bound {
const renderM = this.stage.boundsMargin;
return createBound(
strictBound.x1 - renderM[3],
strictBound.y1 - renderM[0],
strictBound.x2 + renderM[1],
strictBound.y2 + renderM[2],
);
}

updateBoundingRect() {
const { renderCoords, globalTransform: transform } = this;
assertTruthy(transform);
Expand All @@ -1282,34 +1274,43 @@ export class CoreNode extends EventEmitter {
createRenderBounds(): void {
assertTruthy(this.stage);

// no clipping, use parent's bounds
if (this.clipping === false) {
if (this.parent !== null && this.parent.strictBound !== undefined) {
// we have a parent with a valid bound, copy it
this.strictBound = createBound(
this.parent.strictBound.x1,
this.parent.strictBound.y1,
this.parent.strictBound.x2,
this.parent.strictBound.y2,
);
if (this.parent !== null && this.parent.strictBound !== undefined) {
// we have a parent with a valid bound, copy it
const parentBound = this.parent.strictBound;
this.strictBound = createBound(
parentBound.x1,
parentBound.y1,
parentBound.x2,
parentBound.y2,
);

this.preloadBound = this.createPreloadBounds(this.strictBound);
return;
} else {
// no parent or parent does not have a bound, take the stage dimensions
this.strictBound = createBound(
0,
0,
this.stage.root.width,
this.stage.root.height,
);
this.preloadBound = createPreloadBounds(
this.strictBound,
this.stage.boundsMargin,
);
} else {
// no parent or parent does not have a bound, take the stage boundaries
this.strictBound = this.stage.strictBound;
this.preloadBound = this.stage.preloadBound;
}

this.preloadBound = this.createPreloadBounds(this.strictBound);
return;
}
// if clipping is disabled, we're done
if (this.props.clipping === false) {
return;
}

// only create local clipping bounds if node itself is in bounds
// this can only be done if we have a render bound already
if (this.renderBound === undefined) {
return;
}

// clipping is enabled create our own bounds
// if we're out of bounds, we're done
if (boundInsideBound(this.renderBound, this.strictBound) === false) {
return;
}

// clipping is enabled and we are in bounds create our own bounds
const { x, y, width, height } = this.props;
const { tx, ty } = this.globalTransform || {};
const _x = tx ?? x;
Expand All @@ -1322,7 +1323,10 @@ export class CoreNode extends EventEmitter {
this.strictBound,
);

this.preloadBound = this.createPreloadBounds(this.strictBound);
this.preloadBound = createPreloadBounds(
this.strictBound,
this.stage.boundsMargin,
);
}

updateRenderState(renderState: CoreNodeRenderState) {
Expand All @@ -1347,7 +1351,7 @@ export class CoreNode extends EventEmitter {
*/
updateIsRenderable() {
let newIsRenderable;
if (this.worldAlpha === 0 || !this.checkRenderProps()) {
if (this.worldAlpha === 0 || !this.hasRenderableProperties()) {
newIsRenderable = false;
} else {
newIsRenderable = this.renderState > CoreNodeRenderState.OutOfBounds;
Expand Down
Loading
Loading