@woosh/meep-engine 2.38.1 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/core/geom/Quaternion.js +70 -0
  2. package/core/geom/Vector2.js +17 -0
  3. package/core/geom/v3_angle_between.js +17 -3
  4. package/engine/ecs/EntityBuilder.d.ts +3 -1
  5. package/engine/ecs/EntityBuilder.js +10 -1
  6. package/engine/ecs/EntityComponentDataset.js +1 -1
  7. package/engine/ecs/parent/ChildEntities.d.ts +3 -0
  8. package/engine/ecs/parent/ChildEntities.js +41 -0
  9. package/engine/ecs/system/AbstractContextSystem.js +4 -2
  10. package/engine/graphics/ecs/mesh-v2/{sg_compute_hierarchy_bounding_box_by_parent_entity.js → sg_hierarchy_compute_bounding_box_via_parent_entity.js} +1 -1
  11. package/engine/graphics/ecs/path/PathDisplayEvents.js +3 -2
  12. package/engine/graphics/ecs/path/PathDisplaySystem.js +5 -0
  13. package/engine/graphics/ecs/path/highlight/PathDisplayHighlightSystem.d.ts +7 -0
  14. package/engine/graphics/ecs/path/highlight/PathDisplayHighlightSystem.js +141 -0
  15. package/engine/graphics/ecs/path/testPathDisplaySystem.js +78 -25
  16. package/engine/graphics/ecs/path/tube/TubePathStyle.d.ts +12 -0
  17. package/engine/graphics/ecs/path/tube/TubePathStyle.js +53 -8
  18. package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +114 -4
  19. package/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.js +12 -2
  20. package/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +13 -2
  21. package/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +33 -28
  22. package/engine/graphics/ecs/path/tube/build/makeTubeGeometry.js +123 -347
  23. package/engine/graphics/ecs/path/tube/build/make_cap.js +274 -0
  24. package/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -0
  25. package/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +152 -0
  26. package/package.json +1 -1
  27. package/view/View.js +2 -2
  28. package/view/{compose3x3transform.js → m3_cm_compose_transform.js} +11 -3
  29. package/view/m3_rm_compose_transform.js +45 -0
  30. package/view/multiplyMatrices3.js +4 -4
