@woosh/meep-engine 2.35.12 → 2.35.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.
@@ -74,6 +74,13 @@ export class ShadedGeometrySystem extends System {
74
74
  * @private
75
75
  */
76
76
  this.__adapter_suppliers = new Map();
77
+
78
+ /**
79
+ * Keeps count of usage for each geometry by ID
80
+ * @type {Map<number, number>}
81
+ * @private
82
+ */
83
+ this.__geometry_usage_counters = new Map();
77
84
  }
78
85
 
79
86
  /**
@@ -166,7 +173,7 @@ export class ShadedGeometrySystem extends System {
166
173
  this.__render_layer.buildVisibleSet = ((destination, destination_offset, view) => {
167
174
  const ctx = this.__view_contexts.get(view);
168
175
 
169
- return ctx.collect(destination, destination_offset,graphics.renderer, view, this.__bvh);
176
+ return ctx.collect(destination, destination_offset, graphics.renderer, view, this.__bvh);
170
177
  });
171
178
 
172
179
  readyCallback();
@@ -210,6 +217,13 @@ export class ShadedGeometrySystem extends System {
210
217
  sg.__entity = entity;
211
218
 
212
219
  this.__bvh.insertNode(leaf_node);
220
+
221
+ // update usage count
222
+ const geometry_id = sg.geometry.id;
223
+ const count_existing = this.__geometry_usage_counters.get(geometry_id);
224
+ const count_new = count_existing !== undefined ? count_existing + 1 : 1;
225
+
226
+ this.__geometry_usage_counters.set(geometry_id, count_new);
213
227
  }
214
228
 
215
229
  /**
@@ -224,6 +238,21 @@ export class ShadedGeometrySystem extends System {
224
238
  t.scale.onChanged.remove(sg.updateTransform, sg);
225
239
 
226
240
  sg.bvh.disconnect();
241
+
242
+ const geometry_id = sg.geometry.id;
243
+ const count_existing = this.__geometry_usage_counters.get(geometry_id);
244
+
245
+ if (count_existing === 1) {
246
+
247
+ // last instance
248
+ this.__geometry_usage_counters.delete(geometry_id);
249
+
250
+ // release GPU memory
251
+ sg.geometry.dispose();
252
+
253
+ } else {
254
+ this.__geometry_usage_counters.set(geometry_id, count_existing - 1);
255
+ }
227
256
  }
228
257
 
229
258
  /**
@@ -316,6 +316,12 @@ function sample_style_tube() {
316
316
 
317
317
  style.width = 0.1;
318
318
 
319
+ style.path_mask = [
320
+ 0.1, 0.12,
321
+ 0.3, 0.8,
322
+ 0.9, 1
323
+ ];
324
+
319
325
  return style;
320
326
  }
321
327
 
@@ -13,4 +13,5 @@ export class TubePathStyle {
13
13
  public radial_resolution: number
14
14
  public cast_shadow: boolean
15
15
  public receive_shadow: boolean
16
+ public path_mask: number[]
16
17
  }
@@ -3,6 +3,8 @@ import { TubeMaterialType } from "./TubeMaterialType.js";
3
3
  import { assert } from "../../../../../core/assert.js";
4
4
  import { MatcapMaterialDefinition } from "./MatcapMaterialDefinition.js";
5
5
 
6
+ const DEFAULT_COLOR = Object.freeze({ r: 1, g: 1, b: 1 });
7
+
6
8
  export class TubePathStyle {
7
9
  constructor() {
8
10
  /**
@@ -59,6 +61,16 @@ export class TubePathStyle {
59
61
  * @type {boolean}
60
62
  */
61
63
  this.receive_shadow = false;
64
+
65
+ /**
66
+ * Which parts of the path to visualise, in normalized offsets
67
+ * @example [ 0, 1 ] = whole path
68
+ * @example [ 0, 0.5 ] = first half of the path
69
+ * @example [ 0.5, 0.6 ] = segment from 50% along the path, to 60% along the path
70
+ * @example [ 0, 0.1, 0.9, 1] = first 10% of the path, and last 10% of the path
71
+ * @type {number[]}
72
+ */
73
+ this.path_mask = [0, 1];
62
74
  }
63
75
 
