@woosh/meep-engine 2.35.12 → 2.35.15

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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ *
3
+ * @param {number[]|ArrayLike<number>|Float32Array} output 6 component vector, [origin_x, origin_y, origin_z, direction_x, direction_y, direction_z]
4
+ * @param {number[]|ArrayLike<number>|Float32Array} input 6 component vector, [origin_x, origin_y, origin_z, direction_x, direction_y, direction_z]
5
+ * @param {number[]|ArrayLike<number>|Float32Array} m4 4x4 matrix
6
+ */
7
+ export function ray3_array_apply_matrix4(output, input, m4) {
8
+ const origin_x = input[0];
9
+ const origin_y = input[1];
10
+ const origin_z = input[2];
11
+
12
+ const direction_x = input[3];
13
+ const direction_y = input[4];
14
+ const direction_z = input[5];
15
+
16
+ // transform ray to local space (inlined for speed)
17
+ const inv_w = 1 / (m4[3] * origin_x + m4[7] * origin_y + m4[11] * origin_z + m4[15]);
18
+
19
+ const out_origin_x = (m4[0] * origin_x + m4[4] * origin_y + m4[8] * origin_z + m4[12]) * inv_w;
20
+ const out_origin_y = (m4[1] * origin_x + m4[5] * origin_y + m4[9] * origin_z + m4[13]) * inv_w;
21
+ const out_origin_z = (m4[2] * origin_x + m4[6] * origin_y + m4[10] * origin_z + m4[14]) * inv_w;
22
+
23
+ const out_direction_x = m4[0] * direction_x + m4[4] * direction_y + m4[8] * direction_z;
24
+ const out_direction_y = m4[1] * direction_x + m4[5] * direction_y + m4[9] * direction_z;
25
+ const out_direction_z = m4[2] * direction_x + m4[6] * direction_y + m4[10] * direction_z;
26
+
27
+ // normalize direction
28
+ const out_direction_magnitude = Math.hypot(out_direction_x, out_direction_y, out_direction_z);
29
+ const out_direction_magnitude_inverse = 1 / out_direction_magnitude;
30
+
31
+ const out_direction_normalized_x = out_direction_x * out_direction_magnitude_inverse;
32
+ const out_direction_normalized_y = out_direction_y * out_direction_magnitude_inverse;
33
+ const out_direction_normalized_z = out_direction_z * out_direction_magnitude_inverse;
34
+
35
+ output[0] = out_origin_x;
36
+ output[1] = out_origin_y;
37
+ output[2] = out_origin_z;
38
+
39
+ output[3] = out_direction_normalized_x;
40
+ output[4] = out_direction_normalized_y;
41
+ output[5] = out_direction_normalized_z;
42
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ *
3
+ * @param {number[]|ArrayLike<number>|Float32Array} output
4
+ * @param {number} origin_x
5
+ * @param {number} origin_y
6
+ * @param {number} origin_z
7
+ * @param {number} direction_x
8
+ * @param {number} direction_y
9
+ * @param {number} direction_z
10
+ */
11
+ export function ray3_array_compose(output, origin_x, origin_y, origin_z, direction_x, direction_y, direction_z) {
12
+ output[0] = origin_x;
13
+ output[1] = origin_y;
14
+ output[2] = origin_z;
15
+
16
+ output[3] = direction_x;
17
+ output[4] = direction_y;
18
+ output[5] = direction_z;
19
+ }
@@ -0,0 +1,14 @@
1
+ import { ray3_array_compose } from "./ray3_array_compose.js";
2
+
3
+ test('correctness', () => {
4
+ const result = [];
5
+ ray3_array_compose(result, 1, -3, 7, -11, 13, -17);
6
+
7
+ expect(result[0]).toBe(1);
8
+ expect(result[1]).toBe(-3);
9
+ expect(result[2]).toBe(7);
10
+
11
+ expect(result[3]).toBe(-11);
12
+ expect(result[4]).toBe(13);
13
+ expect(result[5]).toBe(-17);
14
+ });
@@ -10,6 +10,8 @@ import { aabb3_intersects_ray } from "../../../../core/bvh2/aabb3/aabb3_intersec
10
10
  import { ShadedGeometryRendererContext } from "./render/ShadedGeometryRendererContext.js";
11
11
  import { BinaryNode } from "../../../../core/bvh2/BinaryNode.js";
12
12
  import { assert } from "../../../../core/assert.js";
13
+ import { ray3_array_compose } from "../../../../core/geom/3d/ray/ray3_array_compose.js";
14
+ import { ray3_array_apply_matrix4 } from "../../../../core/geom/3d/ray/ray3_array_apply_matrix4.js";
13
15
 
14
16
  /**
15
17
  *
@@ -35,6 +37,9 @@ const scratch_point = new SurfacePoint3();
35
37
  */
36
38
  const scratch_m4 = new Float32Array(16);
37
39
 
40
+ const scratch_ray_0 = new Float32Array(6);
41
+ const scratch_ray_1 = new Float32Array(6);
42
+
38
43
  export class ShadedGeometrySystem extends System {
39
44
  /**
40
45
  *
@@ -74,6 +79,13 @@ export class ShadedGeometrySystem extends System {
74
79
  * @private
75
80
  */
76
81
  this.__adapter_suppliers = new Map();
82
+
83
+ /**
84
+ * Keeps count of usage for each geometry by ID
85
+ * @type {Map<number, number>}
86
+ * @private
87
+ */
88
+ this.__geometry_usage_counters = new Map();
77
89
  }
78
90
 
79
91
  /**
@@ -166,7 +178,7 @@ export class ShadedGeometrySystem extends System {
166
178
  this.__render_layer.buildVisibleSet = ((destination, destination_offset, view) => {
167
179
  const ctx = this.__view_contexts.get(view);
168
180
 
169
- return ctx.collect(destination, destination_offset,graphics.renderer, view, this.__bvh);
181
+ return ctx.collect(destination, destination_offset, graphics.renderer, view, this.__bvh);
170
182
  });
171
183
 
172
184
  readyCallback();
@@ -210,6 +222,13 @@ export class ShadedGeometrySystem extends System {
210
222
  sg.__entity = entity;
211
223
 
212
224
  this.__bvh.insertNode(leaf_node);
225
+
226
+ // update usage count
227
+ const geometry_id = sg.geometry.id;
228
+ const count_existing = this.__geometry_usage_counters.get(geometry_id);
229
+ const count_new = count_existing !== undefined ? count_existing + 1 : 1;
230
+
231
+ this.__geometry_usage_counters.set(geometry_id, count_new);
213
232
  }
214
233
 
215
234
  /**
@@ -224,6 +243,21 @@ export class ShadedGeometrySystem extends System {
224
243
  t.scale.onChanged.remove(sg.updateTransform, sg);
225
244
 
226
245
  sg.bvh.disconnect();
246
+
247
+ const geometry_id = sg.geometry.id;
248
+ const count_existing = this.__geometry_usage_counters.get(geometry_id);
249
+
250
+ if (count_existing === 1) {
251
+
252
+ // last instance
253
+ this.__geometry_usage_counters.delete(geometry_id);
254
+
255
+ // release GPU memory
256
+ sg.geometry.dispose();
257
+
258
+ } else {
259
+ this.__geometry_usage_counters.set(geometry_id, count_existing - 1);
260
+ }
227
261
  }
228
262
 
229
263
  /**
@@ -303,29 +337,19 @@ export class ShadedGeometrySystem extends System {
303
337
  // get transform in local space
304
338
  mat4.invert(scratch_m4, m4);
305
339
 
306
- // transform ray to local space
307
- const inv_w = 1 / (scratch_m4[3] * origin_x + scratch_m4[7] * origin_y + scratch_m4[11] * origin_z + scratch_m4[15]);
308
-
309
- const local_origin_x = (scratch_m4[0] * origin_x + scratch_m4[4] * origin_y + scratch_m4[8] * origin_z + scratch_m4[12]) * inv_w;
310
- const local_origin_y = (scratch_m4[1] * origin_x + scratch_m4[5] * origin_y + scratch_m4[9] * origin_z + scratch_m4[13]) * inv_w;
311
- const local_origin_z = (scratch_m4[2] * origin_x + scratch_m4[6] * origin_y + scratch_m4[10] * origin_z + scratch_m4[14]) * inv_w;
312
-
313
- const local_direction_x = scratch_m4[0] * direction_x + scratch_m4[4] * direction_y + scratch_m4[8] * direction_z;
314
- const local_direction_y = scratch_m4[1] * direction_x + scratch_m4[5] * direction_y + scratch_m4[9] * direction_z;
315
- const local_direction_z = scratch_m4[2] * direction_x + scratch_m4[6] * direction_y + scratch_m4[10] * direction_z;
316
-
317
- const local_direction_magnitude = Math.hypot(local_direction_x, local_direction_y, local_direction_z);
318
- const local_direction_magnitude_inverse = 1 / local_direction_magnitude;
340
+ ray3_array_compose(
341
+ scratch_ray_0,
342
+ origin_x, origin_y, origin_z,
343
+ direction_x, direction_y, direction_z
344
+ );
319
345
 
320
- const local_direction_normalized_x = local_direction_x * local_direction_magnitude_inverse;
321
- const local_direction_normalized_y = local_direction_y * local_direction_magnitude_inverse;
322
- const local_direction_normalized_z = local_direction_z * local_direction_magnitude_inverse;
346
+ // transform ray to local space
347
+ ray3_array_apply_matrix4(scratch_ray_0, scratch_ray_0, scratch_m4);
323
348
 
324
- const geometry_hit_found = GeometrySpatialQueryAccelerator.INSTANCE.queryRaycastNearest_primitive(
349
+ const geometry_hit_found = GeometrySpatialQueryAccelerator.INSTANCE.queryRaycastNearest_array(
325
350
  scratch_point,
326
351
  sg.geometry,
327
- local_origin_x, local_origin_y, local_origin_z,
328
- local_direction_normalized_x, local_direction_normalized_y, local_direction_normalized_z
352
+ scratch_ray_0
329
353
  );
330
354
 
331
355
  if (!geometry_hit_found) {
@@ -344,13 +368,12 @@ export class ShadedGeometrySystem extends System {
344
368
  origin_x, origin_y, origin_z
345
369
  );
346
370
 
347
- if (distance_sqr >= best_distance) {
348
- // further than best found
349
- continue;
371
+ if (distance_sqr < best_distance) {
372
+ contact.copy(scratch_point);
373
+ best_result = sg;
374
+ best_distance = distance_sqr;
350
375
  }
351
376
 
352
- contact.copy(scratch_point);
353
- best_result = sg;
354
377
  }
355
378
  }
356
379
 
@@ -0,0 +1,212 @@
1
+ import { EngineHarness } from "../../../../EngineHarness.js";
2
+ import { ShadedGeometrySystem } from "../ShadedGeometrySystem.js";
3
+ import EntityBuilder from "../../../../ecs/EntityBuilder.js";
4
+ import { Transform } from "../../../../ecs/transform/Transform.js";
5
+ import { ShadedGeometry } from "../ShadedGeometry.js";
6
+ import {
7
+ BoxBufferGeometry,
8
+ MeshBasicMaterial,
9
+ MeshStandardMaterial,
10
+ OctahedronGeometry,
11
+ SphereBufferGeometry
12
+ } from "three";
13
+ import { randomFloatBetween } from "../../../../../core/math/random/randomFloatBetween.js";
14
+ import { seededRandom } from "../../../../../core/math/random/seededRandom.js";
15
+ import ViewportPosition from "../../../../ecs/gui/position/ViewportPosition.js";
16
+ import GUIElement from "../../../../ecs/gui/GUIElement.js";
17
+ import ButtonView from "../../../../../view/elements/button/ButtonView.js";
18
+ import { downloadAsFile } from "../../../../../core/binary/ByteArrayTools.js";
19
+ import { convert_bvh_to_dot_format_string } from "../../../../../core/bvh2/visual/convert_bvh_to_dot_format_string.js";
20
+ import ViewportPositionSystem from "../../../../ecs/gui/position/ViewportPositionSystem.js";
21
+ import GUIElementSystem from "../../../../ecs/gui/GUIElementSystem.js";
22
+ import Vector3 from "../../../../../core/geom/Vector3.js";
23
+ import Quaternion from "../../../../../core/geom/Quaternion.js";
24
+ import { ShadedGeometryFlags } from "../ShadedGeometryFlags.js";
25
+ import { randomFromArray } from "../../../../../core/math/random/randomFromArray.js";
26
+ import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
27
+ import { OrbitingBehavior } from "../../../../../../model/game/util/behavior/OrbitingBehavior.js";
28
+ import { BehaviorSystem } from "../../../../intelligence/behavior/ecs/BehaviorSystem.js";
29
+ import { MicronShadedGeometryRenderAdapter } from "../../../micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js";
30
+ import { MicronRenderContext } from "../../../micron/render/v1/MicronRenderContext.js";
31
+ import { randomIntegerBetween } from "../../../../../core/math/random/randomIntegerBetween.js";
32
+ import Vector2 from "../../../../../core/geom/Vector2.js";
33
+ import { SurfacePoint3 } from "../../../../../core/geom/3d/SurfacePoint3.js";
34
+
35
+ const eh = new EngineHarness();
36
+
37
+ const DRAW_METHOD_PLAIN = 0;
38
+ const DRAW_METHOD_INSTANCED = 1;
39
+
40
+ function makeMovingShadedGeometry() {
41
+ const b = new OrbitingBehavior();
42
+ b.offset.set(4, 1, 4);
43
+ b.radius = 2;
44
+ b.rate = 1;
45
+
46
+ const sg = ShadedGeometry.from(new OctahedronGeometry(0.5, 5), new MeshStandardMaterial({ color: 0xFF0000 }));
47
+
48
+ sg.draw_method = DRAW_METHOD_INSTANCED;
49
+
50
+ return new EntityBuilder()
51
+ .add(new Transform())
52
+ .add(sg)
53
+ .add(BehaviorComponent.fromOne(b));
54
+ }
55
+
56
+ /**
57
+ *
58
+ * @param {Engine} engine
59
+ */
60
+ async function main(engine) {
61
+
62
+ const em = engine.entityManager;
63
+
64
+ const sgs = new ShadedGeometrySystem(engine);
65
+
66
+ const mrc = new MicronRenderContext();
67
+ sgs.register_render_adapter(2, () => {
68
+ const adapter = new MicronShadedGeometryRenderAdapter();
69
+ adapter.ctx = mrc;
70
+
71
+ return adapter;
72
+ });
73
+
74
+ await em.addSystem(sgs);
75
+ await em.addSystem(new BehaviorSystem(engine));
76
+
77
+ EngineHarness.buildBasics({
78
+ engine,
79
+ yaw: -0.8,
80
+ pitch: 1,
81
+ distance: 46,
82
+ focus: new Vector3(10, 0, 10)
83
+ });
84
+
85
+
86
+ const ecd = em.dataset;
87
+
88
+ const material = new MeshStandardMaterial({
89
+ color: 0xFFFFFF,
90
+ transparent: true,
91
+ opacity: 0.5
92
+ });
93
+
94
+ // material.visible = false;
95
+
96
+ window.material = material;
97
+
98
+ const random = seededRandom(0);
99
+
100
+ const INSTANCE_COUNT = 100;
101
+
102
+ const geometries = [];
103
+
104
+ for (let i = 0; i < 1; i++) {
105
+ geometries.push(new BoxBufferGeometry());
106
+ }
107
+
108
+ for (let i = 0; i < INSTANCE_COUNT; i++) {
109
+ const geometry = randomFromArray(random, geometries);
110
+ const sg = ShadedGeometry.from(geometry, material);
111
+
112
+ // sg.draw_method = DRAW_METHOD_PLAIN;
113
+ // sg.draw_method = DRAW_METHOD_INSTANCED;
114
+ // sg.draw_method = 2;
115
+ sg.draw_method = randomIntegerBetween(random, 0, 2);
116
+ // sg.draw_method = random() > 0.01 ? DRAW_METHOD_INSTANCED : DRAW_METHOD_PLAIN;
117
+ sg.clearFlag(ShadedGeometryFlags.CastShadow | ShadedGeometryFlags.ReceiveShadow);
118
+
119
+ new EntityBuilder()
120
+ .add(Transform.fromJSON({
121
+ position: {
122
+ x: randomFloatBetween(random, 0, 20),
123
+ y: randomFloatBetween(random, 0, 10),
124
+ z: randomFloatBetween(random, 0, 20)
125
+ },
126
+ rotation: Quaternion.random(random),
127
+ scale: 1
128
+ }))
129
+ .add(sg)
130
+ .build(ecd);
131
+ }
132
+
133
+ // makeMovingShadedGeometry().build(ecd);
134
+
135
+ debug_button(engine, () => {
136
+ const dot = convert_bvh_to_dot_format_string(engine.graphics.layers.getLayerByName('shaded-geometry').bvh);
137
+ downloadAsFile(dot, 'bvh.dot', 'text');
138
+
139
+ }, 'save dot file');
140
+
141
+ const raycast_marker = new EntityBuilder()
142
+ .add(ShadedGeometry.from(new SphereBufferGeometry(1), new MeshBasicMaterial({
143
+ color: 0xFF0000
144
+ })))
145
+ .add(Transform.fromJSON({
146
+ scale: 0.07
147
+ }));
148
+
149
+ raycast_marker.build(engine.entityManager.dataset);
150
+
151
+ engine.devices.pointer.position.onChanged.add((p_x, p_y) => {
152
+ const p = new Vector2(p_x, p_y);
153
+ const ndc = new Vector2();
154
+ const ray_origin = new Vector3();
155
+ const ray_direction = new Vector3();
156
+
157
+ engine.graphics.normalizeViewportPoint(p, ndc);
158
+ engine.graphics.viewportProjectionRay(ndc.x, ndc.y, ray_origin, ray_direction);
159
+
160
+ /**
161
+ *
162
+ * @type {ShadedGeometrySystem}
163
+ */
164
+ const sgm = engine.entityManager.getSystem(ShadedGeometrySystem);
165
+
166
+ const sp = new SurfacePoint3();
167
+
168
+ sgm.raycastNearest(sp, ray_origin.x, ray_origin.y, ray_origin.z, ray_direction.x, ray_direction.y, ray_direction.z, ((entity, mesh) => {
169
+ return (entity !== raycast_marker.entity);
170
+ }));
171
+
172
+ const t = raycast_marker.getComponent(Transform);
173
+
174
+ t.position.copy(sp.position);
175
+ });
176
+ }
177
+
178
+
179
+ const entityManager = engine.entityManager;
180
+
181
+ /**
182
+ *
183
+ * @param {Engine} engine
184
+ * @param {function} action
185
+ * @param {string} [name]
186
+ * @param {EntityComponentDataset} ecd
187
+ */
188
+ function debug_button(engine, action, name = "Button", ecd = entityManager.dataset) {
189
+ if (entityManager.getSystem(ViewportPositionSystem) === null) {
190
+ entityManager.addSystem(new ViewportPositionSystem(engine.graphics.viewport.size));
191
+ }
192
+
193
+ if (entityManager.getSystem(GUIElementSystem) === null) {
194
+ entityManager.addSystem(new GUIElementSystem(engine.gui.view, engine));
195
+ }
196
+
197
+ new EntityBuilder()
198
+ .add(ViewportPosition.fromJSON({}))
199
+ .add(GUIElement.fromView(new ButtonView({
200
+ action,
201
+ name,
202
+ css: {
203
+ background: 'rgba(255,255,255,0.8)',
204
+ border: '1px solid black',
205
+ borderRadius: '4px',
206
+ padding: '2px',
207
+ }
208
+ })))
209
+ .build(ecd);
210
+ }
211
+
212
+ eh.initialize().then(main);
@@ -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) {
@@ -122,17 +130,30 @@ export class TubePathBuilder {
122
130
  material_def.opacity = style.opacity;
123
131
  }
124
132
 
125
- const entityBuilder = new EntityBuilder();
133
+ const segment_count = style.path_mask.length / 2;
134
+
135
+ for (let i = 0; i < segment_count; i++) {
136
+ const i2 = i * 2;
137
+
138
+ const segment_start = style.path_mask[i2];
139
+ const segment_end = style.path_mask[i2 + 1];
140
+
141
+ const geometry = make_geometry_segment(path_component, style, segment_start, segment_end);
126
142
 
127
- entityBuilder.add(new Transform());
143
+ const entityBuilder = new EntityBuilder();
128
144
 
129
- const shaded_geometry = ShadedGeometry.from(geometry, material_def);
145
+ entityBuilder.add(new Transform());
130
146
 
131
- shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
132
- shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
147
+ const shaded_geometry = ShadedGeometry.from(geometry, material_def);
133
148
 
134
- entityBuilder.add(shaded_geometry);
149
+ shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
150
+ shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
151
+
152
+ entityBuilder.add(shaded_geometry);
153
+
154
+ destination.push(entityBuilder);
155
+
156
+ }
135
157
 
136
- destination.push(entityBuilder);
137
158
  }
