@woosh/meep-engine 2.43.11 → 2.43.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/core/binary/is_data_url.js +12 -0
- package/core/geom/3d/shape/AbstractShape3D.js +9 -0
- package/core/geom/3d/shape/TransformedShape3D.js +8 -0
- package/core/geom/3d/shape/UnionShape3D.js +35 -0
- package/core/geom/3d/shape/UnitCubeShape3D.js +10 -0
- package/core/geom/3d/shape/UnitSphereShape3D.js +10 -0
- package/editor/ecs/component/editors/ImagePathEditor.js +14 -37
- package/editor/ecs/component/editors/LargeStrongEditor.js +107 -0
- package/engine/EngineHarness.js +10 -2
- package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +15 -2
- package/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.d.ts +3 -1
- package/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.js +136 -4
- package/engine/graphics/particles/particular/engine/utils/volume/SamplingFunctionKind.d.ts +4 -0
- package/engine/graphics/particles/particular/engine/utils/volume/SamplingFunctionKind.js +8 -0
- package/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +7 -4
- package/engine/graphics/sh3/README.md +1 -0
- package/package.json +1 -1
|
@@ -63,6 +63,15 @@ export class AbstractShape3D {
|
|
|
63
63
|
throw new Error('Not Implemented');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @param {number[]|ArrayLike<number>|Float32Array|Float64Array} result format: x0,y0,z0,x1,y1,z1
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
compute_bounding_box(result) {
|
|
72
|
+
throw new Error('Not Implemented');
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
/**
|
|
67
76
|
* @template {AbstractShape3D} T
|
|
68
77
|
* @param {T} other
|
|
@@ -3,6 +3,7 @@ import { AbstractShape3D } from "./AbstractShape3D.js";
|
|
|
3
3
|
import { sign } from "../../../math/sign.js";
|
|
4
4
|
import { isArrayEqualStrict } from "../../../collection/array/isArrayEqualStrict.js";
|
|
5
5
|
import { computeHashFloatArray } from "../../../math/hash/computeHashFloatArray.js";
|
|
6
|
+
import { aabb3_matrix4_project } from "../aabb/aabb3_matrix4_project.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
*
|
|
@@ -108,6 +109,13 @@ export class TransformedShape3D extends AbstractShape3D {
|
|
|
108
109
|
return this.__subject.volume * scale_modifier;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
compute_bounding_box(result) {
|
|
113
|
+
const tmp = [];
|
|
114
|
+
this.__subject.compute_bounding_box(tmp);
|
|
115
|
+
|
|
116
|
+
aabb3_matrix4_project(result, tmp, this.__matrix);
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
signed_distance_gradient_at_point(result, point) {
|
|
112
120
|
// transform point to subject's local space
|
|
113
121
|
vec3.transformMat4(scratch_v3_0, point, this.__inverse_matrix);
|
|
@@ -5,6 +5,7 @@ import { compareNumbers } from "../../../primitives/numbers/compareNumbers.js";
|
|
|
5
5
|
import { min2 } from "../../../math/min2.js";
|
|
6
6
|
import { compute_signed_distance_gradient_by_sampling } from "./util/compute_signed_distance_gradient_by_sampling.js";
|
|
7
7
|
import { isArrayEqual } from "../../../collection/array/isArrayEqual.js";
|
|
8
|
+
import { max2 } from "../../../math/max2.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* To avoid severe performance overhead, we limit number of possible rejections
|
|
@@ -44,6 +45,40 @@ export class UnionShape3D extends AbstractShape3D {
|
|
|
44
45
|
this.__child_volumes = [];
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
compute_bounding_box(result) {
|
|
49
|
+
let x0 = Infinity, y0 = Infinity, z0 = Infinity,
|
|
50
|
+
x1 = -Infinity, y1 = -Infinity, z1 = -Infinity;
|
|
51
|
+
|
|
52
|
+
const children = this.children;
|
|
53
|
+
const n = children.length;
|
|
54
|
+
|
|
55
|
+
const tmp_aabb3 = new Float32Array(6);
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < n; i++) {
|
|
58
|
+
const child = children[i];
|
|
59
|
+
|
|
60
|
+
child.compute_bounding_box(tmp_aabb3);
|
|
61
|
+
|
|
62
|
+
x0 = min2(x0, tmp_aabb3[0]);
|
|
63
|
+
y0 = min2(y0, tmp_aabb3[1]);
|
|
64
|
+
z0 = min2(z0, tmp_aabb3[2]);
|
|
65
|
+
|
|
66
|
+
x1 = max2(x1, tmp_aabb3[3]);
|
|
67
|
+
y1 = max2(y1, tmp_aabb3[4]);
|
|
68
|
+
z1 = max2(z1, tmp_aabb3[5]);
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
result[0] = x0;
|
|
74
|
+
result[1] = y0;
|
|
75
|
+
result[2] = z0;
|
|
76
|
+
|
|
77
|
+
result[3] = x1;
|
|
78
|
+
result[4] = y1;
|
|
79
|
+
result[5] = z1;
|
|
80
|
+
}
|
|
81
|
+
|
|
47
82
|
/**
|
|
48
83
|
*
|
|
49
84
|
* @private
|
|
@@ -13,6 +13,16 @@ export class UnitCubeShape3D extends AbstractShape3D {
|
|
|
13
13
|
return 1;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
compute_bounding_box(result) {
|
|
17
|
+
result[0] = -0.5;
|
|
18
|
+
result[1] = -0.5;
|
|
19
|
+
result[2] = -0.5;
|
|
20
|
+
|
|
21
|
+
result[3] = 0.5;
|
|
22
|
+
result[4] = 0.5;
|
|
23
|
+
result[5] = 0.5;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
nearest_point_on_surface(result, reference) {
|
|
17
27
|
const r_x = reference[0];
|
|
18
28
|
const r_y = reference[1];
|
|
@@ -15,6 +15,16 @@ export class UnitSphereShape3D extends AbstractShape3D {
|
|
|
15
15
|
return 3.14159265;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
compute_bounding_box(result) {
|
|
19
|
+
result[0] = -0.5;
|
|
20
|
+
result[1] = -0.5;
|
|
21
|
+
result[2] = -0.5;
|
|
22
|
+
|
|
23
|
+
result[3] = 0.5;
|
|
24
|
+
result[4] = 0.5;
|
|
25
|
+
result[5] = 0.5;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
nearest_point_on_surface(result, reference) {
|
|
19
29
|
|
|
20
30
|
const r_x = reference[0];
|
|
@@ -1,44 +1,18 @@
|
|
|
1
1
|
import { TypeEditor } from "../TypeEditor.js";
|
|
2
|
-
import { StringEditor } from "./primitive/StringEditor.js";
|
|
3
2
|
import EmptyView from "../../../../view/elements/EmptyView.js";
|
|
4
3
|
import ImageView from "../../../../view/elements/image/ImageView.js";
|
|
5
|
-
import ObservedString from "../../../../core/model/ObservedString.js";
|
|
6
|
-
import { ObservedStringEditor } from "./ObservedStringEditor.js";
|
|
7
4
|
import ButtonView from "../../../../view/elements/button/ButtonView.js";
|
|
8
5
|
import { url_to_data_url } from "../../../../core/binary/url_to_data_url.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
* @param {string} url
|
|
13
|
-
* @return {boolean}
|
|
14
|
-
*/
|
|
15
|
-
function is_data_url(url) {
|
|
16
|
-
if (typeof url !== "string") {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return url.startsWith('data:');
|
|
21
|
-
}
|
|
6
|
+
import { is_data_url } from "../../../../core/binary/is_data_url.js";
|
|
7
|
+
import { LargeStrongEditor } from "./LargeStrongEditor.js";
|
|
22
8
|
|
|
23
9
|
export class ImagePathEditor extends TypeEditor {
|
|
24
10
|
inline = true;
|
|
25
11
|
|
|
26
12
|
build(parent, field, registry) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (field.type === String) {
|
|
30
|
-
|
|
31
|
-
url_editor = new StringEditor();
|
|
32
|
-
|
|
33
|
-
} else if (field.type === ObservedString) {
|
|
13
|
+
const url_editor = new LargeStrongEditor();
|
|
34
14
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} else {
|
|
38
|
-
throw new Error(`Unsupported type`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const vEditor = url_editor.build(parent, field, registry);
|
|
15
|
+
const editor = url_editor.build(parent, field, registry);
|
|
42
16
|
|
|
43
17
|
const r = new EmptyView({
|
|
44
18
|
css: {
|
|
@@ -59,14 +33,14 @@ export class ImagePathEditor extends TypeEditor {
|
|
|
59
33
|
field.adapter.write(parent, field.name, url);
|
|
60
34
|
|
|
61
35
|
// update editor value
|
|
62
|
-
|
|
36
|
+
editor.methods.set_value(url);
|
|
63
37
|
|
|
64
|
-
|
|
38
|
+
input_changed();
|
|
65
39
|
}
|
|
66
40
|
});
|
|
67
41
|
|
|
68
42
|
b_toDataURL.attr({
|
|
69
|
-
|
|
43
|
+
title: 'Convert URL into DATA_URL, embedding the data. This allows to bypass CORRS restrictions if the URL points to a different origin from the one where the texture resides'
|
|
70
44
|
});
|
|
71
45
|
|
|
72
46
|
function update_data_url_status() {
|
|
@@ -88,15 +62,18 @@ export class ImagePathEditor extends TypeEditor {
|
|
|
88
62
|
}
|
|
89
63
|
}
|
|
90
64
|
|
|
91
|
-
function
|
|
92
|
-
|
|
65
|
+
function input_changed() {
|
|
66
|
+
const url = field.adapter.read(parent, field.name);
|
|
67
|
+
|
|
68
|
+
vPreview.__setSource(url);
|
|
69
|
+
|
|
93
70
|
update_data_url_status();
|
|
94
71
|
}
|
|
95
72
|
|
|
96
73
|
r.addChild(vPreview);
|
|
97
|
-
r.addChild(
|
|
74
|
+
r.addChild(editor.view);
|
|
98
75
|
|
|
99
|
-
|
|
76
|
+
editor.signals.input_changed.add(input_changed);
|
|
100
77
|
|
|
101
78
|
r.on.linked.add(update_data_url_status);
|
|
102
79
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { TypeEditor } from "../TypeEditor.js";
|
|
2
|
+
import { StringEditor } from "./primitive/StringEditor.js";
|
|
3
|
+
import ObservedString from "../../../../core/model/ObservedString.js";
|
|
4
|
+
import { ObservedStringEditor } from "./ObservedStringEditor.js";
|
|
5
|
+
import EmptyView from "../../../../view/elements/EmptyView.js";
|
|
6
|
+
import LabelView from "../../../../view/common/LabelView.js";
|
|
7
|
+
import { MouseEvents } from "../../../../engine/input/devices/events/MouseEvents.js";
|
|
8
|
+
import Signal from "../../../../core/events/signal/Signal.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* If path is larger than this - it will be truncated and displayed as non-input
|
|
12
|
+
* @readonly
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
const MAX_PATH_LENGTH_EDITABLE = 256;
|
|
16
|
+
/**
|
|
17
|
+
* How long to crop to
|
|
18
|
+
* @readonly
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
21
|
+
const MAX_CROP_LENGTH = 32;
|
|
22
|
+
|
|
23
|
+
export class LargeStrongEditor extends TypeEditor {
|
|
24
|
+
inline = true;
|
|
25
|
+
|
|
26
|
+
build(parent, field, registry) {
|
|
27
|
+
let url_editor;
|
|
28
|
+
|
|
29
|
+
if (field.type === String) {
|
|
30
|
+
|
|
31
|
+
url_editor = new StringEditor();
|
|
32
|
+
|
|
33
|
+
} else if (field.type === ObservedString) {
|
|
34
|
+
|
|
35
|
+
url_editor = new ObservedStringEditor();
|
|
36
|
+
|
|
37
|
+
} else {
|
|
38
|
+
throw new Error(`Unsupported type`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let in_focus = false;
|
|
42
|
+
|
|
43
|
+
const vEditor = url_editor.build(parent, field, registry);
|
|
44
|
+
|
|
45
|
+
const truncated_url = new ObservedString('');
|
|
46
|
+
const vTruncated = new LabelView(truncated_url);
|
|
47
|
+
|
|
48
|
+
vTruncated.el.addEventListener(MouseEvents.Click, () => {
|
|
49
|
+
in_focus = true;
|
|
50
|
+
update();
|
|
51
|
+
vEditor.el.focus();
|
|
52
|
+
});
|
|
53
|
+
vEditor.el.addEventListener('focusin', () => {
|
|
54
|
+
in_focus = true;
|
|
55
|
+
update();
|
|
56
|
+
});
|
|
57
|
+
vEditor.el.addEventListener('blur', () => {
|
|
58
|
+
in_focus = false;
|
|
59
|
+
update();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const r = new EmptyView();
|
|
63
|
+
|
|
64
|
+
function update() {
|
|
65
|
+
const url = field.adapter.read(parent, field.name);
|
|
66
|
+
|
|
67
|
+
if (typeof url === "string" && url.length > MAX_PATH_LENGTH_EDITABLE && !in_focus) {
|
|
68
|
+
r.removeChild(vEditor);
|
|
69
|
+
|
|
70
|
+
truncated_url.set('[...]'+url.slice(0, MAX_CROP_LENGTH));
|
|
71
|
+
|
|
72
|
+
if (!r.hasChild(vTruncated)) {
|
|
73
|
+
r.addChild(vTruncated);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
} else if(!r.hasChild(vEditor)){
|
|
77
|
+
r.removeChild(vTruncated);
|
|
78
|
+
r.addChild(vEditor);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const input_changed = new Signal();
|
|
83
|
+
|
|
84
|
+
vEditor.el.addEventListener('input', () => {
|
|
85
|
+
|
|
86
|
+
const value = vEditor.el.value;
|
|
87
|
+
|
|
88
|
+
input_changed.send1(value);
|
|
89
|
+
|
|
90
|
+
update();
|
|
91
|
+
});
|
|
92
|
+
r.on.linked.add(update);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
view: r, signals: {
|
|
96
|
+
input_changed
|
|
97
|
+
},
|
|
98
|
+
methods: {
|
|
99
|
+
set_value(v) {
|
|
100
|
+
field.adapter.write(parent, field.name, v);
|
|
101
|
+
update();
|
|
102
|
+
vEditor.el.value = v;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
package/engine/EngineHarness.js
CHANGED
|
@@ -88,10 +88,12 @@ export class EngineHarness {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* @param {(config:EngineConfiguration,engine:Engine)=>*} configuration
|
|
91
|
+
* @param {boolean} [enable_localization] Whether or not to load localization data
|
|
91
92
|
* @returns {Promise<Engine>}
|
|
92
93
|
*/
|
|
93
94
|
initialize({
|
|
94
|
-
configuration = noop
|
|
95
|
+
configuration = noop,
|
|
96
|
+
enable_localization = false,
|
|
95
97
|
} = {}) {
|
|
96
98
|
|
|
97
99
|
if (this.p !== null) {
|
|
@@ -113,7 +115,13 @@ export class EngineHarness {
|
|
|
113
115
|
|
|
114
116
|
await engine.start();
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
if (enable_localization) {
|
|
119
|
+
try {
|
|
120
|
+
await setLocale(engine);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.warn('Failed to load localization:', e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
117
125
|
|
|
118
126
|
engine.sceneManager.create("test");
|
|
119
127
|
engine.sceneManager.set("test");
|
|
@@ -11,6 +11,7 @@ import { ShadedGeometryFlags } from "../../../mesh-v2/ShadedGeometryFlags.js";
|
|
|
11
11
|
import { m3_rm_compose_transform } from "../../../../../../view/m3_rm_compose_transform.js";
|
|
12
12
|
import { fix_shape_normal_order } from "./fix_shape_normal_order.js";
|
|
13
13
|
import { compute_smooth_profile_normals } from "./compute_smooth_profile_normals.js";
|
|
14
|
+
import { assert } from "../../../../../../core/assert.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
*
|
|
@@ -28,6 +29,9 @@ function make_geometry_segment(
|
|
|
28
29
|
shape, shape_normal, shape_transform,
|
|
29
30
|
segment_start, segment_end
|
|
30
31
|
) {
|
|
32
|
+
assert.notNaN(segment_start, 'segment_start');
|
|
33
|
+
assert.notNaN(segment_end, 'segment_end');
|
|
34
|
+
assert.greaterThanOrEqual(segment_end, segment_start, 'segment_start must be <= segment_end');
|
|
31
35
|
|
|
32
36
|
const interpolation = path_component.interpolation;
|
|
33
37
|
|
|
@@ -161,8 +165,17 @@ export class TubePathBuilder {
|
|
|
161
165
|
for (let i = 0; i < segment_count; i++) {
|
|
162
166
|
const i2 = i * 2;
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
let segment_start = style.path_mask[i2];
|
|
169
|
+
let segment_end = style.path_mask[i2 + 1];
|
|
170
|
+
|
|
171
|
+
// check for wrong ordering
|
|
172
|
+
if (segment_end < segment_start) {
|
|
173
|
+
// violated interval constraint, re-order
|
|
174
|
+
const t = segment_end;
|
|
175
|
+
|
|
176
|
+
segment_end = segment_start;
|
|
177
|
+
segment_start = t;
|
|
178
|
+
}
|
|
166
179
|
|
|
167
180
|
const geometry = make_geometry_segment(
|
|
168
181
|
path_component, style,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import EntityBuilder from "../../../../../../ecs/EntityBuilder";
|
|
2
2
|
import {AbstractShape3D} from "../../../../../../../core/geom/3d/shape/AbstractShape3D";
|
|
3
3
|
import {AttributeValue} from "./AttributeValue";
|
|
4
|
+
import {SamplingFunctionKind} from "./SamplingFunctionKind";
|
|
4
5
|
|
|
5
6
|
interface Args {
|
|
6
7
|
density?: number
|
|
7
8
|
particle_size?: number
|
|
8
9
|
shape?: AbstractShape3D
|
|
9
10
|
sort?: boolean
|
|
10
|
-
sprite?: string
|
|
11
|
+
sprite?: string,
|
|
12
|
+
sampling_function?: SamplingFunctionKind
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
interface Attributes {
|
|
@@ -12,6 +12,10 @@ import { PARTICULAR_PARTICLE_SPECIFICATION } from "../../emitter/PARTICULAR_PART
|
|
|
12
12
|
import { SerializationMetadata } from "../../../../../../ecs/components/SerializationMetadata.js";
|
|
13
13
|
import { UnitCubeShape3D } from "../../../../../../../core/geom/3d/shape/UnitCubeShape3D.js";
|
|
14
14
|
import { shape_from_json } from "../../../../../../../core/geom/3d/shape/json/shape_from_json.js";
|
|
15
|
+
import { min2 } from "../../../../../../../core/math/min2.js";
|
|
16
|
+
import { max2 } from "../../../../../../../core/math/max2.js";
|
|
17
|
+
import { SamplingFunctionKind } from "./SamplingFunctionKind.js";
|
|
18
|
+
import { assert } from "../../../../../../../core/assert.js";
|
|
15
19
|
|
|
16
20
|
const DEFAULT_ATTRIBUTE_VALUES = {
|
|
17
21
|
color: [1, 1, 1, 1],
|
|
@@ -182,6 +186,13 @@ export class ParticleVolume {
|
|
|
182
186
|
*/
|
|
183
187
|
this.__shape = new UnitCubeShape3D();
|
|
184
188
|
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @type {SamplingFunctionKind|number}
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
this.__sampling_function = SamplingFunctionKind.Random;
|
|
195
|
+
|
|
185
196
|
const eb = new EntityBuilder();
|
|
186
197
|
|
|
187
198
|
eb.add(this.__emitter)
|
|
@@ -260,6 +271,7 @@ export class ParticleVolume {
|
|
|
260
271
|
* @param {boolean} sort
|
|
261
272
|
* @param {boolean} lighting
|
|
262
273
|
* @param {string} [sprite]
|
|
274
|
+
* @param {SamplingFunctionKind|number} [sampling_function]
|
|
263
275
|
*/
|
|
264
276
|
from({
|
|
265
277
|
density = 1,
|
|
@@ -267,12 +279,17 @@ export class ParticleVolume {
|
|
|
267
279
|
shape = new UnitCubeShape3D(),
|
|
268
280
|
sort = false,
|
|
269
281
|
lighting = true,
|
|
270
|
-
sprite = "data/textures/particle/smokeparticle.png"
|
|
282
|
+
sprite = "data/textures/particle/smokeparticle.png",
|
|
283
|
+
sampling_function = SamplingFunctionKind.Random
|
|
271
284
|
} = {}) {
|
|
272
285
|
|
|
286
|
+
assert.enum(sampling_function, SamplingFunctionKind, 'sampling_function');
|
|
287
|
+
|
|
273
288
|
this.__density = density;
|
|
274
289
|
this.__shape = shape;
|
|
275
290
|
|
|
291
|
+
this.__sampling_function = sampling_function;
|
|
292
|
+
|
|
276
293
|
const emitter = this.__emitter;
|
|
277
294
|
|
|
278
295
|
emitter.writeFlag(ParticleEmitterFlag.DepthSorting, sort);
|
|
@@ -295,6 +312,7 @@ export class ParticleVolume {
|
|
|
295
312
|
* @param {boolean} [is_lit]
|
|
296
313
|
* @param {string} sprite
|
|
297
314
|
* @param {Object<Object>} [attributes]
|
|
315
|
+
* @param {string} [sampling_function]
|
|
298
316
|
*/
|
|
299
317
|
fromJSON({
|
|
300
318
|
density,
|
|
@@ -304,20 +322,28 @@ export class ParticleVolume {
|
|
|
304
322
|
is_sorted = true,
|
|
305
323
|
is_lit = true,
|
|
306
324
|
sprite,
|
|
307
|
-
attributes = {}
|
|
325
|
+
attributes = {},
|
|
326
|
+
sampling_function = 'Random'
|
|
308
327
|
}) {
|
|
309
328
|
|
|
310
329
|
const shape_object = shape_from_json(shape);
|
|
311
330
|
|
|
312
331
|
const actual_density = is_density_size_adjusted ? density / particle_size : density;
|
|
313
332
|
|
|
333
|
+
const sf_type = SamplingFunctionKind[sampling_function];
|
|
334
|
+
|
|
335
|
+
if (sf_type === undefined) {
|
|
336
|
+
throw new Error(`Unsupported sampling function type '${sampling_function}', valid values:[${Object.keys(SamplingFunctionKind).join(', ')}]`);
|
|
337
|
+
}
|
|
338
|
+
|
|
314
339
|
this.from({
|
|
315
340
|
density: actual_density,
|
|
316
341
|
shape: shape_object,
|
|
317
342
|
particle_size,
|
|
318
343
|
sort: is_sorted,
|
|
319
344
|
lighting: is_lit,
|
|
320
|
-
sprite
|
|
345
|
+
sprite,
|
|
346
|
+
sampling_function: sf_type
|
|
321
347
|
});
|
|
322
348
|
|
|
323
349
|
// parse attributes
|
|
@@ -353,12 +379,118 @@ export class ParticleVolume {
|
|
|
353
379
|
|
|
354
380
|
}
|
|
355
381
|
|
|
382
|
+
__distribute_grid() {
|
|
383
|
+
const emitter = this.__emitter;
|
|
384
|
+
const particles = emitter.particles;
|
|
385
|
+
|
|
386
|
+
const layer = emitter.layers.get(0);
|
|
387
|
+
|
|
388
|
+
// build transform matrix
|
|
389
|
+
const m4_transform = new Matrix4();
|
|
390
|
+
|
|
391
|
+
const t = this.__transform;
|
|
392
|
+
|
|
393
|
+
composeMatrix4(m4_transform, t.position, t.rotation, t.scale);
|
|
394
|
+
|
|
395
|
+
const m4_layer = new Matrix4();
|
|
396
|
+
|
|
397
|
+
composeMatrix4(m4_layer, layer.position, new Quaternion(), layer.scale);
|
|
398
|
+
|
|
399
|
+
const v3_position = [];
|
|
400
|
+
|
|
401
|
+
const m4_final = new Matrix4();
|
|
402
|
+
|
|
403
|
+
m4_final.multiplyMatrices(m4_transform, m4_layer);
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
// cache variables to save a bit on de-referencing
|
|
407
|
+
const matrix_elements = m4_final.elements;
|
|
408
|
+
const shape = this.__shape;
|
|
409
|
+
|
|
410
|
+
const ts = shape;
|
|
411
|
+
|
|
412
|
+
const bounding_box = [];
|
|
413
|
+
|
|
414
|
+
ts.compute_bounding_box(bounding_box);
|
|
415
|
+
|
|
416
|
+
const density_step = 1 / Math.pow(this.__density, 1 / 3);
|
|
417
|
+
|
|
418
|
+
let particle_index = 0;
|
|
419
|
+
|
|
420
|
+
// apply margin to the space, this will shrink the grid spacing to align particles more evenly within the bounding volume
|
|
421
|
+
const density_margin = density_step * 0.5;
|
|
422
|
+
|
|
423
|
+
const mid_x = (bounding_box[3] + bounding_box[0]) * 0.5;
|
|
424
|
+
const mid_y = (bounding_box[4] + bounding_box[1]) * 0.5;
|
|
425
|
+
const mid_z = (bounding_box[5] + bounding_box[2]) * 0.5;
|
|
426
|
+
|
|
427
|
+
const x0 = min2(mid_x, bounding_box[0] + density_margin);
|
|
428
|
+
const x1 = max2(mid_x, bounding_box[3] - density_margin);
|
|
429
|
+
|
|
430
|
+
const y0 = min2(mid_y, bounding_box[1] + density_margin);
|
|
431
|
+
const y1 = max2(mid_y, bounding_box[4] - density_margin);
|
|
432
|
+
|
|
433
|
+
const z0 = min2(mid_z, bounding_box[2] + density_margin);
|
|
434
|
+
const z1 = max2(mid_z, bounding_box[5] - density_margin);
|
|
435
|
+
|
|
436
|
+
main_loop:for (let x = x0; x <= x1; x += density_step) {
|
|
437
|
+
for (let y = y0; y <= y1; y += density_step) {
|
|
438
|
+
for (let z = z0; z <= z1; z += density_step) {
|
|
439
|
+
|
|
440
|
+
v3_position[0] = x;
|
|
441
|
+
v3_position[1] = y;
|
|
442
|
+
v3_position[2] = z;
|
|
443
|
+
|
|
444
|
+
if (ts.contains_point(v3_position)) {
|
|
445
|
+
|
|
446
|
+
vec3.transformMat4(v3_position, v3_position, matrix_elements);
|
|
447
|
+
|
|
448
|
+
particles.writeAttributeVector3(particle_index, PARTICLE_ATTRIBUTE_POSITION, v3_position[0], v3_position[1], v3_position[2]);
|
|
449
|
+
|
|
450
|
+
particle_index++;
|
|
451
|
+
|
|
452
|
+
if (particle_index >= particles.capacity) {
|
|
453
|
+
break main_loop;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (particle_index < particles.capacity) {
|
|
461
|
+
// crop out remaining particles in the pool
|
|
462
|
+
particles.occupancy.clearRange(particle_index, particles.capacity);
|
|
463
|
+
particles.update();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// request bounds update
|
|
467
|
+
emitter.setFlag(ParticleEmitterFlag.ParticleBoundsNeedUpdate);
|
|
468
|
+
emitter.computeBoundingBox();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
*
|
|
473
|
+
* @param {function} random
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
__distribute(random) {
|
|
477
|
+
const sf = this.__sampling_function;
|
|
478
|
+
|
|
479
|
+
if (sf === SamplingFunctionKind.Random) {
|
|
480
|
+
this.__distribute_random(random);
|
|
481
|
+
} else if (sf === SamplingFunctionKind.Grid) {
|
|
482
|
+
this.__distribute_grid();
|
|
483
|
+
} else {
|
|
484
|
+
throw new Error(`Unsupported sampling function '${sf}'`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
356
488
|
/**
|
|
357
489
|
* Distribute particles in the volume
|
|
358
490
|
* @param {function():number} random
|
|
359
491
|
* @private
|
|
360
492
|
*/
|
|
361
|
-
|
|
493
|
+
__distribute_random(random) {
|
|
362
494
|
const emitter = this.__emitter;
|
|
363
495
|
const particles = emitter.particles;
|
|
364
496
|
const particle_count = particles.size();
|
|
@@ -79,6 +79,8 @@ import { MicronRenderPlugin } from "../../../../../micron/plugin/MicronRenderPlu
|
|
|
79
79
|
import { obtainTerrain } from "../../../../../../ecs/terrain/util/obtainTerrain.js";
|
|
80
80
|
import { pick } from "../../../../../../../ecs/grid/pick.js";
|
|
81
81
|
import { clamp01 } from "../../../../../../../core/math/clamp01.js";
|
|
82
|
+
import { randomFromArray } from "../../../../../../../core/math/random/randomFromArray.js";
|
|
83
|
+
import { SamplingFunctionKind } from "./SamplingFunctionKind.js";
|
|
82
84
|
|
|
83
85
|
const engineHarness = new EngineHarness();
|
|
84
86
|
|
|
@@ -895,7 +897,7 @@ async function sample_scene_moicon_0(engine) {
|
|
|
895
897
|
* @param {Engine} engine
|
|
896
898
|
*/
|
|
897
899
|
async function main(engine) {
|
|
898
|
-
EngineHarness.buildBasics({
|
|
900
|
+
await EngineHarness.buildBasics({
|
|
899
901
|
engine,
|
|
900
902
|
enableWater: false,
|
|
901
903
|
enableTerrain: false,
|
|
@@ -914,8 +916,8 @@ async function main(engine) {
|
|
|
914
916
|
engine.graphics.renderer.setClearColor('rgba(99,99,99,1)');
|
|
915
917
|
|
|
916
918
|
// await sample_scene_nam(engine);
|
|
917
|
-
|
|
918
|
-
await sample_scene_moicon_0(engine);
|
|
919
|
+
await sample_scene_0(engine);
|
|
920
|
+
// await sample_scene_moicon_0(engine);
|
|
919
921
|
}
|
|
920
922
|
|
|
921
923
|
/**
|
|
@@ -1068,7 +1070,8 @@ function create_volume({
|
|
|
1068
1070
|
particle_size,
|
|
1069
1071
|
shape: shape,
|
|
1070
1072
|
sort,
|
|
1071
|
-
sprite
|
|
1073
|
+
sprite,
|
|
1074
|
+
sampling_function: randomFromArray(Math.random, [SamplingFunctionKind.Random, SamplingFunctionKind.Grid])
|
|
1072
1075
|
});
|
|
1073
1076
|
|
|
1074
1077
|
v.build();
|
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.43.
|
|
8
|
+
"version": "2.43.13",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"gl-matrix": "3.4.3",
|
|
11
11
|
"fast-levenshtein": "2.0.6",
|