@woosh/meep-engine 2.44.7 → 2.45.1

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 (35) hide show
  1. package/core/cache/Cache.js +1 -1
  2. package/core/collection/HashMap.d.ts +1 -1
  3. package/core/collection/HashMap.js +2 -1
  4. package/core/localization/LanguageMetadata.js +48 -0
  5. package/core/localization/LocaleDataset.js +37 -0
  6. package/core/localization/Localization.js +253 -0
  7. package/core/model/node-graph/DataType.js +1 -1
  8. package/core/model/node-graph/node/Port.js +4 -0
  9. package/core/process/task/Task.js +22 -5
  10. package/core/process/task/util/iteratorTask.js +29 -0
  11. package/editor/view/ecs/components/items/ItemContainerController.stories.js +1 -1
  12. package/engine/Engine.js +1 -1
  13. package/engine/asset/AssetManager.js +4 -0
  14. package/engine/ecs/foliage/ViewState.js +7 -1
  15. package/engine/ecs/speaker/VoiceSystem.js +1 -1
  16. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +17 -11
  17. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +37 -0
  18. package/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +19 -0
  19. package/engine/graphics/ecs/mesh-v2/render/adapters/AbstractRenderAdapter.js +11 -2
  20. package/engine/graphics/render/forward_plus/LightManager.js +33 -123
  21. package/engine/graphics/render/forward_plus/SPECIFICATION.md +155 -0
  22. package/engine/graphics/render/forward_plus/assign_cluster.js +125 -0
  23. package/engine/graphics/render/forward_plus/model/Decal.js +6 -2
  24. package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.d.ts +5 -0
  25. package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +11 -4
  26. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +3 -3
  27. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +2 -2
  28. package/engine/graphics/render/forward_plus/sort_decal_data.js +102 -0
  29. package/engine/graphics/sh3/path_tracer/prototypePathTracer.js +1 -1
  30. package/engine/input/devices/InputDeviceButton.d.ts +7 -0
  31. package/engine/input/devices/InputDeviceButton.js +22 -0
  32. package/engine/input/devices/KeyboardDevice.d.ts +11 -0
  33. package/engine/input/devices/KeyboardDevice.js +17 -7
  34. package/package.json +1 -1
  35. package/core/Localization.js +0 -199
@@ -20,6 +20,7 @@ import TaskState from "../../../../core/process/task/TaskState.js";
20
20
  import {
21
21
  bvh_query_user_data_overlaps_frustum
22
22
  } from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
23
+ import { iteratorTask } from "../../../../core/process/task/util/iteratorTask.js";
23
24
 
24
25
 
25
26
  /**
@@ -34,6 +35,31 @@ const scratch_point = new SurfacePoint3();
34
35
  */
35
36
  const scratch_ray_0 = new Float32Array(6);
36
37
 
