@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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/editor/tools/v2/TransformControlsGizmo.js +1 -1
- package/editor/view/node-graph/NodeGraphEditorView.js +2 -2
- package/package.json +1 -1
- package/src/core/assert.d.ts +0 -2
- package/src/core/assert.d.ts.map +1 -1
- package/src/core/assert.js +0 -6
- package/src/core/color/Color.d.ts +0 -5
- package/src/core/color/Color.d.ts.map +1 -1
- package/src/core/color/Color.js +1 -7
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +386 -386
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +1 -1
- package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
- package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +160 -160
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +96 -96
- package/src/core/geom/packing/max-rect/MaxRectanglesPacker.js +1 -1
- package/src/core/geom/packing/max-rect/findBestContainer.js +4 -4
- package/src/core/geom/packing/max-rect/packOneBox.js +2 -2
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts +23 -0
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.js +96 -0
- package/src/core/graph/layout/box/BoxLayouter.js +7 -7
- package/src/core/graph/layout/box/position_box_next_to_box.js +6 -6
- package/src/core/math/computeWholeDivisorLow.js +33 -33
- package/src/core/math/linalg/eigen/eigen_values_find_spectral_gap.d.ts.map +1 -0
- package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts +10 -0
- package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts.map +1 -0
- package/src/core/{graph → math/linalg}/eigen/matrix_eigenvalues_in_place.js +8 -7
- package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts.map +1 -0
- package/src/core/{graph → math/linalg}/eigen/matrix_householder_in_place.js +11 -5
- package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts +15 -0
- package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts.map +1 -0
- package/src/core/{graph → math/linalg}/eigen/matrix_qr_in_place.js +8 -2
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts +17 -0
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts.map +1 -0
- package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.js +107 -0
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts +19 -0
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts.map +1 -0
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +161 -0
- package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts +15 -0
- package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts.map +1 -0
- package/src/core/math/linalg/polynomial_real_roots_in_interval.js +200 -0
- package/src/core/math/solveCubic.d.ts +15 -0
- package/src/core/math/solveCubic.d.ts.map +1 -0
- package/src/core/math/solveCubic.js +82 -0
- package/src/core/math/spline/spline3_hermite_bounds_t.d.ts +23 -0
- package/src/core/math/spline/spline3_hermite_bounds_t.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_bounds_t.js +109 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +25 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +44 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts +16 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +120 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +11 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +451 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +12 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +339 -0
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts +15 -0
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +21 -0
- package/src/core/math/spline/spline3_hermite_to_monomial.d.ts +24 -0
- package/src/core/math/spline/spline3_hermite_to_monomial.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -0
- package/src/core/math/spline/v3_computeCatmullRomSplineUniformDistance.js +1 -1
- package/src/core/model/node-graph/visual/NodeGraphVisualData.js +1 -1
- package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
- package/src/core/process/delay.js +16 -16
- package/src/engine/animation/async/TimeSeries.js +300 -300
- package/src/engine/animation/curve/AnimationCurve.d.ts +3 -2
- package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
- package/src/engine/animation/curve/AnimationCurve.js +3 -2
- package/src/engine/animation/curve/draw/position_canvas_to_curve.js +2 -2
- package/src/engine/animation/curve/draw/position_curve_to_canvas.js +2 -2
- package/src/engine/ecs/fow/shader/FogOfWarRenderer.js +145 -145
- package/src/engine/ecs/gui/position/ViewportPositionSystem.js +2 -2
- package/src/engine/ecs/parent/entity_node_compute_bounding_box.js +1 -1
- package/src/engine/ecs/transform/Transform.d.ts +0 -10
- package/src/engine/ecs/transform/Transform.d.ts.map +1 -1
- package/src/engine/ecs/transform/Transform.js +0 -12
- package/src/engine/graphics/composit/CompositLayer.js +254 -254
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +1 -1
- package/src/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +2 -2
- package/src/engine/graphics/material/optimization/MaterialOptimizationContext.js +3 -3
- package/src/engine/graphics/particles/particular/engine/utils/volume/AttributeValue.js +201 -201
- package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
- package/src/engine/graphics/render/buffer/slot/parameter/ProgramValueSlotParameterSet.js +2 -2
- package/src/engine/graphics/render/forward_plus/LightManager.js +1226 -1226
- package/src/engine/graphics/render/forward_plus/model/PointLight.js +1 -1
- package/src/engine/graphics/sh3/lpv/lpv_obtain_storage_cached_volume.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +2 -2
- package/src/engine/graphics/texture/atlas/TextureAtlasDebugger.js +1 -1
- package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
- package/src/engine/graphics/texture/sampler/serialization/TextureBinaryBufferSerializer.js +2 -2
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts +2 -6
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts.map +1 -1
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.js +0 -10
- package/src/engine/intelligence/mcts/MonteCarlo.js +275 -275
- package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
- package/src/generation/grid/GridData.js +220 -220
- package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +385 -385
- package/src/view/elements/image/SvgImageView.js +1 -1
- package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
- package/src/view/minimap/gl/MinimapFogOfWar.js +3 -3
- package/src/view/util/DomSizeObserver.js +1 -1
- package/src/core/binary/clz32.d.ts +0 -6
- package/src/core/binary/clz32.d.ts.map +0 -1
- package/src/core/binary/clz32.js +0 -5
- package/src/core/binary/type/dataTypeFromTypedArray.d.ts +0 -8
- package/src/core/binary/type/dataTypeFromTypedArray.d.ts.map +0 -1
- package/src/core/binary/type/dataTypeFromTypedArray.js +0 -11
- package/src/core/collection/array/computeHashIntegerArray.d.ts +0 -1
- package/src/core/collection/array/computeHashIntegerArray.d.ts.map +0 -1
- package/src/core/collection/array/computeHashIntegerArray.js +0 -7
- package/src/core/collection/array/typed/typedArrayToDataType.d.ts +0 -6
- package/src/core/collection/array/typed/typedArrayToDataType.d.ts.map +0 -1
- package/src/core/collection/array/typed/typedArrayToDataType.js +0 -6
- package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.d.ts +0 -6
- package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.d.ts.map +0 -1
- package/src/core/geom/3d/mat4/MATRIX_4_IDENTITY.js +0 -7
- package/src/core/graph/eigen/eigen_values_find_spectral_gap.d.ts.map +0 -1
- package/src/core/graph/eigen/matrix_eigenvalues_in_place.d.ts +0 -8
- package/src/core/graph/eigen/matrix_eigenvalues_in_place.d.ts.map +0 -1
- package/src/core/graph/eigen/matrix_householder_in_place.d.ts.map +0 -1
- package/src/core/graph/eigen/matrix_qr_in_place.d.ts +0 -9
- package/src/core/graph/eigen/matrix_qr_in_place.d.ts.map +0 -1
- package/src/core/math/spline/cubicCurve.d.ts +0 -6
- package/src/core/math/spline/cubicCurve.d.ts.map +0 -1
- package/src/core/math/spline/cubicCurve.js +0 -6
- package/src/core/math/spline/spline_bezier2.d.ts +0 -6
- package/src/core/math/spline/spline_bezier2.d.ts.map +0 -1
- package/src/core/math/spline/spline_bezier2.js +0 -6
- package/src/core/math/spline/spline_bezier3.d.ts +0 -6
- package/src/core/math/spline/spline_bezier3.d.ts.map +0 -1
- package/src/core/math/spline/spline_bezier3.js +0 -6
- package/src/core/math/spline/spline_bezier3_bounds.d.ts +0 -6
- package/src/core/math/spline/spline_bezier3_bounds.d.ts.map +0 -1
- package/src/core/math/spline/spline_bezier3_bounds.js +0 -6
- package/src/core/math/spline/spline_hermite3.d.ts +0 -6
- package/src/core/math/spline/spline_hermite3.d.ts.map +0 -1
- package/src/core/math/spline/spline_hermite3.js +0 -6
- package/src/core/math/spline/spline_hermite3_bounds.d.ts +0 -6
- package/src/core/math/spline/spline_hermite3_bounds.d.ts.map +0 -1
- package/src/core/math/spline/spline_hermite3_bounds.js +0 -6
- package/src/core/math/spline/spline_hermite3_to_bezier.d.ts +0 -2
- package/src/core/math/spline/spline_hermite3_to_bezier.d.ts.map +0 -1
- package/src/core/math/spline/spline_hermite3_to_bezier.js +0 -6
- package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.d.ts +0 -37
- package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.d.ts.map +0 -1
- package/src/engine/intelligence/behavior/decorator/RepeatUntilFailureBehavior.js +0 -70
- /package/src/core/{graph → math/linalg}/eigen/eigen_values_find_spectral_gap.d.ts +0 -0
- /package/src/core/{graph → math/linalg}/eigen/eigen_values_find_spectral_gap.js +0 -0
- /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.
|
|
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
|
+
}
|