@woosh/meep-engine 2.39.5 → 2.39.9
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/core/binary/BinaryBuffer.js +1 -0
- package/core/geom/3d/triangle/computeTriangleRayIntersection.js +39 -0
- package/core/math/FLT_EPSILON_32.js +7 -0
- package/core/math/random/seededRandom_Mulberry32.js +1 -1
- package/core/model/stat/LinearModifier.js +7 -0
- package/core/process/task/RemainingTimeEstimator.js +49 -0
- package/core/process/task/Task.js +3 -1
- package/engine/Engine.js +0 -83
- package/engine/asset/AssetManager.d.ts +3 -0
- package/engine/asset/AssetManager.js +5 -4
- package/engine/ecs/terrain/ecs/Terrain.js +33 -0
- package/engine/ecs/terrain/ecs/layers/TerrainLayer.js +13 -0
- package/engine/ecs/terrain/ecs/layers/TerrainLayers.js +44 -2
- package/engine/ecs/terrain/ecs/splat/SplatMapping.js +12 -4
- package/engine/graphics/ecs/path/testPathDisplaySystem.js +58 -53
- package/engine/graphics/ecs/path/tube/PathNormalType.d.ts +4 -0
- package/engine/graphics/ecs/path/tube/PathNormalType.js +11 -0
- package/engine/graphics/ecs/path/tube/TubePathStyle.d.ts +4 -0
- package/engine/graphics/ecs/path/tube/TubePathStyle.js +21 -3
- package/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.js +4 -1
- package/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +4 -1
- package/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +41 -24
- package/engine/graphics/geometry/VertexDataSpec.js +24 -0
- package/engine/graphics/geometry/buffered/query/RaycastNearestHitComputingVisitor.js +25 -5
- package/engine/graphics/shaders/TerrainShader.js +3 -7
- package/engine/knowledge/database/StaticKnowledgeDataTable.js +66 -28
- package/engine/navigation/ecs/components/Path.js +58 -232
- package/engine/notify/Notification.js +3 -0
- package/engine/scene/transitionToScene.js +2 -1
- package/engine/ui/notification/AnimatedObjectEmitter.js +3 -2
- package/engine/ui/notification/ViewEmitter.js +8 -0
- package/engine/ui/scene/initializeNotifications.js +3 -0
- package/generation/{GridGenerator.js → GridTaskGroup.js} +21 -5
- package/generation/example/SampleGenerator0.js +2 -2
- package/generation/example/main.js +2 -1
- package/generation/filtering/core/CellFilterUnaryOperation.js +5 -8
- package/generation/filtering/numeric/util/CellFilterDisplaced.js +7 -7
- package/generation/grid/generation/GridTaskSequence.js +5 -3
- package/generation/markers/actions/MarkerNodeActionImaginary.js +86 -0
- package/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +265 -0
- package/generation/theme/TerrainLayerDescription.js +11 -0
- package/generation/theme/ThemeEngine.js +5 -6
- package/package.json +1 -1
- package/samples/terrain/from_image.js +2 -1
- package/view/common/ListView.js +7 -1
- package/view/elements/windrose/WindRoseDiagram.js +13 -0
- package/view/task/TaskLoadingScreen.js +172 -0
- package/view/{common → task}/TaskProgressView.js +4 -36
- package/view/tooltip/gml/GMLEngine.js +13 -0
- package/generation/markers/actions/MarkerNodeActionSelectRandom.js +0 -65
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { MarkerNodeAction } from "./MarkerNodeAction.js";
|
|
2
|
+
import { MarkerNodeConsumerBuffer } from "../emitter/MarkerNodeConsumerBuffer.js";
|
|
3
|
+
import Vector3 from "../../../core/geom/Vector3.js";
|
|
4
|
+
import { assert } from "../../../core/assert.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Spawns imaginary markers that are not added to the grid, and executes further actions on these
|
|
8
|
+
* Useful when desired additional markers are not present in the grid
|
|
9
|
+
*/
|
|
10
|
+
export class MarkerNodeActionImaginary extends MarkerNodeAction {
|
|
11
|
+
/**
|
|
12
|
+
* @type {MarkerNodeEmitter}
|
|
13
|
+
*/
|
|
14
|
+
emitter;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @type {MarkerNodeAction}
|
|
18
|
+
*/
|
|
19
|
+
action;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pre-multiplies emitted marker transform by the transform of the consumed node
|
|
23
|
+
* @type {boolean}
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
propagate_scale = true;
|
|
27
|
+
|
|
28
|
+
_buffer = new MarkerNodeConsumerBuffer();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {MarkerNodeEmitter} emitter
|
|
33
|
+
* @param {MarkerNodeAction} action
|
|
34
|
+
* @param {boolean} [propagate_scale]
|
|
35
|
+
* @returns {MarkerNodeActionImaginary}
|
|
36
|
+
*/
|
|
37
|
+
static from({
|
|
38
|
+
emitter,
|
|
39
|
+
action,
|
|
40
|
+
propagate_scale = true
|
|
41
|
+
}) {
|
|
42
|
+
|
|
43
|
+
assert.defined(emitter, 'emitter');
|
|
44
|
+
assert.equal(emitter.isMarkerNodeEmitter, true, 'emitter.isMarkerNodeEmitter !== true');
|
|
45
|
+
|
|
46
|
+
assert.defined(action, 'emitter');
|
|
47
|
+
assert.equal(action.isMarkerNodeAction, true, 'action.isMarkerNodeAction !== true');
|
|
48
|
+
|
|
49
|
+
assert.isBoolean(propagate_scale, 'propagate_scale');
|
|
50
|
+
|
|
51
|
+
const r = new MarkerNodeActionImaginary();
|
|
52
|
+
|
|
53
|
+
r.emitter = emitter;
|
|
54
|
+
r.action = action;
|
|
55
|
+
r.propagate_scale = propagate_scale;
|
|
56
|
+
|
|
57
|
+
return r;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
execute(grid, ecd, node) {
|
|
61
|
+
this._buffer.reset();
|
|
62
|
+
|
|
63
|
+
const rotation = node.transform.rotation.computeTwistAngle(Vector3.down);
|
|
64
|
+
|
|
65
|
+
this.emitter.execute(grid, node.position.x, node.position.y, rotation, this._buffer);
|
|
66
|
+
|
|
67
|
+
const n = this._buffer.size();
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < n; i++) {
|
|
70
|
+
const imaginary_node = this._buffer.get(i);
|
|
71
|
+
|
|
72
|
+
if (this.propagate_scale) {
|
|
73
|
+
imaginary_node.transform.scale.multiply(node.transform.scale);
|
|
74
|
+
|
|
75
|
+
imaginary_node.size *= node.size;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.action.execute(grid, ecd, imaginary_node);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
initialize(grid, ecd, seed) {
|
|
83
|
+
this.emitter.initialize(grid, seed);
|
|
84
|
+
this.action.initialize(grid, ecd, seed);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { MarkerNodeAction } from "../MarkerNodeAction.js";
|
|
2
|
+
import { CellFilterLiteralFloat } from "../../../filtering/numeric/CellFilterLiteralFloat.js";
|
|
3
|
+
import LinearModifier from "../../../../core/model/stat/LinearModifier.js";
|
|
4
|
+
import Terrain from "../../../../engine/ecs/terrain/ecs/Terrain.js";
|
|
5
|
+
import { seededRandom } from "../../../../core/math/random/seededRandom.js";
|
|
6
|
+
import { v2_distance } from "../../../../core/geom/Vector2.js";
|
|
7
|
+
import TransitionFunctions from "../../../../engine/animation/TransitionFunctions.js";
|
|
8
|
+
import { inverseLerp } from "../../../../core/math/inverseLerp.js";
|
|
9
|
+
import { clamp01 } from "../../../../core/math/clamp01.js";
|
|
10
|
+
import { clamp } from "../../../../core/math/clamp.js";
|
|
11
|
+
import { assert } from "../../../../core/assert.js";
|
|
12
|
+
import { TerrainLayer } from "../../../../engine/ecs/terrain/ecs/layers/TerrainLayer.js";
|
|
13
|
+
import { max2 } from "../../../../core/math/max2.js";
|
|
14
|
+
|
|
15
|
+
export class MarkerNodeActionPaintTerrain extends MarkerNodeAction {
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @CellFilter
|
|
19
|
+
*/
|
|
20
|
+
weight = CellFilterLiteralFloat.ONE;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What to paint
|
|
24
|
+
* @type {TerrainLayerDescription}
|
|
25
|
+
*/
|
|
26
|
+
layer = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Actual layer index, will be taken from the terrain upon initialization
|
|
30
|
+
* @type {number}
|
|
31
|
+
* @private
|
|
32
|
+
*/
|
|
33
|
+
_layer_index = -1;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @type {SplatMapping}
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
_splat_map = null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @type {Terrain}
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
_terrain = null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How far does the area extend from the marker's shape, has both % and constant component
|
|
51
|
+
* Can grow or shrink the area
|
|
52
|
+
* @type {LinearModifier}
|
|
53
|
+
*/
|
|
54
|
+
dilation = LinearModifier.CONSTANT_ZERO;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Function to apply smoothing to the area that falls into "edge"
|
|
58
|
+
* @type {function(number): number}
|
|
59
|
+
*/
|
|
60
|
+
weight_edge_easing_function = TransitionFunctions.EaseInOut;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* How wide is the edge, computed from the effective region including padding, must produce a non-negative number
|
|
64
|
+
* @type {LinearModifier}
|
|
65
|
+
*/
|
|
66
|
+
weight_edge_easing = LinearModifier.CONSTANT_ZERO;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @type {function():number}
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
_random;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* @param {LinearModifier} dilation
|
|
78
|
+
* @param {function(number):number} border_blending
|
|
79
|
+
* @param {LinearModifier} border
|
|
80
|
+
* @param {CellFilter} weight
|
|
81
|
+
* @param {TerrainLayerDescription} layer
|
|
82
|
+
* @returns {MarkerNodeActionPaintTerrain}
|
|
83
|
+
*/
|
|
84
|
+
static from({
|
|
85
|
+
dilation = LinearModifier.CONSTANT_ZERO,
|
|
86
|
+
border_blending = TransitionFunctions.EaseInOut,
|
|
87
|
+
border = LinearModifier.CONSTANT_ZERO,
|
|
88
|
+
weight = CellFilterLiteralFloat.ONE,
|
|
89
|
+
layer
|
|
90
|
+
}) {
|
|
91
|
+
|
|
92
|
+
assert.defined(layer, 'layer');
|
|
93
|
+
assert.notNull(layer, 'layer');
|
|
94
|
+
assert.equal(layer.isTerrainLayerDescription, true, 'layer.isTerrainLayerDescription !== true');
|
|
95
|
+
|
|
96
|
+
const r = new MarkerNodeActionPaintTerrain();
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
r.dilation = dilation;
|
|
100
|
+
r.weight_edge_easing = border;
|
|
101
|
+
r.weight_edge_easing_function = border_blending;
|
|
102
|
+
r.weight = weight;
|
|
103
|
+
r.layer = layer;
|
|
104
|
+
|
|
105
|
+
return r;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
*
|
|
110
|
+
* @param {number} x
|
|
111
|
+
* @param {number} y
|
|
112
|
+
* @param {MarkerNode} node
|
|
113
|
+
* @param {GridData} grid
|
|
114
|
+
* @param {number} radius_outer
|
|
115
|
+
* @param {number} radius_inner
|
|
116
|
+
* @returns {number}
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
_getPointWeight(x, y, node, grid, radius_outer, radius_inner) {
|
|
120
|
+
// compute distance from node center
|
|
121
|
+
const distance_from_center = v2_distance(node.position.x, node.position.y, x, y);
|
|
122
|
+
|
|
123
|
+
if (distance_from_center > radius_outer) {
|
|
124
|
+
// too far away
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const weight_sampled = this.weight.execute(grid, x, y, 0);
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// compute position in the padding region, where 0 is at the outer edge of the padding, and 1 is at the
|
|
132
|
+
const edge_normalized_position = clamp01(inverseLerp(radius_inner, radius_outer, distance_from_center));
|
|
133
|
+
|
|
134
|
+
// apply edge smoothing
|
|
135
|
+
const edge_weight_bias = this.weight_edge_easing_function(1 - edge_normalized_position);
|
|
136
|
+
|
|
137
|
+
return weight_sampled * edge_weight_bias;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_getLayerIndex() {
|
|
141
|
+
if (this._layer_index === -1) {
|
|
142
|
+
// bind terrain layer
|
|
143
|
+
const layers = this._terrain.layers;
|
|
144
|
+
const layer_descriptor = this.layer;
|
|
145
|
+
|
|
146
|
+
this._layer_index = layers.findIndexByDiffuseTextureURL(layer_descriptor.diffuse);
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if (this._layer_index !== -1) {
|
|
150
|
+
// check to see if size matches
|
|
151
|
+
const actual_layer = layers.get(this._layer_index);
|
|
152
|
+
if (!actual_layer.size.equals(layer_descriptor.size)) {
|
|
153
|
+
console.warn(`Actual terrain layer with texture '${layer_descriptor.diffuse}' has size ${actual_layer.size}, instead of desired ${layer_descriptor.size}. Ignoring this difference, layer will still be used`);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
|
|
157
|
+
// layer not found, let's create it
|
|
158
|
+
this._layer_index = this._terrain.addLayer(TerrainLayer.fromDescription(layer_descriptor));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this._layer_index;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
execute(grid, ecd, node) {
|
|
167
|
+
const _splat_layer_index = this._getLayerIndex();
|
|
168
|
+
|
|
169
|
+
const splat = this._splat_map;
|
|
170
|
+
|
|
171
|
+
const splat_resolution_x = splat.size.x;
|
|
172
|
+
const splat_resolution_y = splat.size.y;
|
|
173
|
+
|
|
174
|
+
// compute scale between grid and splat
|
|
175
|
+
const splat_scale_x = splat_resolution_x / grid.width;
|
|
176
|
+
const splat_scale_y = splat_resolution_y / grid.height;
|
|
177
|
+
|
|
178
|
+
const splat_to_grid_x = 1 / splat_scale_x;
|
|
179
|
+
const splat_to_grid_y = 1 / splat_scale_y;
|
|
180
|
+
|
|
181
|
+
const radius_outer = node.size * (1 + this.dilation.a) + this.dilation.b;
|
|
182
|
+
|
|
183
|
+
const edge_width = node.size * this.weight_edge_easing.a + this.weight_edge_easing.b;
|
|
184
|
+
const radius_inner = radius_outer - edge_width;
|
|
185
|
+
|
|
186
|
+
// we shift to the center of the grid cell
|
|
187
|
+
const node_p_x = node.position.x + splat_to_grid_x;
|
|
188
|
+
const node_p_y = node.position.y + splat_to_grid_y;
|
|
189
|
+
|
|
190
|
+
// compute affected region in Splat space
|
|
191
|
+
const x0 = Math.floor((node_p_x - radius_outer) * splat_scale_x + 0.5);
|
|
192
|
+
const x1 = Math.floor((node_p_x + radius_outer) * splat_scale_x + 0.5);
|
|
193
|
+
const y0 = Math.ceil((node_p_y - radius_outer) * splat_scale_y + 0.5);
|
|
194
|
+
const y1 = Math.ceil((node_p_y + radius_outer) * splat_scale_y + 0.5);
|
|
195
|
+
|
|
196
|
+
const layer_count = splat.depth;
|
|
197
|
+
|
|
198
|
+
const splat_data = splat.weightData;
|
|
199
|
+
|
|
200
|
+
// execute blending
|
|
201
|
+
for (let y = y0; y <= y1; y++) {
|
|
202
|
+
for (let x = x0; x <= x1; x++) {
|
|
203
|
+
|
|
204
|
+
// convert position into grid coordinates
|
|
205
|
+
const grid_x = (x - 0.5) * splat_to_grid_x - splat_to_grid_x;
|
|
206
|
+
const grid_y = (y - 0.5) * splat_to_grid_y - splat_to_grid_y;
|
|
207
|
+
|
|
208
|
+
const weight = this._getPointWeight(grid_x, grid_y, node, grid, radius_outer, radius_inner);
|
|
209
|
+
|
|
210
|
+
if (weight === 0) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const weight_scaled = weight * 255;
|
|
215
|
+
|
|
216
|
+
// paint
|
|
217
|
+
const weight_reciprocal_multiplier = max2(0, 1 - weight);
|
|
218
|
+
|
|
219
|
+
for (let layer_index = 0; layer_index < layer_count; layer_index++) {
|
|
220
|
+
|
|
221
|
+
const layer_address = splat_resolution_x * splat_resolution_y * layer_index;
|
|
222
|
+
const texel_address = layer_address + y * splat_resolution_x + x;
|
|
223
|
+
|
|
224
|
+
const source_value = splat_data[texel_address];
|
|
225
|
+
|
|
226
|
+
let destination_value;
|
|
227
|
+
|
|
228
|
+
if (_splat_layer_index === layer_index) {
|
|
229
|
+
// the channel that we are stamping
|
|
230
|
+
destination_value = source_value + weight_scaled;
|
|
231
|
+
} else {
|
|
232
|
+
// other layers get erased by proportional amount
|
|
233
|
+
destination_value = source_value * weight_reciprocal_multiplier;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
splat_data[texel_address] = clamp(destination_value, 0, 255);
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// mark splat map for update
|
|
243
|
+
splat.weightTexture.needsUpdate = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
initialize(grid, ecd, seed) {
|
|
247
|
+
// find layer index
|
|
248
|
+
const terrain = ecd.getAnyComponent(Terrain);
|
|
249
|
+
|
|
250
|
+
if (terrain.component === null) {
|
|
251
|
+
throw new Error('No terrain present');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this._terrain = terrain.component;
|
|
255
|
+
|
|
256
|
+
// bind splats
|
|
257
|
+
this._splat_map = terrain.component.splat;
|
|
258
|
+
|
|
259
|
+
this._layer_index = -1;
|
|
260
|
+
|
|
261
|
+
this._random = seededRandom(seed);
|
|
262
|
+
|
|
263
|
+
this.weight.ensureInitialized(grid, seed);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -34,6 +34,17 @@ export class TerrainLayerDescription {
|
|
|
34
34
|
&& this.size.equals(other.size);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
toJSON() {
|
|
38
|
+
return {
|
|
39
|
+
diffuse: this.diffuse,
|
|
40
|
+
size: this.size.toJSON()
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toString() {
|
|
45
|
+
return JSON.stringify(this.toJSON());
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
static from({
|
|
38
49
|
diffuse,
|
|
39
50
|
size
|
|
@@ -244,11 +244,7 @@ export class ThemeEngine {
|
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
layer_descriptors.forEach(l => {
|
|
247
|
-
terrain.layers.addLayer(TerrainLayer.
|
|
248
|
-
l.diffuse,
|
|
249
|
-
l.size.x,
|
|
250
|
-
l.size.y
|
|
251
|
-
));
|
|
247
|
+
terrain.layers.addLayer(TerrainLayer.fromDescription(l));
|
|
252
248
|
});
|
|
253
249
|
|
|
254
250
|
terrain.clearFlag(TerrainFlags.Built);
|
|
@@ -587,10 +583,13 @@ export class ThemeEngine {
|
|
|
587
583
|
const tCells = this.applyCellRules(grid, ecd);
|
|
588
584
|
|
|
589
585
|
tTerrain.addDependency(tInitializeThemes);
|
|
586
|
+
|
|
590
587
|
tNodes.addDependencies([
|
|
591
588
|
tInitializeThemes,
|
|
592
|
-
tTerrainGeometry
|
|
589
|
+
tTerrainGeometry,
|
|
590
|
+
tTerrain
|
|
593
591
|
]);
|
|
592
|
+
|
|
594
593
|
tCells.addDependency(tInitializeThemes);
|
|
595
594
|
tTerrainGeometry.addDependency(tCells);
|
|
596
595
|
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"productName": "Meep",
|
|
6
6
|
"description": "production-ready JavaScript game engine based on Entity Component System Architecture",
|
|
7
7
|
"author": "Alexander Goldring",
|
|
8
|
-
"version": "2.39.
|
|
8
|
+
"version": "2.39.9",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"gl-matrix": "3.4.3",
|
|
11
11
|
"fast-levenshtein": "2.0.6",
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../../engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
|
|
34
34
|
import { CellFilterAngleToNormal } from "../../generation/filtering/numeric/complex/CellFilterAngleToNormal.js";
|
|
35
35
|
import { CellFilterCache } from "../../generation/filtering/numeric/CellFilterCache.js";
|
|
36
|
+
import { TaskLoadingScreen } from "../../view/task/TaskLoadingScreen.js";
|
|
36
37
|
|
|
37
38
|
const HEIGHT_RANGE = 100;
|
|
38
39
|
|
|
@@ -267,7 +268,7 @@ async function main(engine) {
|
|
|
267
268
|
|
|
268
269
|
const task = theme_engine.apply(grid, ecd);
|
|
269
270
|
|
|
270
|
-
const promise_load =
|
|
271
|
+
const promise_load = TaskLoadingScreen.load(engine, task);
|
|
271
272
|
|
|
272
273
|
engine.executor.runGroup(task);
|
|
273
274
|
|
package/view/common/ListView.js
CHANGED
|
@@ -8,6 +8,7 @@ import View from "../View.js";
|
|
|
8
8
|
import { assert } from "../../core/assert.js";
|
|
9
9
|
import { noop, returnTrue } from "../../core/function/Functions.js";
|
|
10
10
|
import { Cache } from "../../core/cache/Cache.js";
|
|
11
|
+
import List from "../../core/collection/list/List.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @template T
|
|
@@ -52,7 +53,7 @@ export class ListView extends View {
|
|
|
52
53
|
/**
|
|
53
54
|
* List representation
|
|
54
55
|
* @template E
|
|
55
|
-
* @param {List<E
|
|
56
|
+
* @param {List<E>|E[]} model List to be represented
|
|
56
57
|
* @param {string[]} [classList] collection of CSS classes
|
|
57
58
|
* @param {function(E):View} elementFactory factory function, takes a list element and returns a view
|
|
58
59
|
* @param {function(E, View, number, ListView<E>)} [addHook] hook function to be called when a view is created
|
|
@@ -90,6 +91,11 @@ export class ListView extends View {
|
|
|
90
91
|
|
|
91
92
|
assert.typeOf(elementFactory, 'function', 'elementFactory');
|
|
92
93
|
|
|
94
|
+
if (Array.isArray(model)) {
|
|
95
|
+
// wrap array as a list
|
|
96
|
+
model = new List(model);
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
/**
|
|
94
100
|
*
|
|
95
101
|
* @type {List<E>}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import TaskProgressView from "./TaskProgressView.js";
|
|
2
|
+
import { AssetLoaderStatusView } from "../asset/AssetLoaderStatusView.js";
|
|
3
|
+
import { logger } from "../../engine/logging/GlobalLogger.js";
|
|
4
|
+
import { array_remove_first } from "../../core/collection/array/array_remove_first.js";
|
|
5
|
+
import { assert } from "../../core/assert.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @type {TaskLoadingScreen}
|
|
9
|
+
*/
|
|
10
|
+
let _instance;
|
|
11
|
+
|
|
12
|
+
export class TaskLoadingScreen {
|
|
13
|
+
_active_promise = Promise.resolve();
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @type {number}
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
_release_delay_seconds = 0.5;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @type {number}
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
_release_delay_handle = -1;
|
|
27
|
+
|
|
28
|
+
_pending_cleanup = [];
|
|
29
|
+
_state_stack = [];
|
|
30
|
+
|
|
31
|
+
_clearReleaseTimeout() {
|
|
32
|
+
if (this._release_delay_handle !== -1) {
|
|
33
|
+
clearTimeout(this._release_delay_handle);
|
|
34
|
+
this._release_delay_handle = -1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {Engine} engine
|
|
41
|
+
* @param {Task|TaskGroup} task
|
|
42
|
+
* @returns {Promise<void>}
|
|
43
|
+
*/
|
|
44
|
+
async load(engine, task) {
|
|
45
|
+
|
|
46
|
+
assert.defined(task, 'task');
|
|
47
|
+
assert.notNull(task, 'task');
|
|
48
|
+
|
|
49
|
+
assert.defined(engine, 'engine');
|
|
50
|
+
assert.notNull(engine, 'engine');
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
await Promise.allSettled([this._active_promise]);
|
|
54
|
+
|
|
55
|
+
this._clearReleaseTimeout();
|
|
56
|
+
|
|
57
|
+
const localization = engine.localization;
|
|
58
|
+
|
|
59
|
+
const taskProgressView = new TaskProgressView({ task, localization });
|
|
60
|
+
taskProgressView.el.classList.add('loading-screen');
|
|
61
|
+
|
|
62
|
+
//add asset manager loading progress
|
|
63
|
+
const loaderStatusView = new AssetLoaderStatusView({ assetManager: engine.assetManager, localization });
|
|
64
|
+
taskProgressView.addChild(loaderStatusView);
|
|
65
|
+
taskProgressView.link();
|
|
66
|
+
|
|
67
|
+
const graphics = engine.graphics;
|
|
68
|
+
|
|
69
|
+
const engine_state = {
|
|
70
|
+
engine_rendering_enabled: engine.renderingEnabled,
|
|
71
|
+
graphics_auto_draw: graphics.autoDraw
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this._state_stack.push(engine_state);
|
|
75
|
+
|
|
76
|
+
const domParent = document.body;
|
|
77
|
+
|
|
78
|
+
domParent.appendChild(taskProgressView.el);
|
|
79
|
+
|
|
80
|
+
// suspend rendering
|
|
81
|
+
engine.renderingEnabled = false;
|
|
82
|
+
graphics.autoDraw = false;
|
|
83
|
+
|
|
84
|
+
const attempt_cleanup = () => {
|
|
85
|
+
if (this._active_promise !== promise) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// clear any pending timeouts if they exist
|
|
90
|
+
this._clearReleaseTimeout();
|
|
91
|
+
|
|
92
|
+
// run all remaining pending cleanup tasks
|
|
93
|
+
for (let i = 0; i < this._pending_cleanup.length; i++) {
|
|
94
|
+
const t = this._pending_cleanup[i];
|
|
95
|
+
|
|
96
|
+
if (t === cleanup) {
|
|
97
|
+
// skip self
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
t();
|
|
103
|
+
} catch (e) {
|
|
104
|
+
logger.error(e);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// just to make sure, try to remove it again
|
|
108
|
+
array_remove_first(this._pending_cleanup, t);
|
|
109
|
+
|
|
110
|
+
// update iterator
|
|
111
|
+
i--;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// this is the last task in the queue
|
|
115
|
+
this._release_delay_handle = setTimeout(() => {
|
|
116
|
+
cleanup();
|
|
117
|
+
}, Math.ceil(this._release_delay_seconds * 1000));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const cleanup = () => {
|
|
121
|
+
array_remove_first(this._pending_cleanup, cleanup);
|
|
122
|
+
|
|
123
|
+
domParent.removeChild(taskProgressView.el);
|
|
124
|
+
|
|
125
|
+
taskProgressView.unlink();
|
|
126
|
+
|
|
127
|
+
// restore state
|
|
128
|
+
const state = this._state_stack.pop();
|
|
129
|
+
|
|
130
|
+
engine.renderingEnabled = state.engine_rendering_enabled;
|
|
131
|
+
engine.graphics.autoDraw = state.graphics_auto_draw;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this._pending_cleanup.push(cleanup);
|
|
135
|
+
|
|
136
|
+
const promise = new Promise((resolve, reject) => {
|
|
137
|
+
|
|
138
|
+
const p = task.promise();
|
|
139
|
+
|
|
140
|
+
p.finally(attempt_cleanup);
|
|
141
|
+
p.then(resolve, reject);
|
|
142
|
+
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this._active_promise = promise;
|
|
146
|
+
|
|
147
|
+
return promise;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param {Engine} engine
|
|
153
|
+
* @param {Task|TaskGroup} task
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
static load(engine, task) {
|
|
157
|
+
return TaskLoadingScreen.instance.load(engine, task);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
* @returns {TaskLoadingScreen}
|
|
163
|
+
*/
|
|
164
|
+
static get instance() {
|
|
165
|
+
if (_instance === undefined) {
|
|
166
|
+
_instance = new TaskLoadingScreen();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return _instance;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
@@ -5,47 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
import View from '../View.js';
|
|
7
7
|
import SmoothProgressBar from '../elements/progress/SmoothProgressBar.js';
|
|
8
|
-
import LabelView from '
|
|
8
|
+
import LabelView from '../common/LabelView.js';
|
|
9
9
|
|
|
10
10
|
import TaskState from '../../core/process/task/TaskState.js';
|
|
11
11
|
|
|
12
12
|
import dom from '../DOM.js';
|
|
13
13
|
import Clock from "../../engine/Clock.js";
|
|
14
14
|
import Vector1 from "../../core/geom/Vector1.js";
|
|
15
|
-
import { LocalizedLabelView } from "
|
|
16
|
-
|
|
17
|
-
function ReimaingTimeEstimator(sampleCount) {
|
|
18
|
-
this.sampleCount = sampleCount;
|
|
19
|
-
this.rates = new Float32Array(sampleCount);
|
|
20
|
-
this.cursor = 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
ReimaingTimeEstimator.prototype.update = function (timeElapsed, progress) {
|
|
24
|
-
const index = this.cursor;
|
|
25
|
-
this.cursor = (this.cursor + 1) % this.sampleCount;
|
|
26
|
-
|
|
27
|
-
const rate = progress / timeElapsed;
|
|
28
|
-
|
|
29
|
-
this.rates[index] = rate;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
ReimaingTimeEstimator.prototype.estimate = function (currentProgress) {
|
|
33
|
-
let rateSum = 0;
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < this.sampleCount; i++) {
|
|
36
|
-
rateSum += this.rates[i];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const rate = rateSum / this.sampleCount;
|
|
40
|
-
|
|
41
|
-
if (rate === 0) {
|
|
42
|
-
return Infinity;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = (1 - currentProgress) / rate;
|
|
46
|
-
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
15
|
+
import { LocalizedLabelView } from "../common/LocalizedLabelView.js";
|
|
16
|
+
import { RemainingTimeEstimator } from "../../core/process/task/RemainingTimeEstimator.js";
|
|
49
17
|
|
|
50
18
|
class TaskProgressView extends View {
|
|
51
19
|
/**
|
|
@@ -131,7 +99,7 @@ class TaskProgressView extends View {
|
|
|
131
99
|
state.on.linked.add(updateState);
|
|
132
100
|
state.bindSignal(task.state.onChanged, updateState);
|
|
133
101
|
|
|
134
|
-
const remainingTimeEstimator = new
|
|
102
|
+
const remainingTimeEstimator = new RemainingTimeEstimator(100);
|
|
135
103
|
const remainingTime = new Vector1(0);
|
|
136
104
|
|
|
137
105
|
const lRemainingTime = new LabelView(remainingTime, {
|