@woosh/meep-engine 2.134.4 → 2.135.0

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 (156) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/editor/tools/v2/TransformControlsGizmo.js +1 -1
  4. package/editor/view/node-graph/NodeGraphEditorView.js +2 -2
  5. package/package.json +1 -1
  6. package/src/core/assert.d.ts +0 -2
  7. package/src/core/assert.d.ts.map +1 -1
  8. package/src/core/assert.js +0 -6
  9. package/src/core/color/Color.d.ts +0 -5
  10. package/src/core/color/Color.d.ts.map +1 -1
  11. package/src/core/color/Color.js +1 -7
  12. package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +386 -386
  13. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +1 -1
  14. package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
  15. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +160 -160
  16. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +96 -96
  17. package/src/core/geom/packing/max-rect/MaxRectanglesPacker.js +1 -1
  18. package/src/core/geom/packing/max-rect/findBestContainer.js +4 -4
  19. package/src/core/geom/packing/max-rect/packOneBox.js +2 -2
  20. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts +23 -0
  21. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts.map +1 -0
  22. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.js +96 -0
  23. package/src/core/graph/layout/box/BoxLayouter.js +7 -7
  24. package/src/core/graph/layout/box/position_box_next_to_box.js +6 -6
  25. package/src/core/math/computeWholeDivisorLow.js +33 -33
  26. package/src/core/math/linalg/eigen/eigen_values_find_spectral_gap.d.ts.map +1 -0
  27. package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts +10 -0
  28. package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts.map +1 -0
  29. package/src/core/{graph → math/linalg}/eigen/matrix_eigenvalues_in_place.js +8 -7
  30. package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts.map +1 -0
  31. package/src/core/{graph → math/linalg}/eigen/matrix_householder_in_place.js +11 -5
  32. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts +15 -0
  33. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts.map +1 -0
  34. package/src/core/{graph → math/linalg}/eigen/matrix_qr_in_place.js +8 -2
  35. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts +17 -0
  36. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts.map +1 -0
  37. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.js +107 -0
  38. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts +19 -0
  39. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts.map +1 -0
  40. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +161 -0
  41. package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts +15 -0
  42. package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts.map +1 -0
  43. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +200 -0
  44. package/src/core/math/solveCubic.d.ts +15 -0
  45. package/src/core/math/solveCubic.d.ts.map +1 -0
  46. package/src/core/math/solveCubic.js +82 -0
  47. package/src/core/math/spline/spline3_hermite_bounds_t.d.ts +23 -0
  48. package/src/core/math/spline/spline3_hermite_bounds_t.d.ts.map +1 -0
  49. package/src/core/math/spline/spline3_hermite_bounds_t.js +109 -0
  50. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +25 -0
  51. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -0
  52. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +44 -0
  53. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts +16 -0
  54. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts.map +1 -0
  55. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +120 -0
  56. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +11 -0
  57. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -0
  58. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +451 -0
  59. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +12 -0
  60. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -0
  61. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +339 -0
  62. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts +15 -0
  63. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts.map +1 -0
  64. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +21 -0
  65. package/src/core/math/spline/spline3_hermite_to_monomial.d.ts +24 -0
  66. package/src/core/math/spline/spline3_hermite_to_monomial.d.ts.map +1 -0
  67. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -0
  68. package/src/core/math/spline/v3_computeCatmullRomSplineUniformDistance.js +1 -1
  69. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +1 -1
  70. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  71. package/src/core/process/delay.js +16 -16
  72. package/src/engine/animation/async/TimeSeries.js +300 -300
  73. package/src/engine/animation/curve/AnimationCurve.d.ts +3 -2
  74. package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
  75. package/src/engine/animation/curve/AnimationCurve.js +3 -2
  76. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +2 -2
  77. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +2 -2
  78. package/src/engine/ecs/fow/shader/FogOfWarRenderer.js +145 -145
  79. package/src/engine/ecs/gui/position/ViewportPositionSystem.js +2 -2
  80. package/src/engine/ecs/parent/entity_node_compute_bounding_box.js +1 -1
  81. package/src/engine/ecs/transform/Transform.d.ts +0 -10
  82. package/src/engine/ecs/transform/Transform.d.ts.map +1 -1
  83. package/src/engine/ecs/transform/Transform.js +0 -12
  84. package/src/engine/graphics/composit/CompositLayer.js +254 -254
  85. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +1 -1
  86. package/src/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +2 -2
  87. package/src/engine/graphics/material/optimization/MaterialOptimizationContext.js +3 -3
  88. package/src/engine/graphics/particles/particular/engine/utils/volume/AttributeValue.js +201 -201
  89. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
  90. package/src/engine/graphics/render/buffer/slot/parameter/ProgramValueSlotParameterSet.js +2 -2
  91. package/src/engine/graphics/render/forward_plus/LightManager.js +1226 -1226
  92. package/src/engine/graphics/render/forward_plus/model/PointLight.js +1 -1
  93. package/src/engine/graphics/sh3/lpv/lpv_obtain_storage_cached_volume.js +1 -1
  94. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +2 -2
  95. package/src/engine/graphics/texture/atlas/TextureAtlasDebugger.js +1 -1
  96. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  97. package/src/engine/graphics/texture/sampler/serialization/TextureBinaryBufferSerializer.js +2 -2
  98. package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts +2 -6
  99. package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts.map +1 -1
  100. package/src/engine/intelligence/behavior/ecs/BehaviorComponent.js +0 -10
  101. package/src/engine/intelligence/mcts/MonteCarlo.js +275 -275
  102. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  103. package/src/generation/grid/GridData.js +220 -220
  104. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +385 -385
  105. package/src/view/elements/image/SvgImageView.js +1 -1
  106. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  107. package/src/view/minimap/gl/MinimapFogOfWar.js +3 -3
  108. package/src/view/util/DomSizeObserver.js +1 -1
  109. package/src/core/binary/clz32.d.ts +0 -6
  110. package/src/core/binary/clz32.d.ts.map +0 -1
  111. package/src/core/binary/clz32.js +0 -5
  112. package/src/core/binary/type/dataTypeFromTypedArray.d.ts +0 -8
  113. package/src/core/binary/type/dataTypeFromTypedArray.d.ts.map +0 -1
  114. package/src/core/binary/type/dataTypeFromTypedArray.js +0 -11
  115. package/src/core/collection/array/computeHashIntegerArray.d.ts +0 -1
  116. package/src/core/collection/array/computeHashIntegerArray.d.ts.map +0 -1
  117. package/src/core/collection/array/computeHashIntegerArray.js +0 -7
  118. package/src/core/collection/array/typed/typedArrayToDataType.d.ts +0 -6
  119. package/src/core/collection/array/typed/typedArrayToDataType.d.ts.map +0 -1
  120. package/src/core/collection/array/typed/typedArrayToDataType.js +0 -6
  121. package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.d.ts +0 -6
  122. package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.d.ts.map +0 -1
  123. package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.js +0 -7
  124. package/src/core/graph/eigen/eigen_values_find_spectral_gap.d.ts.map +0 -1
  125. package/src/core/graph/eigen/matrix_eigenvalues_in_place.d.ts +0 -8
  126. package/src/core/graph/eigen/matrix_eigenvalues_in_place.d.ts.map +0 -1
  127. package/src/core/graph/eigen/matrix_householder_in_place.d.ts.map +0 -1
  128. package/src/core/graph/eigen/matrix_qr_in_place.d.ts +0 -9
  129. package/src/core/graph/eigen/matrix_qr_in_place.d.ts.map +0 -1
  130. package/src/core/math/spline/cubicCurve.d.ts +0 -6
  131. package/src/core/math/spline/cubicCurve.d.ts.map +0 -1
  132. package/src/core/math/spline/cubicCurve.js +0 -6
  133. package/src/core/math/spline/spline_bezier2.d.ts +0 -6
  134. package/src/core/math/spline/spline_bezier2.d.ts.map +0 -1
  135. package/src/core/math/spline/spline_bezier2.js +0 -6
  136. package/src/core/math/spline/spline_bezier3.d.ts +0 -6
  137. package/src/core/math/spline/spline_bezier3.d.ts.map +0 -1
  138. package/src/core/math/spline/spline_bezier3.js +0 -6
  139. package/src/core/math/spline/spline_bezier3_bounds.d.ts +0 -6
  140. package/src/core/math/spline/spline_bezier3_bounds.d.ts.map +0 -1
  141. package/src/core/math/spline/spline_bezier3_bounds.js +0 -6
  142. package/src/core/math/spline/spline_hermite3.d.ts +0 -6
  143. package/src/core/math/spline/spline_hermite3.d.ts.map +0 -1
  144. package/src/core/math/spline/spline_hermite3.js +0 -6
  145. package/src/core/math/spline/spline_hermite3_bounds.d.ts +0 -6
  146. package/src/core/math/spline/spline_hermite3_bounds.d.ts.map +0 -1
  147. package/src/core/math/spline/spline_hermite3_bounds.js +0 -6
  148. package/src/core/math/spline/spline_hermite3_to_bezier.d.ts +0 -2
  149. package/src/core/math/spline/spline_hermite3_to_bezier.d.ts.map +0 -1
  150. package/src/core/math/spline/spline_hermite3_to_bezier.js +0 -6
  151. package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.d.ts +0 -37
  152. package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.d.ts.map +0 -1
  153. package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.js +0 -70
  154. /package/src/core/{graph → math/linalg}/eigen/eigen_values_find_spectral_gap.d.ts +0 -0
  155. /package/src/core/{graph → math/linalg}/eigen/eigen_values_find_spectral_gap.js +0 -0
  156. /package/src/core/{graph → math/linalg}/eigen/matrix_householder_in_place.d.ts +0 -0
