@luma.gl/engine 9.2.5 → 9.3.0-alpha.10

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.
Files changed (186) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +11 -5
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +83 -47
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/compute/computation.d.ts +3 -7
  11. package/dist/compute/computation.d.ts.map +1 -1
  12. package/dist/compute/computation.js +16 -13
  13. package/dist/compute/computation.js.map +1 -1
  14. package/dist/compute/swap.d.ts +2 -0
  15. package/dist/compute/swap.d.ts.map +1 -1
  16. package/dist/compute/swap.js +10 -5
  17. package/dist/compute/swap.js.map +1 -1
  18. package/dist/dist.dev.js +2639 -1290
  19. package/dist/dist.min.js +325 -210
  20. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  21. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  22. package/dist/dynamic-texture/dynamic-texture.js +556 -0
  23. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  24. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  25. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  26. package/dist/dynamic-texture/texture-data.js +208 -0
  27. package/dist/dynamic-texture/texture-data.js.map +1 -0
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  35. package/dist/geometry/gpu-geometry.js +8 -3
  36. package/dist/geometry/gpu-geometry.js.map +1 -1
  37. package/dist/index.cjs +2497 -1212
  38. package/dist/index.cjs.map +4 -4
  39. package/dist/index.d.ts +20 -6
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/material/material-factory.d.ts +73 -0
  44. package/dist/material/material-factory.d.ts.map +1 -0
  45. package/dist/material/material-factory.js +111 -0
  46. package/dist/material/material-factory.js.map +1 -0
  47. package/dist/material/material.d.ts +84 -0
  48. package/dist/material/material.d.ts.map +1 -0
  49. package/dist/material/material.js +176 -0
  50. package/dist/material/material.js.map +1 -0
  51. package/dist/model/model.d.ts +47 -16
  52. package/dist/model/model.d.ts.map +1 -1
  53. package/dist/model/model.js +113 -47
  54. package/dist/model/model.js.map +1 -1
  55. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  56. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  57. package/dist/model/split-uniforms-and-bindings.js +2 -2
  58. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  59. package/dist/models/billboard-texture-model.d.ts +8 -5
  60. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  61. package/dist/models/billboard-texture-model.js +77 -23
  62. package/dist/models/billboard-texture-model.js.map +1 -1
  63. package/dist/models/billboard-texture-module.d.ts +1 -1
  64. package/dist/models/billboard-texture-module.js +1 -1
  65. package/dist/models/clip-space.js +7 -7
  66. package/dist/models/directional-light-model.d.ts +7 -0
  67. package/dist/models/directional-light-model.d.ts.map +1 -0
  68. package/dist/models/directional-light-model.js +23 -0
  69. package/dist/models/directional-light-model.js.map +1 -0
  70. package/dist/models/light-model-utils.d.ts +69 -0
  71. package/dist/models/light-model-utils.d.ts.map +1 -0
  72. package/dist/models/light-model-utils.js +395 -0
  73. package/dist/models/light-model-utils.js.map +1 -0
  74. package/dist/models/point-light-model.d.ts +7 -0
  75. package/dist/models/point-light-model.d.ts.map +1 -0
  76. package/dist/models/point-light-model.js +22 -0
  77. package/dist/models/point-light-model.js.map +1 -0
  78. package/dist/models/spot-light-model.d.ts +7 -0
  79. package/dist/models/spot-light-model.d.ts.map +1 -0
  80. package/dist/models/spot-light-model.js +23 -0
  81. package/dist/models/spot-light-model.js.map +1 -0
  82. package/dist/modules/picking/color-picking.d.ts +5 -9
  83. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  84. package/dist/modules/picking/color-picking.js +122 -115
  85. package/dist/modules/picking/color-picking.js.map +1 -1
  86. package/dist/modules/picking/index-picking.d.ts +4 -4
  87. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  88. package/dist/modules/picking/index-picking.js +36 -16
  89. package/dist/modules/picking/index-picking.js.map +1 -1
  90. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  91. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  92. package/dist/modules/picking/legacy-color-picking.js +7 -0
  93. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  94. package/dist/modules/picking/picking-manager.d.ts +29 -3
  95. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  96. package/dist/modules/picking/picking-manager.js +188 -41
  97. package/dist/modules/picking/picking-manager.js.map +1 -1
  98. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  99. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  100. package/dist/modules/picking/picking-uniforms.js +27 -14
  101. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  102. package/dist/modules/picking/picking.d.ts +25 -0
  103. package/dist/modules/picking/picking.d.ts.map +1 -0
  104. package/dist/modules/picking/picking.js +18 -0
  105. package/dist/modules/picking/picking.js.map +1 -0
  106. package/dist/passes/get-fragment-shader.js +12 -27
  107. package/dist/passes/get-fragment-shader.js.map +1 -1
  108. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  109. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  110. package/dist/passes/shader-pass-renderer.js +16 -42
  111. package/dist/passes/shader-pass-renderer.js.map +1 -1
  112. package/dist/scenegraph/group-node.d.ts +5 -0
  113. package/dist/scenegraph/group-node.d.ts.map +1 -1
  114. package/dist/scenegraph/group-node.js +12 -0
  115. package/dist/scenegraph/group-node.js.map +1 -1
  116. package/dist/scenegraph/model-node.d.ts +2 -2
  117. package/dist/scenegraph/model-node.d.ts.map +1 -1
  118. package/dist/scenegraph/model-node.js.map +1 -1
  119. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  120. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  121. package/dist/scenegraph/scenegraph-node.js +23 -15
  122. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  123. package/dist/shader-inputs.d.ts +9 -7
  124. package/dist/shader-inputs.d.ts.map +1 -1
  125. package/dist/shader-inputs.js +84 -4
  126. package/dist/shader-inputs.js.map +1 -1
  127. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  128. package/dist/utils/buffer-layout-order.js +12 -2
  129. package/dist/utils/buffer-layout-order.js.map +1 -1
  130. package/dist/utils/shader-module-utils.d.ts +7 -0
  131. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  132. package/dist/utils/shader-module-utils.js +46 -0
  133. package/dist/utils/shader-module-utils.js.map +1 -0
  134. package/package.json +6 -6
  135. package/src/animation-loop/animation-loop.ts +89 -50
  136. package/src/animation-loop/make-animation-loop.ts +13 -5
  137. package/src/animation-loop/request-animation-frame.ts +32 -6
  138. package/src/compute/computation.ts +32 -17
  139. package/src/compute/swap.ts +13 -7
  140. package/src/dynamic-texture/dynamic-texture.ts +732 -0
  141. package/src/dynamic-texture/texture-data.ts +336 -0
  142. package/src/geometries/cone-geometry.ts +6 -1
  143. package/src/geometries/cylinder-geometry.ts +5 -1
  144. package/src/geometry/gpu-geometry.ts +8 -3
  145. package/src/index.ts +38 -8
  146. package/src/material/material-factory.ts +157 -0
  147. package/src/material/material.ts +254 -0
  148. package/src/model/model.ts +158 -67
  149. package/src/model/split-uniforms-and-bindings.ts +8 -6
  150. package/src/models/billboard-texture-model.ts +88 -27
  151. package/src/models/billboard-texture-module.ts +1 -1
  152. package/src/models/clip-space.ts +7 -7
  153. package/src/models/directional-light-model.ts +32 -0
  154. package/src/models/light-model-utils.ts +587 -0
  155. package/src/models/point-light-model.ts +31 -0
  156. package/src/models/spot-light-model.ts +32 -0
  157. package/src/modules/picking/color-picking.ts +123 -122
  158. package/src/modules/picking/index-picking.ts +36 -16
  159. package/src/modules/picking/legacy-color-picking.ts +8 -0
  160. package/src/modules/picking/picking-manager.ts +252 -50
  161. package/src/modules/picking/picking-uniforms.ts +39 -24
  162. package/src/modules/picking/picking.ts +22 -0
  163. package/src/passes/get-fragment-shader.ts +12 -27
  164. package/src/passes/shader-pass-renderer.ts +25 -48
  165. package/src/scenegraph/group-node.ts +16 -0
  166. package/src/scenegraph/model-node.ts +2 -2
  167. package/src/scenegraph/scenegraph-node.ts +27 -16
  168. package/src/shader-inputs.ts +165 -15
  169. package/src/utils/buffer-layout-order.ts +18 -2
  170. package/src/utils/shader-module-utils.ts +65 -0
  171. package/dist/async-texture/async-texture.d.ts +0 -166
  172. package/dist/async-texture/async-texture.d.ts.map +0 -1
  173. package/dist/async-texture/async-texture.js +0 -386
  174. package/dist/async-texture/async-texture.js.map +0 -1
  175. package/dist/factories/pipeline-factory.d.ts +0 -37
  176. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  177. package/dist/factories/pipeline-factory.js +0 -181
  178. package/dist/factories/pipeline-factory.js.map +0 -1
  179. package/dist/factories/shader-factory.d.ts +0 -22
  180. package/dist/factories/shader-factory.d.ts.map +0 -1
  181. package/dist/factories/shader-factory.js +0 -88
  182. package/dist/factories/shader-factory.js.map +0 -1
  183. package/src/async-texture/async-texture.ts +0 -551
  184. package/src/factories/pipeline-factory.ts +0 -224
  185. package/src/factories/shader-factory.ts +0 -103
  186. /package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +0 -0
