@flighthq/lighting 0.2.1-next.585.c7219b8 → 0.2.1-next.640.ec2ea9a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lightAnalysis.d.ts +3 -2
- package/dist/lightAnalysis.d.ts.map +1 -1
- package/dist/lightAnalysis.js +47 -10
- package/dist/lightAnalysis.js.map +1 -1
- package/dist/sceneForwardLights.d.ts +3 -0
- package/dist/sceneForwardLights.d.ts.map +1 -0
- package/dist/sceneForwardLights.js +72 -0
- package/dist/sceneForwardLights.js.map +1 -0
- package/package.json +5 -4
- package/src/lightAnalysis.test.ts +58 -10
- package/src/sceneForwardLights.test.ts +120 -0
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './hemisphereLight';
|
|
|
6
6
|
export * from './lightAnalysis';
|
|
7
7
|
export * from './lightIntensity';
|
|
8
8
|
export * from './pointLight';
|
|
9
|
+
export * from './sceneForwardLights';
|
|
9
10
|
export * from './sceneLights';
|
|
10
11
|
export * from './spotLight';
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './hemisphereLight';
|
|
|
6
6
|
export * from './lightAnalysis';
|
|
7
7
|
export * from './lightIntensity';
|
|
8
8
|
export * from './pointLight';
|
|
9
|
+
export * from './sceneForwardLights';
|
|
9
10
|
export * from './sceneLights';
|
|
10
11
|
export * from './spotLight';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
package/dist/lightAnalysis.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { BoundingSphereLike, Light } from '@flighthq/types';
|
|
1
|
+
import type { BoundingSphereLike, Light, PointLight, SpotLight } from '@flighthq/types';
|
|
2
|
+
export declare function getLightContributionAtBoundingSphere(light: Readonly<PointLight | SpotLight>, bounds: Readonly<BoundingSphereLike>): number;
|
|
2
3
|
export declare function getLightInfluenceBounds(out: BoundingSphereLike, light: Readonly<Light>): void;
|
|
3
4
|
export declare function getLightLuminance(light: Readonly<Light>): number;
|
|
4
5
|
export declare function hasLightInfluenceOnBounds(light: Readonly<Light>, bounds: Readonly<BoundingSphereLike>): boolean;
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function isLightCastingShadow(light: Readonly<Light>): boolean;
|
|
6
7
|
//# sourceMappingURL=lightAnalysis.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lightAnalysis.d.ts","sourceRoot":"","sources":["../src/lightAnalysis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lightAnalysis.d.ts","sourceRoot":"","sources":["../src/lightAnalysis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAiBxF,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,QAAQ,CAAC,UAAU,GAAG,SAAS,CAAC,EACvC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GACnC,MAAM,CA+BR;AAMD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,IAAI,CAqC7F;AAMD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAMhE;AAMD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,OAAO,CA0B/G;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAMpE"}
|
package/dist/lightAnalysis.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
+
import { getColorLuminance } from '@flighthq/color';
|
|
1
2
|
import { AmbientLightKind, AreaLightKind, DirectionalLightKind, EnvironmentKind, HemisphereLightKind, PointLightKind, SpotLightKind, } from '@flighthq/types';
|
|
3
|
+
// Estimates the radiance contribution of one punctual light at a world-space bounding sphere.
|
|
4
|
+
// Distance uses the nearest sphere surface (`centerDistance - radius`) so large objects receive
|
|
5
|
+
// radius slack instead of being ranked only at their centre. The attenuation matches the forward
|
|
6
|
+
// shaders: inverse-square falloff multiplied by the squared glTF/UE4 range window; spot lights also
|
|
7
|
+
// apply the same smoothstep cone at the sphere centre. Multiplying by getLightLuminance makes the
|
|
8
|
+
// result suitable for deterministic forward-budget ranking rather than material-specific shading.
|
|
9
|
+
export function getLightContributionAtBoundingSphere(light, bounds) {
|
|
10
|
+
if (bounds.radius < 0)
|
|
11
|
+
return 0;
|
|
12
|
+
const centerDx = bounds.center.x - light.position.x;
|
|
13
|
+
const centerDy = bounds.center.y - light.position.y;
|
|
14
|
+
const centerDz = bounds.center.z - light.position.z;
|
|
15
|
+
const centerDistance = Math.hypot(centerDx, centerDy, centerDz);
|
|
16
|
+
const distance = Math.max(centerDistance - bounds.radius, 0);
|
|
17
|
+
const distanceSquared = distance * distance;
|
|
18
|
+
let window = 1;
|
|
19
|
+
if (light.range > 0) {
|
|
20
|
+
const factor = distanceSquared / (light.range * light.range);
|
|
21
|
+
const windowed = Math.max(0, Math.min(1, 1 - factor * factor));
|
|
22
|
+
window = windowed * windowed;
|
|
23
|
+
}
|
|
24
|
+
let contribution = (getLightLuminance(light) * window) / Math.max(distanceSquared, 1e-4);
|
|
25
|
+
if (light.kind === SpotLightKind) {
|
|
26
|
+
const spot = light;
|
|
27
|
+
const directionLength = Math.hypot(spot.direction.x, spot.direction.y, spot.direction.z);
|
|
28
|
+
const inverseRayLength = centerDistance > 0 ? 1 / centerDistance : 0;
|
|
29
|
+
const inverseDirectionLength = directionLength > 0 ? 1 / directionLength : 0;
|
|
30
|
+
const cosine = (spot.direction.x * centerDx + spot.direction.y * centerDy + spot.direction.z * centerDz) *
|
|
31
|
+
inverseRayLength *
|
|
32
|
+
inverseDirectionLength;
|
|
33
|
+
contribution *= smoothstep(spot.outerConeCos, spot.innerConeCos, centerDistance > 0 ? cosine : 1);
|
|
34
|
+
}
|
|
35
|
+
return contribution;
|
|
36
|
+
}
|
|
2
37
|
// Writes the world-space influence bounding sphere of a light into `out`. The sphere bounds the
|
|
3
38
|
// region the light can illuminate. Lights with a finite `range` produce a sphere centered at
|
|
4
39
|
// `position` with `radius = range`. Non-spatial lights (ambient, hemisphere, environment) and
|
|
@@ -39,21 +74,17 @@ export function getLightInfluenceBounds(out, light) {
|
|
|
39
74
|
out.center.z = 0;
|
|
40
75
|
out.radius = -1;
|
|
41
76
|
}
|
|
42
|
-
// Returns the
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// forward-light budget.
|
|
77
|
+
// Returns the linear-light luminance of a light's color × intensity. Packed sRGB channels are
|
|
78
|
+
// gamma-decoded through the shared color primitive before applying the Rec. 709 weights, matching
|
|
79
|
+
// the radiance that packSceneLightBlock sends to the shader. Useful for ranking lights by their
|
|
80
|
+
// rendered contribution when prioritizing a forward-light budget.
|
|
46
81
|
export function getLightLuminance(light) {
|
|
47
82
|
const colored = light;
|
|
48
83
|
const color = colored.color;
|
|
49
84
|
if (color === undefined)
|
|
50
85
|
return 0;
|
|
51
|
-
const r = ((color >>> 24) & 0xff) / 255;
|
|
52
|
-
const g = ((color >>> 16) & 0xff) / 255;
|
|
53
|
-
const b = ((color >>> 8) & 0xff) / 255;
|
|
54
|
-
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
55
86
|
const intensity = colored.intensity ?? 1;
|
|
56
|
-
return
|
|
87
|
+
return getColorLuminance(color) * intensity;
|
|
57
88
|
}
|
|
58
89
|
// Returns true when `light` could influence a point within `bounds`. Non-spatial lights (ambient,
|
|
59
90
|
// hemisphere, directional, environment) and infinite-range lights always return true. For spatial
|
|
@@ -87,11 +118,17 @@ export function hasLightInfluenceOnBounds(light, bounds) {
|
|
|
87
118
|
}
|
|
88
119
|
// Returns true when the light is configured to cast shadows. Non-shadow-capable light types
|
|
89
120
|
// (AmbientLight, HemisphereLight, Environment) always return false.
|
|
90
|
-
export function
|
|
121
|
+
export function isLightCastingShadow(light) {
|
|
91
122
|
const kind = light.kind;
|
|
92
123
|
if (kind === AmbientLightKind || kind === HemisphereLightKind || kind === EnvironmentKind) {
|
|
93
124
|
return false;
|
|
94
125
|
}
|
|
95
126
|
return light.castsShadow;
|
|
96
127
|
}
|
|
128
|
+
function smoothstep(edge0, edge1, value) {
|
|
129
|
+
if (edge0 === edge1)
|
|
130
|
+
return value < edge0 ? 0 : 1;
|
|
131
|
+
const t = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
|
|
132
|
+
return t * t * (3 - 2 * t);
|
|
133
|
+
}
|
|
97
134
|
//# sourceMappingURL=lightAnalysis.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lightAnalysis.js","sourceRoot":"","sources":["../src/lightAnalysis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lightAnalysis.js","sourceRoot":"","sources":["../src/lightAnalysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BoundingSphereLike, SceneForwardLightSelection, SceneLightsLike } from '@flighthq/types';
|
|
2
|
+
export declare function selectSceneForwardLights(out: SceneForwardLightSelection, lights: Readonly<SceneLightsLike>, bounds: Readonly<BoundingSphereLike>): void;
|
|
3
|
+
//# sourceMappingURL=sceneForwardLights.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneForwardLights.d.ts","sourceRoot":"","sources":["../src/sceneForwardLights.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAElB,0BAA0B,EAC1B,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAazB,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,0BAA0B,EAC/B,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,EACjC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GACnC,IAAI,CAkCN"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MAX_FORWARD_LIGHTS } from '@flighthq/types';
|
|
2
|
+
import { getLightContributionAtBoundingSphere } from './lightAnalysis';
|
|
3
|
+
// Selects the strongest point and spot contributors for one object's forward-light budget. Each
|
|
4
|
+
// family has its own MAX_FORWARD_LIGHTS budget, matching the shader's separate fixed arrays and
|
|
5
|
+
// counts. Ranking uses descriptor-side shader-equivalent attenuation at `bounds`; equal scores
|
|
6
|
+
// retain their family input order, making captures reproducible. Zero-contribution lights are
|
|
7
|
+
// omitted.
|
|
8
|
+
//
|
|
9
|
+
// `out.point` and `out.spot` are reused mutable arrays. All inputs are read into fixed-size scratch
|
|
10
|
+
// before either output array is changed, so `out` may alias `lights`.
|
|
11
|
+
export function selectSceneForwardLights(out, lights, bounds) {
|
|
12
|
+
const points = lights.point;
|
|
13
|
+
const spots = lights.spot;
|
|
14
|
+
const pointCount = selectStrongestLights(points, bounds, scratchSelectedPointLights, scratchSelectedPointIndices, scratchSelectedPointScores);
|
|
15
|
+
const spotCount = selectStrongestLights(spots, bounds, scratchSelectedSpotLights, scratchSelectedSpotIndices, scratchSelectedSpotScores);
|
|
16
|
+
const outPoints = out.point;
|
|
17
|
+
const outSpots = out.spot;
|
|
18
|
+
const outIndices = out.indices;
|
|
19
|
+
outIndices.length = 0;
|
|
20
|
+
outPoints.length = 0;
|
|
21
|
+
outSpots.length = 0;
|
|
22
|
+
for (let i = 0; i < pointCount; i++) {
|
|
23
|
+
outIndices.push(scratchSelectedPointIndices[i]);
|
|
24
|
+
outPoints.push(scratchSelectedPointLights[i]);
|
|
25
|
+
}
|
|
26
|
+
for (let i = 0; i < spotCount; i++) {
|
|
27
|
+
// Bitwise complement makes every spot key negative and every point key non-negative, so two
|
|
28
|
+
// selections with the same family-local index cannot collide during block deduplication.
|
|
29
|
+
outIndices.push(~scratchSelectedSpotIndices[i]);
|
|
30
|
+
outSpots.push(scratchSelectedSpotLights[i]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function selectStrongestLights(lights, bounds, selectedLights, selectedIndices, selectedScores) {
|
|
34
|
+
let selectedCount = 0;
|
|
35
|
+
if (lights === undefined)
|
|
36
|
+
return selectedCount;
|
|
37
|
+
for (let inputIndex = 0; inputIndex < lights.length; inputIndex++) {
|
|
38
|
+
const light = lights[inputIndex];
|
|
39
|
+
const score = getLightContributionAtBoundingSphere(light, bounds);
|
|
40
|
+
if (!(score > 0))
|
|
41
|
+
continue;
|
|
42
|
+
let insertAt = selectedCount;
|
|
43
|
+
while (insertAt > 0) {
|
|
44
|
+
const previous = insertAt - 1;
|
|
45
|
+
if (score < selectedScores[previous])
|
|
46
|
+
break;
|
|
47
|
+
if (score === selectedScores[previous] && inputIndex > selectedIndices[previous])
|
|
48
|
+
break;
|
|
49
|
+
insertAt--;
|
|
50
|
+
}
|
|
51
|
+
if (insertAt >= MAX_FORWARD_LIGHTS)
|
|
52
|
+
continue;
|
|
53
|
+
const nextCount = Math.min(selectedCount + 1, MAX_FORWARD_LIGHTS);
|
|
54
|
+
for (let i = nextCount - 1; i > insertAt; i--) {
|
|
55
|
+
selectedLights[i] = selectedLights[i - 1];
|
|
56
|
+
selectedIndices[i] = selectedIndices[i - 1];
|
|
57
|
+
selectedScores[i] = selectedScores[i - 1];
|
|
58
|
+
}
|
|
59
|
+
selectedLights[insertAt] = light;
|
|
60
|
+
selectedIndices[insertAt] = inputIndex;
|
|
61
|
+
selectedScores[insertAt] = score;
|
|
62
|
+
selectedCount = nextCount;
|
|
63
|
+
}
|
|
64
|
+
return selectedCount;
|
|
65
|
+
}
|
|
66
|
+
const scratchSelectedPointIndices = new Int32Array(MAX_FORWARD_LIGHTS);
|
|
67
|
+
const scratchSelectedPointLights = new Array(MAX_FORWARD_LIGHTS);
|
|
68
|
+
const scratchSelectedPointScores = new Float64Array(MAX_FORWARD_LIGHTS);
|
|
69
|
+
const scratchSelectedSpotIndices = new Int32Array(MAX_FORWARD_LIGHTS);
|
|
70
|
+
const scratchSelectedSpotLights = new Array(MAX_FORWARD_LIGHTS);
|
|
71
|
+
const scratchSelectedSpotScores = new Float64Array(MAX_FORWARD_LIGHTS);
|
|
72
|
+
//# sourceMappingURL=sceneForwardLights.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneForwardLights.js","sourceRoot":"","sources":["../src/sceneForwardLights.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,oCAAoC,EAAE,MAAM,iBAAiB,CAAC;AAEvE,gGAAgG;AAChG,gGAAgG;AAChG,+FAA+F;AAC/F,8FAA8F;AAC9F,WAAW;AACX,EAAE;AACF,oGAAoG;AACpG,sEAAsE;AACtE,MAAM,UAAU,wBAAwB,CACtC,GAA+B,EAC/B,MAAiC,EACjC,MAAoC;IAEpC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAC1B,MAAM,UAAU,GAAG,qBAAqB,CACtC,MAAM,EACN,MAAM,EACN,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,CAC3B,CAAC;IACF,MAAM,SAAS,GAAG,qBAAqB,CACrC,KAAK,EACL,MAAM,EACN,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,CAC1B,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC;IAC1B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC;IAC/B,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IACtB,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IACrB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,SAAS,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAyB,CAAC,CAAC;IACxE,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,4FAA4F;QAC5F,yFAAyF;QACzF,UAAU,CAAC,IAAI,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAwB,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,MAA+D,EAC/D,MAAoC,EACpC,cAAkD,EAClD,eAA2B,EAC3B,cAA4B;IAE5B,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IAE/C,KAAK,IAAI,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC;QAClE,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,oCAAoC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClE,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;YAAE,SAAS;QAE3B,IAAI,QAAQ,GAAG,aAAa,CAAC;QAC7B,OAAO,QAAQ,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC;YAC9B,IAAI,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC;gBAAE,MAAM;YAC5C,IAAI,KAAK,KAAK,cAAc,CAAC,QAAQ,CAAC,IAAI,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC;gBAAE,MAAM;YACxF,QAAQ,EAAE,CAAC;QACb,CAAC;QACD,IAAI,QAAQ,IAAI,kBAAkB;YAAE,SAAS;QAE7C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,EAAE,kBAAkB,CAAC,CAAC;QAClE,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1C,eAAe,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5C,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,cAAc,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QACjC,eAAe,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC;QACvC,cAAc,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QACjC,aAAa,GAAG,SAAS,CAAC;IAC5B,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,2BAA2B,GAAG,IAAI,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACvE,MAAM,0BAA0B,GAAuC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;AACrG,MAAM,0BAA0B,GAAG,IAAI,YAAY,CAAC,kBAAkB,CAAC,CAAC;AACxE,MAAM,0BAA0B,GAAG,IAAI,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACtE,MAAM,yBAAyB,GAAuC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;AACpG,MAAM,yBAAyB,GAAG,IAAI,YAAY,CAAC,kBAAkB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/lighting",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.640.ec2ea9a",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@flighthq/
|
|
36
|
-
"@flighthq/
|
|
37
|
-
"@flighthq/
|
|
35
|
+
"@flighthq/color": "0.2.1-next.640.ec2ea9a",
|
|
36
|
+
"@flighthq/entity": "0.2.1-next.640.ec2ea9a",
|
|
37
|
+
"@flighthq/geometry": "0.2.1-next.640.ec2ea9a",
|
|
38
|
+
"@flighthq/types": "0.2.1-next.640.ec2ea9a"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"typescript": "^5.3.0"
|
|
@@ -6,14 +6,57 @@ import { createDirectionalLight } from './directionalLight';
|
|
|
6
6
|
import { createEnvironment } from './environment';
|
|
7
7
|
import { createHemisphereLight } from './hemisphereLight';
|
|
8
8
|
import {
|
|
9
|
+
getLightContributionAtBoundingSphere,
|
|
9
10
|
getLightInfluenceBounds,
|
|
10
11
|
getLightLuminance,
|
|
11
12
|
hasLightInfluenceOnBounds,
|
|
12
|
-
|
|
13
|
+
isLightCastingShadow,
|
|
13
14
|
} from './lightAnalysis';
|
|
14
15
|
import { createPointLight } from './pointLight';
|
|
15
16
|
import { createSpotLight } from './spotLight';
|
|
16
17
|
|
|
18
|
+
describe('getLightContributionAtBoundingSphere', () => {
|
|
19
|
+
it('matches inverse-square attenuation for an infinite-range point light', () => {
|
|
20
|
+
const light = createPointLight({ intensity: 1, position: { x: 0, y: 0, z: 0 }, range: -1 });
|
|
21
|
+
const near = getLightContributionAtBoundingSphere(light, createBoundingSphere(2, 0, 0, 0));
|
|
22
|
+
const far = getLightContributionAtBoundingSphere(light, createBoundingSphere(4, 0, 0, 0));
|
|
23
|
+
expect(near / far).toBeCloseTo(4);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('applies radius slack at the nearest sphere surface', () => {
|
|
27
|
+
const light = createPointLight({ position: { x: 0, y: 0, z: 0 }, range: -1 });
|
|
28
|
+
const point = getLightContributionAtBoundingSphere(light, createBoundingSphere(4, 0, 0, 0));
|
|
29
|
+
const sphere = getLightContributionAtBoundingSphere(light, createBoundingSphere(4, 0, 0, 2));
|
|
30
|
+
expect(sphere / point).toBeCloseTo(4);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('applies the squared smooth range window and reaches zero at the range', () => {
|
|
34
|
+
const light = createPointLight({ position: { x: 0, y: 0, z: 0 }, range: 4 });
|
|
35
|
+
const inside = getLightContributionAtBoundingSphere(light, createBoundingSphere(2, 0, 0, 0));
|
|
36
|
+
const edge = getLightContributionAtBoundingSphere(light, createBoundingSphere(4, 0, 0, 0));
|
|
37
|
+
expect(inside).toBeGreaterThan(0);
|
|
38
|
+
expect(edge).toBe(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('applies spot smoothstep cone attenuation', () => {
|
|
42
|
+
const light = createSpotLight({
|
|
43
|
+
direction: { x: 1, y: 0, z: 0 },
|
|
44
|
+
innerConeDegrees: 10,
|
|
45
|
+
outerConeDegrees: 30,
|
|
46
|
+
position: { x: 0, y: 0, z: 0 },
|
|
47
|
+
range: -1,
|
|
48
|
+
});
|
|
49
|
+
const inside = getLightContributionAtBoundingSphere(light, createBoundingSphere(4, 0, 0, 0));
|
|
50
|
+
const outside = getLightContributionAtBoundingSphere(light, createBoundingSphere(0, 4, 0, 0));
|
|
51
|
+
expect(inside).toBeGreaterThan(0);
|
|
52
|
+
expect(outside).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns zero for empty bounds', () => {
|
|
56
|
+
expect(getLightContributionAtBoundingSphere(createPointLight(), createBoundingSphere(0, 0, 0, -1))).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
17
60
|
describe('getLightInfluenceBounds', () => {
|
|
18
61
|
it('returns sentinel radius (-1) for ambient lights', () => {
|
|
19
62
|
const light = createAmbientLight();
|
|
@@ -95,6 +138,11 @@ describe('getLightLuminance', () => {
|
|
|
95
138
|
expect(getLightLuminance(light)).toBeCloseTo(1, 4);
|
|
96
139
|
});
|
|
97
140
|
|
|
141
|
+
it('gamma-decodes packed sRGB before computing luminance', () => {
|
|
142
|
+
const light = createAmbientLight({ color: 0x808080ff, intensity: 1 });
|
|
143
|
+
expect(getLightLuminance(light)).toBeCloseTo(0.21586, 5);
|
|
144
|
+
});
|
|
145
|
+
|
|
98
146
|
it('scales luminance by intensity', () => {
|
|
99
147
|
const a = createAmbientLight({ color: 0xffffffff, intensity: 2 });
|
|
100
148
|
const b = createAmbientLight({ color: 0xffffffff, intensity: 4 });
|
|
@@ -148,41 +196,41 @@ describe('hasLightInfluenceOnBounds', () => {
|
|
|
148
196
|
});
|
|
149
197
|
});
|
|
150
198
|
|
|
151
|
-
describe('
|
|
199
|
+
describe('isLightCastingShadow', () => {
|
|
152
200
|
it('returns false for ambient lights', () => {
|
|
153
|
-
expect(
|
|
201
|
+
expect(isLightCastingShadow(createAmbientLight())).toBe(false);
|
|
154
202
|
});
|
|
155
203
|
|
|
156
204
|
it('returns false for hemisphere lights', () => {
|
|
157
|
-
expect(
|
|
205
|
+
expect(isLightCastingShadow(createHemisphereLight())).toBe(false);
|
|
158
206
|
});
|
|
159
207
|
|
|
160
208
|
it('returns false for environment lights', () => {
|
|
161
|
-
expect(
|
|
209
|
+
expect(isLightCastingShadow(createEnvironment())).toBe(false);
|
|
162
210
|
});
|
|
163
211
|
|
|
164
212
|
it('returns false for directional lights with castsShadow: false', () => {
|
|
165
213
|
const light = createDirectionalLight({ castsShadow: false });
|
|
166
|
-
expect(
|
|
214
|
+
expect(isLightCastingShadow(light)).toBe(false);
|
|
167
215
|
});
|
|
168
216
|
|
|
169
217
|
it('returns true for directional lights with castsShadow: true', () => {
|
|
170
218
|
const light = createDirectionalLight({ castsShadow: true });
|
|
171
|
-
expect(
|
|
219
|
+
expect(isLightCastingShadow(light)).toBe(true);
|
|
172
220
|
});
|
|
173
221
|
|
|
174
222
|
it('returns true for point lights with castsShadow: true', () => {
|
|
175
223
|
const light = createPointLight({ castsShadow: true });
|
|
176
|
-
expect(
|
|
224
|
+
expect(isLightCastingShadow(light)).toBe(true);
|
|
177
225
|
});
|
|
178
226
|
|
|
179
227
|
it('returns true for spot lights with castsShadow: true', () => {
|
|
180
228
|
const light = createSpotLight({ castsShadow: true });
|
|
181
|
-
expect(
|
|
229
|
+
expect(isLightCastingShadow(light)).toBe(true);
|
|
182
230
|
});
|
|
183
231
|
|
|
184
232
|
it('returns true for area lights with castsShadow: true', () => {
|
|
185
233
|
const light = createAreaLight({ castsShadow: true });
|
|
186
|
-
expect(
|
|
234
|
+
expect(isLightCastingShadow(light)).toBe(true);
|
|
187
235
|
});
|
|
188
236
|
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createBoundingSphere } from '@flighthq/geometry';
|
|
2
|
+
import type { SceneForwardLightSelection, SceneLightsLike } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { createPointLight } from './pointLight';
|
|
5
|
+
import { selectSceneForwardLights } from './sceneForwardLights';
|
|
6
|
+
import { createSpotLight } from './spotLight';
|
|
7
|
+
|
|
8
|
+
function selection(): SceneForwardLightSelection {
|
|
9
|
+
return { indices: [], point: [], spot: [] };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function spotLights(xs: readonly number[]) {
|
|
13
|
+
return xs.map((x) =>
|
|
14
|
+
createSpotLight({
|
|
15
|
+
direction: { x: -1, y: 0, z: 0 },
|
|
16
|
+
innerConeDegrees: 45,
|
|
17
|
+
outerConeDegrees: 60,
|
|
18
|
+
position: { x, y: 0, z: 0 },
|
|
19
|
+
range: -1,
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('selectSceneForwardLights', () => {
|
|
25
|
+
it('keeps all three points and three spots when both families are within budget', () => {
|
|
26
|
+
const points = [8, 7, 6].map((x) => createPointLight({ position: { x, y: 0, z: 0 }, range: -1 }));
|
|
27
|
+
const spots = spotLights([5, 4, 3]);
|
|
28
|
+
const out = selection();
|
|
29
|
+
selectSceneForwardLights(
|
|
30
|
+
out,
|
|
31
|
+
{ ambient: null, directional: null, point: points, spot: spots },
|
|
32
|
+
createBoundingSphere(0, 0, 0, 0),
|
|
33
|
+
);
|
|
34
|
+
expect(out.point).toHaveLength(3);
|
|
35
|
+
expect(out.spot).toHaveLength(3);
|
|
36
|
+
expect(new Set(out.point)).toEqual(new Set(points));
|
|
37
|
+
expect(new Set(out.spot)).toEqual(new Set(spots));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('ranks the family cutoff by linear-light radiance instead of packed sRGB channels', () => {
|
|
41
|
+
const gray = Array.from({ length: 4 }, () =>
|
|
42
|
+
createPointLight({
|
|
43
|
+
color: 0x808080ff,
|
|
44
|
+
intensity: 1,
|
|
45
|
+
position: { x: 2, y: 0, z: 0 },
|
|
46
|
+
range: -1,
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
const white = createPointLight({
|
|
50
|
+
color: 0xffffffff,
|
|
51
|
+
intensity: 0.3,
|
|
52
|
+
position: { x: 2, y: 0, z: 0 },
|
|
53
|
+
range: -1,
|
|
54
|
+
});
|
|
55
|
+
const out = selection();
|
|
56
|
+
selectSceneForwardLights(
|
|
57
|
+
out,
|
|
58
|
+
{ ambient: null, directional: null, point: [...gray, white] },
|
|
59
|
+
createBoundingSphere(0, 0, 0, 0),
|
|
60
|
+
);
|
|
61
|
+
expect(out.point).toContain(white);
|
|
62
|
+
expect(out.point).toHaveLength(4);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('selects the strongest four points and strongest four spots independently', () => {
|
|
66
|
+
const points = [8, 7, 6, 5, 4, 3].map((x) => createPointLight({ position: { x, y: 0, z: 0 }, range: -1 }));
|
|
67
|
+
const spots = spotLights([8, 7, 6, 5, 4, 3]);
|
|
68
|
+
const out = selection();
|
|
69
|
+
selectSceneForwardLights(
|
|
70
|
+
out,
|
|
71
|
+
{ ambient: null, directional: null, point: points, spot: spots },
|
|
72
|
+
createBoundingSphere(0, 0, 0, 0),
|
|
73
|
+
);
|
|
74
|
+
expect(out.point).toEqual(points.slice(2).reverse());
|
|
75
|
+
expect(out.spot).toEqual(spots.slice(2).reverse());
|
|
76
|
+
expect(out.indices).toEqual([5, 4, 3, 2, ~5, ~4, ~3, ~2]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('uses stable input order to break equal-contribution ties', () => {
|
|
80
|
+
const points = Array.from({ length: 6 }, () => createPointLight({ position: { x: 2, y: 0, z: 0 }, range: -1 }));
|
|
81
|
+
const out = selection();
|
|
82
|
+
selectSceneForwardLights(
|
|
83
|
+
out,
|
|
84
|
+
{ ambient: null, directional: null, point: points },
|
|
85
|
+
createBoundingSphere(0, 0, 0, 0),
|
|
86
|
+
);
|
|
87
|
+
expect(out.point).toEqual(points.slice(0, 4));
|
|
88
|
+
expect(out.indices).toEqual([0, 1, 2, 3]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('omits zero-contribution lights', () => {
|
|
92
|
+
const out = selection();
|
|
93
|
+
selectSceneForwardLights(
|
|
94
|
+
out,
|
|
95
|
+
{
|
|
96
|
+
ambient: null,
|
|
97
|
+
directional: null,
|
|
98
|
+
point: [createPointLight({ position: { x: 10, y: 0, z: 0 }, range: 2 })],
|
|
99
|
+
},
|
|
100
|
+
createBoundingSphere(0, 0, 0, 0),
|
|
101
|
+
);
|
|
102
|
+
expect(out.point).toHaveLength(0);
|
|
103
|
+
expect(out.indices).toHaveLength(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('reads all inputs before writing an aliased output', () => {
|
|
107
|
+
const points = [
|
|
108
|
+
createPointLight({ position: { x: 4, y: 0, z: 0 }, range: -1 }),
|
|
109
|
+
createPointLight({ position: { x: 1, y: 0, z: 0 }, range: -1 }),
|
|
110
|
+
];
|
|
111
|
+
const far = points[0];
|
|
112
|
+
const near = points[1];
|
|
113
|
+
const lights: SceneLightsLike = { ambient: null, directional: null, point: points, spot: [] };
|
|
114
|
+
const out = lights as SceneLightsLike & SceneForwardLightSelection;
|
|
115
|
+
out.indices = [];
|
|
116
|
+
selectSceneForwardLights(out, lights, createBoundingSphere(0, 0, 0, 0));
|
|
117
|
+
expect(out.point).toEqual([near, far]);
|
|
118
|
+
expect(out.indices).toEqual([1, 0]);
|
|
119
|
+
});
|
|
120
|
+
});
|