@@ -1,1226 +1,1226 @@
1
- import { mat4 } from "gl-matrix";
2
- import {
3
- ClampToEdgeWrapping,
4
- DataTexture,
5
- DataTexture3D,
6
- Frustum,
7
- LinearFilter,
8
- LinearMipMapLinearFilter,
9
- NearestFilter,
10
- RGBAFormat,
11
- RGBAIntegerFormat,
12
- UnsignedByteType,
13
- UnsignedShortType
14
- } from "three";
15
- import { assert } from "../../../../core/assert.js";
16
- import { computeBinaryDataTypeByPrecision } from "../../../../core/binary/type/computeBinaryDataTypeByPrecision.js";
17
- import {
18
- DataType2TypedArrayConstructorMapping
19
- } from "../../../../core/binary/type/DataType2TypedArrayConstructorMapping.js";
20
- import { BinaryUint32BVH } from "../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
21
- import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
22
- import {
23
- bvh_query_user_data_overlaps_frustum
24
- } from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
25
- import { array_copy } from "../../../../core/collection/array/array_copy.js";
26
- import { array_sort_quick } from "../../../../core/collection/array/array_sort_quick.js";
27
- import { array_swap_one } from "../../../../core/collection/array/array_swap_one.js";
28
- import { read_cluster_frustum_corners } from "../../../../core/geom/3d/frustum/read_cluster_frustum_corners.js";
29
- import { read_three_planes_to_array } from "../../../../core/geom/3d/frustum/read_three_planes_to_array.js";
30
- import { slice_frustum_linear_to_points } from "../../../../core/geom/3d/frustum/slice_frustum_linear_to_points.js";
31
- import { v3_morton_encode_transformed } from "../../../../core/geom/3d/morton/v3_morton_encode_transformed.js";
32
- import { v3_distance } from "../../../../core/geom/vec3/v3_distance.js";
33
- import Vector3 from "../../../../core/geom/Vector3.js";
34
- import { NumericType } from "../../../../core/math/NumericType.js";
35
- import { frustum_from_camera } from "../../ecs/camera/frustum_from_camera.js";
36
- import { CachingTextureAtlas } from "../../texture/atlas/CachingTextureAtlas.js";
37
- import { ReferencedTextureAtlas } from "../../texture/atlas/ReferencedTextureAtlas.js";
38
- import { TextureAtlas } from "../../texture/atlas/TextureAtlas.js";
39
- import {
40
- computeThreeTextureInternalFormatFromDataType
41
- } from "../../texture/computeThreeTextureInternalFormatFromDataType.js";
42
- import { computeThreeTextureTypeFromDataType } from "../../texture/computeThreeTextureTypeFromDataType.js";
43
- import { writeSample2DDataToDataTexture } from "../../texture/sampler/writeSampler2DDataToDataTexture.js";
44
- import { TextureBackedMemoryRegion } from "../../texture/TextureBackedMemoryRegion.js";
45
- import { IncrementalDeltaSet } from "../visibility/IncrementalDeltaSet.js";
46
- import { assign_cluster, LOOKUP_CACHE, scratch_corners, scratch_frustum_planes } from "./assign_cluster.js";
47
- import { compute_cluster_planes_from_points } from "./cluster/compute_cluster_planes_from_points.js";
48
- import { read_plane_pair } from "./cluster/read_plane_pair.js";
49
- import { computeFrustumCorners } from "./computeFrustumCorners.js";
50
- import { LightRenderMetadata } from "./LightRenderMetadata.js";
51
- import { Decal } from "./model/Decal.js";
52
- import { point_light_inside_volume } from "./query/point_light_inside_volume.js";
53
-
54
-
55
- /**
56
- * How many bits are used to encode light type
57
- * @type {number}
58
- */
59
- const LIGHT_ENCODING_TYPE_BIT_COUNT = 2;
60
-
61
- /**
62
- *
63
- * @param {AbstractLight|PointLight|Decal} light
64
- * @returns {number}
65
- */
66
- function encode_light_type(light) {
67
- if (light.isPointLight === true) {
68
- return 0;
69
- } else if (light.isDecal === true) {
70
- return 3;
71
- } else {
72
- throw new Error('Unsupported light type');
73
- }
74
- }
75
-
76
- /**
77
- *
78
- * @param {number} address
79
- * @param {AbstractLight} light
80
- * @returns {number}
81
- */
82
- function encode_light_descriptor(address, light) {
83
- const light_type = encode_light_type(light);
84
- return light_type | (address << 2);
85
- }
86
-
87
- /**
88
- *
89
- * @param {IncrementalDeltaSet<LightRenderMetadata>} data_set
90
- * @param {BinaryUint32BVH} bvh
91
- */
92
- function build_light_data_bvh(data_set, bvh) {
93
- const elements = data_set.elements;
94
- const element_count = data_set.size;
95
-
96
- bvh.setLeafCount(element_count);
97
- bvh.initialize_structure();
98
-
99
- for (let i = 0; i < element_count; i++) {
100
-
101
- /**
102
- *
103
- * @type {LightRenderMetadata}
104
- */
105
- const datum = elements[i];
106
-
107
- const bb = datum.bvh_leaf.bounds;
108
-
109
- const payload = encode_light_descriptor(datum.address, datum.light);
110
-
111
- bvh.setLeafData(
112
- i, payload,
113
- bb[0], bb[1], bb[2],
114
- bb[3], bb[4], bb[5],
115
- );
116
- }
117
-
118
- // vbvh.sort_morton(this.__projection_matrix);
119
- // vbvh.sort_bubble_sah();
120
-
121
- bvh.build();
122
- }
123
-
124
- /**
125
- *
126
- * @param {LightRenderMetadata} a
127
- * @param {LightRenderMetadata} b
128
- * @returns {number}
129
- */
130
- function compareLRMByLightId(a, b) {
131
- return b.light.id - a.light.id;
132
- }
133
-
134
- export class LightManager {
135
-
136
- /**
137
- * @readonly
138
- * @type {BVH}
139
- */
140
- #light_data_bvh = new BVH();
141
-
142
- /**
143
- * Mapping from a light/decal object ID internal metadata
144
- * @type {Map<number,LightRenderMetadata>}
145
- */
146
- #metadata_map = new Map();
147
-
148
-
149
- constructor() {
150
-
151
- /**
152
- * Number of cluster slices along each dimension
153
- * @type {Vector3}
154
- * @private
155
- */
156
- this.__tiles_resolution = new Vector3(32, 16, 8);
157
-
158
- this.__cluster_texture_precision = 8;
159
- this.__cluster_texture_needs_rebuild = false;
160
-
161
- /**
162
- * Texel layout:
163
- * - R: offset into lookup texture for lights
164
- * - G: light count
165
- * - B: offset into lookup texture for decals
166
- * - A: decal count
167
- * @type {DataTexture3D}
168
- * @private
169
- */
170
- this.__cluster_texture = new DataTexture3D(
171
- new Uint16Array(4),
172
- 1,
173
- 1,
174
- 1,
175
- );
176
-
177
- this.__cluster_texture.flipY = false;
178
- this.__cluster_texture.generateMipmaps = false;
179
- this.__cluster_texture.unpackAlignment = 2;
180
-
181
- this.__cluster_texture.magFilter = NearestFilter;
182
- this.__cluster_texture.minFilter = NearestFilter;
183
-
184
- this.__cluster_texture.type = UnsignedShortType;
185
- this.__cluster_texture.format = RGBAIntegerFormat;
186
- this.__cluster_texture.internalFormat = "RGBA16UI";
187
-
188
- /**
189
- *
190
- * @type {TextureBackedMemoryRegion}
191
- * @readonly
192
- * @private
193
- */
194
- this.__lookup_data = new TextureBackedMemoryRegion();
195
- this.__lookup_data.type = NumericType.Uint;
196
- this.__lookup_data.channel_count = 1;
197
- this.__lookup_data.precision = 8;
198
- this.__lookup_data.resize(1);
199
-
200
- /**
201
- *
202
- * @type {TextureBackedMemoryRegion}
203
- * @readonly
204
- * @private
205
- */
206
- this.__light_data = new TextureBackedMemoryRegion();
207
- this.__light_data.type = NumericType.Float;
208
- this.__light_data.precision = 32;
209
- this.__light_data.channel_count = 4;
210
- this.__light_data.resize(8);
211
-
212
- /**
213
- * @type {DataTexture}
214
- * @readonly
215
- * @private
216
- */
217
- this.__decal_atlas_texture = new DataTexture(new Uint8Array(4), 1, 1, RGBAFormat);
218
- this.__decal_atlas_texture.type = UnsignedByteType;
219
- this.__decal_atlas_texture.flipY = false;
220
- this.__decal_atlas_texture.wrapS = ClampToEdgeWrapping;
221
- this.__decal_atlas_texture.wrapT = ClampToEdgeWrapping;
222
- this.__decal_atlas_texture.minFilter = LinearMipMapLinearFilter;
223
- this.__decal_atlas_texture.magFilter = LinearFilter;
224
- this.__decal_atlas_texture.generateMipmaps = true;
225
- this.__decal_atlas_texture.unpackAlignment = 4;
226
- this.__decal_atlas_texture.anisotropy = 8;
227
-
228
- /**
229
- *
230
- * @type {AbstractTextureAtlas}
231
- * @readonly
232
- * @private
233
- */
234
- this.__decal_atlas = new CachingTextureAtlas({
235
- atlas: new TextureAtlas(512)
236
- });
237
-
238
- /**
239
- *
240
- * @type {ReferencedTextureAtlas}
241
- * @private
242
- */
243
- this.__decal_patch_references = new ReferencedTextureAtlas(this.__decal_atlas);
244
-
245
- /**
246
- *
247
- * @type {Map<Decal, Reference<AtlasPatch>>}
248
- * @private
249
- */
250
- this.__decal_references = new Map();
251
-
252
- /**
253
- *
254
- * @type {IncrementalDeltaSet<LightRenderMetadata>}
255
- * @readonly
256
- * @private
257
- */
258
- this.__visible_lights = new IncrementalDeltaSet(compareLRMByLightId);
259
- /**
260
- *
261
- * @type {IncrementalDeltaSet<LightRenderMetadata>}
262
- * @readonly
263
- * @private
264
- */
265
- this.__visible_decals = new IncrementalDeltaSet(compareLRMByLightId);
266
-
267
- /**
268
- *
269
- * @type {LightRenderMetadata[]}
270
- * @private
271
- */
272
- this.__sorted_visible_lights = [];
273
-
274
- /**
275
- *
276
- * @type {BinaryUint32BVH}
277
- * @private
278
- */
279
- this.__visible_bvh_lights = new BinaryUint32BVH();
280
- /**
281
- *
282
- * @type {BinaryUint32BVH}
283
- * @private
284
- */
285
- this.__visible_bvh_decals = new BinaryUint32BVH();
286
-
287
- /**
288
- *
289
- * @type {Frustum}
290
- * @private
291
- */
292
- this.__view_frustum = new Frustum();
293
-
294
- /**
295
- * Corner points of the view frustum
296
- * @type {Float32Array}
297
- * @private
298
- */
299
- this.__view_frustum_points = new Float32Array(24);
300
-
301
- /**
302
- *
303
- * @type {Float32Array|mat4|number[]}
304
- * @private
305
- */
306
- this.__projection_matrix = new Float32Array(16);
307
-
308
-
309
- /**
310
- * Accelerated data structure with pre-computed plane normals for clusters in each dimensions. Order: X, Y, Z
311
- * @type {Float32Array}
312
- * @private
313
- */
314
- this.__cluster_planes = new Float32Array(1);
315
-
316
- /**
317
- * Accelerated data structure with pre-computed corner coordinates of each cluster's frustum
318
- * @type {Float32Array}
319
- * @private
320
- */
321
- this.__cluster_frustum_points = new Float32Array(1);
322
-
323
- this.setTileMapResolution(32, 16, 16);
324
-
325
- this.__visible_lights.onAdded.add(this.__handle_visible_light_added, this);
326
- this.__visible_lights.onRemoved.add(this.__handle_visible_light_removed, this);
327
-
328
- this.__visible_decals.onAdded.add(this.__handle_visible_decal_added, this);
329
- this.__visible_decals.onRemoved.add(this.__handle_visible_decal_removed, this);
330
-
331
- /**
332
- * Data needs to be re-written into the data texture
333
- * Usually set when source lights change
334
- * @type {boolean}
335
- * @private
336
- */
337
- this.__light_data_needs_update = true;
338
-
339
- /**
340
- *
341
- * @type {boolean}
342
- * @private
343
- */
344
- this.__visible_bvh_needs_update = true;
345
-
346
- // window.light_manager = this; // DEBUG
347
- }
348
-
349
- requestDataUpdate() {
350
- this.__light_data_needs_update = true;
351
- }
352
-
353
- /**
354
- * Please set this to false if you have a lot of overlapping decals in the scene
355
- * Overlapping decals can provide filtering artifacts due to incorrect mipmap level detection by WebGL
356
- * see article: https://0fps.net/2013/07/09/texture-atlases-wrapping-and-mip-mapping/
357
- * NOTE: the technique mentioned in the article above is not implemented, as it would require significant increase in number of texture fetches
358
- * @param {boolean} v
359
- */
360
- set decal_filtering_enabled(v) {
361
- const t = this.__decal_atlas_texture;
362
-
363
- if (t.generateMipmaps === v) {
364
- return;
365
- }
366
-
367
- t.generateMipmaps = v;
368
- }
369
-
370
- /**
371
- *
372
- * @return {boolean}
373
- */
374
- get decal_filtering_enabled() {
375
- return this.__decal_atlas_texture.generateMipmaps;
376
- }
377
-
378
- __update_decal_atlas_texture() {
379
- const sampler = this.__decal_atlas.sampler;
380
-
381
- if (sampler.version !== this.__decal_atlas_texture.version) {
382
- writeSample2DDataToDataTexture(sampler, this.__decal_atlas_texture);
383
-
384
- this.__decal_atlas_texture.version = sampler.version;
385
- }
386
- }
387
-
388
- /**
389
- * Sometimes light lookup table can get quite large, to make sure we can store these addresses in the cluster texture,
390
- * we may need to switch to a larger data type, such as Uint8->Uint16 or Uint16->Uint32
391
- * @param {number} bit_count How many bits should be addressable from the cluster content
392
- * @returns {boolean} true if texture was rebuilt (underlying type change), false otherwise
393
- * @private
394
- */
395
- __set_cluster_addressable_bit_range(bit_count) {
396
- const rounded_value = Math.ceil(bit_count);
397
-
398
- this.__cluster_texture_precision = rounded_value;
399
- const dataType = computeBinaryDataTypeByPrecision(NumericType.Uint, rounded_value);
400
-
401
- const threeTextureType = computeThreeTextureTypeFromDataType(dataType);
402
-
403
- const texture = this.__cluster_texture;
404
- if (threeTextureType !== texture.type) {
405
- this.__cluster_texture_needs_rebuild = true;
406
-
407
- return true;
408
- } else {
409
- return false;
410
- }
411
- }
412
-
413
- __update_cluster_texture() {
414
- if (this.__cluster_texture_needs_rebuild) {
415
- this.__build_cluster_texture();
416
- }
417
- }
418
-
419
- __build_cluster_texture() {
420
- const dataType = computeBinaryDataTypeByPrecision(NumericType.Uint, this.__cluster_texture_precision);
421
-
422
- const threeTextureType = computeThreeTextureTypeFromDataType(dataType);
423
-
424
- const texture = this.__cluster_texture;
425
-
426
- const channelCount = 4;
427
-
428
- texture.dispose();
429
-
430
- const image = texture.image;
431
-
432
- const resolution = this.__tiles_resolution;
433
-
434
- image.width = resolution.x;
435
- image.height = resolution.y;
436
- image.depth = resolution.z;
437
-
438
- texture.type = threeTextureType;
439
- texture.internalFormat = computeThreeTextureInternalFormatFromDataType(dataType, channelCount);
440
-
441
- const ArrayConstructor = DataType2TypedArrayConstructorMapping[dataType];
442
-
443
- image.data = new ArrayConstructor(resolution.x * resolution.y * resolution.z * channelCount);
444
-
445
- texture.needsUpdate = true;
446
-
447
- this.__cluster_texture_needs_rebuild = false;
448
- }
449
-
450
- /**
451
- *
452
- * @param {number} size
453
- * @returns {boolean}
454
- * @private
455
- */
456
- __ensure_lookup_size(size) {
457
- return this.__lookup_data.resize(size);
458
- }
459
-
460
- /**
461
- *
462
- * @returns {DataTexture}
463
- */
464
- getTextureLookup() {
465
- return this.__lookup_data.getTexture();
466
- }
467
-
468
- /**
469
- *
470
- * @returns {DataTexture}
471
- */
472
- getTextureData() {
473
- return this.__light_data.getTexture();
474
- }
475
-
476
- /**
477
- * @returns {DataTexture3D}
478
- */
479
- getTextureClusters() {
480
- return this.__cluster_texture;
481
- }
482
-
483
- /**
484
- * @returns {DataTexture}
485
- */
486
- getTextureDecalAtlas() {
487
- return this.__decal_atlas_texture;
488
- }
489
-
490
- /**
491
- * NOTE: do not modify the value
492
- * @returns {Readonly<Vector3>}
493
- */
494
- getResolution() {
495
- return this.__tiles_resolution;
496
- }
497
-
498
- /**
499
- *
500
- * @private
501
- */
502
- __handle_light_dimensions_change() {
503
- // internal BVH needs to be rebuilt
504
- this.__visible_bvh_needs_update = true;
505
- // when dimensions change, typically data will need to be re-written to the GPU, things like light positions or projection matrix
506
- this.__light_data_needs_update = true;
507
- }
508
-
509
- /**
510
- *
511
- * @param {LightRenderMetadata} data
512
- * @private
513
- */
514
- __handle_visible_decal_added(data) {
515
- this.__light_data_needs_update = true;
516
-
517
- /**
518
- *
519
- * @type {Decal}
520
- */
521
- const light = data.light;
522
-
523
- light.onDimensionChanged(this.__handle_light_dimensions_change, this);
524
-
525
- const ref = this.__decal_patch_references.acquire(light.texture_diffuse);
526
-
527
- const patch = ref.getValue();
528
- const patch_uv = patch.uv;
529
-
530
- patch_uv.position.onChanged.add(light.handleUvPositionChange, light);
531
- patch_uv.size.onChanged.add(light.handleUvSizeChange, light);
532
-
533
- light.uv[0] = patch_uv.position.x;
534
- light.uv[1] = patch_uv.position.y;
535
- light.uv[2] = patch_uv.size.x;
536
- light.uv[3] = patch_uv.size.y;
537
-
538
- this.__decal_references.set(light, ref);
539
- }
540
-
541
- /**
542
- *
543
- * @param {LightRenderMetadata} data
544
- * @private
545
- */
546
- __handle_visible_decal_removed(data) {
547
- this.__light_data_needs_update = true;
548
-
549
- /**
550
- *
551
- * @type {Decal}
552
- */
553
- const light = data.light;
554
-
555
- light.offDimensionChanged(this.__handle_light_dimensions_change, this);
556
-
557
- const ref = this.__decal_references.get(light);
558
-
559
- if (ref === undefined) {
560
- // This can occur when decal changes while being in the visible list, messing with the IncrementalDeltaSet's compare order
561
- console.warn(`Decal reference not found: ${light}`);
562
- return;
563
- }
564
-
565
- const patch = ref.getValue();
566
- const patch_uv = patch.uv;
567
-
568
- // unsubscribe
569
- patch_uv.position.onChanged.remove(light.handleUvPositionChange, light);
570
- patch_uv.size.onChanged.remove(light.handleUvSizeChange, light);
571
-
572
-
573
- ref.release();
574
-
575
- this.__decal_references.delete(light);
576
- }
577
-
578
- /**
579
- *
580
- * @param {LightRenderMetadata} data
581
- * @private
582
- */
583
- __handle_visible_light_added(data) {
584
- this.__light_data_needs_update = true;
585
-
586
- const light = data.light;
587
-
588
- light.onDimensionChanged(this.__handle_light_dimensions_change, this);
589
- }
590
-
591
- /**
592
- *
593
- * @param {LightRenderMetadata} data
594
- * @private
595
- */
596
- __handle_visible_light_removed(data) {
597
- this.__light_data_needs_update = true;
598
-
599
- const light = data.light;
600
- light.offDimensionChanged(this.__handle_light_dimensions_change, this);
601
- }
602
-
603
- /**
604
- *
605
- * @param {LightRenderMetadata} light
606
- * @returns {number}
607
- * @protected
608
- */
609
- __sort_visible_light_score(light) {
610
- const center = scratch_corners;
611
-
612
- light.light.getCenter(center)
613
-
614
- const x = center[0];
615
- const y = center[1];
616
- const z = center[2];
617
-
618
- return v3_morton_encode_transformed(x, y, z, this.__projection_matrix);
619
- }
620
-
621
- /**
622
- * DEBUG method
623
- * @param {LightRenderMetadata} lights
624
- * @param {number} count
625
- * @returns {number} lower = better sorting score
626
- * @private
627
- */
628
- __assess_sorting_score(lights, count) {
629
-
630
- let r = 0;
631
-
632
- const center_a = [];
633
- const center_b = [];
634
-
635
- for (let i = 0; i < count; i++) {
636
- const a = lights[i];
637
-
638
- const score_a = this.__sort_visible_light_score(a);
639
- a.light.getCenter(center_a);
640
-
641
- for (let j = i + 1; j < count; j++) {
642
- const b = lights[j];
643
-
644
- const score_b = this.__sort_visible_light_score(b);
645
- b.light.getCenter(center_b);
646
-
647
- //
648
- const score_distance = Math.abs(score_a - score_b);
649
- const position_distance = v3_distance(
650
- center_a[0], center_a[1], center_a[2],
651
- center_b[0], center_b[1], center_b[2],
652
- );
653
-
654
- r += score_distance / position_distance;
655
- }
656
- }
657
-
658
-
659
- return r / (count * (count - 1) * 0.5);
660
- }
661
-
662
- /**
663
- * Sort lights for better data locality
664
- * @private
665
- */
666
- __sort_visible_light() {
667
- const sorted_lights = this.__sorted_visible_lights;
668
-
669
- const visible_light_set = this.__visible_lights;
670
- const visible_light_count = visible_light_set.size;
671
-
672
- const visible_decal_set = this.__visible_decals;
673
- const visible_decal_count = visible_decal_set.size;
674
-
675
- const expected_sorted_size = visible_light_count + visible_decal_count;
676
-
677
- if (expected_sorted_size > visible_light_count) {
678
- // crop array if it's too big
679
- sorted_lights.splice(visible_light_count, expected_sorted_size - visible_light_count)
680
- }
681
-
682
- array_copy(visible_light_set.elements, 0, sorted_lights, 0, visible_light_count);
683
- array_copy(visible_decal_set.elements, 0, sorted_lights, visible_light_count, visible_decal_count);
684
-
685
- array_sort_quick(sorted_lights, this.__sort_visible_light_score, this, 0, expected_sorted_size - 1);
686
- }
687
-
688
- __update_visible_bvh() {
689
- this.__visible_bvh_needs_update = false;
690
-
691
- build_light_data_bvh(
692
- this.__visible_lights,
693
- this.__visible_bvh_lights
694
- );
695
-
696
- build_light_data_bvh(
697
- this.__visible_decals,
698
- this.__visible_bvh_decals
699
- );
700
- }
701
-
702
-
703
- /**
704
- *
705
- * @param {Camera} camera
706
- * @private
707
- */
708
- __build_view_frustum(camera) {
709
- frustum_from_camera(camera, this.__view_frustum, false);
710
-
711
- array_swap_one(this.__view_frustum.planes, 4, 5);
712
-
713
- this.__build_view_frustum_points();
714
- }
715
-
716
- __build_visible_light_list() {
717
-
718
- /**
719
- *
720
- * @type {IncrementalDeltaSet<LightRenderMetadata>}
721
- */
722
- const visible_lights = this.__visible_lights;
723
-
724
- visible_lights.initializeUpdate();
725
-
726
- const visible_decals = this.__visible_decals;
727
-
728
- visible_decals.initializeUpdate();
729
-
730
- const nodes = [];
731
-
732
- read_three_planes_to_array(this.__view_frustum.planes, scratch_frustum_planes)
733
-
734
- /*
735
- Search is done in 2 phases:
736
- 1) broad phase using bounding boxes
737
- 2) granular phase where we use object-specific shape to check against frustum
738
- */
739
-
740
- const broad_match_count = bvh_query_user_data_overlaps_frustum(nodes, 0, this.#light_data_bvh, scratch_frustum_planes);
741
-
742
- for (let i = 0; i < broad_match_count; i++) {
743
-
744
- const light_id = nodes[i];
745
-
746
- /**
747
- *
748
- * @type {LightRenderMetadata}
749
- */
750
- const light_data = this.#metadata_map.get(light_id);
751
-
752
- /**
753
- *
754
- * @type {PointLight|Decal}
755
- */
756
- const light = light_data.light;
757
-
758
- if (light.isDecal === true) {
759
-
760
-
761
- // decals go into a separate bucket
762
- visible_decals.push(light_data);
763
-
764
- } else if (light.isPointLight === true) {
765
- // perform granular check
766
-
767
- const light_position = light.position;
768
-
769
- const light_x = light_position.x;
770
- const light_y = light_position.y;
771
- const light_z = light_position.z;
772
-
773
- const radius = light.radius.getValue();
774
-
775
- if (!point_light_inside_volume(light_x, light_y, light_z, radius, this.__view_frustum_points, scratch_frustum_planes)) {
776
- // outside of view frustum
777
- continue;
778
- }
779
-
780
-
781
- // register as visible
782
- visible_lights.push(light_data);
783
- }
784
-
785
- }
786
-
787
- visible_lights.finalizeUpdate();
788
-
789
- visible_decals.finalizeUpdate();
790
- }
791
-
792
- __write_light_data_texture() {
793
-
794
- /**
795
- *
796
- * @type {LightRenderMetadata[]}
797
- */
798
- const visible_lights = this.__sorted_visible_lights;
799
-
800
- // write light data into texture
801
- const visible_light_count = visible_lights.length;
802
-
803
- // compute amount of space required for the lights
804
- let address = 0;
805
- for (let i = 0; i < visible_light_count; i++) {
806
- const datum = visible_lights[i];
807
-
808
- /**
809
- *
810
- * @type {PointLight}
811
- */
812
- const light = datum.light;
813
-
814
- address += light.ENCODED_SLOT_COUNT;
815
- }
816
-
817
- const expected_light_data_size = address;
818
-
819
-
820
- const light_data = this.__light_data;
821
- light_data.resize(Math.ceil(address / 4));
822
-
823
- light_data.update();
824
-
825
- // update required lookup precision
826
- this.__lookup_data.precision = Math.max(
827
- 0,
828
- Math.ceil(Math.log2(light_data.size))
829
- ) + LIGHT_ENCODING_TYPE_BIT_COUNT;
830
-
831
- const tx_light = light_data.getTexture();
832
-
833
- /**
834
- *
835
- * @type {Float32Array}
836
- */
837
- const tx_light_data = tx_light.image.data;
838
-
839
- address = 0;
840
-
841
- for (let i = 0; i < visible_light_count; i++) {
842
- const datum = visible_lights[i];
843
-
844
- assert.lessThan(address, tx_light_data.length, "overflow");
845
-
846
- /**
847
- *
848
- * @type {PointLight}
849
- */
850
- const light = datum.light;
851
-
852
- datum.address = address >> 2;
853
-
854
- const written_slots = light.toArray(tx_light_data, address);
855
-
856
- address += written_slots;
857
-
858
- // align address to 4 slot boundary
859
- if ((address & 3) !== 0) {
860
- // not on 4 slot boundary
861
- address = ((address >> 2) + 1) << 2;
862
- }
863
- }
864
-
865
- if (address !== expected_light_data_size) {
866
- throw new Error(`Expected light data size is ${expected_light_data_size}, actual written data is size is ${address}`);
867
- }
868
-
869
- tx_light.needsUpdate = true;
870
-
871
- this.__visible_bvh_needs_update = true;
872
- }
873
-
874
- /**
875
- * Perform cluster assignment where each cluster is filled with overlapping lights
876
- * @private
877
- */
878
- __assign_lights_to_clusters() {
879
-
880
- const tiles_resolution = this.__tiles_resolution;
881
-
882
- const tr_z = tiles_resolution.z;
883
- const tr_y = tiles_resolution.y;
884
- const tr_x = tiles_resolution.x;
885
-
886
- const tr_x_1 = tr_x + 1;
887
- const tr_y_1 = tr_y + 1;
888
-
889
- const tr_xy_1 = tr_x_1 * tr_y_1;
890
-
891
- /**
892
- *
893
- * @type {Float32Array}
894
- */
895
- const cluster_planes = this.__cluster_planes;
896
-
897
- const bvh_lights = this.__visible_bvh_lights;
898
- const bvh_decals = this.__visible_bvh_decals;
899
-
900
-
901
- const cluster_planes_x_offset = 0;
902
- const cluster_planes_y_offset = tr_x_1 * 4;
903
- const cluster_planes_z_offset = cluster_planes_y_offset + tr_y_1 * 4;
904
-
905
- /**
906
- *
907
- * @type {number[]|Float32Array}
908
- */
909
- const light_source_data = this.__light_data.getTexture().image.data;
910
-
911
- const tile_slice_size = tr_x * tr_y;
912
-
913
- const light_cluster_texture = this.__cluster_texture;
914
- const tile_texture_data = light_cluster_texture.image.data;
915
-
916
- const light_lookup = this.__lookup_data;
917
- light_lookup.update();
918
- const light_lookup_texture = light_lookup.getTexture();
919
- const light_lookup_texture_data = light_lookup_texture.image.data;
920
-
921
- const cluster_frustum_points = this.__cluster_frustum_points;
922
-
923
- let lookup_address_offset = 0;
924
- let i_x_0, i_y_0, i_z_0;
925
-
926
- // clear cache
927
- LOOKUP_CACHE.fill(0xFFFFFFFF);
928
-
929
- // console.log('Assignment Start');
930
-
931
- for (i_z_0 = 0; i_z_0 < tr_z; i_z_0++) {
932
-
933
- // construct z planes
934
-
935
- const slice_offset = i_z_0 * tile_slice_size;
936
-
937
- const z_plane_index_offset_0 = cluster_planes_z_offset + i_z_0 * 4;
938
-
939
- read_plane_pair(cluster_planes, z_plane_index_offset_0, scratch_frustum_planes, 16);
940
-
941
- for (i_y_0 = 0; i_y_0 < tr_y; i_y_0++) {
942
-
943
- // construct y planes
944
-
945
- const y_plane_index_offset_0 = cluster_planes_y_offset + i_y_0 * 4;
946
-
947
- read_plane_pair(cluster_planes, y_plane_index_offset_0, scratch_frustum_planes, 8);
948
-
949
- for (i_x_0 = 0; i_x_0 < tr_x; i_x_0++) {
950
-
951
- // construct x planes
952
-
953
- const x_plane_index_offset_0 = cluster_planes_x_offset + i_x_0 * 4;
954
-
955
- read_plane_pair(cluster_planes, x_plane_index_offset_0, scratch_frustum_planes, 0);
956
-
957
- read_cluster_frustum_corners(scratch_corners, i_z_0, tr_xy_1, i_y_0, tr_x_1, i_x_0, cluster_frustum_points);
958
-
959
- const tile_index = slice_offset + (i_y_0 * tr_x) + i_x_0;
960
-
961
- const tile_data_offset = tile_index * 4;
962
-
963
- //assign lights
964
- lookup_address_offset += assign_cluster(
965
- tile_data_offset,
966
- bvh_lights,
967
- lookup_address_offset,
968
- light_lookup_texture_data,
969
- tile_texture_data,
970
- light_source_data
971
- );
972
-
973
- // assign decals
974
- const decal_count = assign_cluster(
975
- tile_data_offset + 2,
976
- bvh_decals,
977
- lookup_address_offset,
978
- light_lookup_texture_data,
979
- tile_texture_data,
980
- light_source_data
981
- );
982
-
983
- /*
984
- TODO fix decal sorting artifacts
985
-
986
- if (decal_count > 1) { // only sort when there are 2 or more, sorting 1 object is pointless
987
- // decals need to be sorted for correct blending result
988
- sort_decal_data(
989
- light_lookup_texture_data,
990
- light_source_data,
991
- lookup_address_offset,
992
- decal_count
993
- );
994
- }
995
-
996
- */
997
-
998
- lookup_address_offset += decal_count;
999
- }
1000
- }
1001
- }
1002
-
1003
- light_cluster_texture.needsUpdate = true;
1004
- light_lookup_texture.needsUpdate = true;
1005
-
1006
- // Post-fact lookup texture resize, this frame will be wrong, but next one should be ok
1007
- const current_lookup_size = this.__lookup_data.size;
1008
-
1009
- const lookup_memory_resized = this.__ensure_lookup_size(lookup_address_offset);
1010
-
1011
- const cluster_resized = this.__set_cluster_addressable_bit_range(Math.max(0, Math.log2(lookup_address_offset)));
1012
-
1013
- if (
1014
- (lookup_memory_resized && current_lookup_size < lookup_address_offset)
1015
- || cluster_resized
1016
- ) {
1017
- // overflow, we ended up trying to write more lights into some clusters than what they could contain
1018
- this.__update_cluster_texture();
1019
-
1020
- // re-do assignment
1021
- this.__assign_lights_to_clusters();
1022
- }
1023
-
1024
- // console.log('Unique assignment count: ', assignment_count, ', Hash reuse:', hash_reuse_count);
1025
- }
1026
-
1027
- /**
1028
- *
1029
- * @private
1030
- */
1031
- __build_cluster_frustum_planes() {
1032
- const tiles_resolution = this.__tiles_resolution;
1033
-
1034
- const tr_z = tiles_resolution.z;
1035
- const tr_y = tiles_resolution.y;
1036
- const tr_x = tiles_resolution.x;
1037
-
1038
- const destination = this.__cluster_planes;
1039
-
1040
- compute_cluster_planes_from_points(destination, this.__cluster_frustum_points, tr_x, tr_y, tr_z);
1041
- }
1042
-
1043
- /**
1044
- *
1045
- * @private
1046
- */
1047
- __build_view_frustum_points() {
1048
-
1049
- const view_frustum = this.__view_frustum;
1050
-
1051
- const points = this.__view_frustum_points;
1052
-
1053
- computeFrustumCorners(points, view_frustum.planes);
1054
- }
1055
-
1056
- /**
1057
- *
1058
- * @private
1059
- */
1060
- __build_cluster_frustum_points() {
1061
- const tiles_resolution = this.__tiles_resolution;
1062
-
1063
- const tr_z = tiles_resolution.z;
1064
- const tr_y = tiles_resolution.y;
1065
- const tr_x = tiles_resolution.x;
1066
-
1067
- const view_frustum_points = this.__view_frustum_points;
1068
- const points_data = this.__cluster_frustum_points;
1069
-
1070
- slice_frustum_linear_to_points(view_frustum_points, tr_x, tr_y, tr_z, points_data);
1071
- }
1072
-
1073
- /**
1074
- * Build light tile texture
1075
- * @param {Camera|THREE.PerspectiveCamera} camera
1076
- */
1077
- buildTiles(camera) {
1078
- // camera.updateProjectionMatrix();
1079
- // camera.updateMatrix();
1080
- // camera.updateMatrixWorld(true);
1081
-
1082
- //update world inverse matrix
1083
- camera.matrixWorldInverse.copy(camera.matrixWorld);
1084
- camera.matrixWorldInverse.invert();
1085
-
1086
- // this.__projection_matrix.set(camera.matrixWorldInverse.elements);
1087
- mat4.multiply(this.__projection_matrix, camera.projectionMatrix.elements, camera.matrixWorldInverse.elements);
1088
-
1089
- // console.time('__build_view_frustum');
1090
- this.__build_view_frustum(camera);
1091
- // console.timeEnd('__build_view_frustum');
1092
-
1093
-
1094
- // console.time('__build_visible_light_list');
1095
- this.__build_visible_light_list();
1096
- // console.timeEnd('__build_visible_light_list');
1097
-
1098
- // update decal atlas
1099
- this.__decal_atlas.update();
1100
- this.__update_decal_atlas_texture();
1101
-
1102
- // console.time('__write_light_data_texture');
1103
- if (this.__light_data_needs_update) {
1104
- // sort light for better search and query locality
1105
- this.__sort_visible_light();
1106
-
1107
- this.__write_light_data_texture();
1108
-
1109
- // clear flag
1110
- this.__light_data_needs_update = false;
1111
- }
1112
- // console.timeEnd('__write_light_data_texture');
1113
-
1114
- if (this.__visible_bvh_needs_update) {
1115
- this.__update_visible_bvh();
1116
- }
1117
-
1118
- this.__build_cluster_frustum_points();
1119
- // console.time('__build_cluster_frustum_planes');
1120
- this.__build_cluster_frustum_planes();
1121
- // console.timeEnd('__build_cluster_frustum_planes');
1122
-
1123
- // console.time('__assign_lights_to_clusters');
1124
- this.__assign_lights_to_clusters();
1125
- // console.timeEnd('__assign_lights_to_clusters');
1126
- }
1127
-
1128
- dispose() {
1129
- this.__cluster_texture.dispose();
1130
- this.__light_data.dispose();
1131
- this.__lookup_data.dispose();
1132
- this.__decal_atlas_texture.dispose();
1133
- }
1134
-
1135
- /**
1136
- * Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
1137
- * @param {number} x
1138
- * @param {number} y
1139
- * @param {number} z
1140
- */
1141
- setTileMapResolution(x, y, z) {
1142
- assert.isNumber(x, 'x');
1143
- assert.isNonNegativeInteger(x, 'x');
1144
- assert.isFiniteNumber(x, 'x');
1145
- assert.greaterThan(x, 0, 'x must be > 0');
1146
-
1147
- assert.isNumber(y, 'y');
1148
- assert.isNonNegativeInteger(y, 'y');
1149
- assert.isFiniteNumber(y, 'y');
1150
- assert.greaterThan(y, 0, 'y must be > 0');
1151
-
1152
- assert.isNumber(z, 'z');
1153
- assert.isNonNegativeInteger(z, 'z');
1154
- assert.isFiniteNumber(z, 'z');
1155
- assert.greaterThan(z, 0, 'z must be > 0');
1156
-
1157
- const r = this.__tiles_resolution;
1158
-
1159
- if (x === r.x && y === r.y && z === r.z) {
1160
- // special case, no change
1161
- return;
1162
- }
1163
-
1164
- r.set(x, y, z);
1165
-
1166
- this.__cluster_texture_needs_rebuild = true;
1167
- this.__update_cluster_texture();
1168
-
1169
- const cluster_planes_size = ((x + 1) + (y + 1) + (z + 1)) * 4;
1170
- const cluster_frustum_points_size = (x + 1) * (y + 1) * (z + 1) * 3;
1171
-
1172
- const buffer = new ArrayBuffer((cluster_planes_size + cluster_frustum_points_size) * 4);
1173
-
1174
- this.__cluster_planes = new Float32Array(
1175
- buffer, 0,
1176
- cluster_planes_size
1177
- );
1178
- this.__cluster_frustum_points = new Float32Array(
1179
- buffer, cluster_planes_size * 4,
1180
- cluster_frustum_points_size
1181
- );
1182
- }
1183
-
1184
- /**
1185
- *
1186
- * @param {AbstractLight} light
1187
- * @returns {boolean}
1188
- */
1189
- hasLight(light) {
1190
- return this.#metadata_map.has(light.id);
1191
- }
1192
-
1193
- /**
1194
- *
1195
- * @param {AbstractLight} light
1196
- */
1197
- addLight(light) {
1198
- const lightData = new LightRenderMetadata(light);
1199
-
1200
- lightData.link(this.#light_data_bvh);
1201
-
1202
- this.#metadata_map.set(light.id, lightData);
1203
- }
1204
-
1205
- /**
1206
- *
1207
- * @param {AbstractLight} light
1208
- * @returns {boolean}
1209
- */
1210
- removeLight(light) {
1211
-
1212
- const light_id = light.id;
1213
-
1214
- const data = this.#metadata_map.get(light_id);
1215
-
1216
- if (data === undefined) {
1217
- return false;
1218
- }
1219
-
1220
- data.unlink();
1221
-
1222
- this.#metadata_map.delete(light_id);
1223
-
1224
- return true;
1225
- }
1226
- }
1
+ import { mat4 } from "gl-matrix";
2
+ import {
3
+ ClampToEdgeWrapping,
4
+ DataTexture,
5
+ DataTexture3D,
6
+ Frustum,
7
+ LinearFilter,
8
+ LinearMipMapLinearFilter,
9
+ NearestFilter,
10
+ RGBAFormat,
11
+ RGBAIntegerFormat,
12
+ UnsignedByteType,
13
+ UnsignedShortType
14
+ } from "three";
15
+ import { assert } from "../../../../core/assert.js";
16
+ import { computeBinaryDataTypeByPrecision } from "../../../../core/binary/type/computeBinaryDataTypeByPrecision.js";
17
+ import {
18
+ DataType2TypedArrayConstructorMapping
19
+ } from "../../../../core/binary/type/DataType2TypedArrayConstructorMapping.js";
20
+ import { BinaryUint32BVH } from "../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
21
+ import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
22
+ import {
23
+ bvh_query_user_data_overlaps_frustum
24
+ } from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
25
+ import { array_copy } from "../../../../core/collection/array/array_copy.js";
26
+ import { array_sort_quick } from "../../../../core/collection/array/array_sort_quick.js";
27
+ import { array_swap_one } from "../../../../core/collection/array/array_swap_one.js";
28
+ import { read_cluster_frustum_corners } from "../../../../core/geom/3d/frustum/read_cluster_frustum_corners.js";
29
+ import { read_three_planes_to_array } from "../../../../core/geom/3d/frustum/read_three_planes_to_array.js";
30
+ import { slice_frustum_linear_to_points } from "../../../../core/geom/3d/frustum/slice_frustum_linear_to_points.js";
31
+ import { v3_morton_encode_transformed } from "../../../../core/geom/3d/morton/v3_morton_encode_transformed.js";
32
+ import { v3_distance } from "../../../../core/geom/vec3/v3_distance.js";
33
+ import Vector3 from "../../../../core/geom/Vector3.js";
34
+ import { NumericType } from "../../../../core/math/NumericType.js";
35
+ import { frustum_from_camera } from "../../ecs/camera/frustum_from_camera.js";
36
+ import { CachingTextureAtlas } from "../../texture/atlas/CachingTextureAtlas.js";
37
+ import { ReferencedTextureAtlas } from "../../texture/atlas/ReferencedTextureAtlas.js";
38
+ import { TextureAtlas } from "../../texture/atlas/TextureAtlas.js";
39
+ import {
40
+ computeThreeTextureInternalFormatFromDataType
41
+ } from "../../texture/computeThreeTextureInternalFormatFromDataType.js";
42
+ import { computeThreeTextureTypeFromDataType } from "../../texture/computeThreeTextureTypeFromDataType.js";
43
+ import { writeSample2DDataToDataTexture } from "../../texture/sampler/writeSampler2DDataToDataTexture.js";
44
+ import { TextureBackedMemoryRegion } from "../../texture/TextureBackedMemoryRegion.js";
45
+ import { IncrementalDeltaSet } from "../visibility/IncrementalDeltaSet.js";
46
+ import { assign_cluster, LOOKUP_CACHE, scratch_corners, scratch_frustum_planes } from "./assign_cluster.js";
47
+ import { compute_cluster_planes_from_points } from "./cluster/compute_cluster_planes_from_points.js";
48
+ import { read_plane_pair } from "./cluster/read_plane_pair.js";
49
+ import { computeFrustumCorners } from "./computeFrustumCorners.js";
50
+ import { LightRenderMetadata } from "./LightRenderMetadata.js";
51
+ import { Decal } from "./model/Decal.js";
52
+ import { point_light_inside_volume } from "./query/point_light_inside_volume.js";
53
+
54
+
55
+ /**
56
+ * How many bits are used to encode light type
57
+ * @type {number}
58
+ */
59
+ const LIGHT_ENCODING_TYPE_BIT_COUNT = 2;
60
+
61
+ /**
62
+ *
63
+ * @param {AbstractLight|PointLight|Decal} light
64
+ * @returns {number}
65
+ */
66
+ function encode_light_type(light) {
67
+ if (light.isPointLight === true) {
68
+ return 0;
69
+ } else if (light.isDecal === true) {
70
+ return 3;
71
+ } else {
72
+ throw new Error('Unsupported light type');
73
+ }
74
+ }
75
+
76
+ /**
77
+ *
78
+ * @param {number} address
79
+ * @param {AbstractLight} light
80
+ * @returns {number}
81
+ */
82
+ function encode_light_descriptor(address, light) {
83
+ const light_type = encode_light_type(light);
84
+ return light_type | (address << 2);
85
+ }
86
+
87
+ /**
88
+ *
89
+ * @param {IncrementalDeltaSet<LightRenderMetadata>} data_set
90
+ * @param {BinaryUint32BVH} bvh
91
+ */
92
+ function build_light_data_bvh(data_set, bvh) {
93
+ const elements = data_set.elements;
94
+ const element_count = data_set.size;
95
+
96
+ bvh.setLeafCount(element_count);
97
+ bvh.initialize_structure();
98
+
99
+ for (let i = 0; i < element_count; i++) {
100
+
101
+ /**
102
+ *
103
+ * @type {LightRenderMetadata}
104
+ */
105
+ const datum = elements[i];
106
+
107
+ const bb = datum.bvh_leaf.bounds;
108
+
109
+ const payload = encode_light_descriptor(datum.address, datum.light);
110
+
111
+ bvh.setLeafData(
112
+ i, payload,
113
+ bb[0], bb[1], bb[2],
114
+ bb[3], bb[4], bb[5],
115
+ );
116
+ }
117
+
118
+ // vbvh.sort_morton(this.__projection_matrix);
119
+ // vbvh.sort_bubble_sah();
120
+
121
+ bvh.build();
122
+ }
123
+
124
+ /**
125
+ *
126
+ * @param {LightRenderMetadata} a
127
+ * @param {LightRenderMetadata} b
128
+ * @returns {number}
129
+ */
130
+ function compareLRMByLightId(a, b) {
131
+ return b.light.id - a.light.id;
132
+ }
133
+
134
+ export class LightManager {
135
+
136
+ /**
137
+ * @readonly
138
+ * @type {BVH}
139
+ */
140
+ #light_data_bvh = new BVH();
141
+
142
+ /**
143
+ * Mapping from a light/decal object ID internal metadata
144
+ * @type {Map<number,LightRenderMetadata>}
145
+ */
146
+ #metadata_map = new Map();
147
+
148
+
149
+ constructor() {
150
+
151
+ /**
152
+ * Number of cluster slices along each dimension
153
+ * @type {Vector3}
154
+ * @private
155
+ */
156
+ this.__tiles_resolution = new Vector3(32, 16, 8);
157
+
158
+ this.__cluster_texture_precision = 8;
159
+ this.__cluster_texture_needs_rebuild = false;
160
+
161
+ /**
162
+ * Texel layout:
163
+ * - R: offset into lookup texture for lights
164
+ * - G: light count
165
+ * - B: offset into lookup texture for decals
166
+ * - A: decal count
167
+ * @type {DataTexture3D}
168
+ * @private
169
+ */
170
+ this.__cluster_texture = new DataTexture3D(
171
+ new Uint16Array(4),
172
+ 1,
173
+ 1,
174
+ 1,
175
+ );
176
+
177
+ this.__cluster_texture.flipY = false;
178
+ this.__cluster_texture.generateMipmaps = false;
179
+ this.__cluster_texture.unpackAlignment = 2;
180
+
181
+ this.__cluster_texture.magFilter = NearestFilter;
182
+ this.__cluster_texture.minFilter = NearestFilter;
183
+
184
+ this.__cluster_texture.type = UnsignedShortType;
185
+ this.__cluster_texture.format = RGBAIntegerFormat;
186
+ this.__cluster_texture.internalFormat = "RGBA16UI";
187
+
188
+ /**
189
+ *
190
+ * @type {TextureBackedMemoryRegion}
191
+ * @readonly
192
+ * @private
193
+ */
194
+ this.__lookup_data = new TextureBackedMemoryRegion();
195
+ this.__lookup_data.type = NumericType.Uint;
196
+ this.__lookup_data.channel_count = 1;
197
+ this.__lookup_data.precision = 8;
198
+ this.__lookup_data.resize(1);
199
+
200
+ /**
201
+ *
202
+ * @type {TextureBackedMemoryRegion}
203
+ * @readonly
204
+ * @private
205
+ */
206
+ this.__light_data = new TextureBackedMemoryRegion();
207
+ this.__light_data.type = NumericType.Float;
208
+ this.__light_data.precision = 32;
209
+ this.__light_data.channel_count = 4;
210
+ this.__light_data.resize(8);
211
+
212
+ /**
213
+ * @type {DataTexture}
214
+ * @readonly
215
+ * @private
216
+ */
217
+ this.__decal_atlas_texture = new DataTexture(new Uint8Array(4), 1, 1, RGBAFormat);
218
+ this.__decal_atlas_texture.type = UnsignedByteType;
219
+ this.__decal_atlas_texture.flipY = false;
220
+ this.__decal_atlas_texture.wrapS = ClampToEdgeWrapping;
221
+ this.__decal_atlas_texture.wrapT = ClampToEdgeWrapping;
222
+ this.__decal_atlas_texture.minFilter = LinearMipMapLinearFilter;
223
+ this.__decal_atlas_texture.magFilter = LinearFilter;
224
+ this.__decal_atlas_texture.generateMipmaps = true;
225
+ this.__decal_atlas_texture.unpackAlignment = 4;
226
+ this.__decal_atlas_texture.anisotropy = 8;
227
+
228
+ /**
229
+ *
230
+ * @type {AbstractTextureAtlas}
231
+ * @readonly
232
+ * @private
233
+ */
234
+ this.__decal_atlas = new CachingTextureAtlas({
235
+ atlas: new TextureAtlas(512)
236
+ });
237
+
238
+ /**
239
+ *
240
+ * @type {ReferencedTextureAtlas}
241
+ * @private
242
+ */
243
+ this.__decal_patch_references = new ReferencedTextureAtlas(this.__decal_atlas);
244
+
245
+ /**
246
+ *
247
+ * @type {Map<Decal, Reference<AtlasPatch>>}
248
+ * @private
249
+ */
250
+ this.__decal_references = new Map();
251
+
252
+ /**
253
+ *
254
+ * @type {IncrementalDeltaSet<LightRenderMetadata>}
255
+ * @readonly
256
+ * @private
257
+ */
258
+ this.__visible_lights = new IncrementalDeltaSet(compareLRMByLightId);
259
+ /**
260
+ *
261
+ * @type {IncrementalDeltaSet<LightRenderMetadata>}
262
+ * @readonly
263
+ * @private
264
+ */
265
+ this.__visible_decals = new IncrementalDeltaSet(compareLRMByLightId);
266
+
267
+ /**
268
+ *
269
+ * @type {LightRenderMetadata[]}
270
+ * @private
271
+ */
272
+ this.__sorted_visible_lights = [];
273
+
274
+ /**
275
+ *
276
+ * @type {BinaryUint32BVH}
277
+ * @private
278
+ */
279
+ this.__visible_bvh_lights = new BinaryUint32BVH();
280
+ /**
281
+ *
282
+ * @type {BinaryUint32BVH}
283
+ * @private
284
+ */
285
+ this.__visible_bvh_decals = new BinaryUint32BVH();
286
+
287
+ /**
288
+ *
289
+ * @type {Frustum}
290
+ * @private
291
+ */
292
+ this.__view_frustum = new Frustum();
293
+
294
+ /**
295
+ * Corner points of the view frustum
296
+ * @type {Float32Array}
297
+ * @private
298
+ */
299
+ this.__view_frustum_points = new Float32Array(24);
300
+
301
+ /**
302
+ *
303
+ * @type {Float32Array|mat4|number[]}
304
+ * @private
305
+ */
306
+ this.__projection_matrix = new Float32Array(16);
307
+
308
+
309
+ /**
310
+ * Accelerated data structure with pre-computed plane normals for clusters in each dimensions. Order: X, Y, Z
311
+ * @type {Float32Array}
312
+ * @private
313
+ */
314
+ this.__cluster_planes = new Float32Array(1);
315
+
316
+ /**
317
+ * Accelerated data structure with pre-computed corner coordinates of each cluster's frustum
318
+ * @type {Float32Array}
319
+ * @private
320
+ */
321
+ this.__cluster_frustum_points = new Float32Array(1);
322
+
323
+ this.setTileMapResolution(32, 16, 16);
324
+
325
+ this.__visible_lights.onAdded.add(this.__handle_visible_light_added, this);
326
+ this.__visible_lights.onRemoved.add(this.__handle_visible_light_removed, this);
327
+
328
+ this.__visible_decals.onAdded.add(this.__handle_visible_decal_added, this);
329
+ this.__visible_decals.onRemoved.add(this.__handle_visible_decal_removed, this);
330
+
331
+ /**
332
+ * Data needs to be re-written into the data texture
333
+ * Usually set when source lights change
334
+ * @type {boolean}
335
+ * @private
336
+ */
337
+ this.__light_data_needs_update = true;
338
+
339
+ /**
340
+ *
341
+ * @type {boolean}
342
+ * @private
343
+ */
344
+ this.__visible_bvh_needs_update = true;
345
+
346
+ // window.light_manager = this; // DEBUG
347
+ }
348
+
349
+ requestDataUpdate() {
350
+ this.__light_data_needs_update = true;
351
+ }
352
+
353
+ /**
354
+ * Please set this to false if you have a lot of overlapping decals in the scene
355
+ * Overlapping decals can provide filtering artifacts due to incorrect mipmap level detection by WebGL
356
+ * see article: https://0fps.net/2013/07/09/texture-atlases-wrapping-and-mip-mapping/
357
+ * NOTE: the technique mentioned in the article above is not implemented, as it would require significant increase in number of texture fetches
358
+ * @param {boolean} v
359
+ */
360
+ set decal_filtering_enabled(v) {
361
+ const t = this.__decal_atlas_texture;
362
+
363
+ if (t.generateMipmaps === v) {
364
+ return;
365
+ }
366
+
367
+ t.generateMipmaps = v;
368
+ }
369
+
370
+ /**
371
+ *
372
+ * @return {boolean}
373
+ */
374
+ get decal_filtering_enabled() {
375
+ return this.__decal_atlas_texture.generateMipmaps;
376
+ }
377
+
378
+ __update_decal_atlas_texture() {
379
+ const sampler = this.__decal_atlas.sampler;
380
+
381
+ if (sampler.version !== this.__decal_atlas_texture.version) {
382
+ writeSample2DDataToDataTexture(sampler, this.__decal_atlas_texture);
383
+
384
+ this.__decal_atlas_texture.version = sampler.version;
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Sometimes light lookup table can get quite large, to make sure we can store these addresses in the cluster texture,
390
+ * we may need to switch to a larger data type, such as Uint8->Uint16 or Uint16->Uint32
391
+ * @param {number} bit_count How many bits should be addressable from the cluster content
392
+ * @returns {boolean} true if texture was rebuilt (underlying type change), false otherwise
393
+ * @private
394
+ */
395
+ __set_cluster_addressable_bit_range(bit_count) {
396
+ const rounded_value = Math.ceil(bit_count);
397
+
398
+ this.__cluster_texture_precision = rounded_value;
399
+ const dataType = computeBinaryDataTypeByPrecision(NumericType.Uint, rounded_value);
400
+
401
+ const threeTextureType = computeThreeTextureTypeFromDataType(dataType);
402
+
403
+ const texture = this.__cluster_texture;
404
+ if (threeTextureType !== texture.type) {
405
+ this.__cluster_texture_needs_rebuild = true;
406
+
407
+ return true;
408
+ } else {
409
+ return false;
410
+ }
411
+ }
412
+
413
+ __update_cluster_texture() {
414
+ if (this.__cluster_texture_needs_rebuild) {
415
+ this.__build_cluster_texture();
416
+ }
417
+ }
418
+
419
+ __build_cluster_texture() {
420
+ const dataType = computeBinaryDataTypeByPrecision(NumericType.Uint, this.__cluster_texture_precision);
421
+
422
+ const threeTextureType = computeThreeTextureTypeFromDataType(dataType);
423
+
424
+ const texture = this.__cluster_texture;
425
+
426
+ const channelCount = 4;
427
+
428
+ texture.dispose();
429
+
430
+ const image = texture.image;
431
+
432
+ const resolution = this.__tiles_resolution;
433
+
434
+ image.width = resolution.x;
435
+ image.height = resolution.y;
436
+ image.depth = resolution.z;
437
+
438
+ texture.type = threeTextureType;
439
+ texture.internalFormat = computeThreeTextureInternalFormatFromDataType(dataType, channelCount);
440
+
441
+ const ArrayConstructor = DataType2TypedArrayConstructorMapping[dataType];
442
+
443
+ image.data = new ArrayConstructor(resolution.x * resolution.y * resolution.z * channelCount);
444
+
445
+ texture.needsUpdate = true;
446
+
447
+ this.__cluster_texture_needs_rebuild = false;
448
+ }
449
+
450
+ /**
451
+ *
452
+ * @param {number} size
453
+ * @returns {boolean}
454
+ * @private
455
+ */
456
+ __ensure_lookup_size(size) {
457
+ return this.__lookup_data.resize(size);
458
+ }
459
+
460
+ /**
461
+ *
462
+ * @returns {DataTexture}
463
+ */
464
+ getTextureLookup() {
465
+ return this.__lookup_data.getTexture();
466
+ }
467
+
468
+ /**
469
+ *
470
+ * @returns {DataTexture}
471
+ */
472
+ getTextureData() {
473
+ return this.__light_data.getTexture();
474
+ }
475
+
476
+ /**
477
+ * @returns {DataTexture3D}
478
+ */
479
+ getTextureClusters() {
480
+ return this.__cluster_texture;
481
+ }
482
+
483
+ /**
484
+ * @returns {DataTexture}
485
+ */
486
+ getTextureDecalAtlas() {
487
+ return this.__decal_atlas_texture;
488
+ }
489
+
490
+ /**
491
+ * NOTE: do not modify the value
492
+ * @returns {Readonly<Vector3>}
493
+ */
494
+ getResolution() {
495
+ return this.__tiles_resolution;
496
+ }
497
+
498
+ /**
499
+ *
500
+ * @private
501
+ */
502
+ __handle_light_dimensions_change() {
503
+ // internal BVH needs to be rebuilt
504
+ this.__visible_bvh_needs_update = true;
505
+ // when dimensions change, typically data will need to be re-written to the GPU, things like light positions or projection matrix
506
+ this.__light_data_needs_update = true;
507
+ }
508
+
509
+ /**
510
+ *
511
+ * @param {LightRenderMetadata} data
512
+ * @private
513
+ */
514
+ __handle_visible_decal_added(data) {
515
+ this.__light_data_needs_update = true;
516
+
517
+ /**
518
+ *
519
+ * @type {Decal}
520
+ */
521
+ const light = data.light;
522
+
523
+ light.onDimensionChanged(this.__handle_light_dimensions_change, this);
524
+
525
+ const ref = this.__decal_patch_references.acquire(light.texture_diffuse);
526
+
527
+ const patch = ref.getValue();
528
+ const patch_uv = patch.uv;
529
+
530
+ patch_uv.position.onChanged.add(light.handleUvPositionChange, light);
531
+ patch_uv.size.onChanged.add(light.handleUvSizeChange, light);
532
+
533
+ light.uv[0] = patch_uv.position.x;
534
+ light.uv[1] = patch_uv.position.y;
535
+ light.uv[2] = patch_uv.size.x;
536
+ light.uv[3] = patch_uv.size.y;
537
+
538
+ this.__decal_references.set(light, ref);
539
+ }
540
+
541
+ /**
542
+ *
543
+ * @param {LightRenderMetadata} data
544
+ * @private
545
+ */
546
+ __handle_visible_decal_removed(data) {
547
+ this.__light_data_needs_update = true;
548
+
549
+ /**
550
+ *
551
+ * @type {Decal}
552
+ */
553
+ const light = data.light;
554
+
555
+ light.offDimensionChanged(this.__handle_light_dimensions_change, this);
556
+
557
+ const ref = this.__decal_references.get(light);
558
+
559
+ if (ref === undefined) {
560
+ // This can occur when decal changes while being in the visible list, messing with the IncrementalDeltaSet's compare order
561
+ console.warn(`Decal reference not found: ${light}`);
562
+ return;
563
+ }
564
+
565
+ const patch = ref.getValue();
566
+ const patch_uv = patch.uv;
567
+
568
+ // unsubscribe
569
+ patch_uv.position.onChanged.remove(light.handleUvPositionChange, light);
570
+ patch_uv.size.onChanged.remove(light.handleUvSizeChange, light);
571
+
572
+
573
+ ref.release();
574
+
575
+ this.__decal_references.delete(light);
576
+ }
577
+
578
+ /**
579
+ *
580
+ * @param {LightRenderMetadata} data
581
+ * @private
582
+ */
583
+ __handle_visible_light_added(data) {
584
+ this.__light_data_needs_update = true;
585
+
586
+ const light = data.light;
587
+
588
+ light.onDimensionChanged(this.__handle_light_dimensions_change, this);
589
+ }
590
+
591
+ /**
592
+ *
593
+ * @param {LightRenderMetadata} data
594
+ * @private
595
+ */
596
+ __handle_visible_light_removed(data) {
597
+ this.__light_data_needs_update = true;
598
+
599
+ const light = data.light;
600
+ light.offDimensionChanged(this.__handle_light_dimensions_change, this);
601
+ }
602
+
603
+ /**
604
+ *
605
+ * @param {LightRenderMetadata} light
606
+ * @returns {number}
607
+ * @protected
608
+ */
609
+ __sort_visible_light_score(light) {
610
+ const center = scratch_corners;
611
+
612
+ light.light.getCenter(center)
613
+
614
+ const x = center[0];
615
+ const y = center[1];
616
+ const z = center[2];
617
+
618
+ return v3_morton_encode_transformed(x, y, z, this.__projection_matrix);
619
+ }
620
+
621
+ /**
622
+ * DEBUG method
623
+ * @param {LightRenderMetadata} lights
624
+ * @param {number} count
625
+ * @returns {number} lower = better sorting score
626
+ * @private
627
+ */
628
+ __assess_sorting_score(lights, count) {
629
+
630
+ let r = 0;
631
+
632
+ const center_a = [];
633
+ const center_b = [];
634
+
635
+ for (let i = 0; i < count; i++) {
636
+ const a = lights[i];
637
+
638
+ const score_a = this.__sort_visible_light_score(a);
639
+ a.light.getCenter(center_a);
640
+
641
+ for (let j = i + 1; j < count; j++) {
642
+ const b = lights[j];
643
+
644
+ const score_b = this.__sort_visible_light_score(b);
645
+ b.light.getCenter(center_b);
646
+
647
+ //
648
+ const score_distance = Math.abs(score_a - score_b);
649
+ const position_distance = v3_distance(
650
+ center_a[0], center_a[1], center_a[2],
651
+ center_b[0], center_b[1], center_b[2],
652
+ );
653
+
654
+ r += score_distance / position_distance;
655
+ }
656
+ }
657
+
658
+
659
+ return r / (count * (count - 1) * 0.5);
660
+ }
661
+
662
+ /**
663
+ * Sort lights for better data locality
664
+ * @private
665
+ */
666
+ __sort_visible_light() {
667
+ const sorted_lights = this.__sorted_visible_lights;
668
+
669
+ const visible_light_set = this.__visible_lights;
670
+ const visible_light_count = visible_light_set.size;
671
+
672
+ const visible_decal_set = this.__visible_decals;
673
+ const visible_decal_count = visible_decal_set.size;
674
+
675
+ const expected_sorted_size = visible_light_count + visible_decal_count;
676
+
677
+ if (expected_sorted_size > visible_light_count) {
678
+ // crop array if it's too big
679
+ sorted_lights.splice(visible_light_count, expected_sorted_size - visible_light_count)
680
+ }
681
+
682
+ array_copy(visible_light_set.elements, 0, sorted_lights, 0, visible_light_count);
683
+ array_copy(visible_decal_set.elements, 0, sorted_lights, visible_light_count, visible_decal_count);
684
+
685
+ array_sort_quick(sorted_lights, this.__sort_visible_light_score, this, 0, expected_sorted_size - 1);
686
+ }
687
+
688
+ __update_visible_bvh() {
689
+ this.__visible_bvh_needs_update = false;
690
+
691
+ build_light_data_bvh(
692
+ this.__visible_lights,
693
+ this.__visible_bvh_lights
694
+ );
695
+
696
+ build_light_data_bvh(
697
+ this.__visible_decals,
698
+ this.__visible_bvh_decals
699
+ );
700
+ }
701
+
702
+
703
+ /**
704
+ *
705
+ * @param {Camera} camera
706
+ * @private
707
+ */
708
+ __build_view_frustum(camera) {
709
+ frustum_from_camera(camera, this.__view_frustum, false);
710
+
711
+ array_swap_one(this.__view_frustum.planes, 4, 5);
712
+
713
+ this.__build_view_frustum_points();
714
+ }
715
+
716
+ __build_visible_light_list() {
717
+
718
+ /**
719
+ *
720
+ * @type {IncrementalDeltaSet<LightRenderMetadata>}
721
+ */
722
+ const visible_lights = this.__visible_lights;
723
+
724
+ visible_lights.initializeUpdate();
725
+
726
+ const visible_decals = this.__visible_decals;
727
+
728
+ visible_decals.initializeUpdate();
729
+
730
+ const nodes = [];
731
+
732
+ read_three_planes_to_array(this.__view_frustum.planes, scratch_frustum_planes)
733
+
734
+ /*
735
+ Search is done in 2 phases:
736
+ 1) broad phase using bounding boxes
737
+ 2) granular phase where we use object-specific shape to check against frustum
738
+ */
739
+
740
+ const broad_match_count = bvh_query_user_data_overlaps_frustum(nodes, 0, this.#light_data_bvh, scratch_frustum_planes);
741
+
742
+ for (let i = 0; i < broad_match_count; i++) {
743
+
744
+ const light_id = nodes[i];
745
+
746
+ /**
747
+ *
748
+ * @type {LightRenderMetadata}
749
+ */
750
+ const light_data = this.#metadata_map.get(light_id);
751
+
752
+ /**
753
+ *
754
+ * @type {PointLight|Decal}
755
+ */
756
+ const light = light_data.light;
757
+
758
+ if (light.isDecal === true) {
759
+
760
+
761
+ // decals go into a separate bucket
762
+ visible_decals.push(light_data);
763
+
764
+ } else if (light.isPointLight === true) {
765
+ // perform granular check
766
+
767
+ const light_position = light.position;
768
+
769
+ const light_x = light_position.x;
770
+ const light_y = light_position.y;
771
+ const light_z = light_position.z;
772
+
773
+ const radius = light.radius.getValue();
774
+
775
+ if (!point_light_inside_volume(light_x, light_y, light_z, radius, this.__view_frustum_points, scratch_frustum_planes)) {
776
+ // outside of view frustum
777
+ continue;
778
+ }
779
+
780
+
781
+ // register as visible
782
+ visible_lights.push(light_data);
783
+ }
784
+
785
+ }
786
+
787
+ visible_lights.finalizeUpdate();
788
+
789
+ visible_decals.finalizeUpdate();
790
+ }
791
+
792
+ __write_light_data_texture() {
793
+
794
+ /**
795
+ *
796
+ * @type {LightRenderMetadata[]}
797
+ */
798
+ const visible_lights = this.__sorted_visible_lights;
799
+
800
+ // write light data into texture
801
+ const visible_light_count = visible_lights.length;
802
+
803
+ // compute amount of space required for the lights
804
+ let address = 0;
805
+ for (let i = 0; i < visible_light_count; i++) {
806
+ const datum = visible_lights[i];
807
+
808
+ /**
809
+ *
810
+ * @type {PointLight}
811
+ */
812
+ const light = datum.light;
813
+
814
+ address += light.ENCODED_SLOT_COUNT;
815
+ }
816
+
817
+ const expected_light_data_size = address;
818
+
819
+
820
+ const light_data = this.__light_data;
821
+ light_data.resize(Math.ceil(address / 4));
822
+
823
+ light_data.update();
824
+
825
+ // update required lookup precision
826
+ this.__lookup_data.precision = Math.max(
827
+ 0,
828
+ Math.ceil(Math.log2(light_data.size))
829
+ ) + LIGHT_ENCODING_TYPE_BIT_COUNT;
830
+
831
+ const tx_light = light_data.getTexture();
832
+
833
+ /**
834
+ *
835
+ * @type {Float32Array}
836
+ */
837
+ const tx_light_data = tx_light.image.data;
838
+
839
+ address = 0;
840
+
841
+ for (let i = 0; i < visible_light_count; i++) {
842
+ const datum = visible_lights[i];
843
+
844
+ assert.lessThan(address, tx_light_data.length, "overflow");
845
+
846
+ /**
847
+ *
848
+ * @type {PointLight}
849
+ */
850
+ const light = datum.light;
851
+
852
+ datum.address = address >> 2;
853
+
854
+ const written_slots = light.toArray(tx_light_data, address);
855
+
856
+ address += written_slots;
857
+
858
+ // align address to 4 slot boundary
859
+ if ((address & 3) !== 0) {
860
+ // not on 4 slot boundary
861
+ address = ((address >> 2) + 1) << 2;
862
+ }
863
+ }
864
+
865
+ if (address !== expected_light_data_size) {
866
+ throw new Error(`Expected light data size is ${expected_light_data_size}, actual written data is size is ${address}`);
867
+ }
868
+
869
+ tx_light.needsUpdate = true;
870
+
871
+ this.__visible_bvh_needs_update = true;
872
+ }
873
+
874
+ /**
875
+ * Perform cluster assignment where each cluster is filled with overlapping lights
876
+ * @private
877
+ */
878
+ __assign_lights_to_clusters() {
879
+
880
+ const tiles_resolution = this.__tiles_resolution;
881
+
882
+ const tr_z = tiles_resolution.z;
883
+ const tr_y = tiles_resolution.y;
884
+ const tr_x = tiles_resolution.x;
885
+
886
+ const tr_x_1 = tr_x + 1;
887
+ const tr_y_1 = tr_y + 1;
888
+
889
+ const tr_xy_1 = tr_x_1 * tr_y_1;
890
+
891
+ /**
892
+ *
893
+ * @type {Float32Array}
894
+ */
895
+ const cluster_planes = this.__cluster_planes;
896
+
897
+ const bvh_lights = this.__visible_bvh_lights;
898
+ const bvh_decals = this.__visible_bvh_decals;
899
+
900
+
901
+ const cluster_planes_x_offset = 0;
902
+ const cluster_planes_y_offset = tr_x_1 * 4;
903
+ const cluster_planes_z_offset = cluster_planes_y_offset + tr_y_1 * 4;
904
+
905
+ /**
906
+ *
907
+ * @type {number[]|Float32Array}
908
+ */
909
+ const light_source_data = this.__light_data.getTexture().image.data;
910
+
911
+ const tile_slice_size = tr_x * tr_y;
912
+
913
+ const light_cluster_texture = this.__cluster_texture;
914
+ const tile_texture_data = light_cluster_texture.image.data;
915
+
916
+ const light_lookup = this.__lookup_data;
917
+ light_lookup.update();
918
+ const light_lookup_texture = light_lookup.getTexture();
919
+ const light_lookup_texture_data = light_lookup_texture.image.data;
920
+
921
+ const cluster_frustum_points = this.__cluster_frustum_points;
922
+
923
+ let lookup_address_offset = 0;
924
+ let i_x_0, i_y_0, i_z_0;
925
+
926
+ // clear cache
927
+ LOOKUP_CACHE.fill(0xFFFFFFFF);
928
+
929
+ // console.log('Assignment Start');
930
+
931
+ for (i_z_0 = 0; i_z_0 < tr_z; i_z_0++) {
932
+
933
+ // construct z planes
934
+
935
+ const slice_offset = i_z_0 * tile_slice_size;
936
+
937
+ const z_plane_index_offset_0 = cluster_planes_z_offset + i_z_0 * 4;
938
+
939
+ read_plane_pair(cluster_planes, z_plane_index_offset_0, scratch_frustum_planes, 16);
940
+
941
+ for (i_y_0 = 0; i_y_0 < tr_y; i_y_0++) {
942
+
943
+ // construct y planes
944
+
945
+ const y_plane_index_offset_0 = cluster_planes_y_offset + i_y_0 * 4;
946
+
947
+ read_plane_pair(cluster_planes, y_plane_index_offset_0, scratch_frustum_planes, 8);
948
+
949
+ for (i_x_0 = 0; i_x_0 < tr_x; i_x_0++) {
950
+
951
+ // construct x planes
952
+
953
+ const x_plane_index_offset_0 = cluster_planes_x_offset + i_x_0 * 4;
954
+
955
+ read_plane_pair(cluster_planes, x_plane_index_offset_0, scratch_frustum_planes, 0);
956
+
957
+ read_cluster_frustum_corners(scratch_corners, i_z_0, tr_xy_1, i_y_0, tr_x_1, i_x_0, cluster_frustum_points);
958
+
959
+ const tile_index = slice_offset + (i_y_0 * tr_x) + i_x_0;
960
+
961
+ const tile_data_offset = tile_index * 4;
962
+
963
+ //assign lights
964
+ lookup_address_offset += assign_cluster(
965
+ tile_data_offset,
966
+ bvh_lights,
967
+ lookup_address_offset,
968
+ light_lookup_texture_data,
969
+ tile_texture_data,
970
+ light_source_data
971
+ );
972
+
973
+ // assign decals
974
+ const decal_count = assign_cluster(
975
+ tile_data_offset + 2,
976
+ bvh_decals,
977
+ lookup_address_offset,
978
+ light_lookup_texture_data,
979
+ tile_texture_data,
980
+ light_source_data
981
+ );
982
+
983
+ /*
984
+ TODO fix decal sorting artifacts
985
+
986
+ if (decal_count > 1) { // only sort when there are 2 or more, sorting 1 object is pointless
987
+ // decals need to be sorted for correct blending result
988
+ sort_decal_data(
989
+ light_lookup_texture_data,
990
+ light_source_data,
991
+ lookup_address_offset,
992
+ decal_count
993
+ );
994
+ }
995
+
996
+ */
997
+
998
+ lookup_address_offset += decal_count;
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ light_cluster_texture.needsUpdate = true;
1004
+ light_lookup_texture.needsUpdate = true;
1005
+
1006
+ // Post-fact lookup texture resize, this frame will be wrong, but next one should be ok
1007
+ const current_lookup_size = this.__lookup_data.size;
1008
+
1009
+ const lookup_memory_resized = this.__ensure_lookup_size(lookup_address_offset);
1010
+
1011
+ const cluster_resized = this.__set_cluster_addressable_bit_range(Math.max(0, Math.log2(lookup_address_offset)));
1012
+
1013
+ if (
1014
+ (lookup_memory_resized && current_lookup_size < lookup_address_offset)
1015
+ || cluster_resized
1016
+ ) {
1017
+ // overflow, we ended up trying to write more lights into some clusters than what they could contain
1018
+ this.__update_cluster_texture();
1019
+
1020
+ // re-do assignment
1021
+ this.__assign_lights_to_clusters();
1022
+ }
1023
+
1024
+ // console.log('Unique assignment count: ', assignment_count, ', Hash reuse:', hash_reuse_count);
1025
+ }
1026
+
1027
+ /**
1028
+ *
1029
+ * @private
1030
+ */
1031
+ __build_cluster_frustum_planes() {
1032
+ const tiles_resolution = this.__tiles_resolution;
1033
+
1034
+ const tr_z = tiles_resolution.z;
1035
+ const tr_y = tiles_resolution.y;
1036
+ const tr_x = tiles_resolution.x;
1037
+
1038
+ const destination = this.__cluster_planes;
1039
+
1040
+ compute_cluster_planes_from_points(destination, this.__cluster_frustum_points, tr_x, tr_y, tr_z);
1041
+ }
1042
+
1043
+ /**
1044
+ *
1045
+ * @private
1046
+ */
1047
+ __build_view_frustum_points() {
1048
+
1049
+ const view_frustum = this.__view_frustum;
1050
+
1051
+ const points = this.__view_frustum_points;
1052
+
1053
+ computeFrustumCorners(points, view_frustum.planes);
1054
+ }
1055
+
1056
+ /**
1057
+ *
1058
+ * @private
1059
+ */
1060
+ __build_cluster_frustum_points() {
1061
+ const tiles_resolution = this.__tiles_resolution;
1062
+
1063
+ const tr_z = tiles_resolution.z;
1064
+ const tr_y = tiles_resolution.y;
1065
+ const tr_x = tiles_resolution.x;
1066
+
1067
+ const view_frustum_points = this.__view_frustum_points;
1068
+ const points_data = this.__cluster_frustum_points;
1069
+
1070
+ slice_frustum_linear_to_points(view_frustum_points, tr_x, tr_y, tr_z, points_data);
1071
+ }
1072
+
1073
+ /**
1074
+ * Build light tile texture
1075
+ * @param {Camera|THREE.PerspectiveCamera} camera
1076
+ */
1077
+ buildTiles(camera) {
1078
+ // camera.updateProjectionMatrix();
1079
+ // camera.updateMatrix();
1080
+ // camera.updateMatrixWorld(true);
1081
+
1082
+ //update world inverse matrix
1083
+ camera.matrixWorldInverse.copy(camera.matrixWorld);
1084
+ camera.matrixWorldInverse.invert();
1085
+
1086
+ // this.__projection_matrix.set(camera.matrixWorldInverse.elements);
1087
+ mat4.multiply(this.__projection_matrix, camera.projectionMatrix.elements, camera.matrixWorldInverse.elements);
1088
+
1089
+ // console.time('__build_view_frustum');
1090
+ this.__build_view_frustum(camera);
1091
+ // console.timeEnd('__build_view_frustum');
1092
+
1093
+
1094
+ // console.time('__build_visible_light_list');
1095
+ this.__build_visible_light_list();
1096
+ // console.timeEnd('__build_visible_light_list');
1097
+
1098
+ // update decal atlas
1099
+ this.__decal_atlas.update();
1100
+ this.__update_decal_atlas_texture();
1101
+
1102
+ // console.time('__write_light_data_texture');
1103
+ if (this.__light_data_needs_update) {
1104
+ // sort light for better search and query locality
1105
+ this.__sort_visible_light();
1106
+
1107
+ this.__write_light_data_texture();
1108
+
1109
+ // clear flag
1110
+ this.__light_data_needs_update = false;
1111
+ }
1112
+ // console.timeEnd('__write_light_data_texture');
1113
+
1114
+ if (this.__visible_bvh_needs_update) {
1115
+ this.__update_visible_bvh();
1116
+ }
1117
+
1118
+ this.__build_cluster_frustum_points();
1119
+ // console.time('__build_cluster_frustum_planes');
1120
+ this.__build_cluster_frustum_planes();
1121
+ // console.timeEnd('__build_cluster_frustum_planes');
1122
+
1123
+ // console.time('__assign_lights_to_clusters');
1124
+ this.__assign_lights_to_clusters();
1125
+ // console.timeEnd('__assign_lights_to_clusters');
1126
+ }
1127
+
1128
+ dispose() {
1129
+ this.__cluster_texture.dispose();
1130
+ this.__light_data.dispose();
1131
+ this.__lookup_data.dispose();
1132
+ this.__decal_atlas_texture.dispose();
1133
+ }
1134
+
1135
+ /**
1136
+ * Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
1137
+ * @param {number} x
1138
+ * @param {number} y
1139
+ * @param {number} z
1140
+ */
1141
+ setTileMapResolution(x, y, z) {
1142
+ assert.isNumber(x, 'x');
1143
+ assert.isNonNegativeInteger(x, 'x');
1144
+ assert.isFinite(x, 'x');
1145
+ assert.greaterThan(x, 0, 'x must be > 0');
1146
+
1147
+ assert.isNumber(y, 'y');
1148
+ assert.isNonNegativeInteger(y, 'y');
1149
+ assert.isFinite(y, 'y');
1150
+ assert.greaterThan(y, 0, 'y must be > 0');
1151
+
1152
+ assert.isNumber(z, 'z');
1153
+ assert.isNonNegativeInteger(z, 'z');
1154
+ assert.isFinite(z, 'z');
1155
+ assert.greaterThan(z, 0, 'z must be > 0');
1156
+
1157
+ const r = this.__tiles_resolution;
1158
+
1159
+ if (x === r.x && y === r.y && z === r.z) {
1160
+ // special case, no change
1161
+ return;
1162
+ }
1163
+
1164
+ r.set(x, y, z);
1165
+
1166
+ this.__cluster_texture_needs_rebuild = true;
1167
+ this.__update_cluster_texture();
1168
+
1169
+ const cluster_planes_size = ((x + 1) + (y + 1) + (z + 1)) * 4;
1170
+ const cluster_frustum_points_size = (x + 1) * (y + 1) * (z + 1) * 3;
1171
+
1172
+ const buffer = new ArrayBuffer((cluster_planes_size + cluster_frustum_points_size) * 4);
1173
+
1174
+ this.__cluster_planes = new Float32Array(
1175
+ buffer, 0,
1176
+ cluster_planes_size
1177
+ );
1178
+ this.__cluster_frustum_points = new Float32Array(
1179
+ buffer, cluster_planes_size * 4,
1180
+ cluster_frustum_points_size
1181
+ );
1182
+ }
1183
+
1184
+ /**
1185
+ *
1186
+ * @param {AbstractLight} light
1187
+ * @returns {boolean}
1188
+ */
1189
+ hasLight(light) {
1190
+ return this.#metadata_map.has(light.id);
1191
+ }
1192
+
1193
+ /**
1194
+ *
1195
+ * @param {AbstractLight} light
1196
+ */
1197
+ addLight(light) {
1198
+ const lightData = new LightRenderMetadata(light);
1199
+
1200
+ lightData.link(this.#light_data_bvh);
1201
+
1202
+ this.#metadata_map.set(light.id, lightData);
1203
+ }
1204
+
1205
+ /**
1206
+ *
1207
+ * @param {AbstractLight} light
1208
+ * @returns {boolean}
1209
+ */
1210
+ removeLight(light) {
1211
+
1212
+ const light_id = light.id;
1213
+
1214
+ const data = this.#metadata_map.get(light_id);
1215
+
1216
+ if (data === undefined) {
1217
+ return false;
1218
+ }
1219
+
1220
+ data.unlink();
1221
+
1222
+ this.#metadata_map.delete(light_id);
1223
+
1224
+ return true;
1225
+ }
1226
+ }