@@ -37,19 +37,25 @@ export class TubePathStyle {
37
37
  */
38
38
  this.width = 1;
39
39
 
40
- // TODO expose shading style "flat/smooth"
40
+ /**
41
+ *
42
+ * @type {number[]}
43
+ */
44
+ this.shape = build_circle_shape(16);
41
45
 
42
46
  /**
43
47
  *
44
- * @type {number}
48
+ * @type {number[]|Float32Array|null}
45
49
  */
46
- this.resolution = 10;
50
+ this.shape_normals = null;
51
+
52
+ // TODO expose shading style "flat/smooth"
47
53
 
48
54
  /**
49
55
  *
50
56
  * @type {number}
51
57
  */
52
- this.radial_resolution = 16;
58
+ this.resolution = 10;
53
59
 
54
60
  /**
55
61
  *
@@ -79,6 +85,16 @@ export class TubePathStyle {
79
85
  this.cap_type = CapType.Flat;
80
86
  }
81
87
 
88
+ /**
89
+ * @deprecated
90
+ * @param {number} v
91
+ */
92
+ set radial_resolution(v) {
93
+ console.warn(".radial_resolution property is deprecated, use .shape directly instead");
94
+
95
+ this.shape = build_circle_shape(v);
96
+ }
97
+
82
98
  static fromJSON(j) {
83
99
  const r = new TubePathStyle();
84
100
 
@@ -93,18 +109,21 @@ export class TubePathStyle {
93
109
  material,
94
110
  opacity = 1,
95
111
  width = 1,
96
- resolution = 10,
112
+ /**
113
+ * @deprecated
114
+ */
97
115
  radial_resolution = 16,
116
+ resolution = 10,
98
117
  cast_shadow = false,
99
118
  receive_shadow = false,
100
119
  path_mask = [0, 1],
101
- cap_type = CapType.None
120
+ cap_type = CapType.None,
121
+ shape
102
122
  }) {
103
123
  assert.enum(material_type, TubeMaterialType, 'material_type');
104
124
  assert.isNumber(opacity, 'opacity');
105
125
  assert.isNumber(width, 'width');
106
126
  assert.isNumber(resolution, 'resolution');
107
- assert.isNumber(radial_resolution, 'radial_resolution');
108
127
  assert.isBoolean(cast_shadow, 'cast_shadow');
109
128
  assert.isBoolean(receive_shadow, 'receive_shadow');
110
129
 
@@ -115,6 +134,14 @@ export class TubePathStyle {
115
134
  assert.ok(path_mask.length % 2 === 0, 'path_mask length bust be multiple of 2');
116
135
  assert.enum(cap_type, CapType, 'cap_type');
117
136
 
137
+ if (shape === undefined) {
138
+ assert.isNumber(radial_resolution, 'radial_resolution');
139
+
140
+ shape = build_circle_shape(radial_resolution);
141
+ }
142
+
143
+ this.shape = shape;
144
+
118
145
  if (typeof color === 'string') {
119
146
  this.color.parse(color);
120
147
  } else {
@@ -125,7 +152,6 @@ export class TubePathStyle {
125
152
  this.opacity = opacity;
126
153
  this.width = width;
127
154
  this.resolution = resolution;
128
- this.radial_resolution = radial_resolution;
129
155
 
130
156
  this.cast_shadow = cast_shadow;
131
157
  this.receive_shadow = receive_shadow;
@@ -144,3 +170,22 @@ export class TubePathStyle {
144
170
  this.material.fromJSON(material);
145
171
  }
146
172
  }
173
+
174
+ /**
175
+ *
176
+ * @param {number} radial_segments
177
+ * @returns {Float32Array}
178
+ */
179
+ function build_circle_shape(radial_segments) {
180
+ const shape = new Float32Array(2 * radial_segments);
181
+ for (let i = 0; i < radial_segments; i++) {
182
+ const angle = Math.PI * 2 - Math.PI * 2 * (i / radial_segments);
183
+
184
+ const i2 = i * 2;
185
+
186
+ shape[i2] = Math.cos(angle);
187
+ shape[i2 + 1] = Math.sin(angle);
188
+ }
189
+
190
+ return shape;
191
+ }
@@ -8,6 +8,8 @@ import { build_geometry_linear } from "./build_geometry_linear.js";
8
8
  import { build_geometry_catmullrom } from "./build_geometry_catmullrom.js";
9
9
  import { ShadedGeometry } from "../../../mesh-v2/ShadedGeometry.js";
10
10
  import { ShadedGeometryFlags } from "../../../mesh-v2/ShadedGeometryFlags.js";
11
+ import { m3_rm_compose_transform } from "../../../../../../view/m3_rm_compose_transform.js";
12
+ import { v2_bearing_angle_towards } from "../../../../../../core/geom/Vector2.js";
11
13
 
12
14
  /**
13
15
  *
@@ -16,15 +18,22 @@ import { ShadedGeometryFlags } from "../../../mesh-v2/ShadedGeometryFlags.js";
16
18
  * @returns {BufferGeometry}
17
19
  * @param {Path} path_component
18
20
  * @param {TubePathStyle} style
21
+ * @param {number[]|Float32Array} shape_normal
22
+ * @param {number[]|Float32Array} shape
23
+ * @param {number[]|Float32Array} shape_transform
19
24
  */
20
- function make_geometry_segment(path_component, style, segment_start, segment_end) {
25
+ function make_geometry_segment(
26
+ path_component, style,
27
+ shape, shape_normal, shape_transform,
28
+ segment_start, segment_end
29
+ ) {
21
30
 
22
31
  const interpolation = path_component.interpolation;
23
32
 
24
33
  if (interpolation === InterpolationType.Linear) {
25
- return build_geometry_linear(path_component, style, segment_start, segment_end);
34
+ return build_geometry_linear(path_component, style, shape, shape_normal, shape_transform, segment_start, segment_end);
26
35
  } else if (interpolation === InterpolationType.CatmullRom) {
27
- return build_geometry_catmullrom(path_component, style, segment_start, segment_end);
36
+ return build_geometry_catmullrom(path_component, style, shape, shape_normal, shape_transform, segment_start, segment_end);
28
37
  } else {
29
38
  throw new Error(`Unsupported interpolation type '${interpolation}'`);
30
39
  }
@@ -132,13 +141,28 @@ export class TubePathBuilder {
132
141
 
133
142
  const segment_count = style.path_mask.length / 2;
134
143
 
144
+ const shape = fix_shape_normal_order(style.shape);
145
+
146
+ let shape_normals = style.shape_normals;
147
+ if (shape_normals === undefined || shape_normals === null) {
148
+ shape_normals = new Float32Array(shape.length);
149
+ compute_smooth_profile_normals(shape, shape_normals);
150
+ }
151
+
152
+ const shape_transform = new Float32Array(9);
153
+ m3_rm_compose_transform(shape_transform, 0, 0, style.width, style.width, 0, 0, 0);
154
+
135
155
  for (let i = 0; i < segment_count; i++) {
136
156
  const i2 = i * 2;
137
157
 
138
158
  const segment_start = style.path_mask[i2];
139
159
  const segment_end = style.path_mask[i2 + 1];
140
160
 
141
- const geometry = make_geometry_segment(path_component, style, segment_start, segment_end);
161
+ const geometry = make_geometry_segment(
162
+ path_component, style,
163
+ shape, shape_normals, shape_transform,
164
+ segment_start, segment_end
165
+ );
142
166
 
143
167
  const entityBuilder = new EntityBuilder();
144
168
 
@@ -157,3 +181,89 @@ export class TubePathBuilder {
157
181
 
158
182
  }
159
183
  }
184
+
185
+ /**
186
+ *
187
+ * @param {number[]|Float32Array} shape
188
+ * @param {number[]|Float32Array} normals
189
+ */
190
+ function compute_smooth_profile_normals(shape, normals) {
191
+ const n = shape.length / 2;
192
+
193
+ for (let i = 0; i < n; i++) {
194
+ const i0 = (n + i - 1) % n;
195
+ const i1 = i;
196
+ const i2 = (i + 1) % n;
197
+
198
+ const i0_x = shape[i0 * 2];
199
+ const i0_y = shape[i0 * 2 + 1];
200
+
201
+ const i1_x = shape[i1 * 2];
202
+ const i1_y = shape[i1 * 2 + 1];
203
+
204
+ const i2_x = shape[i2 * 2];
205
+ const i2_y = shape[i2 * 2 + 1];
206
+
207
+ const d0_x = i0_x - i1_x;
208
+ const d0_y = i0_y - i1_y;
209
+
210
+ const d1_x = i1_x - i2_x;
211
+ const d1_y = i1_y - i2_y;
212
+
213
+ const d0_l_inv = 1 / Math.hypot(d0_x, d0_y);
214
+ const d1_l_inv = 1 / Math.hypot(d1_x, d1_y);
215
+
216
+ const nx = d0_x * d0_l_inv + d1_x * d1_l_inv;
217
+ const ny = d0_y * d0_l_inv + d1_y * d1_l_inv;
218
+
219
+ const dn_l_inv = 1 / Math.hypot(nx, ny);
220
+
221
+ // write out normal
222
+ normals[i * 2] = nx * dn_l_inv;
223
+ normals[i * 2 + 1] = ny * dn_l_inv;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Depending on the direction in which shape winds, normals of the generated geometry faces might end up flipped,
229
+ * so we pre-process the shape to avoid that here
230
+ * @param {number[]|Float32Array} shape
231
+ * @returns {Float32Array}
232
+ */
233
+ function fix_shape_normal_order(shape) {
234
+ const shape_array_length = shape.length;
235
+
236
+ if (shape_array_length <= 2) {
237
+ // less than 2 points in the shape
238
+ return shape;
239
+ }
240
+
241
+ // compute initial direction
242
+ const x0 = shape[0];
243
+ const y0 = shape[1];
244
+ const x1 = shape[2];
245
+ const y1 = shape[3];
246
+
247
+ const angle = v2_bearing_angle_towards(x0, y0, x1, y1);
248
+
249
+ if (angle > Math.PI) {
250
+ // order is fine
251
+ return shape;
252
+ }
253
+
254
+ // reverse order
255
+ const reversed = new Float32Array(shape_array_length);
256
+
257
+ const n = shape_array_length / 2;
258
+
259
+ for (let i = 0; i < n; i++) {
260
+ const i2 = i * 2;
261
+
262
+ const j2 = (n - (i + 1)) * 2;
263
+
264
+ reversed[i2] = shape[j2];
265
+ reversed[i2 + 1] = shape[j2 + 1];
266
+ }
267
+
268
+ return reversed;
269
+ }
@@ -6,11 +6,18 @@ import Vector3 from "../../../../../../core/geom/Vector3.js";
6
6
  *
7
7
  * @param {Path} path
8
8
  * @param {TubePathStyle} style
9
+ * @param {number[]} shape
10
+ * @param {number[]|Float32Array} shape_normal
11
+ * @param {number[]} shape_transform
9
12
  * @param {number} segment_start
10
13
  * @param {number} segment_end
11
14
  * @return {THREE.BufferGeometry}
12
15
  */
13
- export function build_geometry_catmullrom(path, style, segment_start, segment_end) {
16
+ export function build_geometry_catmullrom(
17
+ path, style,
18
+ shape, shape_normal, shape_transform,
19
+ segment_start, segment_end
20
+ ) {
14
21
 
15
22
  const point_count = path.getPointCount();
16
23
 
@@ -58,5 +65,8 @@ export function build_geometry_catmullrom(path, style, segment_start, segment_en
58
65
 
59
66
  const frames = computeFrenetFrames(points_f32, false);
60
67
 
61
- return makeTubeGeometry(points_f32, frames.normals, frames.binormals, style.width, style.radial_resolution, false, style.cap_type);
68
+ return makeTubeGeometry(
69
+ points_f32, frames.normals, frames.binormals, frames.tangents,
70
+ shape, shape_normal, shape.length / 2, shape_transform, false, style.cap_type
71
+ );
62
72
  }
@@ -12,11 +12,18 @@ const scratch_v3 = new Vector3();
12
12
  *
13
13
  * @param {Path} path
14
14
  * @param {TubePathStyle} style
15
+ * @param {number[]} shape
16
+ * @param {number[]|Float32Array} shape_normal
17
+ * @param {number[]} shape_transform
15
18
  * @param {number} segment_start
16
19
  * @param {number} segment_end
17
20
  * @return {THREE.BufferGeometry}
18
21
  */
19
- export function build_geometry_linear(path, style, segment_start, segment_end) {
22
+ export function build_geometry_linear(
23
+ path, style,
24
+ shape, shape_normal, shape_transform,
25
+ segment_start, segment_end
26
+ ) {
20
27
  const points = [];
21
28
 
22
29
  const path_length = path.length;
@@ -52,5 +59,9 @@ export function build_geometry_linear(path, style, segment_start, segment_end) {
52
59
 
53
60
  const frames = computeFrenetFrames(points, false);
54
61
 
55
- return makeTubeGeometry(points, frames.normals, frames.binormals, style.width, style.radial_resolution, false, style.cap_type);
62
+ return makeTubeGeometry(
63
+ points, frames.normals, frames.binormals, frames.tangents,
64
+ shape, shape_normal, shape.length / 2, shape_transform,
65
+ false, style.cap_type
66
+ );
56
67
  }
@@ -9,6 +9,7 @@ import { array_copy } from "../../../../../../core/collection/array/copyArray.js
9
9
  * @see https://github.com/mrdoob/three.js/blob/c12c9a166a1369cdd58622fff2aff7e3a84305d7/src/extras/core/Curve.js#L260
10
10
  * @param {Float32Array|number[]} points
11
11
  * @param {boolean} [closed]
12
+ * @returns {{normals:Vector3[], binormals:Vector3[], tangents:Vector3[]}}
12
13
  */
13
14
  export function computeFrenetFrames(points, closed = false) {
14
15
  // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
@@ -32,12 +33,10 @@ export function computeFrenetFrames(points, closed = false) {
32
33
  const pR_a = [];
33
34
  const p1_a = [];
34
35
 
35
- /**
36
- *
37
- * @param {number} i
38
- * @param {Vector3} destination
39
- */
40
- function getTangent(i, destination) {
36
+ // compute the tangent vectors for each segment on the curve
37
+
38
+ for (let i = 0; i <= segments; i++) {
39
+
41
40
  // get points on either side
42
41
  const i0 = max2(i - 1, 0);
43
42
  const i1 = min2(i + 1, segments);
@@ -54,22 +53,27 @@ export function computeFrenetFrames(points, closed = false) {
54
53
  vec3.sub(v3_1, p1_a, pR_a);
55
54
  vec3.normalize(v3_1, v3_1);
56
55
 
57
- destination.set(
58
- v3_1[0] - v3_0[0],
59
- v3_1[1] - v3_0[1],
60
- v3_1[2] - v3_0[2],
61
- );
62
- destination.normalize();
63
- }
64
-
65
- // compute the tangent vectors for each segment on the curve
66
-
67
- for (let i = 0; i <= segments; i++) {
56
+ vec3.sub(v3_1, v3_1, v3_0);
57
+ vec3.normalize(v3_1, v3_1);
68
58
 
69
- const tangent = new Vector3();
70
- getTangent(i, tangent);
59
+ // write out
60
+ const tangent_x = v3_1[0];
61
+ const tangent_y = v3_1[1];
62
+ const tangent_z = v3_1[2];
63
+
64
+ if (tangent_x === 0 && tangent_y === 0 && tangent_z === 0) {
65
+ // no tangent, copy previous one
66
+ if (i > 0) {
67
+ tangents[i] = tangents[i - 1];
68
+ continue;
69
+ }
70
+ }
71
71
 
72
- tangents[i] = tangent;
72
+ tangents[i] = new Vector3(
73
+ tangent_x,
74
+ tangent_y,
75
+ tangent_z
76
+ );
73
77
 
74
78
  }
75
79
 
@@ -83,13 +87,6 @@ export function computeFrenetFrames(points, closed = false) {
83
87
  const ty = Math.abs(tangents[0].y);
84
88
  const tz = Math.abs(tangents[0].z);
85
89
 
86
- if (tx <= min) {
87
-
88
- min = tx;
89
- normal.set(1, 0, 0);
90
-
91
- }
92
-
93
90
  if (ty <= min) {
94
91
 
95
92
  min = ty;
@@ -97,12 +94,20 @@ export function computeFrenetFrames(points, closed = false) {
97
94
 
98
95
  }
99
96
 
100
- if (tz <= min) {
97
+ if (tz < min) {
98
+ min = tz;
101
99
 
102
100
  normal.set(0, 0, 1);
103
101
 
104
102
  }
105
103
 
104
+ if (tx < min) {
105
+
106
+ min = tx;
107
+ normal.set(1, 0, 0);
108
+
109
+ }
110
+
106
111
  vec.crossVectors(tangents[0], normal).normalize();
107
112
 
108
113
  normals[0].crossVectors(tangents[0], vec);