Warning, /firebird/packages/firebird-ng/display/src/lib/services/three-clipping-patch.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * Pins the three.js facts `useFragmentShaderClipping()` relies on (see
0003 * three-clipping-patch.ts). If one of these fails after a three upgrade,
0004 * re-check the patch against the new `NodeMaterial.setupHardwareClipping`.
0005 *
0006 * The `three/src/...` import below is TEST-ONLY and deliberate: RenderObject
0007 * is not exported from `three/webgpu`, and this spec never runs in the
0008 * browser bundle (where a second copy of three's node system would corrupt
0009 * TSL state). Do not copy this import into app code.
0010 */
0011 // @ts-ignore -- RenderObject ships no type declarations; test-only import.
0012 import RenderObject from 'three/src/renderers/common/RenderObject.js';
0013 import { Plane, Vector3 } from 'three';
0014 import { NodeBuilder, NodeMaterial } from 'three/webgpu';
0015 import { useFragmentShaderClipping } from './three-clipping-patch';
0016
0017 type SetupHardwareClipping = (this: unknown, builder: unknown) => void;
0018 const prototype = NodeMaterial.prototype as unknown as { setupHardwareClipping: SetupHardwareClipping };
0019 const original = prototype.setupHardwareClipping;
0020
0021 /** A builder stub offering clip distances, with two union planes in its clipping context. */
0022 function stubBuilder() {
0023 const stack: unknown[] = [];
0024 return {
0025 hardwareClipping: false,
0026 clippingContext: {
0027 unionPlanes: [new Plane(new Vector3(0, -1, 0), 0), new Plane(new Vector3(0, 1, 0), 0)],
0028 intersectionPlanes: [],
0029 },
0030 isAvailable: (feature: string) => feature === 'clipDistance',
0031 stack: { addToStack: (node: unknown) => stack.push(node) },
0032 pushed: stack,
0033 };
0034 }
0035
0036 describe('three hardware clipping (pinned for useFragmentShaderClipping)', () => {
0037 afterAll(() => {
0038 prototype.setupHardwareClipping = original;
0039 });
0040
0041 it('unpatched three selects hardware clipping when the device offers clip distances', () => {
0042 const builder = stubBuilder();
0043 original.call(new NodeMaterial(), builder);
0044 expect(builder.hardwareClipping).toBe(true);
0045 expect(builder.pushed.length).toBe(1);
0046 });
0047
0048 it('a new NodeBuilder starts with hardwareClipping === false (the fragment-shader union loop)', () => {
0049 // NodeBuilder is abstract in the type declarations, concrete at runtime.
0050 const Builder = NodeBuilder as unknown as new (object: null, renderer: null, parser: null) => { hardwareClipping: unknown };
0051 const builder = new Builder(null, null, null);
0052 expect(builder.hardwareClipping).toBe(false);
0053 });
0054
0055 it('the draw call reads the decision from the node builder state, not from the material', () => {
0056 const getter = Object.getOwnPropertyDescriptor(RenderObject.prototype, 'hardwareClippingPlanes')?.get;
0057 expect(getter).toBeDefined();
0058 const withState = (hardwareClipping: boolean) => ({
0059 getNodeBuilderState: () => ({ hardwareClipping }),
0060 clippingContext: { unionClippingCount: 2 },
0061 material: { hardwareClipping: true },
0062 });
0063 expect(getter!.call(withState(false))).toBe(0);
0064 expect(getter!.call(withState(true))).toBe(2);
0065 });
0066
0067 it('after the patch, union planes never select hardware clipping', () => {
0068 useFragmentShaderClipping();
0069 const builder = stubBuilder();
0070 builder.hardwareClipping = true;
0071 prototype.setupHardwareClipping.call(new NodeMaterial(), builder);
0072 expect(builder.hardwareClipping).toBe(false);
0073 expect(builder.pushed.length).toBe(0);
0074 });
0075 });