64
76
  static fromJSON(j) {
@@ -70,7 +82,7 @@ export class TubePathStyle {
70
82
  }
71
83
 
72
84
  fromJSON({
73
- color,
85
+ color = DEFAULT_COLOR,
74
86
  material_type,
75
87
  material,
76
88
  opacity = 1,
@@ -78,9 +90,22 @@ export class TubePathStyle {
78
90
  resolution = 10,
79
91
  radial_resolution = 16,
80
92
  cast_shadow = false,
81
- receive_shadow = false
93
+ receive_shadow = false,
94
+ path_mask = [0, 1]
82
95
  }) {
83
96
  assert.enum(material_type, TubeMaterialType, 'material_type');
97
+ assert.isNumber(opacity, 'opacity');
98
+ assert.isNumber(width, 'width');
99
+ assert.isNumber(resolution, 'resolution');
100
+ assert.isNumber(radial_resolution, 'radial_resolution');
101
+ assert.isBoolean(cast_shadow, 'cast_shadow');
102
+ assert.isBoolean(receive_shadow, 'receive_shadow');
103
+
104
+ assert.defined(path_mask, 'path_mask');
105
+ assert.notNull(path_mask, 'path_mask');
106
+
107
+ assert.greaterThanOrEqual(path_mask.length, 2, 'path_mask length bust be at least 2');
108
+ assert.ok(path_mask.length % 2 === 0, 'path_mask length bust be multiple of 2');
84
109
 
85
110
  if (typeof color === 'string') {
86
111
  this.color.parse(color);
@@ -97,6 +122,8 @@ export class TubePathStyle {
97
122
  this.cast_shadow = cast_shadow;
98
123
  this.receive_shadow = receive_shadow;
99
124
 
125
+ this.path_mask = path_mask;
126
+
100
127
  switch (material_type) {
101
128
  case TubeMaterialType.Matcap:
102
129
  this.material = new MatcapMaterialDefinition();
@@ -9,6 +9,28 @@ 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
11
 
12
+ /**
13
+ *
14
+ * @param {number} segment_start
15
+ * @param {number} segment_end
16
+ * @returns {BufferGeometry}
17
+ * @param {Path} path_component
18
+ * @param {TubePathStyle} style
19
+ */
20
+ function make_geometry_segment(path_component, style, segment_start, segment_end) {
21
+
22
+ const interpolation = path_component.interpolation;
23
+
24
+ if (interpolation === InterpolationType.Linear) {
25
+ return build_geometry_linear(path_component, style, segment_start, segment_end);
26
+ } else if (interpolation === InterpolationType.CatmullRom) {
27
+ return build_geometry_catmullrom(path_component, style, segment_start, segment_end);
28
+ } else {
29
+ throw new Error(`Unsupported interpolation type '${interpolation}'`);
30
+ }
31
+
32
+ }
33
+
12
34
  export class TubePathBuilder {
13
35
  constructor() {
14
36
  /**
@@ -80,20 +102,6 @@ export class TubePathBuilder {
80
102
  }
81
103
 
82
104
 
83
- /**
84
- * @type {THREE.BufferGeometry}
85
- */
86
- let geometry;
87
-
88
- if (path_component.interpolation === InterpolationType.Linear) {
89
- geometry = build_geometry_linear(three_points, pointCount, style);
90
- } else if (path_component.interpolation === InterpolationType.CatmullRom) {
91
- geometry = build_geometry_catmullrom(three_points, pointCount, style);
92
- } else {
93
- throw new Error(`Unsupported interpolation type '${path_component.interpolation}'`);
94
- }
95
-
96
-
97
105
  let material_def;
98
106
 
99
107
  if (style.material_type === TubeMaterialType.Standard) {
@@ -116,23 +124,37 @@ export class TubePathBuilder {
116
124
  }
117
125
 
118
126
  material_def.color.set(style.color.toUint());
127
+ material_def.wireframe = true;
119
128
 
120
129
  if (style.opacity < 1) {
121
130
  material_def.transparent = true;
122
131
  material_def.opacity = style.opacity;
123
132
  }
124
133
 
125
- const entityBuilder = new EntityBuilder();
134
+ const segment_count = style.path_mask.length / 2;
135
+
136
+ for (let i = 0; i < segment_count; i++) {
137
+ const i2 = i * 2;
138
+
139
+ const segment_start = style.path_mask[i2];
140
+ const segment_end = style.path_mask[i2 + 1];
141
+
142
+ const geometry = make_geometry_segment(path_component, style, segment_start, segment_end);
126
143
 
127
- entityBuilder.add(new Transform());
144
+ const entityBuilder = new EntityBuilder();
128
145
 
129
- const shaded_geometry = ShadedGeometry.from(geometry, material_def);
146
+ entityBuilder.add(new Transform());
130
147
 
131
- shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
132
- shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
148
+ const shaded_geometry = ShadedGeometry.from(geometry, material_def);
133
149
 
134
- entityBuilder.add(shaded_geometry);
150
+ shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
151
+ shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
152
+
153
+ entityBuilder.add(shaded_geometry);
154
+
155
+ destination.push(entityBuilder);
156
+
157
+ }
135
158
 
136
- destination.push(entityBuilder);
137
159
  }
138
160
  }
@@ -1,32 +1,61 @@
1
- import { CatmullRomCurve3 } from "three";
2
1
  import { computeFrenetFrames } from "./computeFrenetFrames.js";
3
2
  import { makeTubeGeometry } from "./makeTubeGeometry.js";
3
+ import Vector3 from "../../../../../../core/geom/Vector3.js";
4
4
 
5
5
  /**
6
6
  *
7
- * @param {THREE.Vector3[]} points
8
- * @param {number} point_count
7
+ * @param {Path} path
9
8
  * @param {TubePathStyle} style
9
+ * @param {number} segment_start
10
+ * @param {number} segment_end
10
11
  * @return {THREE.BufferGeometry}
11
12
  */
12
- export function build_geometry_catmullrom(points, point_count, style) {
13
+ export function build_geometry_catmullrom(path, style, segment_start, segment_end) {
13
14
 
14
- const path = new CatmullRomCurve3(points, false, "centripetal");
15
-
16
- const sample_points = [];
15
+ const point_count = path.getPointCount();
17
16
 
18
17
  // resample curve
19
18
  const total_points = Math.ceil(point_count * style.resolution);
20
19
 
21
- for (let i = 0; i < total_points; i++) {
20
+ const path_length = path.length;
21
+
22
+ const point = new Vector3();
23
+
24
+ const points_f32 = [];
25
+
26
+
27
+ let added_points = 0;
28
+ let i;
29
+
30
+ // initial segment
31
+ path.sample_catmull_rom(point, segment_start * path_length);
32
+
33
+ point.writeToArray(points_f32, added_points * 3);
34
+
35
+ added_points++;
36
+
37
+ const max_i = segment_end * (total_points - 1);
38
+ for (i = Math.ceil(segment_start * total_points + 0.0001); i < max_i; i++) {
22
39
  const t = i / (total_points - 1);
23
40
 
24
- const point = path.getPoint(t);
41
+ path.sample_catmull_rom(point, path_length * t);
42
+
43
+ point.writeToArray(points_f32, added_points * 3);
44
+
45
+ added_points++
46
+ }
47
+
48
+ if (((i - 1) / (total_points - 1)) - segment_end > 0.0001) {
49
+ // end
50
+
51
+ path.sample_catmull_rom(point, segment_end * path_length);
52
+
53
+ point.writeToArray(points_f32, added_points * 3);
25
54
 
26
- sample_points.push(point);
55
+ added_points++;
27
56
  }
28
57
 
29
- const frames = computeFrenetFrames(sample_points, false);
58
+ const frames = computeFrenetFrames(points_f32, false);
30
59
 
31
- return makeTubeGeometry(sample_points, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
60
+ return makeTubeGeometry(points_f32, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
32
61
  }
@@ -1,14 +1,54 @@
1
1
  import { computeFrenetFrames } from "./computeFrenetFrames.js";
2
2
  import { makeTubeGeometry } from "./makeTubeGeometry.js";
3
+ import Vector3 from "../../../../../../core/geom/Vector3.js";
4
+
5
+
6
+ const scratch_array_0 = [];
7
+ const scratch_array_1 = [];
8
+
9
+ const scratch_v3 = new Vector3();
3
10
 
4
11
  /**
5
12
  *
6
- * @param {THREE.Vector3[]} points
7
- * @param {number} point_count
13
+ * @param {Path} path
8
14
  * @param {TubePathStyle} style
15
+ * @param {number} segment_start
16
+ * @param {number} segment_end
9
17
  * @return {THREE.BufferGeometry}
10
18
  */
11
- export function build_geometry_linear(points, point_count, style) {
19
+ export function build_geometry_linear(path, style, segment_start, segment_end) {
20
+ const points = [];
21
+
22
+ const path_length = path.length;
23
+
24
+ path.find_index_and_normalized_distance(scratch_array_0, segment_start * path_length);
25
+ path.find_index_and_normalized_distance(scratch_array_1, segment_end * path_length);
26
+
27
+ let points_added = 0;
28
+
29
+ if (scratch_array_0[1] !== 0) {
30
+ path.sample_linear(scratch_v3, segment_start * path_length);
31
+
32
+ scratch_v3.writeToArray(points, points_added * 3);
33
+ points_added++;
34
+
35
+ // offset first sampled point, we're already past it
36
+ scratch_array_0[0]++;
37
+ }
38
+
39
+ const max_i = scratch_array_1[0];
40
+ for (let i = scratch_array_0[0]; i <= max_i; i++) {
41
+ path.readPositionToArray(i, points, points_added * 3);
42
+ points_added++;
43
+ }
44
+
45
+ if (scratch_array_1[1] !== 0) {
46
+ //add end
47
+ path.sample_linear(scratch_v3, segment_end * path_length);
48
+
49
+ scratch_v3.writeToArray(points, points_added * 3);
50
+ points_added++;
51
+ }
12
52
 
13
53
  const frames = computeFrenetFrames(points, false);
14
54
 
@@ -2,15 +2,17 @@ import { Vector3 } from "three";
2
2
  import { Matrix4 } from "three/src/math/Matrix4.js";
3
3
  import { max2 } from "../../../../../../core/math/max2.js";
4
4
  import { min2 } from "../../../../../../core/math/min2.js";
5
- import * as MathUtils from "three/src/math/MathUtils.js";
5
+ import { clamp } from "../../../../../../core/math/clamp.js";
6
+ import { vec3 } from "gl-matrix";
7
+ import { array_copy } from "../../../../../../core/collection/array/copyArray.js";
6
8
 
7
9
  /**
8
10
  * @see https://github.com/mrdoob/three.js/blob/c12c9a166a1369cdd58622fff2aff7e3a84305d7/src/extras/core/Curve.js#L260
9
- * @param {Vector3[]} points
11
+ * @param {Float32Array|number[]} points
10
12
  * @param {boolean} [closed]
11
13
  */
12
14
  export function computeFrenetFrames(points, closed = false) {
13
- // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
15
+ // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
14
16
 
15
17
  const normal = new Vector3();
16
18
 
@@ -21,10 +23,15 @@ export function computeFrenetFrames(points, closed = false) {
21
23
  const vec = new Vector3();
22
24
  const mat = new Matrix4();
23
25
 
24
- const segments = points.length - 1;
26
+ const point_count = points.length / 3;
27
+ const segments = point_count - 1;
25
28
 
26
- const v3_0 = new Vector3();
27
- const v3_1 = new Vector3();
29
+ const v3_0 = [];
30
+ const v3_1 = [];
31
+
32
+ const p0_a = [];
33
+ const pR_a = [];
34
+ const p1_a = [];
28
35
 
29
36
  /**
30
37
  *
@@ -36,18 +43,23 @@ export function computeFrenetFrames(points, closed = false) {
36
43
  const i0 = max2(i - 1, 0);
37
44
  const i1 = min2(i + 1, segments);
38
45
 
39
- const p0 = points[i0];
40
- const pR = points[i];
41
- const p1 = points[i1];
46
+ array_copy(points, i0 * 3, p0_a, 0, 3);
47
+ array_copy(points, i * 3, pR_a, 0, 3);
48
+ array_copy(points, i1 * 3, p1_a, 0, 3);
42
49
 
43
50
  // get points along the lines close to the reference
44
- v3_0.subVectors(p0, pR);
45
- v3_0.normalize();
46
51
 
47
- v3_1.subVectors(p1, pR);
48
- v3_1.normalize();
52
+ vec3.sub(v3_0, p0_a, pR_a);
53
+ vec3.normalize(v3_0, v3_0);
54
+
55
+ vec3.sub(v3_1, p1_a, pR_a);
56
+ vec3.normalize(v3_1, v3_1);
49
57
 
50
- destination.subVectors(v3_1, v3_0);
58
+ destination.set(
59
+ v3_1[0] - v3_0[0],
60
+ v3_1[1] - v3_0[1],
61
+ v3_1[2] - v3_0[2],
62
+ );
51
63
  destination.normalize();
52
64
  }
53
65
 
@@ -112,7 +124,7 @@ export function computeFrenetFrames(points, closed = false) {
112
124
 
113
125
  vec.normalize();
114
126
 
115
- const theta = Math.acos(MathUtils.clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors
127
+ const theta = Math.acos(clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors
116
128
 
117
129
  normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));
118
130
 
@@ -126,7 +138,7 @@ export function computeFrenetFrames(points, closed = false) {
126
138
 
127
139
  if (closed === true) {
128
140
 
129
- let theta = Math.acos(MathUtils.clamp(normals[0].dot(normals[segments]), -1, 1));
141
+ let theta = Math.acos(clamp(normals[0].dot(normals[segments]), -1, 1));
130
142
  theta /= segments;
131
143
 
132
144
  if (tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0) {
@@ -2,7 +2,7 @@ import { BufferGeometry, Float32BufferAttribute, Vector3 } from "three";
2
2
 
3
3
  /**
4
4
  * @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
5
- * @param {Vector3[]} points
5
+ * @param {Float32Array|number[]} points
6
6
  * @param {Vector3[]} _normals
7
7
  * @param {Vector3[]} binormals
8
8
  * @param {number} radius
@@ -24,7 +24,8 @@ export function makeTubeGeometry(points, _normals, binormals, radius, radialSegm
24
24
  const uvs = [];
25
25
  const indices = [];
26
26
 
27
- const tubularSegments = points.length - 1;
27
+ const point_count = points.length / 3;
28
+ const tubularSegments = point_count - 1;
28
29
 
29
30
  // create buffer data
30
31
 
@@ -69,7 +70,11 @@ export function makeTubeGeometry(points, _normals, binormals, radius, radialSegm
69
70
 
70
71
  // we use getPointAt to sample evenly distributed points from the given path
71
72
 
72
- const P = points[i];
73
+ const i3 = i * 3;
74
+
75
+ const Px = points[i3];
76
+ const Py = points[i3 + 1];
77
+ const Pz = points[i3 + 2];
73
78
 
74
79
  // retrieve corresponding normal and binormal
75
80
 
@@ -97,9 +102,9 @@ export function makeTubeGeometry(points, _normals, binormals, radius, radialSegm
97
102
 
98
103
  // vertex
99
104
 
100
- vertex.x = P.x + radius * normal.x;
101
- vertex.y = P.y + radius * normal.y;
102
- vertex.z = P.z + radius * normal.z;
105
+ vertex.x = Px + radius * normal.x;
106
+ vertex.y = Py + radius * normal.y;
107
+ vertex.z = Pz + radius * normal.z;
103
108
 
104
109
  vertices.push(vertex.x, vertex.y, vertex.z);
105
110
 
@@ -436,7 +436,7 @@ class Path {
436
436
  */
437
437
  sample_linear(result, offset) {
438
438
 
439
- const found = this.__find_index_and_normalized_distance(scratch_array_0, offset);
439
+ const found = this.find_index_and_normalized_distance(scratch_array_0, offset);
440
440
 
441
441
  if (!found) {
442
442
  return false;
@@ -466,9 +466,8 @@ class Path {
466
466
  * @param {number[]} result
467
467
  * @param {number} absolute_distance absolute distance along the path
468
468
  * @returns {boolean}
469
- * @private
470
469
  */
471
- __find_index_and_normalized_distance(result, absolute_distance) {
470
+ find_index_and_normalized_distance(result, absolute_distance) {
472
471
  const pointCount = this.getPointCount();
473
472
 
474
473
  if (pointCount === 0) {
@@ -495,10 +494,10 @@ class Path {
495
494
  // we found v1
496
495
  const local_offset = absolute_distance - cursor;
497
496
 
498
- const local_normalize_offset = local_offset / distance;
497
+ const local_normalized_offset = local_offset / distance;
499
498
 
500
499
  result[0] = i - 1;
501
- result[1] = local_normalize_offset;
500
+ result[1] = local_normalized_offset;
502
501
  return true;
503
502
  }
504
503
 
@@ -525,7 +524,7 @@ class Path {
525
524
  * @returns {boolean}
526
525
  */
527
526
  sample_catmull_rom(result, offset) {
528
- const found = this.__find_index_and_normalized_distance(scratch_array_0, offset);
527
+ const found = this.find_index_and_normalized_distance(scratch_array_0, offset);
529
528
 
530
529
  if (!found) {
531
530
  return false;
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.35.12",
8
+ "version": "2.35.13",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",