@@ -0,0 +1,587 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Buffer, Device, type RenderPass, type RenderPipelineParameters} from '@luma.gl/core';
6
+ import {Matrix4, type NumericArray} from '@math.gl/core';
7
+ import type {
8
+ DirectionalLight,
9
+ Light,
10
+ PointLight,
11
+ ShaderModule,
12
+ SpotLight
13
+ } from '@luma.gl/shadertools';
14
+ import {Model, type ModelProps} from '../model/model';
15
+ import {ShaderInputs} from '../shader-inputs';
16
+ import type {Geometry} from '../geometry/geometry';
17
+
18
+ const DEFAULT_POINT_LIGHT_RADIUS_FACTOR = 0.02;
19
+ const DEFAULT_SPOT_LIGHT_LENGTH_FACTOR = 0.12;
20
+ const DEFAULT_DIRECTIONAL_LIGHT_LENGTH_FACTOR = 0.15;
21
+ const DEFAULT_DIRECTIONAL_LIGHT_RADIUS_FACTOR = 0.2;
22
+ const DEFAULT_DIRECTION_FALLBACK: [number, number, number] = [0, 1, 0];
23
+ const DEFAULT_LIGHT_COLOR: [number, number, number] = [255, 255, 255];
24
+ const DEFAULT_MARKER_SCALE = 1;
25
+ const DIRECTIONAL_ANCHOR_DISTANCE_FACTOR = 0.35;
26
+ const LIGHT_COLOR_FACTOR = 255;
27
+ const MIN_SCENE_SCALE = 1;
28
+ const SPOTLIGHT_OUTER_CONE_EPSILON = 0.01;
29
+
30
+ export type LightModelBounds = [[number, number, number], [number, number, number]];
31
+
32
+ export type BaseLightModelProps = Omit<
33
+ ModelProps,
34
+ 'geometry' | 'modules' | 'shaderInputs' | 'source' | 'vs' | 'fs' | 'instanceCount'
35
+ > & {
36
+ lights: ReadonlyArray<Light>;
37
+ viewMatrix: NumericArray;
38
+ projectionMatrix: NumericArray;
39
+ bounds?: LightModelBounds;
40
+ markerScale?: number;
41
+ };
42
+
43
+ export type PointLightModelProps = BaseLightModelProps & {
44
+ pointLightRadius?: number;
45
+ };
46
+
47
+ export type SpotLightModelProps = BaseLightModelProps & {
48
+ spotLightLength?: number;
49
+ };
50
+
51
+ export type DirectionalLightModelProps = BaseLightModelProps & {
52
+ directionalLightLength?: number;
53
+ };
54
+
55
+ type LightMarkerUniforms = {
56
+ viewProjectionMatrix: Matrix4;
57
+ };
58
+
59
+ export type LightMarkerInstanceData = {
60
+ instanceCount: number;
61
+ instancePositions: Float32Array;
62
+ instanceDirections: Float32Array;
63
+ instanceScales: Float32Array;
64
+ instanceColors: Float32Array;
65
+ };
66
+
67
+ type ManagedInstanceBuffers = Record<
68
+ 'instancePosition' | 'instanceDirection' | 'instanceScale' | 'instanceColor',
69
+ Buffer
70
+ >;
71
+
72
+ type LightMarkerAnchorMode = 'centered' | 'apex';
73
+
74
+ type LightMarkerModelOptions<PropsT extends BaseLightModelProps> = {
75
+ anchorMode: LightMarkerAnchorMode;
76
+ buildInstanceData: (props: PropsT) => LightMarkerInstanceData;
77
+ geometry: Geometry;
78
+ idPrefix: string;
79
+ sizePropNames: Array<keyof PropsT>;
80
+ };
81
+
82
+ const LIGHT_MARKER_PARAMETERS: RenderPipelineParameters = {
83
+ depthCompare: 'less-equal',
84
+ depthWriteEnabled: false,
85
+ cullMode: 'none'
86
+ };
87
+
88
+ const INSTANCE_BUFFER_LAYOUT = [
89
+ {name: 'instancePosition', format: 'float32x3', stepMode: 'instance'},
90
+ {name: 'instanceDirection', format: 'float32x3', stepMode: 'instance'},
91
+ {name: 'instanceScale', format: 'float32x3', stepMode: 'instance'},
92
+ {name: 'instanceColor', format: 'float32x4', stepMode: 'instance'}
93
+ ] as const;
94
+
95
+ const lightMarker = {
96
+ name: 'lightMarker',
97
+ props: {} as LightMarkerUniforms,
98
+ uniforms: {} as LightMarkerUniforms,
99
+ uniformTypes: {
100
+ viewProjectionMatrix: 'mat4x4<f32>'
101
+ }
102
+ } as const satisfies ShaderModule<LightMarkerUniforms, LightMarkerUniforms>;
103
+
104
+ const CENTERED_LOCAL_POSITION_WGSL = 'inputs.positions * inputs.instanceScale';
105
+ const APEX_LOCAL_POSITION_WGSL =
106
+ 'vec3<f32>(inputs.positions.x * inputs.instanceScale.x, (inputs.positions.y - 0.5) * inputs.instanceScale.y, inputs.positions.z * inputs.instanceScale.z)';
107
+ const CENTERED_LOCAL_POSITION_GLSL = 'positions * instanceScale';
108
+ const APEX_LOCAL_POSITION_GLSL =
109
+ 'vec3(positions.x * instanceScale.x, (positions.y - 0.5) * instanceScale.y, positions.z * instanceScale.z)';
110
+
111
+ export abstract class BaseLightModel<PropsT extends BaseLightModelProps> extends Model {
112
+ protected lightModelProps: PropsT;
113
+ protected _instanceData: LightMarkerInstanceData;
114
+ protected _managedBuffers: ManagedInstanceBuffers;
115
+
116
+ private readonly buildInstanceData: (props: PropsT) => LightMarkerInstanceData;
117
+ private readonly sizePropNames: Array<keyof PropsT>;
118
+
119
+ constructor(device: Device, props: PropsT, options: LightMarkerModelOptions<PropsT>) {
120
+ const instanceData = options.buildInstanceData(props);
121
+ const managedBuffers = createManagedInstanceBuffers(
122
+ device,
123
+ props.id || options.idPrefix,
124
+ instanceData
125
+ );
126
+ const shaderInputs = new ShaderInputs<{
127
+ lightMarker: typeof lightMarker.props;
128
+ }>({lightMarker});
129
+ shaderInputs.setProps({
130
+ lightMarker: {viewProjectionMatrix: createViewProjectionMatrix(props)}
131
+ });
132
+
133
+ const {source, vs, fs} = getLightMarkerShaders(options.anchorMode);
134
+ const modelProps: ModelProps = props;
135
+
136
+ super(device, {
137
+ ...modelProps,
138
+ id: props.id || options.idPrefix,
139
+ source,
140
+ vs,
141
+ fs,
142
+ geometry: options.geometry,
143
+ shaderInputs,
144
+ bufferLayout: [...INSTANCE_BUFFER_LAYOUT],
145
+ attributes: managedBuffers,
146
+ instanceCount: instanceData.instanceCount,
147
+ parameters: mergeLightMarkerParameters(props.parameters)
148
+ });
149
+
150
+ this.lightModelProps = props;
151
+ this._instanceData = instanceData;
152
+ this._managedBuffers = managedBuffers;
153
+ this.buildInstanceData = options.buildInstanceData;
154
+ this.sizePropNames = options.sizePropNames;
155
+ }
156
+
157
+ override destroy(): void {
158
+ super.destroy();
159
+ destroyManagedInstanceBuffers(this._managedBuffers);
160
+ this._managedBuffers = {} as ManagedInstanceBuffers;
161
+ }
162
+
163
+ override draw(renderPass: RenderPass): boolean {
164
+ if (this.instanceCount === 0) {
165
+ return true;
166
+ }
167
+ return super.draw(renderPass);
168
+ }
169
+
170
+ setProps(props: Partial<PropsT>): void {
171
+ this.lightModelProps = {...this.lightModelProps, ...props};
172
+
173
+ if (props.parameters) {
174
+ this.setParameters(mergeLightMarkerParameters(this.lightModelProps.parameters));
175
+ }
176
+
177
+ if ('viewMatrix' in props || 'projectionMatrix' in props) {
178
+ this.shaderInputs.setProps({
179
+ lightMarker: {viewProjectionMatrix: createViewProjectionMatrix(this.lightModelProps)}
180
+ });
181
+ this.setNeedsRedraw('lightMarker camera');
182
+ }
183
+
184
+ if (shouldRebuildInstanceData(props, this.sizePropNames)) {
185
+ this.rebuildInstanceData();
186
+ }
187
+ }
188
+
189
+ private rebuildInstanceData(): void {
190
+ const nextInstanceData = this.buildInstanceData(this.lightModelProps);
191
+ const nextManagedBuffers = createManagedInstanceBuffers(this.device, this.id, nextInstanceData);
192
+
193
+ this.setAttributes(nextManagedBuffers);
194
+ this.setInstanceCount(nextInstanceData.instanceCount);
195
+
196
+ destroyManagedInstanceBuffers(this._managedBuffers);
197
+ this._managedBuffers = nextManagedBuffers;
198
+ this._instanceData = nextInstanceData;
199
+ }
200
+ }
201
+
202
+ export function buildPointLightInstanceData(props: PointLightModelProps): LightMarkerInstanceData {
203
+ const pointLights = getPointLights(props.lights);
204
+ const context = getLightMarkerContext(props);
205
+ const pointLightRadius =
206
+ props.pointLightRadius ??
207
+ DEFAULT_POINT_LIGHT_RADIUS_FACTOR * context.sceneScale * context.markerScale;
208
+
209
+ return createLightMarkerInstanceData(
210
+ pointLights.length,
211
+ (light, _index) => ({
212
+ color: getDisplayColor(light),
213
+ direction: DEFAULT_DIRECTION_FALLBACK,
214
+ position: light.position,
215
+ scale: [pointLightRadius, pointLightRadius, pointLightRadius]
216
+ }),
217
+ pointLights
218
+ );
219
+ }
220
+
221
+ export function buildSpotLightInstanceData(props: SpotLightModelProps): LightMarkerInstanceData {
222
+ const spotLights = getSpotLights(props.lights);
223
+ const context = getLightMarkerContext(props);
224
+ const spotLightLength =
225
+ props.spotLightLength ??
226
+ DEFAULT_SPOT_LIGHT_LENGTH_FACTOR * context.sceneScale * context.markerScale;
227
+
228
+ return createLightMarkerInstanceData(
229
+ spotLights.length,
230
+ (light, _index) => {
231
+ const outerConeAngle = clamp(
232
+ light.outerConeAngle ?? Math.PI / 4,
233
+ 0,
234
+ Math.PI / 2 - SPOTLIGHT_OUTER_CONE_EPSILON
235
+ );
236
+ const radius = Math.tan(outerConeAngle) * spotLightLength;
237
+
238
+ return {
239
+ color: getDisplayColor(light),
240
+ direction: normalizeDirection(light.direction),
241
+ position: light.position,
242
+ scale: [radius, spotLightLength, radius]
243
+ };
244
+ },
245
+ spotLights
246
+ );
247
+ }
248
+
249
+ export function buildDirectionalLightInstanceData(
250
+ props: DirectionalLightModelProps
251
+ ): LightMarkerInstanceData {
252
+ const directionalLights = getDirectionalLights(props.lights);
253
+ const context = getLightMarkerContext(props);
254
+ const directionalLightLength =
255
+ props.directionalLightLength ??
256
+ DEFAULT_DIRECTIONAL_LIGHT_LENGTH_FACTOR * context.sceneScale * context.markerScale;
257
+ const directionalLightRadius = directionalLightLength * DEFAULT_DIRECTIONAL_LIGHT_RADIUS_FACTOR;
258
+
259
+ return createLightMarkerInstanceData(
260
+ directionalLights.length,
261
+ (light, _index) => {
262
+ const direction = normalizeDirection(light.direction);
263
+ const position = [
264
+ context.sceneCenter[0] -
265
+ direction[0] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR,
266
+ context.sceneCenter[1] -
267
+ direction[1] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR,
268
+ context.sceneCenter[2] -
269
+ direction[2] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR
270
+ ] as [number, number, number];
271
+
272
+ return {
273
+ color: getDisplayColor(light),
274
+ direction,
275
+ position,
276
+ scale: [directionalLightRadius, directionalLightLength, directionalLightRadius]
277
+ };
278
+ },
279
+ directionalLights
280
+ );
281
+ }
282
+
283
+ export function getPointLights(lights: ReadonlyArray<Light>): PointLight[] {
284
+ return lights.filter((light): light is PointLight => light.type === 'point');
285
+ }
286
+
287
+ export function getSpotLights(lights: ReadonlyArray<Light>): SpotLight[] {
288
+ return lights.filter((light): light is SpotLight => light.type === 'spot');
289
+ }
290
+
291
+ export function getDirectionalLights(lights: ReadonlyArray<Light>): DirectionalLight[] {
292
+ return lights.filter((light): light is DirectionalLight => light.type === 'directional');
293
+ }
294
+
295
+ export function getLightMarkerContext(props: BaseLightModelProps): {
296
+ bounds: LightModelBounds;
297
+ markerScale: number;
298
+ sceneCenter: [number, number, number];
299
+ sceneScale: number;
300
+ } {
301
+ const bounds = getSceneBounds(props.lights, props.bounds);
302
+ const sceneCenter = [
303
+ (bounds[0][0] + bounds[1][0]) / 2,
304
+ (bounds[0][1] + bounds[1][1]) / 2,
305
+ (bounds[0][2] + bounds[1][2]) / 2
306
+ ] as [number, number, number];
307
+ const sceneScale = Math.max(
308
+ Math.hypot(
309
+ bounds[1][0] - bounds[0][0],
310
+ bounds[1][1] - bounds[0][1],
311
+ bounds[1][2] - bounds[0][2]
312
+ ),
313
+ MIN_SCENE_SCALE
314
+ );
315
+
316
+ return {
317
+ bounds,
318
+ markerScale: Math.max(props.markerScale ?? DEFAULT_MARKER_SCALE, 0),
319
+ sceneCenter,
320
+ sceneScale
321
+ };
322
+ }
323
+
324
+ export function getDisplayColor(light: {
325
+ color?: Readonly<[number, number, number]>;
326
+ intensity?: number;
327
+ }): [number, number, number, number] {
328
+ const color = light.color || DEFAULT_LIGHT_COLOR;
329
+ const intensity = Math.max(light.intensity ?? 1, 0);
330
+ const brightness = clamp(0.35 + 0.3 * Math.log10(intensity + 1), 0.35, 1);
331
+
332
+ return [
333
+ clamp(color[0] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
334
+ clamp(color[1] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
335
+ clamp(color[2] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
336
+ 1
337
+ ];
338
+ }
339
+
340
+ export function normalizeDirection(
341
+ direction?: Readonly<[number, number, number]>
342
+ ): [number, number, number] {
343
+ const [x, y, z] = direction || DEFAULT_DIRECTION_FALLBACK;
344
+ const length = Math.hypot(x, y, z);
345
+ if (length === 0) {
346
+ return [...DEFAULT_DIRECTION_FALLBACK];
347
+ }
348
+ return [x / length, y / length, z / length];
349
+ }
350
+
351
+ function createLightMarkerInstanceData<TLight>(
352
+ instanceCount: number,
353
+ getInstance: (
354
+ light: TLight,
355
+ index: number
356
+ ) => {
357
+ color: [number, number, number, number];
358
+ direction: [number, number, number];
359
+ position: Readonly<NumericArray>;
360
+ scale: [number, number, number];
361
+ },
362
+ lights: TLight[] = []
363
+ ): LightMarkerInstanceData {
364
+ const instancePositions = new Float32Array(instanceCount * 3);
365
+ const instanceDirections = new Float32Array(instanceCount * 3);
366
+ const instanceScales = new Float32Array(instanceCount * 3);
367
+ const instanceColors = new Float32Array(instanceCount * 4);
368
+
369
+ for (const [index, light] of lights.entries()) {
370
+ const instance = getInstance(light, index);
371
+ instancePositions.set(instance.position, index * 3);
372
+ instanceDirections.set(instance.direction, index * 3);
373
+ instanceScales.set(instance.scale, index * 3);
374
+ instanceColors.set(instance.color, index * 4);
375
+ }
376
+
377
+ return {
378
+ instanceCount,
379
+ instancePositions,
380
+ instanceDirections,
381
+ instanceScales,
382
+ instanceColors
383
+ };
384
+ }
385
+
386
+ function getSceneBounds(lights: ReadonlyArray<Light>, bounds?: LightModelBounds): LightModelBounds {
387
+ if (bounds) {
388
+ return cloneBounds(bounds);
389
+ }
390
+
391
+ const positions = [
392
+ ...getPointLights(lights).map(light => light.position),
393
+ ...getSpotLights(lights).map(light => light.position)
394
+ ];
395
+
396
+ if (positions.length === 0) {
397
+ return [
398
+ [-0.5, -0.5, -0.5],
399
+ [0.5, 0.5, 0.5]
400
+ ];
401
+ }
402
+
403
+ const minBounds: [number, number, number] = [...positions[0]] as [number, number, number];
404
+ const maxBounds: [number, number, number] = [...positions[0]] as [number, number, number];
405
+
406
+ for (const position of positions.slice(1)) {
407
+ minBounds[0] = Math.min(minBounds[0], position[0]);
408
+ minBounds[1] = Math.min(minBounds[1], position[1]);
409
+ minBounds[2] = Math.min(minBounds[2], position[2]);
410
+
411
+ maxBounds[0] = Math.max(maxBounds[0], position[0]);
412
+ maxBounds[1] = Math.max(maxBounds[1], position[1]);
413
+ maxBounds[2] = Math.max(maxBounds[2], position[2]);
414
+ }
415
+
416
+ return [minBounds, maxBounds];
417
+ }
418
+
419
+ function cloneBounds(bounds: LightModelBounds): LightModelBounds {
420
+ return [[...bounds[0]] as [number, number, number], [...bounds[1]] as [number, number, number]];
421
+ }
422
+
423
+ function createManagedInstanceBuffers(
424
+ device: Device,
425
+ idPrefix: string,
426
+ instanceData: LightMarkerInstanceData
427
+ ): ManagedInstanceBuffers {
428
+ return {
429
+ instancePosition: device.createBuffer({
430
+ id: `${idPrefix}-instance-position`,
431
+ data: getBufferDataOrPlaceholder(instanceData.instancePositions, 3)
432
+ }),
433
+ instanceDirection: device.createBuffer({
434
+ id: `${idPrefix}-instance-direction`,
435
+ data: getBufferDataOrPlaceholder(instanceData.instanceDirections, 3)
436
+ }),
437
+ instanceScale: device.createBuffer({
438
+ id: `${idPrefix}-instance-scale`,
439
+ data: getBufferDataOrPlaceholder(instanceData.instanceScales, 3)
440
+ }),
441
+ instanceColor: device.createBuffer({
442
+ id: `${idPrefix}-instance-color`,
443
+ data: getBufferDataOrPlaceholder(instanceData.instanceColors, 4)
444
+ })
445
+ };
446
+ }
447
+
448
+ function getBufferDataOrPlaceholder(data: Float32Array, size: number): Float32Array {
449
+ return data.length > 0 ? data : new Float32Array(size);
450
+ }
451
+
452
+ function destroyManagedInstanceBuffers(managedBuffers: Partial<ManagedInstanceBuffers>): void {
453
+ for (const buffer of Object.values(managedBuffers)) {
454
+ buffer?.destroy();
455
+ }
456
+ }
457
+
458
+ function createViewProjectionMatrix(
459
+ props: Pick<BaseLightModelProps, 'projectionMatrix' | 'viewMatrix'>
460
+ ): Matrix4 {
461
+ return new Matrix4(props.projectionMatrix).multiplyRight(props.viewMatrix);
462
+ }
463
+
464
+ function shouldRebuildInstanceData<PropsT extends BaseLightModelProps>(
465
+ props: Partial<PropsT>,
466
+ sizePropNames: Array<keyof PropsT>
467
+ ): boolean {
468
+ if ('lights' in props || 'bounds' in props || 'markerScale' in props) {
469
+ return true;
470
+ }
471
+ return sizePropNames.some(sizePropName => sizePropName in props);
472
+ }
473
+
474
+ function mergeLightMarkerParameters(
475
+ parameters?: RenderPipelineParameters
476
+ ): RenderPipelineParameters {
477
+ return {
478
+ ...LIGHT_MARKER_PARAMETERS,
479
+ ...(parameters || {})
480
+ };
481
+ }
482
+
483
+ function getLightMarkerShaders(anchorMode: LightMarkerAnchorMode): {
484
+ fs: string;
485
+ source: string;
486
+ vs: string;
487
+ } {
488
+ const localPositionWGSL =
489
+ anchorMode === 'apex' ? APEX_LOCAL_POSITION_WGSL : CENTERED_LOCAL_POSITION_WGSL;
490
+ const localPositionGLSL =
491
+ anchorMode === 'apex' ? APEX_LOCAL_POSITION_GLSL : CENTERED_LOCAL_POSITION_GLSL;
492
+
493
+ return {
494
+ source: `\
495
+ struct lightMarkerUniforms {
496
+ viewProjectionMatrix: mat4x4<f32>,
497
+ };
498
+
499
+ @binding(0) @group(0) var<uniform> lightMarker : lightMarkerUniforms;
500
+
501
+ struct VertexInputs {
502
+ @location(0) positions : vec3<f32>,
503
+ @location(1) instancePosition : vec3<f32>,
504
+ @location(2) instanceDirection : vec3<f32>,
505
+ @location(3) instanceScale : vec3<f32>,
506
+ @location(4) instanceColor : vec4<f32>,
507
+ };
508
+
509
+ struct FragmentInputs {
510
+ @builtin(position) Position : vec4<f32>,
511
+ @location(0) color : vec4<f32>,
512
+ };
513
+
514
+ fn lightMarker_rotate(localPosition: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
515
+ let forward = normalize(direction);
516
+ var helperAxis = vec3<f32>(0.0, 1.0, 0.0);
517
+ if (abs(forward.y) > 0.999) {
518
+ helperAxis = vec3<f32>(1.0, 0.0, 0.0);
519
+ }
520
+
521
+ let tangent = normalize(cross(helperAxis, forward));
522
+ let bitangent = cross(forward, tangent);
523
+ return tangent * localPosition.x + forward * localPosition.y + bitangent * localPosition.z;
524
+ }
525
+
526
+ @vertex
527
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
528
+ var outputs : FragmentInputs;
529
+ let localPosition = ${localPositionWGSL};
530
+ let worldPosition = inputs.instancePosition + lightMarker_rotate(localPosition, inputs.instanceDirection);
531
+ outputs.Position = lightMarker.viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
532
+ outputs.color = inputs.instanceColor;
533
+ return outputs;
534
+ }
535
+
536
+ @fragment
537
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
538
+ return inputs.color;
539
+ }
540
+ `,
541
+ vs: `\
542
+ #version 300 es
543
+
544
+ in vec3 positions;
545
+ in vec3 instancePosition;
546
+ in vec3 instanceDirection;
547
+ in vec3 instanceScale;
548
+ in vec4 instanceColor;
549
+
550
+ layout(std140) uniform lightMarkerUniforms {
551
+ mat4 viewProjectionMatrix;
552
+ } lightMarker;
553
+
554
+ out vec4 vColor;
555
+
556
+ vec3 lightMarker_rotate(vec3 localPosition, vec3 direction) {
557
+ vec3 forward = normalize(direction);
558
+ vec3 helperAxis = abs(forward.y) > 0.999 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);
559
+ vec3 tangent = normalize(cross(helperAxis, forward));
560
+ vec3 bitangent = cross(forward, tangent);
561
+ return tangent * localPosition.x + forward * localPosition.y + bitangent * localPosition.z;
562
+ }
563
+
564
+ void main(void) {
565
+ vec3 localPosition = ${localPositionGLSL};
566
+ vec3 worldPosition = instancePosition + lightMarker_rotate(localPosition, instanceDirection);
567
+ gl_Position = lightMarker.viewProjectionMatrix * vec4(worldPosition, 1.0);
568
+ vColor = instanceColor;
569
+ }
570
+ `,
571
+ fs: `\
572
+ #version 300 es
573
+ precision highp float;
574
+
575
+ in vec4 vColor;
576
+ out vec4 fragColor;
577
+
578
+ void main(void) {
579
+ fragColor = vColor;
580
+ }
581
+ `
582
+ };
583
+ }
584
+
585
+ function clamp(value: number, minValue: number, maxValue: number): number {
586
+ return Math.min(maxValue, Math.max(minValue, value));
587
+ }
@@ -0,0 +1,31 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Device} from '@luma.gl/core';
6
+ import {SphereGeometry} from '../geometries/sphere-geometry';
7
+ import {
8
+ BaseLightModel,
9
+ buildPointLightInstanceData,
10
+ type PointLightModelProps
11
+ } from './light-model-utils';
12
+
13
+ const POINT_LIGHT_GEOMETRY = new SphereGeometry({
14
+ nlat: 8,
15
+ nlong: 12,
16
+ radius: 1
17
+ });
18
+
19
+ export type {PointLightModelProps} from './light-model-utils';
20
+
21
+ export class PointLightModel extends BaseLightModel<PointLightModelProps> {
22
+ constructor(device: Device, props: PointLightModelProps) {
23
+ super(device, props, {
24
+ anchorMode: 'centered',
25
+ buildInstanceData: buildPointLightInstanceData,
26
+ geometry: POINT_LIGHT_GEOMETRY,
27
+ idPrefix: 'point-light-model',
28
+ sizePropNames: ['pointLightRadius']
29
+ });
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Device} from '@luma.gl/core';
6
+ import {ConeGeometry} from '../geometries/cone-geometry';
7
+ import {
8
+ BaseLightModel,
9
+ buildSpotLightInstanceData,
10
+ type SpotLightModelProps
11
+ } from './light-model-utils';
12
+
13
+ const SPOT_LIGHT_GEOMETRY = new ConeGeometry({
14
+ cap: true,
15
+ nradial: 16,
16
+ nvertical: 1,
17
+ radius: 1
18
+ });
19
+
20
+ export type {SpotLightModelProps} from './light-model-utils';
21
+
22
+ export class SpotLightModel extends BaseLightModel<SpotLightModelProps> {
23
+ constructor(device: Device, props: SpotLightModelProps) {
24
+ super(device, props, {
25
+ anchorMode: 'apex',
26
+ buildInstanceData: buildSpotLightInstanceData,
27
+ geometry: SPOT_LIGHT_GEOMETRY,
28
+ idPrefix: 'spot-light-model',
29
+ sizePropNames: ['spotLightLength']
30
+ });
31
+ }
32
+ }