Skip to content
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
66 changes: 66 additions & 0 deletions src/core/CoreNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1113,5 +1113,71 @@ describe('set color()', () => {

expect(node.clippingRect.valid).toBe(false);
});

it('shares the invalid default rect when neither node nor ancestor clips', () => {
const parent = new CoreNode(stage, defaultProps());
parent.globalTransform = Matrix3d.identity();
parent.worldAlpha = 1;

const node = new CoreNode(stage, defaultProps({ parent }));
node.alpha = 1;
node.setUpdateType(UpdateType.Clipping);

node.update(0, { x: 0, y: 0, w: 0, h: 0, valid: false });

// No own allocation: the field still points at the shared default that
// every freshly-constructed node starts with.
const fresh = new CoreNode(stage, defaultProps());
expect(node.clippingRect).toBe(fresh.clippingRect);
expect(node.clippingRect.valid).toBe(false);
});

it('never mutates the shared default when a sibling node clips', () => {
const clipParent = new CoreNode(stage, defaultProps());
clipParent.globalTransform = Matrix3d.identity();
clipParent.worldAlpha = 1;

const clippingNode = new CoreNode(
stage,
defaultProps({ parent: clipParent }),
);
clippingNode.worldAlpha = 1;
clippingNode.alpha = 1;
clippingNode.x = 10;
clippingNode.y = 20;
clippingNode.w = 30;
clippingNode.h = 40;
clippingNode.clipping = true;
clippingNode.update(0, { x: 0, y: 0, w: 1000, h: 1000, valid: true });

// A separate non-clipping node must still see a pristine invalid default.
const plain = new CoreNode(stage, defaultProps());
expect(plain.clippingRect.valid).toBe(false);
expect(plain.clippingRect.x).toBe(0);
expect(plain.clippingRect.y).toBe(0);
expect(plain.clippingRect.w).toBe(0);
expect(plain.clippingRect.h).toBe(0);
});

it('allocates its own rect to inherit a valid ancestor clip rect', () => {
const parent = new CoreNode(stage, defaultProps());
parent.globalTransform = Matrix3d.identity();
parent.worldAlpha = 1;

const node = new CoreNode(stage, defaultProps({ parent }));
node.alpha = 1;
node.setUpdateType(UpdateType.Clipping);

const fresh = new CoreNode(stage, defaultProps());
node.update(0, { x: 5, y: 6, w: 20, h: 30, valid: true });

// Now owns a private rect carrying the parent's clip values.
expect(node.clippingRect).not.toBe(fresh.clippingRect);
expect(node.clippingRect.valid).toBe(true);
expect(node.clippingRect.x).toBe(5);
expect(node.clippingRect.y).toBe(6);
expect(node.clippingRect.w).toBe(20);
expect(node.clippingRect.h).toBe(30);
});
});
});
41 changes: 32 additions & 9 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,13 +808,14 @@ export class CoreNode extends EventEmitter {
public renderBound?: Bound;
public strictBound?: Bound;
public preloadBound?: Bound;
public clippingRect: RectWithValid = {
x: 0,
y: 0,
w: 0,
h: 0,
valid: false,
};
/**
* Points at the shared `NO_CLIPPING_RECT` until this node actually
* participates in clipping (either it clips, or an ancestor's clip rect
* propagates down). Clipping is rare across the scene graph, so most nodes
* never allocate their own rect — `calculateClippingRect` swaps in a private
* object lazily the first time one is needed.
*/
public clippingRect: RectWithValid = NO_CLIPPING_RECT;
public textureCoords?: TextureCoords;
public updateShaderUniforms: boolean = false;
public isRenderable = false;
Expand Down Expand Up @@ -1933,11 +1934,33 @@ export class CoreNode extends EventEmitter {
* Finally, the node's parentClippingRect and clippingRect properties are updated.
*/
calculateClippingRect(parentClippingRect: RectWithValid) {
const { clippingRect, props, globalTransform: gt } = this;
const { props, globalTransform: gt } = this;
const { clipping } = props;
const isRotated = gt!.tb !== 0 || gt!.tc !== 0;
const nodeClips = clipping !== false && isRotated === false;

// Common case: this node doesn't clip and no ancestor clip rect needs to
// propagate. No node-owned rect is required, so point at the shared
// invalid default and skip the allocation entirely.
if (nodeClips === false && parentClippingRect.valid === false) {
this.clippingRect = NO_CLIPPING_RECT;
return;
}

// A node-owned, mutable rect is needed. Allocate one lazily the first time
// (the default shares NO_CLIPPING_RECT, which must never be written to).
let clippingRect = this.clippingRect;
if (clippingRect === NO_CLIPPING_RECT) {
clippingRect = this.clippingRect = {
x: 0,
y: 0,
w: 0,
h: 0,
valid: false,
};
}

if (clipping !== false && isRotated === false) {
if (nodeClips === true) {
let mT = 0;
let mR = 0;
let mB = 0;
Expand Down
Loading