@woosh/meep-engine 2.48.11 → 2.48.13
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/package.json +1 -1
- package/src/core/collection/HashMap.js +2 -2
- package/src/core/collection/HashMap.spec.js +24 -1
- package/src/core/collection/queue/Deque.d.ts +9 -0
- package/src/core/collection/queue/Deque.js +3 -0
- package/src/core/collection/queue/Deque.spec.js +51 -0
- package/src/core/debug/matchers/matchers.js +10 -0
- package/src/core/math/noise/create_noise_2d.js +193 -0
- package/src/engine/Engine.js +9 -5
- package/src/engine/asset/AssetManager.d.ts +10 -5
- package/src/engine/asset/AssetManager.js +22 -9
- package/src/engine/asset/PendingAsset.js +6 -6
- package/src/engine/asset/loaders/AssetLoader.d.ts +7 -2
- package/src/engine/asset/loaders/AssetLoader.js +17 -15
- package/src/engine/asset/loaders/GLTFAssetLoader.js +1 -1
- package/src/engine/asset/loaders/SoundAssetLoader.js +3 -5
- package/src/engine/development/performance/MetricStatistics.js +7 -5
- package/src/engine/development/performance/RingBufferMetric.js +2 -2
- package/src/engine/ecs/foliage/ecs/InstancedMeshUtils.js +14 -1
- package/src/engine/ecs/gui/GUIElementSystem.d.ts +1 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +3 -21
- package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +4 -2
- package/src/engine/graphics/texture/3d/SingleChannelSampler3D.js +146 -0
- package/src/engine/graphics/texture/3d/scs3d_read_2d_slice.js +26 -0
- package/src/engine/graphics/texture/sampler/Sampler2D.js +2 -2
- package/src/engine/physics/fluid/FluidField.js +153 -1
- package/src/engine/physics/fluid/prototype.js +201 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +67 -0
- package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +17 -12
- package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +14 -10
|
@@ -78,6 +78,7 @@ export function convertInstancedMeshComponents2Entities(dataset, assetManager) {
|
|
|
78
78
|
export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold = 30) {
|
|
79
79
|
//get all entities that have a translation and mesh only
|
|
80
80
|
const candidates = {};
|
|
81
|
+
const not_loaded = {};
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
84
|
*
|
|
@@ -99,7 +100,14 @@ export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold =
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
if (!mesh.getFlag(MeshFlags.Loaded)) {
|
|
102
|
-
|
|
103
|
+
let misses = not_loaded[model_url];
|
|
104
|
+
|
|
105
|
+
if (misses === undefined) {
|
|
106
|
+
misses = not_loaded[model_url] = [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
misses.push(entity);
|
|
110
|
+
|
|
103
111
|
return;
|
|
104
112
|
}
|
|
105
113
|
|
|
@@ -125,6 +133,11 @@ export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold =
|
|
|
125
133
|
// traverse entities that have only Mesh and a Transform
|
|
126
134
|
dataset.traverseEntitiesExact([Mesh, Transform], visitMeshTransformEntity);
|
|
127
135
|
|
|
136
|
+
for (const mesh_url in not_loaded) {
|
|
137
|
+
|
|
138
|
+
console.warn(`Mesh[${mesh_url}] not loaded, ${not_loaded[mesh_url].length} entities affected`);
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
const tasks = [];
|
|
129
142
|
|
|
130
143
|
const foliage2 = new InstancedMeshComponent();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {System} from "../System";
|
|
2
2
|
import Engine from "../../Engine";
|
|
3
3
|
import View from "../../../view/View";
|
|
4
|
-
import
|
|
4
|
+
import GUIElement from "./GUIElement";
|
|
5
5
|
|
|
6
6
|
export default class GUIElementSystem extends System<GUIElement> {
|
|
7
7
|
constructor(container: View, engine: Engine)
|
|
@@ -99,13 +99,10 @@ class ClingToTerrainSystem extends System {
|
|
|
99
99
|
* @param {int} entityId
|
|
100
100
|
*/
|
|
101
101
|
link(cling, transform, entityId) {
|
|
102
|
-
const self = this;
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
self.requestUpdate(entityId);
|
|
106
|
-
}
|
|
103
|
+
const update = () => this.requestUpdate(entityId);
|
|
107
104
|
|
|
108
|
-
|
|
105
|
+
this.data[entityId] = {
|
|
109
106
|
update,
|
|
110
107
|
transform,
|
|
111
108
|
component: cling
|
|
@@ -139,21 +136,6 @@ class ClingToTerrainSystem extends System {
|
|
|
139
136
|
this.updateQueue.remove(entityId);
|
|
140
137
|
}
|
|
141
138
|
|
|
142
|
-
reset() {
|
|
143
|
-
this.updateQueue.clear();
|
|
144
|
-
|
|
145
|
-
const data = this.data;
|
|
146
|
-
for (let entity in data) {
|
|
147
|
-
if (!data.hasOwnProperty(entity)) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const datum = data[entity];
|
|
151
|
-
deregister(datum);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.data = [];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
139
|
update(timeDelta) {
|
|
158
140
|
const em = this.entityManager;
|
|
159
141
|
|
|
@@ -192,7 +174,7 @@ class ClingToTerrainSystem extends System {
|
|
|
192
174
|
the entity is gone
|
|
193
175
|
*/
|
|
194
176
|
|
|
195
|
-
console.warn("ClingToTerrain component was in the update queue, but no data found. Skipping");
|
|
177
|
+
//console.warn("ClingToTerrain component was in the update queue, but no data found. Skipping");
|
|
196
178
|
continue;
|
|
197
179
|
}
|
|
198
180
|
|
|
@@ -80,6 +80,8 @@ export class PerfectPanner {
|
|
|
80
80
|
|
|
81
81
|
const ray = make_ray_from_viewport_position(engine, new Vector2(viewport_x, viewport_y));
|
|
82
82
|
|
|
83
|
+
// console.log(`Ray origin:${ray.origin}, direction:${ray.direction}`);
|
|
84
|
+
|
|
83
85
|
this.#camera.rayPlaneIntersection(
|
|
84
86
|
out,
|
|
85
87
|
ray.origin.x, ray.origin.y, ray.origin.z,
|
|
@@ -93,7 +95,7 @@ export class PerfectPanner {
|
|
|
93
95
|
* @param {Vector2} screen_position
|
|
94
96
|
*/
|
|
95
97
|
update(screen_position) {
|
|
96
|
-
//console.log(`PerfectPanner/update: ${screen_position}`)
|
|
98
|
+
// console.log(`PerfectPanner/update: ${screen_position}`)
|
|
97
99
|
|
|
98
100
|
const C = this.#grab_world_reference;
|
|
99
101
|
const A = this.#grab_eye_position;
|
|
@@ -121,7 +123,7 @@ export class PerfectPanner {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
start(screen_position) {
|
|
124
|
-
//console.log(`PerfectPanner/start: ${screen_position}`)
|
|
126
|
+
// console.log(`PerfectPanner/start: ${screen_position}`)
|
|
125
127
|
|
|
126
128
|
// obtain reference point
|
|
127
129
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { clamp } from "../../../../core/math/clamp.js";
|
|
2
|
+
import { lerp } from "../../../../core/math/lerp.js";
|
|
3
|
+
|
|
4
|
+
export class SingleChannelSampler3D {
|
|
5
|
+
|
|
6
|
+
data = new Float32Array(0)
|
|
7
|
+
#resolution = [0, 0, 0]
|
|
8
|
+
|
|
9
|
+
set resolution(v) {
|
|
10
|
+
const width = v[0];
|
|
11
|
+
const height = v[1];
|
|
12
|
+
const depth = v[2];
|
|
13
|
+
|
|
14
|
+
const old_width = this.#resolution[0];
|
|
15
|
+
const old_height = this.#resolution[1];
|
|
16
|
+
const old_depth = this.#resolution[2];
|
|
17
|
+
|
|
18
|
+
if (old_width === width && old_height === height && old_depth === depth) {
|
|
19
|
+
// no change
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.#resolution[0] = width;
|
|
24
|
+
this.#resolution[1] = height;
|
|
25
|
+
this.#resolution[2] = depth
|
|
26
|
+
|
|
27
|
+
const new_data_size = width * height * depth;
|
|
28
|
+
|
|
29
|
+
if (new_data_size !== old_width * old_height * old_depth) {
|
|
30
|
+
|
|
31
|
+
this.data = new Float32Array(new_data_size);
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get resolution() {
|
|
37
|
+
return this.#resolution;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {number} x
|
|
43
|
+
* @param {number} y
|
|
44
|
+
* @param {number} z
|
|
45
|
+
* @param {number} value
|
|
46
|
+
*/
|
|
47
|
+
write(x, y, z, value) {
|
|
48
|
+
const resolution = this.#resolution;
|
|
49
|
+
|
|
50
|
+
const res_x = resolution[0];
|
|
51
|
+
const res_y = resolution[1];
|
|
52
|
+
|
|
53
|
+
this.data[z * res_y * res_x + y * res_x + x] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {number} x
|
|
59
|
+
* @param {number} y
|
|
60
|
+
* @param {number} z
|
|
61
|
+
* @returns {number}
|
|
62
|
+
*/
|
|
63
|
+
sample_channel_linear(x, y, z) {
|
|
64
|
+
const resolution = this.#resolution;
|
|
65
|
+
|
|
66
|
+
const res_x = resolution[0];
|
|
67
|
+
const res_y = resolution[1];
|
|
68
|
+
const res_z = resolution[2];
|
|
69
|
+
|
|
70
|
+
const x_max = res_x - 1;
|
|
71
|
+
const y_max = res_y - 1;
|
|
72
|
+
const z_max = res_z - 1;
|
|
73
|
+
|
|
74
|
+
const x_clamped = clamp(x, 0, x_max);
|
|
75
|
+
const y_clamped = clamp(y, 0, y_max);
|
|
76
|
+
const z_clamped = clamp(z, 0, z_max);
|
|
77
|
+
|
|
78
|
+
const x0 = x_clamped | 0;
|
|
79
|
+
const y0 = y_clamped | 0;
|
|
80
|
+
const z0 = z_clamped | 0;
|
|
81
|
+
|
|
82
|
+
const f_x = x_clamped - x0;
|
|
83
|
+
const f_y = y_clamped - y0;
|
|
84
|
+
const f_z = z_clamped - z0;
|
|
85
|
+
|
|
86
|
+
const x1 = x_clamped === x0 ? x0 : x0 + 1;
|
|
87
|
+
const y1 = y_clamped === y0 ? y0 : y0 + 1;
|
|
88
|
+
const z1 = z_clamped === z0 ? z0 : z0 + 1;
|
|
89
|
+
|
|
90
|
+
// get 8 points
|
|
91
|
+
const data = this.data;
|
|
92
|
+
|
|
93
|
+
const z0_offset = z0 * (res_x * res_y);
|
|
94
|
+
|
|
95
|
+
const y0_offset = y0 * res_x;
|
|
96
|
+
|
|
97
|
+
const x0_offset = x0;
|
|
98
|
+
const x1_offset = x1;
|
|
99
|
+
|
|
100
|
+
const x0y0z0 = data[z0_offset + y0_offset + x0_offset];
|
|
101
|
+
const x1y0z0 = data[z0_offset + y0_offset + x1_offset];
|
|
102
|
+
|
|
103
|
+
const y1_offset = y1 * res_x;
|
|
104
|
+
|
|
105
|
+
const x0y1z0 = data[z0_offset + y1_offset + x0_offset];
|
|
106
|
+
const x1y1z0 = data[z0_offset + y1_offset + x1_offset];
|
|
107
|
+
|
|
108
|
+
const z1_offset = z1 * (res_x * res_y);
|
|
109
|
+
|
|
110
|
+
const x0y0z1 = data[z1_offset + y0_offset + x0_offset];
|
|
111
|
+
const x1y0z1 = data[z1_offset + y0_offset + x1_offset];
|
|
112
|
+
|
|
113
|
+
const x0y1z1 = data[z1_offset + y1_offset + x0_offset];
|
|
114
|
+
const x1y1z1 = data[z1_offset + y1_offset + x1_offset];
|
|
115
|
+
|
|
116
|
+
// filter on z0
|
|
117
|
+
const lerp_z0_y0 = lerp(x0y0z0, x1y0z0, f_x);
|
|
118
|
+
const lerp_z0_y1 = lerp(x0y1z0, x1y1z0, f_x);
|
|
119
|
+
const lerp_z0 = lerp(lerp_z0_y0, lerp_z0_y1, f_y);
|
|
120
|
+
|
|
121
|
+
// filter on z1
|
|
122
|
+
const lerp_z1_y0 = lerp(x0y0z1, x1y0z1, f_x);
|
|
123
|
+
const lerp_z1_y1 = lerp(x0y1z1, x1y1z1, f_x);
|
|
124
|
+
const lerp_z1 = lerp(lerp_z1_y0, lerp_z1_y1, f_y);
|
|
125
|
+
|
|
126
|
+
return lerp(lerp_z0, lerp_z1, f_z);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
*
|
|
131
|
+
* @param {SingleChannelSampler3D} other
|
|
132
|
+
*/
|
|
133
|
+
copy(other) {
|
|
134
|
+
this.resolution = other.#resolution;
|
|
135
|
+
|
|
136
|
+
this.data.set(other.data);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
clone() {
|
|
140
|
+
const r = new SingleChannelSampler3D();
|
|
141
|
+
|
|
142
|
+
r.copy(this);
|
|
143
|
+
|
|
144
|
+
return r;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { array_copy } from "../../../../core/collection/array/copyArray.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {SingleChannelSampler3D} source
|
|
6
|
+
* @param {Sampler2D} output
|
|
7
|
+
* @param {number} z_index
|
|
8
|
+
*/
|
|
9
|
+
export function scs3d_read_2d_slice(source, output, z_index) {
|
|
10
|
+
const source_resolution = source.resolution;
|
|
11
|
+
|
|
12
|
+
const source_width = source_resolution[0];
|
|
13
|
+
const source_height = source_resolution[1];
|
|
14
|
+
|
|
15
|
+
if (source_width !== output.width || source_height !== output.height) {
|
|
16
|
+
throw new Error('Incompatible resolutions');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (output.itemSize !== 1) {
|
|
20
|
+
throw new Error(`Expected output to have 1 channel, instead got ${output.itemSize}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const slice_size = source_width * source_height;
|
|
24
|
+
|
|
25
|
+
array_copy(source.data, slice_size * z_index, output.data, 0, slice_size);
|
|
26
|
+
}
|
|
@@ -474,14 +474,14 @@ export class Sampler2D {
|
|
|
474
474
|
//
|
|
475
475
|
let x1, y1;
|
|
476
476
|
|
|
477
|
-
if (clamped_x === x0
|
|
477
|
+
if (clamped_x === x0) {
|
|
478
478
|
x1 = x0;
|
|
479
479
|
} else {
|
|
480
480
|
x1 = x0 + 1;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
|
|
484
|
-
if (clamped_y === y0
|
|
484
|
+
if (clamped_y === y0) {
|
|
485
485
|
y1 = y0;
|
|
486
486
|
} else {
|
|
487
487
|
y1 = y0 + 1;
|
|
@@ -1,9 +1,161 @@
|
|
|
1
|
+
import { clamp } from "../../../core/math/clamp.js";
|
|
2
|
+
import { lerp } from "../../../core/math/lerp.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {Float32Array} data
|
|
7
|
+
* @param {number[]} resolution
|
|
8
|
+
*/
|
|
9
|
+
function v3_grid_apply_advection_forward(data, resolution) {
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {Float32Array} data
|
|
16
|
+
* @param {number[]} resolution
|
|
17
|
+
*/
|
|
18
|
+
function v3_grid_apply_advection_reverse(data, resolution) {
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
1
22
|
/**
|
|
2
23
|
* Represents a space where fluid simulation happens
|
|
3
24
|
*
|
|
4
25
|
* @see 2019 GDC talk by Runard Rupert "Wind Simulation in God of War" https://www.youtube.com/watch?v=dDgyBKkSf7A
|
|
5
26
|
* @see Inspired by GDC talk "Interactive Wind and Vegetation in 'God of War'" - https://www.youtube.com/watch?v=MKX45_riWQA
|
|
6
27
|
*/
|
|
7
|
-
export class FluidField{
|
|
28
|
+
export class FluidField {
|
|
29
|
+
data = new Float32Array(0)
|
|
30
|
+
#size = [0, 0, 0]
|
|
31
|
+
#resolution = [0, 0, 0]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
build() {
|
|
35
|
+
this.data = new Float32Array(
|
|
36
|
+
this.#resolution[0] * this.#resolution[1] * this.#resolution[2] * 3
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {number[]} out
|
|
43
|
+
* @param {number} u
|
|
44
|
+
* @param {number} v
|
|
45
|
+
* @param {number} w
|
|
46
|
+
*/
|
|
47
|
+
sample_linear_uv(out, u, v, w) {
|
|
48
|
+
const max_x = this.#resolution[0] - 1;
|
|
49
|
+
const max_y = this.#resolution[1] - 1;
|
|
50
|
+
const max_z = this.#resolution[2] - 1;
|
|
51
|
+
|
|
52
|
+
this.sample_linear(out, u * max_x, v * max_y, w * max_z);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param {number[]} out
|
|
58
|
+
* @param {number} x
|
|
59
|
+
* @param {number} y
|
|
60
|
+
* @param {number} z
|
|
61
|
+
*/
|
|
62
|
+
sample_linear(out, x, y, z) {
|
|
63
|
+
out[0] = this.sample_channel_linear(x, y, z, 0);
|
|
64
|
+
out[1] = this.sample_channel_linear(x, y, z, 1);
|
|
65
|
+
out[2] = this.sample_channel_linear(x, y, z, 2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param {number} x
|
|
71
|
+
* @param {number} y
|
|
72
|
+
* @param {number} z
|
|
73
|
+
* @param {number} channel
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
sample_channel_linear(x, y, z, channel) {
|
|
77
|
+
const resolution = this.#resolution;
|
|
78
|
+
|
|
79
|
+
const res_x = resolution[0];
|
|
80
|
+
const res_y = resolution[1];
|
|
81
|
+
const res_z = resolution[2];
|
|
82
|
+
|
|
83
|
+
const x_max = res_x - 1;
|
|
84
|
+
const y_max = res_y - 1;
|
|
85
|
+
const z_max = res_z - 1;
|
|
86
|
+
|
|
87
|
+
const x_clamped = clamp(x, 0, x_max);
|
|
88
|
+
const y_clamped = clamp(y, 0, y_max);
|
|
89
|
+
const z_clamped = clamp(z, 0, z_max);
|
|
90
|
+
|
|
91
|
+
const x0 = x_clamped | 0;
|
|
92
|
+
const y0 = y_clamped | 0;
|
|
93
|
+
const z0 = z_clamped | 0;
|
|
94
|
+
|
|
95
|
+
const f_x = x_clamped - x0;
|
|
96
|
+
const f_y = y_clamped - y0;
|
|
97
|
+
const f_z = z_clamped - z0;
|
|
98
|
+
|
|
99
|
+
const x1 = x_clamped === x0 ? x0 : x0 + 1;
|
|
100
|
+
const y1 = y_clamped === y0 ? y0 : y0 + 1;
|
|
101
|
+
const z1 = z_clamped === z0 ? z0 : z0 + 1;
|
|
102
|
+
|
|
103
|
+
// get 8 points
|
|
104
|
+
const data = this.data;
|
|
105
|
+
|
|
106
|
+
const z0_offset = z0 * (res_x * res_y) * 3;
|
|
107
|
+
|
|
108
|
+
const y0_offset = y0 * res_x * 3;
|
|
109
|
+
|
|
110
|
+
const x0_offset = x0 * 3 + channel;
|
|
111
|
+
const x1_offset = x1 * 3 + channel;
|
|
112
|
+
|
|
113
|
+
const x0y0z0 = data[z0_offset + y0_offset + x0_offset];
|
|
114
|
+
const x1y0z0 = data[z0_offset + y0_offset + x1_offset];
|
|
115
|
+
|
|
116
|
+
const y1_offset = y1 * res_x * 3;
|
|
117
|
+
|
|
118
|
+
const x0y1z0 = data[z0_offset + y1_offset + x0_offset];
|
|
119
|
+
const x1y1z0 = data[z0_offset + y1_offset + x1_offset];
|
|
120
|
+
|
|
121
|
+
const z1_offset = z1 * (res_x * res_y) * 3;
|
|
122
|
+
|
|
123
|
+
const x0y0z1 = data[z1_offset + y0_offset + x0_offset];
|
|
124
|
+
const x1y0z1 = data[z1_offset + y0_offset + x1_offset];
|
|
125
|
+
|
|
126
|
+
const x0y1z1 = data[z1_offset + y1_offset + x0_offset];
|
|
127
|
+
const x1y1z1 = data[z1_offset + y1_offset + x1_offset];
|
|
128
|
+
|
|
129
|
+
// filter on z0
|
|
130
|
+
const lerp_z0_y0 = lerp(x0y0z0, x1y0z0, f_x);
|
|
131
|
+
const lerp_z0_y1 = lerp(x0y1z0, x1y1z0, f_x);
|
|
132
|
+
const lerp_z0 = lerp(lerp_z0_y0, lerp_z0_y1, f_y);
|
|
133
|
+
|
|
134
|
+
// filter on z1
|
|
135
|
+
const lerp_z1_y0 = lerp(x0y0z1, x1y0z1, f_x);
|
|
136
|
+
const lerp_z1_y1 = lerp(x0y1z1, x1y1z1, f_x);
|
|
137
|
+
const lerp_z1 = lerp(lerp_z1_y0, lerp_z1_y1, f_y);
|
|
138
|
+
|
|
139
|
+
return lerp(lerp_z0, lerp_z1, f_z);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
set size(v) {
|
|
143
|
+
const width = v[0];
|
|
144
|
+
const height = v[1];
|
|
145
|
+
const depth = v[2];
|
|
146
|
+
|
|
147
|
+
this.#size[0] = width;
|
|
148
|
+
this.#size[1] = height;
|
|
149
|
+
this.#size[2] = depth
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
set resolution(v) {
|
|
153
|
+
const width = v[0];
|
|
154
|
+
const height = v[1];
|
|
155
|
+
const depth = v[2];
|
|
8
156
|
|
|
157
|
+
this.#resolution[0] = width;
|
|
158
|
+
this.#resolution[1] = height;
|
|
159
|
+
this.#resolution[2] = depth
|
|
160
|
+
}
|
|
9
161
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { EngineHarness } from "../../EngineHarness.js";
|
|
2
|
+
import { SingleChannelSampler3D } from "../../graphics/texture/3d/SingleChannelSampler3D.js";
|
|
3
|
+
import GUIElementSystem from "../../ecs/gui/GUIElementSystem.js";
|
|
4
|
+
import EntityBuilder from "../../ecs/EntityBuilder.js";
|
|
5
|
+
import GUIElement from "../../ecs/gui/GUIElement.js";
|
|
6
|
+
import EmptyView from "../../../view/elements/EmptyView.js";
|
|
7
|
+
import { Sampler2D } from "../../graphics/texture/sampler/Sampler2D.js";
|
|
8
|
+
import { CanvasView } from "../../../view/elements/CanvasView.js";
|
|
9
|
+
import { float2uint8 } from "../../../core/binary/float2uint8.js";
|
|
10
|
+
import { clamp } from "../../../core/math/clamp.js";
|
|
11
|
+
import { randomIntegerBetween } from "../../../core/math/random/randomIntegerBetween.js";
|
|
12
|
+
import { seededRandom } from "../../../core/math/random/seededRandom.js";
|
|
13
|
+
import { BehaviorComponent } from "../../intelligence/behavior/ecs/BehaviorComponent.js";
|
|
14
|
+
import { BehaviorSystem } from "../../intelligence/behavior/ecs/BehaviorSystem.js";
|
|
15
|
+
import { v3_grid_apply_diffusion } from "./solver/v3_grid_apply_diffusion.js";
|
|
16
|
+
import { max2 } from "../../../core/math/max2.js";
|
|
17
|
+
import { MetricCollection } from "../../development/performance/MetricCollection.js";
|
|
18
|
+
import { PeriodicConsolePrinter } from "../../development/performance/monitor/PeriodicConsolePrinter.js";
|
|
19
|
+
import { MetricStatistics } from "../../development/performance/MetricStatistics.js";
|
|
20
|
+
|
|
21
|
+
const harness = new EngineHarness();
|
|
22
|
+
|
|
23
|
+
class SliceVisualiser extends EmptyView {
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {SingleChannelSampler3D} source
|
|
28
|
+
*/
|
|
29
|
+
constructor(source) {
|
|
30
|
+
super({ classList: ['slice-visualizer'] });
|
|
31
|
+
|
|
32
|
+
this.source = source;
|
|
33
|
+
|
|
34
|
+
const slices = [];
|
|
35
|
+
const views = [];
|
|
36
|
+
|
|
37
|
+
const resolution = source.resolution;
|
|
38
|
+
const resolution_x = resolution[0];
|
|
39
|
+
const resolution_y = resolution[1];
|
|
40
|
+
const resolution_z = resolution[2];
|
|
41
|
+
|
|
42
|
+
const slice_size = resolution_x * resolution_y;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @type {ImageData|null}
|
|
47
|
+
*/
|
|
48
|
+
let imageData = null;
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < resolution_z; i++) {
|
|
51
|
+
slices[i] = new Sampler2D(new Float32Array(source.data.buffer, i * slice_size * 4, slice_size), 1, resolution_x, resolution_y);
|
|
52
|
+
|
|
53
|
+
const child = new CanvasView();
|
|
54
|
+
child.size.set(resolution_x, resolution_y);
|
|
55
|
+
|
|
56
|
+
if (imageData === null) {
|
|
57
|
+
imageData = child.context2d.getImageData(0, 0, resolution_x, resolution_y);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
child.css({
|
|
61
|
+
position: "absolute",
|
|
62
|
+
left: 0,
|
|
63
|
+
top: 0
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
views[i] = child;
|
|
67
|
+
|
|
68
|
+
this.addChild(child);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.draw = () => {
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < resolution_z; i++) {
|
|
74
|
+
const view = views[i];
|
|
75
|
+
const slice = slices[i];
|
|
76
|
+
|
|
77
|
+
for (let j = 0; j < slice_size; j++) {
|
|
78
|
+
const j4 = j * 4;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const uint8_value = float2uint8(slice.data[j]);
|
|
82
|
+
|
|
83
|
+
imageData.data[j4] = clamp(uint8_value, 0, 255);
|
|
84
|
+
imageData.data[j4 + 3] = 255;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
view.context2d.putImageData(imageData, 0, 0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const layout = () => {
|
|
93
|
+
const width = this.size.x;
|
|
94
|
+
|
|
95
|
+
const grid_width = max2(1, Math.floor(width / resolution_x));
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < resolution_z; i++) {
|
|
98
|
+
const view = views[i];
|
|
99
|
+
|
|
100
|
+
const grid_x = i % grid_width;
|
|
101
|
+
const grid_y = Math.floor(i / grid_width);
|
|
102
|
+
|
|
103
|
+
view.position.set(grid_x * resolution_x, grid_y * resolution_y);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.on.linked.add(layout);
|
|
108
|
+
this.size.onChanged.add(layout);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const metrics = new MetricCollection();
|
|
115
|
+
|
|
116
|
+
metrics.create({ name: 'sim' })
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
*
|
|
120
|
+
* @param {Engine} engine
|
|
121
|
+
*/
|
|
122
|
+
function main(engine) {
|
|
123
|
+
|
|
124
|
+
const ecd = engine.entityManager.dataset;
|
|
125
|
+
|
|
126
|
+
const sampler = new SingleChannelSampler3D();
|
|
127
|
+
|
|
128
|
+
sampler.resolution = [32, 32, 16];
|
|
129
|
+
|
|
130
|
+
const random = seededRandom();
|
|
131
|
+
|
|
132
|
+
sampler.data.fill(1000, 20, 40);
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < 100; i++) {
|
|
135
|
+
|
|
136
|
+
sampler.write(
|
|
137
|
+
randomIntegerBetween(random, 0, 16),
|
|
138
|
+
randomIntegerBetween(random, 0, 16),
|
|
139
|
+
randomIntegerBetween(random, 0, 4),
|
|
140
|
+
random()
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
const slice_view = new SliceVisualiser(sampler);
|
|
147
|
+
|
|
148
|
+
slice_view.size.set(432, 1000);
|
|
149
|
+
slice_view.scale.setScalar(2);
|
|
150
|
+
slice_view.transformOrigin.set(0, 0);
|
|
151
|
+
|
|
152
|
+
const sampler_0 = sampler.clone();
|
|
153
|
+
const sampler_1 = sampler.clone();
|
|
154
|
+
|
|
155
|
+
const ping_pong = [sampler_0, sampler_1];
|
|
156
|
+
let ping_pong_index = 0;
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
new EntityBuilder()
|
|
160
|
+
.add(BehaviorComponent.looping_function(time_delta => {
|
|
161
|
+
|
|
162
|
+
const source = ping_pong[ping_pong_index % 2];
|
|
163
|
+
const target = ping_pong[(ping_pong_index + 1) % 2];
|
|
164
|
+
|
|
165
|
+
ping_pong_index++;
|
|
166
|
+
|
|
167
|
+
const t0 = performance.now();
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < 5; i++) {
|
|
170
|
+
v3_grid_apply_diffusion(target.data, source.data, source.resolution);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
metrics.get('sim').record(performance.now() - t0);
|
|
174
|
+
|
|
175
|
+
sampler.copy(target);
|
|
176
|
+
|
|
177
|
+
slice_view.draw();
|
|
178
|
+
|
|
179
|
+
}))
|
|
180
|
+
.build(ecd);
|
|
181
|
+
|
|
182
|
+
const metric_stats = new MetricStatistics();
|
|
183
|
+
|
|
184
|
+
new PeriodicConsolePrinter(3, () => metrics.list().map(m => {
|
|
185
|
+
metrics.get(m).computeStats(metric_stats);
|
|
186
|
+
|
|
187
|
+
return `${m}: ${metric_stats}`;
|
|
188
|
+
}).join('\n')).start()
|
|
189
|
+
|
|
190
|
+
new EntityBuilder()
|
|
191
|
+
.add(GUIElement.fromView(slice_view))
|
|
192
|
+
.build(ecd);
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
harness.initialize({
|
|
197
|
+
configuration(config, engine) {
|
|
198
|
+
config.addSystem(new GUIElementSystem(engine.gameView, engine))
|
|
199
|
+
config.addSystem(new BehaviorSystem(engine))
|
|
200
|
+
}
|
|
201
|
+
}).then(main)
|