138
159
  }
@@ -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
 
@@ -56,6 +56,22 @@ export class GeometrySpatialQueryAccelerator {
56
56
  return this.__visitor_clip.getContainment();
57
57
  }
58
58
 
59
+ /**
60
+ *
61
+ * @param {SurfacePoint3} destination
62
+ * @param {THREE.BufferGeometry} geometry
63
+ * @param {number[]|ArrayLike<number>|Float32Array} ray 6 component vector, [origin_x, origin_y, origin_z, direction_x, direction_y, direction_z]
64
+ * @return {boolean}
65
+ */
66
+ queryRaycastNearest_array(destination, geometry, ray) {
67
+ return this.queryRaycastNearest_primitive(
68
+ destination,
69
+ geometry,
70
+ ray[0], ray[1], ray[2],
71
+ ray[3], ray[4], ray[5]
72
+ );
73
+ }
74
+
59
75
  /**
60
76
  *
61
77
  * @param {SurfacePoint3} destination
@@ -1,3 +1,6 @@
1
+ import { compose_matrix4_array } from "./composeMatrix4.js";
2
+ import Vector3 from "../../../core/geom/Vector3.js";
3
+
1
4
  /**
2
5
  *
3
6
  * @param {Matrix4} result
@@ -7,31 +10,5 @@
7
10
  export function composeMatrix4RotationScale(result, rotation, scale) {
8
11
  const te = result.elements;
9
12
 
10
- const x = rotation.x, y = rotation.y, z = rotation.z, w = rotation.w;
11
- const x2 = x + x, y2 = y + y, z2 = z + z;
12
- const xx = x * x2, xy = x * y2, xz = x * z2;
13
- const yy = y * y2, yz = y * z2, zz = z * z2;
14
- const wx = w * x2, wy = w * y2, wz = w * z2;
15
-
16
- const sx = scale.x, sy = scale.y, sz = scale.z;
17
-
18
- te[0] = (1 - (yy + zz)) * sx;
19
- te[1] = (xy + wz) * sx;
20
- te[2] = (xz - wy) * sx;
21
- te[3] = 0;
22
-
23
- te[4] = (xy - wz) * sy;
24
- te[5] = (1 - (xx + zz)) * sy;
25
- te[6] = (yz + wx) * sy;
26
- te[7] = 0;
27
-
28
- te[8] = (xz + wy) * sz;
29
- te[9] = (yz - wx) * sz;
30
- te[10] = (1 - (xx + yy)) * sz;
31
- te[11] = 0;
32
-
33
- te[12] = 0;
34
- te[13] = 0;
35
- te[14] = 0;
36
- te[15] = 1;
13
+ compose_matrix4_array(te, Vector3.zero, rotation, scale);
37
14
  }
@@ -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.15",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",