@woosh/meep-engine 2.43.9 → 2.43.12
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/binary/url_to_data_url.js +28 -0
- package/core/collection/queue/Deque.js +2 -2
- 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 +15 -66
- package/editor/ecs/component/editors/LargeStrongEditor.js +107 -0
- package/engine/graphics/ecs/path/PathDisplaySystem.js +92 -4
- package/engine/graphics/ecs/path/tube/build/{GeometryOutput.js → StreamGeometryBuilder.js} +1 -1
- package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +16 -14
- package/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.spec.js +32 -0
- package/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +6 -1
- package/engine/graphics/ecs/path/tube/build/makeTubeGeometry.js +6 -4
- package/engine/graphics/ecs/path/tube/build/make_cap.js +10 -10
- package/engine/graphics/ecs/path/tube/build/make_ring_faces.js +1 -1
- package/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +1 -1
- package/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +143 -27
- package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +5 -0
- package/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.d.ts +3 -1
- package/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.js +148 -5
- 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/navigation/ecs/components/Path.spec.js +21 -0
- package/package.json +1 -1
- package/samples/terrain/from_image_2.js +33 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {string} url
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export async function url_to_data_url(url) {
|
|
7
|
+
return fetch(url)
|
|
8
|
+
.then(response => {
|
|
9
|
+
return response.blob();
|
|
10
|
+
})
|
|
11
|
+
.then(blob => {
|
|
12
|
+
const fr = new FileReader();
|
|
13
|
+
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
|
|
16
|
+
fr.onload = () => {
|
|
17
|
+
resolve(fr.result);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
fr.onerror = reject;
|
|
21
|
+
fr.onabort = reject;
|
|
22
|
+
|
|
23
|
+
fr.readAsDataURL(blob);
|
|
24
|
+
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -302,7 +302,7 @@ Deque.prototype.peek = Deque.prototype.getFirst;
|
|
|
302
302
|
Deque.prototype.push = Deque.prototype.addFirst;
|
|
303
303
|
Deque.prototype.pop = Deque.prototype.removeFirst;
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
Standard queue method
|
|
305
|
+
/**
|
|
306
|
+
* Standard queue method
|
|
307
307
|
*/
|
|
308
308
|
Deque.prototype.add = Deque.prototype.addLast;
|
|
@@ -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,72 +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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @param {string} url
|
|
12
|
-
* @return {boolean}
|
|
13
|
-
*/
|
|
14
|
-
function is_data_url(url) {
|
|
15
|
-
if (typeof url !== "string") {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return url.startsWith('data:');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
* @param {string} url
|
|
25
|
-
* @returns {string}
|
|
26
|
-
*/
|
|
27
|
-
async function url_to_data_url(url) {
|
|
28
|
-
return fetch(url)
|
|
29
|
-
.then(response => {
|
|
30
|
-
return response.blob();
|
|
31
|
-
})
|
|
32
|
-
.then(blob => {
|
|
33
|
-
const fr = new FileReader();
|
|
34
|
-
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
|
|
37
|
-
fr.onload = () => {
|
|
38
|
-
resolve(fr.result);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
fr.onerror = reject;
|
|
42
|
-
fr.onabort = reject;
|
|
43
|
-
|
|
44
|
-
fr.readAsDataURL(blob);
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
}
|
|
5
|
+
import { url_to_data_url } from "../../../../core/binary/url_to_data_url.js";
|
|
6
|
+
import { is_data_url } from "../../../../core/binary/is_data_url.js";
|
|
7
|
+
import { LargeStrongEditor } from "./LargeStrongEditor.js";
|
|
50
8
|
|
|
51
9
|
export class ImagePathEditor extends TypeEditor {
|
|
52
10
|
inline = true;
|
|
53
11
|
|
|
54
12
|
build(parent, field, registry) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (field.type === String) {
|
|
13
|
+
const url_editor = new LargeStrongEditor();
|
|
58
14
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} else if (field.type === ObservedString) {
|
|
62
|
-
|
|
63
|
-
url_editor = new ObservedStringEditor();
|
|
64
|
-
|
|
65
|
-
} else {
|
|
66
|
-
throw new Error(`Unsupported type`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const vEditor = url_editor.build(parent, field, registry);
|
|
15
|
+
const editor = url_editor.build(parent, field, registry);
|
|
70
16
|
|
|
71
17
|
const r = new EmptyView({
|
|
72
18
|
css: {
|
|
@@ -87,14 +33,14 @@ export class ImagePathEditor extends TypeEditor {
|
|
|
87
33
|
field.adapter.write(parent, field.name, url);
|
|
88
34
|
|
|
89
35
|
// update editor value
|
|
90
|
-
|
|
36
|
+
editor.methods.set_value(url);
|
|
91
37
|
|
|
92
|
-
|
|
38
|
+
input_changed();
|
|
93
39
|
}
|
|
94
40
|
});
|
|
95
41
|
|
|
96
42
|
b_toDataURL.attr({
|
|
97
|
-
|
|
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'
|
|
98
44
|
});
|
|
99
45
|
|
|
100
46
|
function update_data_url_status() {
|
|
@@ -116,15 +62,18 @@ export class ImagePathEditor extends TypeEditor {
|
|
|
116
62
|
}
|
|
117
63
|
}
|
|
118
64
|
|
|
119
|
-
function
|
|
120
|
-
|
|
65
|
+
function input_changed() {
|
|
66
|
+
const url = field.adapter.read(parent, field.name);
|
|
67
|
+
|
|
68
|
+
vPreview.__setSource(url);
|
|
69
|
+
|
|
121
70
|
update_data_url_status();
|
|
122
71
|
}
|
|
123
72
|
|
|
124
73
|
r.addChild(vPreview);
|
|
125
|
-
r.addChild(
|
|
74
|
+
r.addChild(editor.view);
|
|
126
75
|
|
|
127
|
-
|
|
76
|
+
editor.signals.input_changed.add(input_changed);
|
|
128
77
|
|
|
129
78
|
r.on.linked.add(update_data_url_status);
|
|
130
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
|
+
}
|
|
@@ -11,6 +11,7 @@ import { RibbonPathBuilder } from "./ribbon/RibbonPathBuilder.js";
|
|
|
11
11
|
import { PathEvents } from "../../../navigation/ecs/components/PathEvents.js";
|
|
12
12
|
import { assert } from "../../../../core/assert.js";
|
|
13
13
|
import { TubePathBuilder } from "./tube/build/TubePathBuilder.js";
|
|
14
|
+
import { Deque } from "../../../../core/collection/queue/Deque.js";
|
|
14
15
|
|
|
15
16
|
const builders = {
|
|
16
17
|
[PathDisplayType.None]: function (style, path, result) {
|
|
@@ -82,6 +83,14 @@ const builders = {
|
|
|
82
83
|
}
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Maximum amount of time allowed per system tick to spend on processing update queue
|
|
88
|
+
* in milliseconds
|
|
89
|
+
* @readonly
|
|
90
|
+
* @type {number}
|
|
91
|
+
*/
|
|
92
|
+
const UPDATE_PROCESSING_BUDGET_MS = 10;
|
|
93
|
+
|
|
85
94
|
class PathDisplayContext extends SystemEntityContext {
|
|
86
95
|
constructor() {
|
|
87
96
|
super();
|
|
@@ -100,6 +109,8 @@ class PathDisplayContext extends SystemEntityContext {
|
|
|
100
109
|
const ownedEntities = this.__owned_entities;
|
|
101
110
|
ownedEntities.splice(0, ownedEntities.length);
|
|
102
111
|
|
|
112
|
+
// todo check that the main entity still exists before we decide to spawn new entities
|
|
113
|
+
|
|
103
114
|
/**
|
|
104
115
|
* @type {PathDisplay}
|
|
105
116
|
*/
|
|
@@ -180,6 +191,16 @@ class PathDisplayContext extends SystemEntityContext {
|
|
|
180
191
|
this.__build();
|
|
181
192
|
}
|
|
182
193
|
|
|
194
|
+
request_update() {
|
|
195
|
+
/**
|
|
196
|
+
*
|
|
197
|
+
* @type {PathDisplaySystem}
|
|
198
|
+
*/
|
|
199
|
+
const system = this.system;
|
|
200
|
+
|
|
201
|
+
system.request_entity_update(this.entity);
|
|
202
|
+
}
|
|
203
|
+
|
|
183
204
|
link() {
|
|
184
205
|
super.link();
|
|
185
206
|
|
|
@@ -189,8 +210,8 @@ class PathDisplayContext extends SystemEntityContext {
|
|
|
189
210
|
|
|
190
211
|
const entity = this.entity;
|
|
191
212
|
|
|
192
|
-
ecd.addEntityEventListener(entity, PathEvents.Changed, this.
|
|
193
|
-
ecd.addEntityEventListener(entity, PathDisplayEvents.Changed, this.
|
|
213
|
+
ecd.addEntityEventListener(entity, PathEvents.Changed, this.request_update, this);
|
|
214
|
+
ecd.addEntityEventListener(entity, PathDisplayEvents.Changed, this.request_update, this);
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
unlink() {
|
|
@@ -200,8 +221,16 @@ class PathDisplayContext extends SystemEntityContext {
|
|
|
200
221
|
|
|
201
222
|
const entity = this.entity;
|
|
202
223
|
|
|
203
|
-
ecd.removeEntityEventListener(entity, PathEvents.Changed, this.
|
|
204
|
-
ecd.removeEntityEventListener(entity, PathDisplayEvents.Changed, this.
|
|
224
|
+
ecd.removeEntityEventListener(entity, PathEvents.Changed, this.request_update, this);
|
|
225
|
+
ecd.removeEntityEventListener(entity, PathDisplayEvents.Changed, this.request_update, this);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
*
|
|
229
|
+
* @type {PathDisplaySystem}
|
|
230
|
+
*/
|
|
231
|
+
const system = this.system;
|
|
232
|
+
|
|
233
|
+
system.cancel_entity_update(entity);
|
|
205
234
|
|
|
206
235
|
// destroy existing owned entities
|
|
207
236
|
this.__destroy_existing_entities();
|
|
@@ -238,6 +267,35 @@ export class PathDisplaySystem extends AbstractContextSystem {
|
|
|
238
267
|
* @type {Reference<RibbonXPlugin>}
|
|
239
268
|
*/
|
|
240
269
|
this.plugin = null;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Deferred queue of entities slated to be rebuilt
|
|
273
|
+
* @type {Deque<number>}
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
this.__rebuild_queue = new Deque();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Request that path displays be rebuilt
|
|
281
|
+
* @package
|
|
282
|
+
* @param {number} entity
|
|
283
|
+
*/
|
|
284
|
+
request_entity_update(entity) {
|
|
285
|
+
assert.isNonNegativeInteger(entity, 'entity');
|
|
286
|
+
|
|
287
|
+
if (!this.__rebuild_queue.has(entity)) {
|
|
288
|
+
this.__rebuild_queue.add(entity);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Cancel any pending updates
|
|
294
|
+
* @package
|
|
295
|
+
* @param {number} entity
|
|
296
|
+
*/
|
|
297
|
+
cancel_entity_update(entity) {
|
|
298
|
+
this.__rebuild_queue.remove(entity);
|
|
241
299
|
}
|
|
242
300
|
|
|
243
301
|
async startup(entityManager, readyCallback, errorCallback) {
|
|
@@ -253,4 +311,34 @@ export class PathDisplaySystem extends AbstractContextSystem {
|
|
|
253
311
|
|
|
254
312
|
super.shutdown(entityManager, readyCallback, errorCallback);
|
|
255
313
|
}
|
|
314
|
+
|
|
315
|
+
update(time_delta) {
|
|
316
|
+
// process update queue
|
|
317
|
+
|
|
318
|
+
const queue = this.__rebuild_queue;
|
|
319
|
+
if (queue.isEmpty()) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const t0 = performance.now();
|
|
324
|
+
|
|
325
|
+
do {
|
|
326
|
+
// note that we don't need to check if the queue is empty here for the first iteration, as we already checked earlier
|
|
327
|
+
const entity = queue.pop();
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
*
|
|
331
|
+
* @type {PathDisplayContext|undefined}
|
|
332
|
+
*/
|
|
333
|
+
const ctx = this.__getEntityContext(entity);
|
|
334
|
+
|
|
335
|
+
// check that entity still exists
|
|
336
|
+
if (ctx === undefined) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
ctx.rebuild();
|
|
341
|
+
|
|
342
|
+
} while ((performance.now() - t0) < UPDATE_PROCESSING_BUDGET_MS && !queue.isEmpty());
|
|
343
|
+
}
|
|
256
344
|
}
|
|
@@ -4,7 +4,7 @@ import { BufferAttribute, BufferGeometry } from "three";
|
|
|
4
4
|
/**
|
|
5
5
|
* Helper class, allowing us to treat geometry build process as a stream without having to create intermediate arrays and performing unnecessary copying
|
|
6
6
|
*/
|
|
7
|
-
export class
|
|
7
|
+
export class StreamGeometryBuilder {
|
|
8
8
|
constructor() {
|
|
9
9
|
/**
|
|
10
10
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BufferGeometry, MeshBasicMaterial, MeshMatcapMaterial, MeshStandardMaterial
|
|
1
|
+
import { BufferGeometry, MeshBasicMaterial, MeshMatcapMaterial, MeshStandardMaterial } from "three";
|
|
2
2
|
import { InterpolationType } from "../../../../../navigation/ecs/components/InterpolationType.js";
|
|
3
3
|
import EntityBuilder from "../../../../../ecs/EntityBuilder.js";
|
|
4
4
|
import { Transform } from "../../../../../ecs/transform/Transform.js";
|
|
@@ -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
|
|
|
@@ -129,8 +133,6 @@ export class TubePathBuilder {
|
|
|
129
133
|
// generate three.js curve from path
|
|
130
134
|
const path_component = this.path;
|
|
131
135
|
|
|
132
|
-
const three_points = [];
|
|
133
|
-
|
|
134
136
|
const pointCount = path_component.getPointCount();
|
|
135
137
|
|
|
136
138
|
if (pointCount < 2) {
|
|
@@ -138,15 +140,6 @@ export class TubePathBuilder {
|
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
for (let i = 0; i < pointCount; i++) {
|
|
142
|
-
const v3 = new Vector3();
|
|
143
|
-
|
|
144
|
-
path_component.getPosition(i, v3);
|
|
145
|
-
|
|
146
|
-
three_points.push(v3);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
143
|
const material_def = make_material(style, this.assetManager);
|
|
151
144
|
|
|
152
145
|
material_def.color.set(style.color.toUint());
|
|
@@ -172,8 +165,17 @@ export class TubePathBuilder {
|
|
|
172
165
|
for (let i = 0; i < segment_count; i++) {
|
|
173
166
|
const i2 = i * 2;
|
|
174
167
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|
|
177
179
|
|
|
178
180
|
const geometry = make_geometry_segment(
|
|
179
181
|
path_component, style,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Path from "../../../../../navigation/ecs/components/Path.js";
|
|
2
|
+
import { build_geometry_catmullrom } from "./build_geometry_catmullrom.js";
|
|
3
|
+
import { TubePathStyle } from "../TubePathStyle.js";
|
|
4
|
+
import { CapType } from "../CapType.js";
|
|
5
|
+
|
|
6
|
+
test('very tiny segment of a path', () => {
|
|
7
|
+
const path = Path.fromJSON({
|
|
8
|
+
points: [3, 1, 1, 3, 1, 3, 7, 1, 3, 7, 1, 7, 3, 1, 7, 3, 1, 8]
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const style = new TubePathStyle();
|
|
12
|
+
style.cap_type = CapType.Flat;
|
|
13
|
+
style.width = 0.1;
|
|
14
|
+
style.path_mask = [0, 0.00005900000113712167];
|
|
15
|
+
style.resolution = 10;
|
|
16
|
+
style.shape = [1, -2.4492937051703357e-16, 0.9238795042037964, -0.3826834261417389, 0.7071067690849304, -0.7071067690849304, 0.3826834261417389, -0.9238795042037964, -1.8369701465288538e-16, -1, -0.3826834261417389, -0.9238795042037964, -0.7071067690849304, -0.7071067690849304, -0.9238795042037964, -0.3826834261417389, -1, 1.2246468525851679e-16, -0.9238795042037964, 0.3826834261417389, -0.7071067690849304, 0.7071067690849304, -0.3826834261417389, 0.9238795042037964, 6.123234262925839e-17, 1, 0.3826834261417389, 0.9238795042037964, 0.7071067690849304, 0.7071067690849304, 0.9238795042037964, 0.3826834261417389];
|
|
17
|
+
|
|
18
|
+
const result = build_geometry_catmullrom(path, style, style.shape, [
|
|
19
|
+
1.131973639570565e-16, 1, 0.3826834559440613, 0.9238795042037964, 0.7071067690849304, 0.7071067690849304, 0.9238795042037964, 0.3826834559440613, 1, -8.489801965906992e-17, 0.9238795042037964, -0.3826834559440613, 0.7071067690849304, -0.7071067690849304, 0.3826834559440613, -0.9238795042037964, -5.659868197852824e-17, -1, -0.3826834559440613, -0.9238795042037964, -0.7071067690849304, -0.7071067690849304, -0.9238795042037964, -0.3826834559440613, -1, 4.244900982953496e-17, -0.9238795042037964, 0.3826834559440613, -0.7071067690849304, 0.7071067690849304, -0.3826834559440613, 0.9238795042037964
|
|
20
|
+
], [
|
|
21
|
+
0.10000000149011612, 0, 0, -0, 0.10000000149011612, 0, 0, 0, 1
|
|
22
|
+
], 0, 0.00005900000113712167);
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const attribute_position = result.getAttribute("position");
|
|
26
|
+
expect(attribute_position).toBeDefined();
|
|
27
|
+
expect(attribute_position).not.toBeNull();
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < attribute_position.array.length; i++) {
|
|
30
|
+
expect(attribute_position.array[i]).not.toBeNaN();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -101,8 +101,13 @@ export function computeFrenetFrames(points, closed = false, normal_hint) {
|
|
|
101
101
|
// no tangent, copy previous one
|
|
102
102
|
if (i > 0) {
|
|
103
103
|
tangents[i] = tangents[i - 1];
|
|
104
|
-
|
|
104
|
+
} else {
|
|
105
|
+
// very first tangent is undefined, set something arbitrary
|
|
106
|
+
// TODO take normal_hint into account
|
|
107
|
+
tangents[i] = new Vector3(0, 0, 1);
|
|
105
108
|
}
|
|
109
|
+
continue;
|
|
110
|
+
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
tangents[i] = new Vector3(
|