144 lines
3.3 KiB
TypeScript
144 lines
3.3 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|