38
+ /**
39
+ *
40
+ * @param {ShadedGeometrySystem} system
41
+ * @return {Generator}
42
+ */
43
+ function* maintenance_generator(system) {
44
+ while (true) { //infinite loop
45
+
46
+ const contexts = system.__view_contexts;
47
+
48
+ for (const [key, value] of contexts) {
49
+
50
+ if (value === undefined) {
51
+ continue;
52
+ }
53
+
54
+ yield* value.maintain();
55
+
56
+ }
57
+
58
+ // a small break between re-runs
59
+ yield;
60
+ }
61
+ }
62
+
37
63
  export class ShadedGeometrySystem extends System {
38
64
  /**
39
65
  *
@@ -100,8 +126,15 @@ export class ShadedGeometrySystem extends System {
100
126
  }
101
127
  }
102
128
  });
129
+
130
+ this.__maintenance_task = iteratorTask(
131
+ 'Maintenance',
132
+ maintenance_generator(this),
133
+ TaskSignal.Yield
134
+ );
103
135
  }
104
136
 
137
+
105
138
  /**
106
139
  *
107
140
  * @returns {ExplicitBinaryBoundingVolumeHierarchy}
@@ -222,6 +255,9 @@ export class ShadedGeometrySystem extends System {
222
255
  this.__optimization_task.state.set(TaskState.INITIAL);
223
256
  engine.executor.run(this.__optimization_task);
224
257
 
258
+ this.__maintenance_task.state.set(TaskState.INITIAL);
259
+ engine.executor.run(this.__maintenance_task);
260
+
225
261
  readyCallback();
226
262
  }
227
263
 
@@ -233,6 +269,7 @@ export class ShadedGeometrySystem extends System {
233
269
  const view_list = graphics.views.elements;
234
270
 
235
271
  engine.executor.removeTask(this.__optimization_task);
272
+ engine.executor.removeTask(this.__maintenance_task);
236
273
 
237
274
  view_list.forEach(this.__handle_view_removed, this);
238
275
  view_list.on.added.remove(this.__handle_view_added, this);
@@ -30,6 +30,25 @@ export class ShadedGeometryRendererContext {
30
30
  ];
31
31
  }
32
32
 
33
+ /**
34
+ * Performs maintenance on the context, mainly to check the hash tables for changes
35
+ * @return {Generator}
36
+ */
37
+ * maintain() {
38
+ const adapters = this.adapters;
39
+ for (let i = 0; i < adapters.length; i++) {
40
+ const adapter = adapters[i];
41
+
42
+ if (adapter === undefined) {
43
+ // adapter may disappear between invocations, since this is a continuation-type function
44
+ continue;
45
+ }
46
+
47
+ // delegate to adapter's maintenance
48
+ yield* adapter.maintain();
49
+ }
50
+ }
51
+
33
52
  /**
34
53
  *
35
54
  * @param {number} index
@@ -2,8 +2,8 @@ import { array_quick_sort_by_comparator } from "../../../../../../core/collectio
2
2
 
3
3
  /**
4
4
  *
5
- * @param {THREE.Object3D} a
6
- * @param {THREE.Object3D} b
5
+ * @param {THREE.Object3D|{material?:Material}} a
6
+ * @param {THREE.Object3D|{material?:Material}} b
7
7
  * @returns {number}
8
8
  */
9
9
  function compare_by_material(a, b) {
@@ -96,6 +96,15 @@ export class AbstractRenderAdapter {
96
96
  this.__object_count = 0;
97
97
  }
98
98
 
99
+ /**
100
+ * Used to perform infrequent housekeeping on the adapter's internal data structures
101
+ * Returns a maintenance sequence
102
+ * @return {Generator}
103
+ */
104
+ * maintain() {
105
+ // override as necessary
106
+ }
107
+
99
108
  /**
100
109
  * @returns {THREE.Object3D[]}
101
110
  */
@@ -17,7 +17,6 @@ import Vector3 from "../../../../core/geom/Vector3.js";
17
17
  import { query_bvh_frustum_from_objects } from "./query/query_bvh_frustum_from_objects.js";
18
18
  import { IncrementalDeltaSet } from "../visibility/IncrementalDeltaSet.js";
19
19
  import { computeFrustumCorners } from "./computeFrustumCorners.js";
20
- import { compute_light_data_hash_0 } from "./cluster/compute_light_data_hash_0.js";
21
20
  import { read_plane_pair } from "./cluster/read_plane_pair.js";
22
21
  import { read_frustum_planes_to_array } from "../../../../core/geom/3d/frustum/read_frustum_planes_to_array.js";
23
22
  import { compute_cluster_planes_from_points } from "./cluster/compute_cluster_planes_from_points.js";
@@ -30,9 +29,7 @@ import { NumericType } from "./data/NumericType.js";
30
29
  import { computeDataType } from "./data/computeDataType.js";
31
30
  import { computeThreeTextureTypeFromDataType } from "./data/computeThreeTextureTypeFromDataType.js";
32
31
  import { computeThreeTextureInternalFormatFromDataType } from "./data/computeThreeTextureInternalFormatFromDataType.js";
33
- import { testClusterEquality } from "./testClusterEquality.js";
34
32
  import { BinaryUint32BVH } from "../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
35
- import { query_bvh_frustum_from_texture } from "./query/query_bvh_frustum_from_texture.js";
36
33
  import { mat4 } from "gl-matrix";
37
34
  import { TextureAtlas } from "../../texture/atlas/TextureAtlas.js";
38
35
  import { CachingTextureAtlas } from "../../texture/atlas/CachingTextureAtlas.js";
@@ -48,46 +45,8 @@ import { arraySwapElements } from "../../../../core/collection/array/arraySwapEl
48
45
  import { compareObjectsByNumericId } from "../../../../core/model/object/compareObjectsByNumericId.js";
49
46
  import { slice_frustum_linear_to_points } from "../../../../core/geom/3d/frustum/slice_frustum_linear_to_points.js";
50
47
  import { read_cluster_frustum_corners } from "../../../../core/geom/3d/frustum/read_cluster_frustum_corners.js";
48
+ import { assign_cluster, LOOKUP_CACHE, scratch_corners, scratch_frustum_planes } from "./assign_cluster.js";
51
49
 
52
- const LOOKUP_CACHE_SIZE = 1024; // must be power of two
53
-
54
- /**
55
- * Using continuous buffer for related attributes to get better cache utilization
56
- * @type {ArrayBuffer}
57
- * @private
58
- */
59
- const _scratch_array_buffer = new ArrayBuffer(
60
- 24 * 4
61
- + 24 * 4
62
- + LOOKUP_CACHE_SIZE * 4
63
- );
64
-
65
- /**
66
- *
67
- * @type {number[]|Float32Array|Float64Array}
68
- */
69
- const scratch_corners = new Float32Array(_scratch_array_buffer, 0, 24);
70
-
71
-
72
- /**
73
- * Linear access data structure with parameters of each plane of the frustum
74
- * 6 planes, 4 values per plane
75
- * Layout: [normal.x, normal.y, normal.z, constant]
76
- * @type {number[]|Float32Array|Float64Array}
77
- */
78
- const scratch_frustum_planes = new Float32Array(_scratch_array_buffer, 24 * 4, 24);
79
-
80
- /**
81
- * Cache used to identify duplicate cells. Helps reduce memory usage and just point to equivalent existing cells instead of allocating new ones.
82
- * @type {Uint16Array|Uint32Array}
83
- */
84
- const LOOKUP_CACHE = new Uint32Array(_scratch_array_buffer, (24 + 24) * 4, LOOKUP_CACHE_SIZE);
85
-
86
- /**
87
- *
88
- * @type {number[]}
89
- */
90
- const scratch_light_nodes = [];
91
50
 
92
51
  /**
93
52
  * How many bits are used to encode light type
@@ -121,84 +80,6 @@ function encode_light_descriptor(address, light) {
121
80
  return light_type | (address << 2);
122
81
  }
123
82
 
124
- function assign_cluster(
125
- tile_texture_data_offset,
126
- light_bvh,
127
- lookup_address_offset,
128
- light_lookup_texture_data,
129
- tile_texture_data,
130
- light_source_data
131
- ) {
132
-
133
- // at this point we have a fully constructed cluster, we can query lights within the cluster
134
- const match_count = query_bvh_frustum_from_texture(
135
- scratch_light_nodes,
136
- 0,
137
- light_bvh,
138
- light_source_data,
139
- scratch_frustum_planes,
140
- scratch_corners
141
- );
142
-
143
- // construct tile data
144
-
145
- // write number of light entries
146
- tile_texture_data[tile_texture_data_offset + 1] = match_count;
147
-
148
- if (match_count === 0) {
149
- // don't waste time on assignment, there are no lights in the cluster
150
- return 0;
151
- }
152
-
153
- /*
154
- We can sort lights by index to get higher cache utilization, but it turns out quite costly.
155
- Through experimentation, sorting was taking around 20% of the whole assignment time
156
- while improving number of cache hits by around 40% (8000 without sorting -> 11000 with sorting)
157
- */
158
- const cluster_data_hash = compute_light_data_hash_0(scratch_light_nodes, match_count) & (LOOKUP_CACHE_SIZE - 1);
159
-
160
- const existing_lookup_address = LOOKUP_CACHE[cluster_data_hash];
161
-
162
- if (existing_lookup_address !== 0xFFFFFFFF) {
163
- // found a hash match, validate equality
164
- const existing_lookup_entry_match = testClusterEquality(
165
- scratch_light_nodes,
166
- match_count,
167
- light_lookup_texture_data,
168
- existing_lookup_address
169
- );
170
-
171
- if (existing_lookup_entry_match) {
172
- tile_texture_data[tile_texture_data_offset] = existing_lookup_address;
173
-
174
- //hash_reuse_count++;
175
-
176
- return 0;
177
- }
178
- } else {
179
- // no hash entry exists, create one
180
- LOOKUP_CACHE[cluster_data_hash] = lookup_address_offset;
181
- }
182
-
183
- // if we get to this point, that means no hash match was found, or equality check was failed
184
-
185
- // write address of the lookup table
186
- tile_texture_data[tile_texture_data_offset] = lookup_address_offset;
187
-
188
- for (let i = 0; i < match_count; i++) {
189
- /**
190
- *
191
- * @type {number}
192
- */
193
- const light_descriptor = scratch_light_nodes[i];
194
-
195
- // construct lookup table
196
- light_lookup_texture_data[lookup_address_offset + i] = light_descriptor;
197
- }
198
-
199
- return match_count;
200
- }
201
-
202
83
  /**
203
84
  *
204
85
  * @param {IncrementalDeltaSet<PointLightData>} data_set
@@ -968,7 +849,7 @@ export class LightManager {
968
849
 
969
850
  /**
970
851
  *
971
- * @type {number[]|Uint8ClampedArray}
852
+ * @type {number[]|Float32Array}
972
853
  */
973
854
  const light_source_data = this.__light_data.getTexture().image.data;
974
855
 
@@ -1033,7 +914,7 @@ export class LightManager {
1033
914
  light_source_data
1034
915
  );
1035
916
 
1036
- lookup_address_offset += assign_cluster(
917
+ const decal_count = assign_cluster(
1037
918
  tile_data_offset + 2,
1038
919
  bvh_decals,
1039
920
  lookup_address_offset,
@@ -1041,6 +922,23 @@ export class LightManager {
1041
922
  tile_texture_data,
1042
923
  light_source_data
1043
924
  );
925
+
926
+ /*
927
+ TODO fix decal sorting artifacts
928
+
929
+ if (decal_count > 1) { // only sort when there are 2 or more, sorting 1 object is pointless
930
+ // decals need to be sorted for correct blending result
931
+ sort_decal_data(
932
+ light_lookup_texture_data,
933
+ light_source_data,
934
+ lookup_address_offset,
935
+ decal_count
936
+ );
937
+ }
938
+
939
+ */
940
+
941
+ lookup_address_offset += decal_count;
1044
942
  }
1045
943
  }
1046
944
  }
