@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,385 +1,385 @@
1
- import { assert } from "../../../core/assert.js";
2
- import { ArrayIteratorRandom } from "../../../core/collection/array/iterator/ArrayIteratorRandom.js";
3
- import { circle_area } from "../../../core/geom/2d/circle/circle_area.js";
4
- import { clamp } from "../../../core/math/clamp.js";
5
- import { clamp01 } from "../../../core/math/clamp01.js";
6
- import { NumericInterval } from "../../../core/math/interval/NumericInterval.js";
7
- import { min2 } from "../../../core/math/min2.js";
8
- import { seededRandom } from "../../../core/math/random/seededRandom.js";
9
- import { computeStatisticalMean } from "../../../core/math/statistics/computeStatisticalMean.js";
10
- import Task from "../../../core/process/task/Task.js";
11
- import TaskGroup from "../../../core/process/task/TaskGroup.js";
12
- import { TaskSignal } from "../../../core/process/task/TaskSignal.js";
13
- import { actionTask } from "../../../core/process/task/util/actionTask.js";
14
- import { MarkerNodeMatcherAny } from "../../markers/matcher/MarkerNodeMatcherAny.js";
15
- import { GridTaskGenerator } from "../GridTaskGenerator.js";
16
-
17
- export class GridTaskDensityMarkerDistribution extends GridTaskGenerator {
18
-
19
- /**
20
- *
21
- * @type {CellFilter}
22
- */
23
- density = null;
24
-
25
- /**
26
- *
27
- * @type {GridCellActionPlaceMarker}
28
- */
29
- action = null;
30
-
31
- /**
32
- *
33
- * @type {NumericInterval}
34
- */
35
- scale = new NumericInterval(1, 1);
36
-
37
- /**
38
- * RNG seed
39
- * @type {number}
40
- * @private
41
- */
42
- __seed = 0;
43
-
44
-
45
- /**
46
- *
47
- * @param {CellFilter} density
48
- * @param {GridCellActionPlaceMarker} action
49
- * @param {NumericInterval} scale
50
- * @param {number} [seed]
51
- */
52
- static from(density, action, scale, seed = 0) {
53
- assert.ok(density.isCellFilter, 'density.isCellFilter');
54
- assert.ok(action.isGridCellActionPlaceMarker, 'action.isGridCellActionPlaceMarker');
55
- assert.ok(scale.isNumericInterval, 'scale.isNumericInterval');
56
-
57
- const r = new GridTaskDensityMarkerDistribution();
58
-
59
- r.scale = scale;
60
- r.action = action;
61
- r.density = density;
62
-
63
- r.__seed = seed;
64
-
65
- return r;
66
- }
67
-
68
-
69
- /**
70
- *
71
- * @param {GridData} grid
72
- * @param {number} x0
73
- * @param {number} y0
74
- * @param {number} x1
75
- * @param {number} y1
76
- * @returns {number}
77
- */
78
- estimateTapCount(grid, x0, y0, x1, y1) {
79
- const random = seededRandom(this.__seed + 91234);
80
-
81
- /*
82
- for tap count we ween 3 things:
83
- 1) size of the field
84
- 2) average density
85
- 3) average size of the marker
86
- */
87
-
88
- const width = x1 - x0;
89
- const height = y1 - y0;
90
-
91
- const grid_cell_count = width * height;
92
-
93
- const SAMPLE_SIZE = 60 + grid_cell_count * 0.01;
94
-
95
- const x_max = width - 1;
96
- const y_max = height - 1;
97
-
98
- /**
99
- *
100
- * @type {number[]}
101
- */
102
- const samplesCollisions = [];
103
-
104
- /**
105
- *
106
- * @type {number[]}
107
- */
108
- const samplesSize = [];
109
-
110
- const SIZE_SAMPLE_LIMIT = 10 + SAMPLE_SIZE * 3;
111
-
112
- for (let i = 0; i < SIZE_SAMPLE_LIMIT; i++) {
113
- const u = random();
114
- const v = random();
115
-
116
- const x = x0 + u * x_max;
117
- const y = y0 + v * y_max;
118
-
119
- const densityValue = this.density.execute(grid, x, y, 0);
120
-
121
- const density = clamp01(densityValue);
122
-
123
- if (density <= 0) {
124
- continue;
125
- }
126
-
127
- const node = this.action.buildNode(grid, x, y, 0);
128
-
129
- const markerScale = this.scale.sampleRandom(random);
130
-
131
- //modify size and scale
132
- node.size *= markerScale;
133
-
134
- samplesSize.push(node.size);
135
-
136
-
137
- const overlap = grid.containsMarkerInCircle(x, y, node.size, MarkerNodeMatcherAny.INSTANCE);
138
-
139
- if (overlap) {
140
- //collision
141
- samplesCollisions.push(1);
142
- } else {
143
- samplesCollisions.push(0);
144
- }
145
-
146
- if (samplesSize.length >= SAMPLE_SIZE) {
147
- break;
148
- }
149
- }
150
-
151
- const collisionProbability = samplesCollisions.length > 0 ? computeStatisticalMean(samplesCollisions) : 0.01;
152
-
153
- const meanNodeSize = samplesSize.length > 0 ? computeStatisticalMean(samplesSize) : 1;
154
-
155
- //compute relative density per unit square of a single marker
156
- const meanSingleNodeDensity = circle_area(meanNodeSize);
157
-
158
- const saturationTapCount = grid_cell_count / meanSingleNodeDensity;
159
-
160
- // it is possible that a tap will collide with other existing nodes, in which situation the tap is rejected, to account for that we want to estimate how often this will happen and increase the tap count accordingly
161
- const collisionCompensation = clamp(1 / (1 - collisionProbability), 1, 10);
162
-
163
- return saturationTapCount * collisionCompensation;
164
- }
165
-
166
- /**
167
- *
168
- * @param {number} seed
169
- * @param {GridData} grid
170
- * @param {number} x0
171
- * @param {number} y0
172
- * @param {number} x1
173
- * @param {number} y1
174
- * @returns {Task}
175
- */
176
- processArea(
177
- seed,
178
- grid,
179
- x0, y0, x1, y1
180
- ) {
181
-
182
- //we want to estimate average size of a marker
183
-
184
- let rejectedSampleBudget = 100;
185
-
186
- let iterationLimit = 0;
187
- let iteration = 0;
188
-
189
- /**
190
- *
191
- * @type {CellFilter}
192
- */
193
- const density = this.density;
194
-
195
- /**
196
- *
197
- * @type {GridCellActionPlaceMarker}
198
- */
199
- const action = this.action;
200
-
201
- /**
202
- *
203
- * @type {NumericInterval}
204
- */
205
- const scale = this.scale;
206
-
207
- const random = seededRandom(seed);
208
-
209
- const width = x1 - x0;
210
- const height = y1 - y0;
211
-
212
- /**
213
- * @returns {TaskSignal}
214
- */
215
- function cycleFunction() {
216
-
217
- if (iteration >= iterationLimit) {
218
- //iteration budget exhausted
219
- return TaskSignal.EndSuccess;
220
- }
221
-
222
- iteration++;
223
-
224
- //pick point
225
- const u = random();
226
- const v = random();
227
-
228
- const _x = x0 + u * width;
229
- const _y = y0 + v * height;
230
-
231
- //sample density mask
232
- const densityValue = density.execute(grid, _x, _y, 0);
233
-
234
- if (densityValue <= 1e-6) {
235
- // 0% chance
236
- return TaskSignal.Continue;
237
- }
238
-
239
- //do a roll to check if we should place a marker here
240
- const densityRoll = random();
241
-
242
- if (densityRoll > densityValue) {
243
- //probability roll against density value failed
244
- return TaskSignal.Continue;
245
- }
246
-
247
- const node = action.buildNode(grid, _x, _y, 0);
248
-
249
- // remember the filter value for debug purposes
250
- node.properties['@filter_density'] = densityValue;
251
-
252
- const markerScale = scale.sampleRandom(random);
253
-
254
- //modify size and scale
255
- node.size *= markerScale;
256
-
257
- node.transform.scale.multiplyScalar(markerScale);
258
-
259
- const overlap = grid.containsMarkerInCircle(_x, _y, node.size, MarkerNodeMatcherAny.INSTANCE);
260
-
261
- if (overlap) {
262
- rejectedSampleBudget--;
263
- iterationLimit++;
264
-
265
- if (rejectedSampleBudget <= 0) {
266
- //rejection budget exhausted, terminate
267
- return TaskSignal.EndSuccess;
268
- } else {
269
- return TaskSignal.Continue;
270
- }
271
-
272
- }
273
-
274
- grid.addMarker(node);
275
-
276
- return TaskSignal.Continue;
277
- }
278
-
279
- return new Task({
280
- name: 'Density marker distribution',
281
- cycleFunction,
282
- initializer: () => {
283
-
284
- random.setCurrentSeed(seed);
285
-
286
- iterationLimit = this.estimateTapCount(grid, x0, y0, x1, y1);
287
- iteration = 0;
288
-
289
- assert.isNumber(iterationLimit, 'iterationLimit');
290
- assert.isFiniteNumber(iterationLimit, 'iterationLimit');
291
- assert.ok(!Number.isNaN(iterationLimit), 'iterationLimit is NaN');
292
-
293
- rejectedSampleBudget = iterationLimit * 0.15;
294
- },
295
- estimatedDuration: height * width / 6000,
296
- computeProgress() {
297
- if (iterationLimit === 0) {
298
- return 0;
299
- }
300
-
301
- return clamp01(iteration / iterationLimit);
302
- }
303
- });
304
- }
305
-
306
-
307
- build(grid, ecd, seed) {
308
-
309
- const random = seededRandom(seed);
310
-
311
- const tile_size = 16;
312
-
313
-
314
- const tasks = [];
315
-
316
- //we want to estimate average size of a marker
317
- const tInitialize = actionTask(() => {
318
-
319
- if (!this.density.initialized) {
320
- this.density.initialize(grid, seed);
321
- }
322
-
323
- this.action.initialize(grid, seed);
324
- });
325
-
326
- /*
327
- we break the entire region into smaller tiles (16x16)
328
- this puts an upper bound of sorts on how many samples we have to take, irrespective of actual grid size
329
- these tiles also give us something closer to blue noise distribution
330
- The assumption with which the tile size was chosen is average marker size of ~1
331
- Actual tile size was tweaked using empirical tests
332
- */
333
-
334
- for (let j = 0; j < grid.height; j += tile_size) {
335
-
336
- const y0 = j;
337
- const y1 = min2(grid.height, j + tile_size);
338
-
339
- for (let i = 0; i < grid.width; i += tile_size) {
340
-
341
- const x0 = i;
342
- const x1 = min2(grid.width, i + tile_size);
343
-
344
- const seed = Math.floor(random() * Number.MAX_SAFE_INTEGER);
345
-
346
- const task = this.processArea(seed, grid, x0, y0, x1, y1);
347
-
348
- task.addDependency(tInitialize);
349
-
350
- tasks.push(task);
351
- }
352
- }
353
-
354
- // build a random dependency chain to ensure that tiles are not processed in parallel, thus crating race conditions and leading to non-deterministic result
355
- const iteratorRandom = new ArrayIteratorRandom();
356
- iteratorRandom.initialize(tasks);
357
-
358
- const iteratorValue = {
359
- done: false,
360
- value: undefined
361
- };
362
-
363
- iteratorRandom.next(iteratorValue);
364
-
365
- let previous = iteratorValue.value;
366
-
367
- for (; ;) {
368
- iteratorRandom.next(iteratorValue);
369
-
370
- if (iteratorValue.done) {
371
- break;
372
- }
373
-
374
- const task = iteratorValue.value;
375
-
376
- task.addDependency(previous);
377
-
378
- previous = task;
379
- }
380
-
381
- tasks.push(tInitialize);
382
-
383
- return new TaskGroup(tasks, "Density marker distribution");
384
- }
385
- }
1
+ import { assert } from "../../../core/assert.js";
2
+ import { ArrayIteratorRandom } from "../../../core/collection/array/iterator/ArrayIteratorRandom.js";
3
+ import { circle_area } from "../../../core/geom/2d/circle/circle_area.js";
4
+ import { clamp } from "../../../core/math/clamp.js";
5
+ import { clamp01 } from "../../../core/math/clamp01.js";
6
+ import { NumericInterval } from "../../../core/math/interval/NumericInterval.js";
7
+ import { min2 } from "../../../core/math/min2.js";
8
+ import { seededRandom } from "../../../core/math/random/seededRandom.js";
9
+ import { computeStatisticalMean } from "../../../core/math/statistics/computeStatisticalMean.js";
10
+ import Task from "../../../core/process/task/Task.js";
11
+ import TaskGroup from "../../../core/process/task/TaskGroup.js";
12
+ import { TaskSignal } from "../../../core/process/task/TaskSignal.js";
13
+ import { actionTask } from "../../../core/process/task/util/actionTask.js";
14
+ import { MarkerNodeMatcherAny } from "../../markers/matcher/MarkerNodeMatcherAny.js";
15
+ import { GridTaskGenerator } from "../GridTaskGenerator.js";
16
+
17
+ export class GridTaskDensityMarkerDistribution extends GridTaskGenerator {
18
+
19
+ /**
20
+ *
21
+ * @type {CellFilter}
22
+ */
23
+ density = null;
24
+
25
+ /**
26
+ *
27
+ * @type {GridCellActionPlaceMarker}
28
+ */
29
+ action = null;
30
+
31
+ /**
32
+ *
33
+ * @type {NumericInterval}
34
+ */
35
+ scale = new NumericInterval(1, 1);
36
+
37
+ /**
38
+ * RNG seed
39
+ * @type {number}
40
+ * @private
41
+ */
42
+ __seed = 0;
43
+
44
+
45
+ /**
46
+ *
47
+ * @param {CellFilter} density
48
+ * @param {GridCellActionPlaceMarker} action
49
+ * @param {NumericInterval} scale
50
+ * @param {number} [seed]
51
+ */
52
+ static from(density, action, scale, seed = 0) {
53
+ assert.ok(density.isCellFilter, 'density.isCellFilter');
54
+ assert.ok(action.isGridCellActionPlaceMarker, 'action.isGridCellActionPlaceMarker');
55
+ assert.ok(scale.isNumericInterval, 'scale.isNumericInterval');
56
+
57
+ const r = new GridTaskDensityMarkerDistribution();
58
+
59
+ r.scale = scale;
60
+ r.action = action;
61
+ r.density = density;
62
+
63
+ r.__seed = seed;
64
+
65
+ return r;
66
+ }
67
+
68
+
69
+ /**
70
+ *
71
+ * @param {GridData} grid
72
+ * @param {number} x0
73
+ * @param {number} y0
74
+ * @param {number} x1
75
+ * @param {number} y1
76
+ * @returns {number}
77
+ */
78
+ estimateTapCount(grid, x0, y0, x1, y1) {
79
+ const random = seededRandom(this.__seed + 91234);
80
+
81
+ /*
82
+ for tap count we ween 3 things:
83
+ 1) size of the field
84
+ 2) average density
85
+ 3) average size of the marker
86
+ */
87
+
88
+ const width = x1 - x0;
89
+ const height = y1 - y0;
90
+
91
+ const grid_cell_count = width * height;
92
+
93
+ const SAMPLE_SIZE = 60 + grid_cell_count * 0.01;
94
+
95
+ const x_max = width - 1;
96
+ const y_max = height - 1;
97
+
98
+ /**
99
+ *
100
+ * @type {number[]}
101
+ */
102
+ const samplesCollisions = [];
103
+
104
+ /**
105
+ *
106
+ * @type {number[]}
107
+ */
108
+ const samplesSize = [];
109
+
110
+ const SIZE_SAMPLE_LIMIT = 10 + SAMPLE_SIZE * 3;
111
+
112
+ for (let i = 0; i < SIZE_SAMPLE_LIMIT; i++) {
113
+ const u = random();
114
+ const v = random();
115
+
116
+ const x = x0 + u * x_max;
117
+ const y = y0 + v * y_max;
118
+
119
+ const densityValue = this.density.execute(grid, x, y, 0);
120
+
121
+ const density = clamp01(densityValue);
122
+
123
+ if (density <= 0) {
124
+ continue;
125
+ }
126
+
127
+ const node = this.action.buildNode(grid, x, y, 0);
128
+
129
+ const markerScale = this.scale.sampleRandom(random);
130
+
131
+ //modify size and scale
132
+ node.size *= markerScale;
133
+
134
+ samplesSize.push(node.size);
135
+
136
+
137
+ const overlap = grid.containsMarkerInCircle(x, y, node.size, MarkerNodeMatcherAny.INSTANCE);
138
+
139
+ if (overlap) {
140
+ //collision
141
+ samplesCollisions.push(1);
142
+ } else {
143
+ samplesCollisions.push(0);
144
+ }
145
+
146
+ if (samplesSize.length >= SAMPLE_SIZE) {
147
+ break;
148
+ }
149
+ }
150
+
151
+ const collisionProbability = samplesCollisions.length > 0 ? computeStatisticalMean(samplesCollisions) : 0.01;
152
+
153
+ const meanNodeSize = samplesSize.length > 0 ? computeStatisticalMean(samplesSize) : 1;
154
+
155
+ //compute relative density per unit square of a single marker
156
+ const meanSingleNodeDensity = circle_area(meanNodeSize);
157
+
158
+ const saturationTapCount = grid_cell_count / meanSingleNodeDensity;
159
+
160
+ // it is possible that a tap will collide with other existing nodes, in which situation the tap is rejected, to account for that we want to estimate how often this will happen and increase the tap count accordingly
161
+ const collisionCompensation = clamp(1 / (1 - collisionProbability), 1, 10);
162
+
163
+ return saturationTapCount * collisionCompensation;
164
+ }
165
+
166
+ /**
167
+ *
168
+ * @param {number} seed
169
+ * @param {GridData} grid
170
+ * @param {number} x0
171
+ * @param {number} y0
172
+ * @param {number} x1
173
+ * @param {number} y1
174
+ * @returns {Task}
175
+ */
176
+ processArea(
177
+ seed,
178
+ grid,
179
+ x0, y0, x1, y1
180
+ ) {
181
+
182
+ //we want to estimate average size of a marker
183
+
184
+ let rejectedSampleBudget = 100;
185
+
186
+ let iterationLimit = 0;
187
+ let iteration = 0;
188
+
189
+ /**
190
+ *
191
+ * @type {CellFilter}
192
+ */
193
+ const density = this.density;
194
+
195
+ /**
196
+ *
197
+ * @type {GridCellActionPlaceMarker}
198
+ */
199
+ const action = this.action;
200
+
201
+ /**
202
+ *
203
+ * @type {NumericInterval}
204
+ */
205
+ const scale = this.scale;
206
+
207
+ const random = seededRandom(seed);
208
+
209
+ const width = x1 - x0;
210
+ const height = y1 - y0;
211
+
212
+ /**
213
+ * @returns {TaskSignal}
214
+ */
215
+ function cycleFunction() {
216
+
217
+ if (iteration >= iterationLimit) {
218
+ //iteration budget exhausted
219
+ return TaskSignal.EndSuccess;
220
+ }
221
+
222
+ iteration++;
223
+
224
+ //pick point
225
+ const u = random();
226
+ const v = random();
227
+
228
+ const _x = x0 + u * width;
229
+ const _y = y0 + v * height;
230
+
231
+ //sample density mask
232
+ const densityValue = density.execute(grid, _x, _y, 0);
233
+
234
+ if (densityValue <= 1e-6) {
235
+ // 0% chance
236
+ return TaskSignal.Continue;
237
+ }
238
+
239
+ //do a roll to check if we should place a marker here
240
+ const densityRoll = random();
241
+
242
+ if (densityRoll > densityValue) {
243
+ //probability roll against density value failed
244
+ return TaskSignal.Continue;
245
+ }
246
+
247
+ const node = action.buildNode(grid, _x, _y, 0);
248
+
249
+ // remember the filter value for debug purposes
250
+ node.properties['@filter_density'] = densityValue;
251
+
252
+ const markerScale = scale.sampleRandom(random);
253
+
254
+ //modify size and scale
255
+ node.size *= markerScale;
256
+
257
+ node.transform.scale.multiplyScalar(markerScale);
258
+
259
+ const overlap = grid.containsMarkerInCircle(_x, _y, node.size, MarkerNodeMatcherAny.INSTANCE);
260
+
261
+ if (overlap) {
262
+ rejectedSampleBudget--;
263
+ iterationLimit++;
264
+
265
+ if (rejectedSampleBudget <= 0) {
266
+ //rejection budget exhausted, terminate
267
+ return TaskSignal.EndSuccess;
268
+ } else {
269
+ return TaskSignal.Continue;
270
+ }
271
+
272
+ }
273
+
274
+ grid.addMarker(node);
275
+
276
+ return TaskSignal.Continue;
277
+ }
278
+
279
+ return new Task({
280
+ name: 'Density marker distribution',
281
+ cycleFunction,
282
+ initializer: () => {
283
+
284
+ random.setCurrentSeed(seed);
285
+
286
+ iterationLimit = this.estimateTapCount(grid, x0, y0, x1, y1);
287
+ iteration = 0;
288
+
289
+ assert.isNumber(iterationLimit, 'iterationLimit');
290
+ assert.isFinite(iterationLimit, 'iterationLimit');
291
+ assert.ok(!Number.isNaN(iterationLimit), 'iterationLimit is NaN');
292
+
293
+ rejectedSampleBudget = iterationLimit * 0.15;
294
+ },
295
+ estimatedDuration: height * width / 6000,
296
+ computeProgress() {
297
+ if (iterationLimit === 0) {
298
+ return 0;
299
+ }
300
+
301
+ return clamp01(iteration / iterationLimit);
302
+ }
303
+ });
304
+ }
305
+
306
+
307
+ build(grid, ecd, seed) {
308
+
309
+ const random = seededRandom(seed);
310
+
311
+ const tile_size = 16;
312
+
313
+
314
+ const tasks = [];
315
+
316
+ //we want to estimate average size of a marker
317
+ const tInitialize = actionTask(() => {
318
+
319
+ if (!this.density.initialized) {
320
+ this.density.initialize(grid, seed);
321
+ }
322
+
323
+ this.action.initialize(grid, seed);
324
+ });
325
+
326
+ /*
327
+ we break the entire region into smaller tiles (16x16)
328
+ this puts an upper bound of sorts on how many samples we have to take, irrespective of actual grid size
329
+ these tiles also give us something closer to blue noise distribution
330
+ The assumption with which the tile size was chosen is average marker size of ~1
331
+ Actual tile size was tweaked using empirical tests
332
+ */
333
+
334
+ for (let j = 0; j < grid.height; j += tile_size) {
335
+
336
+ const y0 = j;
337
+ const y1 = min2(grid.height, j + tile_size);
338
+
339
+ for (let i = 0; i < grid.width; i += tile_size) {
340
+
341
+ const x0 = i;
342
+ const x1 = min2(grid.width, i + tile_size);
343
+
344
+ const seed = Math.floor(random() * Number.MAX_SAFE_INTEGER);
345
+
346
+ const task = this.processArea(seed, grid, x0, y0, x1, y1);
347
+
348
+ task.addDependency(tInitialize);
349
+
350
+ tasks.push(task);
351
+ }
352
+ }
353
+
354
+ // build a random dependency chain to ensure that tiles are not processed in parallel, thus crating race conditions and leading to non-deterministic result
355
+ const iteratorRandom = new ArrayIteratorRandom();
356
+ iteratorRandom.initialize(tasks);
357
+
358
+ const iteratorValue = {
359
+ done: false,
360
+ value: undefined
361
+ };
362
+
363
+ iteratorRandom.next(iteratorValue);
364
+
365
+ let previous = iteratorValue.value;
366
+
367
+ for (; ;) {
368
+ iteratorRandom.next(iteratorValue);
369
+
370
+ if (iteratorValue.done) {
371
+ break;
372
+ }
373
+
374
+ const task = iteratorValue.value;
375
+
376
+ task.addDependency(previous);
377
+
378
+ previous = task;
379
+ }
380
+
381
+ tasks.push(tInitialize);
382
+
383
+ return new TaskGroup(tasks, "Density marker distribution");
384
+ }
385
+ }