From f00673d3acf2cdf3d31c6c8faae455c96bb30b7b Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 22 Apr 2026 14:56:45 +0200 Subject: [PATCH] auto-git: [add] tests/domain/celestial-shadows.test.ts --- tests/domain/celestial-shadows.test.ts | 143 +++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/domain/celestial-shadows.test.ts diff --git a/tests/domain/celestial-shadows.test.ts b/tests/domain/celestial-shadows.test.ts new file mode 100644 index 00000000..318b418b --- /dev/null +++ b/tests/domain/celestial-shadows.test.ts @@ -0,0 +1,143 @@ +import { + OrthographicCamera, + PerspectiveCamera +} from "three"; +import { describe, expect, it } from "vitest"; + +import { + fitCelestialDirectionalShadow, + resolveDominantCelestialShadowCaster +} from "../../src/rendering/celestial-shadows"; + +describe("celestial shadows", () => { + it("chooses one deterministic dominant celestial shadow caster", () => { + const sun = { + colorHex: "#fff1d5", + intensity: 1.6, + direction: { + x: 0.45, + y: 0.88, + z: 0.15 + } + }; + const moon = { + colorHex: "#99b5ff", + intensity: 0.4, + direction: { + x: -0.45, + y: 0.72, + z: -0.2 + } + }; + + expect(resolveDominantCelestialShadowCaster(sun, moon)?.key).toBe("sun"); + expect( + resolveDominantCelestialShadowCaster( + { + ...sun, + intensity: 0.1 + }, + { + ...moon, + intensity: 0.35 + } + )?.key + ).toBe("moon"); + expect( + resolveDominantCelestialShadowCaster( + { + ...sun, + intensity: 0 + }, + null + ) + ).toBeNull(); + }); + + it("fits a bounded perspective shadow volume around the active view", () => { + const camera = new PerspectiveCamera(60, 16 / 9, 0.1, 1000); + camera.position.set(0, 12, 24); + camera.lookAt(0, 4, 0); + camera.updateMatrixWorld(); + + const fit = fitCelestialDirectionalShadow({ + activeCamera: camera, + focusTarget: { + center: { + x: 0, + y: 4, + z: 0 + }, + radius: 6 + }, + lightDirection: { + x: 0.45, + y: 0.88, + z: 0.15 + }, + mapSize: 2048, + sceneBounds: { + min: { + x: -500, + y: -10, + z: -500 + }, + max: { + x: 500, + y: 40, + z: 500 + } + } + }); + + expect(fit).not.toBeNull(); + expect(fit?.cameraBounds.right).toBeGreaterThan(0); + expect(fit?.cameraBounds.top).toBeGreaterThan(0); + expect((fit?.cameraBounds.right ?? 0) - (fit?.cameraBounds.left ?? 0)).toBeGreaterThan(30); + expect((fit?.cameraBounds.right ?? 0) - (fit?.cameraBounds.left ?? 0)).toBeLessThan(280); + expect(fit?.cameraBounds.far).toBeGreaterThan(fit?.cameraBounds.near ?? 0); + expect(fit?.normalBias ?? 0).toBeGreaterThan(0); + }); + + it("fits orthographic viewport shadows without collapsing depth coverage", () => { + const camera = new OrthographicCamera(-12, 12, 10, -10, 0.1, 1000); + camera.position.set(0, 30, 0); + camera.lookAt(0, 0, 0); + camera.updateMatrixWorld(); + + const fit = fitCelestialDirectionalShadow({ + activeCamera: camera, + focusTarget: { + center: { + x: 0, + y: 0, + z: 0 + }, + radius: 8 + }, + lightDirection: { + x: -0.25, + y: 0.92, + z: 0.3 + }, + mapSize: 1024, + sceneBounds: { + min: { + x: -40, + y: -2, + z: -40 + }, + max: { + x: 40, + y: 18, + z: 40 + } + } + }); + + expect(fit).not.toBeNull(); + expect((fit?.cameraBounds.right ?? 0) - (fit?.cameraBounds.left ?? 0)).toBeGreaterThan(20); + expect((fit?.cameraBounds.top ?? 0) - (fit?.cameraBounds.bottom ?? 0)).toBeGreaterThan(20); + expect(fit?.cameraBounds.far).toBeGreaterThan(20); + }); +});