@@ -1178,12 +1076,24 @@ export class LightManager {
1178
1076
  }
1179
1077
 
1180
1078
  /**
1181
- *
1079
+ * 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
1182
1080
  * @param {number} x
1183
1081
  * @param {number} y
1184
1082
  * @param {number} z
1185
1083
  */
1186
1084
  setTileMapResolution(x, y, z) {
1085
+ assert.isNumber(x,'x');
1086
+ assert.isNonNegativeInteger(x,'x');
1087
+ assert.isFiniteNumber(x,'x');
1088
+
1089
+ assert.isNumber(y,'y');
1090
+ assert.isNonNegativeInteger(y,'y');
1091
+ assert.isFiniteNumber(y,'y');
1092
+
1093
+ assert.isNumber(z,'z');
1094
+ assert.isNonNegativeInteger(z,'z');
1095
+ assert.isFiniteNumber(z,'z');
1096
+
1187
1097
  const r = this.__tiles_resolution;
1188
1098
 
1189
1099
  if (x === r.x && y === r.y && z === r.z) {
@@ -0,0 +1,155 @@
1
+ # Clustered Lighting Specification
2
+
3
+ ## 1. GPU Data structures
4
+
5
+ Data is organized into 3 main textures:
6
+
7
+ - Clusters. `type: UINT, format: RGBA, dimension: 3D, filtering: nearest(none)`
8
+ - R - Offset into lookup texture for lights
9
+ - G - number of lights
10
+ - B - Offset into lookup texture for decals
11
+ - A - number of decals
12
+ - Lookup. `type: UINT, format: R, dimensions: 2D, filtering: nearest(none)`
13
+ - R - encoded reference into data texture, where actual lighting information is stored
14
+ - Data. `type: FLOAT, format: RGBA, dimensions: 2D, filtering: nearest(none)`
15
+ - RGBA - single `word` of data, you can think of it as a collection of `VEC4`s
16
+
17
+ Below is a glsl snippet with initialization of uniforms for these textures:
18
+ ```glsl
19
+ uniform usampler3D fp_t_light_clusters;
20
+ uniform usampler2D fp_t_light_lookup;
21
+ uniform sampler2D fp_t_light_data;
22
+ ```
23
+
24
+ ## 2. Light Data Format
25
+
26
+ Each light is represented as a collection of `VEC4`s inside the `Data` texture.
27
+
28
+ - Point Light:
29
+ - `[ position.x, position.y, position.z, radius ]`
30
+ - `[ color.r, color.g, color.b, intensity ]`
31
+ - Directional Light:
32
+ - `[ direction.x, direction.y, direction.z, UNUSED ]`
33
+ - `[ color.r, color.g, color.b, intensity ]`
34
+
35
+ ## 3. Light reference encoding
36
+
37
+ Lookup texture contains integers that encode both the address of light data, and the type of light found at that address, this is done by encoding light type into lower 2 bits of a UINT32
38
+
39
+ Light type codes:
40
+ - 0 - PointLight
41
+ - 1 - Directional Light
42
+ - 2 - Spotlight
43
+
44
+ Encoding snippet in JavaScript:
45
+ ```js
46
+ /**
47
+ *
48
+ * @param {number} address
49
+ * @param {AbstractLight} light
50
+ * @returns {number}
51
+ */
52
+ function encode_light_descriptor(address, light) {
53
+ const light_type = encode_light_type(light);
54
+ return light_type | (address << 2);
55
+ }
56
+ ```
57
+
58
+ Decoding snippet in GLSL
59
+ ```glsl
60
+ ivec2 address_to_data_texture_coordinates(uint address){
61
+ // Lookup texture has 128 width
62
+ uint lookup_index_x = address % 128u;
63
+ uint lookup_index_y = address >> 7;
64
+
65
+ return ivec2(int(lookup_index_x),int(lookup_index_y));
66
+ }
67
+
68
+ // ...
69
+ uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;
70
+
71
+ uint type = (light_descriptor) & 0x3u;
72
+ uint light_address = light_descriptor >> 2;
73
+ ```
74
+
75
+ ## 4. Auxiliary material
76
+
77
+ Convert projection-space depth to linear space
78
+ ```glsl
79
+ float convert_depth_to_linear(in float d){
80
+ float d_n = 2.0*d - 1.0;
81
+
82
+ float f = fp_f_camera_far;
83
+ float n = fp_f_camera_near;
84
+
85
+ float fn = f*n;
86
+
87
+ float z_diff = f - n;
88
+
89
+ float denominator = (f + n - d_n * z_diff );
90
+
91
+ float z_view = (2.0*fn) / denominator;
92
+
93
+ return (z_view - n) / z_diff;
94
+ }
95
+ ```
96
+
97
+ Fetching cluster information from fragment shader inside a forward pass
98
+ ```glsl
99
+ // v3_cluster_resolution is resolution of our cluster 3d texture
100
+ ivec3 v3_cluster_resolution = textureSize(fp_t_light_tiles, 0);
101
+ ivec3 v3_cluster_position = ivec3( clip_v.x * float(v3_cluster_resolution.x), clip_v.y*float(v3_cluster_resolution.y), (clip_v.z)*float(v3_cluster_resolution.z) );
102
+
103
+ uvec4 fp_cluster_metadata = texelFetch( fp_t_light_tiles, v3_cluster_position, 0 ).rgba;
104
+ ```
105
+
106
+ Perform lighting (point-light only)
107
+ ```glsl
108
+ // read light data
109
+ for(uint i=0u; i < fp_cluster_metadata.y; i++){
110
+ uint lookup_index = fp_cluster_metadata.x + i;
111
+
112
+ uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;
113
+
114
+ uint type = (light_descriptor) & 0x3u;
115
+ uint light_address = light_descriptor >> 2;
116
+
117
+
118
+ if(type == 0u){
119
+ // point light
120
+
121
+ vec4 light_data_0 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address), 0);
122
+ vec4 light_data_1 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address+1u), 0);
123
+
124
+ vec3 light_position = light_data_0.xyz;
125
+ float light_radius = light_data_0.w;
126
+
127
+ vec3 light_color = light_data_1.xyz;
128
+ float light_intensity = light_data_1.w;
129
+
130
+ PointLight pointlight;
131
+
132
+ pointlight.position = light_position;
133
+ pointlight.distance = light_radius;
134
+ pointlight.color = light_color*light_intensity;
135
+ pointlight.decay = 1.0;
136
+
137
+ fp_getPointDirectLightIrradiance( pointlight, directLight, vViewPosition );
138
+ RE_Direct( directLight, geometry, material, reflectedLight );
139
+
140
+ } else if(type == 1u){
141
+ // directional light
142
+
143
+ // ...
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## References:
149
+ - [Dr.Strangelight or: How I learned to Stop Worrying and Love the Cluster](https://discourse.threejs.org/t/dr-strangelight-or-how-i-learned-to-stop-worrying-and-love-the-cluster/23104)
150
+ - [Practical Clustered Shading by Emil Persson](http://www.humus.name/Articles/PracticalClusteredShading.pdf)
151
+
152
+ ---
153
+ Author: Alexander Goldring, Company Named Limited
154
+
155
+ Private and confidential, copyright (c) Company Named Limited, 2022
@@ -0,0 +1,125 @@
1
+ import { query_bvh_frustum_from_texture } from "./query/query_bvh_frustum_from_texture.js";
2
+ import { compute_light_data_hash_0 } from "./cluster/compute_light_data_hash_0.js";
3
+ import { testClusterEquality } from "./testClusterEquality.js";
4
+
5
+ const LOOKUP_CACHE_SIZE = 1024; // must be power of two
6
+ /**
7
+ * Using continuous buffer for related attributes to get better cache utilization
8
+ * @type {ArrayBuffer}
9
+ * @private
10
+ */
11
+ const _scratch_array_buffer = new ArrayBuffer(
12
+ 24 * 4
13
+ + 24 * 4
14
+ + LOOKUP_CACHE_SIZE * 4
15
+ );
16
+ /**
17
+ *
18
+ * @type {number[]|Float32Array|Float64Array}
19
+ */
20
+ export const scratch_corners = new Float32Array(_scratch_array_buffer, 0, 24);
21
+ /**
22
+ * Linear access data structure with parameters of each plane of the frustum
23
+ * 6 planes, 4 values per plane
24
+ * Layout: [normal.x, normal.y, normal.z, constant]
25
+ * @type {number[]|Float32Array|Float64Array}
26
+ */
27
+ export const scratch_frustum_planes = new Float32Array(_scratch_array_buffer, 24 * 4, 24);
28
+ /**
29
+ * Cache used to identify duplicate cells. Helps reduce memory usage and just point to equivalent existing cells instead of allocating new ones.
30
+ * @type {Uint16Array|Uint32Array}
31
+ */
32
+ export const LOOKUP_CACHE = new Uint32Array(_scratch_array_buffer, (24 + 24) * 4, LOOKUP_CACHE_SIZE);
33
+ /**
34
+ *
35
+ * @type {number[]}
36
+ */
37
+ const scratch_light_nodes = [];
38
+
39
+ /**
40
+ *
41
+ * @param {number} tile_texture_data_offset
42
+ * @param {BinaryUint32BVH} bvh
43
+ * @param {number} lookup_address_offset
44
+ * @param {number[]|ArrayLike<number>|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array} lookup_data
45
+ * @param {number[]|ArrayLike<number>|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array} cluster_data
46
+ * @param {number[]|ArrayLike<number>|Float32Array} source_data Light/Decal source information is stored in here, such as position, color etc.
47
+ * @return {number}
48
+ */
49
+ export function assign_cluster(
50
+ tile_texture_data_offset,
51
+ bvh,
52
+ lookup_address_offset,
53
+ lookup_data,
54
+ cluster_data,
55
+ source_data
56
+ ) {
57
+
58
+ // at this point we have a fully constructed cluster, we can query lights within the cluster
59
+ const match_count = query_bvh_frustum_from_texture(
60
+ scratch_light_nodes,
61
+ 0,
62
+ bvh,
63
+ source_data,
64
+ scratch_frustum_planes,
65
+ scratch_corners
66
+ );
67
+
68
+ // construct tile data
69
+
70
+ // write number of light entries
71
+ cluster_data[tile_texture_data_offset + 1] = match_count;
72
+
73
+ if (match_count === 0) {
74
+ // don't waste time on assignment, there are no lights in the cluster
75
+ return 0;
76
+ }
77
+
78
+ /*
79
+ We can sort lights by index to get higher cache utilization, but it turns out quite costly.
80
+ Through experimentation, sorting was taking around 20% of the whole assignment time
81
+ while improving number of cache hits by around 40% (8000 without sorting -> 11000 with sorting)
82
+ */
83
+ const cluster_data_hash = compute_light_data_hash_0(scratch_light_nodes, match_count) & (LOOKUP_CACHE_SIZE - 1);
84
+
85
+ const existing_lookup_address = LOOKUP_CACHE[cluster_data_hash];
86
+
87
+ if (existing_lookup_address !== 0xFFFFFFFF) {
88
+ // found a hash match, validate equality
89
+ const existing_lookup_entry_match = testClusterEquality(
90
+ scratch_light_nodes,
91
+ match_count,
92
+ lookup_data,
93
+ existing_lookup_address
94
+ );
95
+
96
+ if (existing_lookup_entry_match) {
97
+ cluster_data[tile_texture_data_offset] = existing_lookup_address;
98
+
99
+ //hash_reuse_count++;
100
+
101
+ return 0;
102
+ }
103
+ } else {
104
+ // no hash entry exists, create one
105
+ LOOKUP_CACHE[cluster_data_hash] = lookup_address_offset;
106
+ }
107
+
108
+ // if we get to this point, that means no hash match was found, or equality check was failed
109
+
110
+ // write address of the lookup table
111
+ cluster_data[tile_texture_data_offset] = lookup_address_offset;
112
+
113
+ for (let i = 0; i < match_count; i++) {
114
+ /**
115
+ *
116
+ * @type {number}
117
+ */
118
+ const light_descriptor = scratch_light_nodes[i];
119
+
120
+ // construct lookup table
121
+ lookup_data[lookup_address_offset + i] = light_descriptor;
122
+ }
123
+
124
+ return match_count;
125
+ }
@@ -100,7 +100,11 @@ export class Decal extends AbstractLight {
100
100
 
101
101
  array_copy(this.uv, 0, destination, address + 16, 4);
102
102
 
103
- return 20;
103
+ // required for sorting
104
+ destination[address + 20] = this.draw_priority;
105
+ destination[address + 21] = this.id;
106
+
107
+ return 24;
104
108
  }
105
109
  }
106
110
 
@@ -110,4 +114,4 @@ export class Decal extends AbstractLight {
110
114
  */
111
115
  Decal.prototype.isDecal = true;
112
116
 
113
- Decal.prototype.ENCODED_SLOT_COUNT = 20;
117
+ Decal.prototype.ENCODED_SLOT_COUNT = 24;
@@ -0,0 +1,5 @@
1
+ import {EnginePlugin} from "../../../../plugin/EnginePlugin";
2
+
3
+ export class ForwardPlusRenderingPlugin extends EnginePlugin {
4
+ setClusterResolution(x: number, y: number, z: number): void
5
+ }
@@ -9,6 +9,7 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
9
9
  super();
10
10
 
11
11
  this.__light_manager = new LightManager();
12
+ lm.setTileMapResolution(16, 8, 8);
12
13
 
13
14
  this.__resolution = new ThreeVector2();
14
15
 
@@ -19,6 +20,16 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
19
20
  });
20
21
  }
21
22
 
23
+ /**
24
+ * 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
25
+ * @param {number} x
26
+ * @param {number} y
27
+ * @param {number} z
28
+ */
29
+ setClusterResolution(x, y, z) {
30
+ this.__light_manager.setTileMapResolution(x, y, z);
31
+ }
32
+
22
33
  /**
23
34
  *
24
35
  * @param {CameraView} view
@@ -37,10 +48,6 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
37
48
  }
38
49
 
39
50
  async startup() {
40
- const lm = this.__light_manager;
41
-
42
- lm.setTileMapResolution(16, 8, 8);
43
-
44
51
  const graphics = this.engine.graphics;
45
52
 
46
53
  graphics.getMaterialManager().addCompileStep(this.__material_transformer);