@woosh/meep-engine 2.37.13 → 2.37.16

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.
@@ -8,12 +8,26 @@ export class AABB3 {
8
8
  y1: number
9
9
  z1: number
10
10
 
11
+ constructor(x0?: number, y0?: number, z0?: number, x1?: number, y1?: number, z1?: number)
12
+
11
13
  setBounds(x0: number, y0: number, z0: number, x1: number, y1: number, z1: number): void
12
14
 
13
15
  getCenter(target: Vector3): void
14
16
 
17
+ getCenterX(): number
18
+
19
+ getCenterY(): number
20
+
21
+ getCenterZ(): number
22
+
15
23
  getExtents(target: Vector3): void
16
24
 
25
+ getExtentsX(): number
26
+
27
+ getExtentsY(): number
28
+
29
+ getExtentsZ(): number
30
+
17
31
  expandToFit(box: AABB3): void
18
32
 
19
33
  _expandToFitPoint(x: number, y: number, z: number): boolean
@@ -23,4 +37,10 @@ export class AABB3 {
23
37
  setInfiniteBounds(): void
24
38
 
25
39
  distanceToBox(other: AABB3): number
40
+
41
+ applyMatrix4(matrix: ArrayLike<number>): void
42
+
43
+ equals(other: AABB3): boolean
44
+
45
+ copy(other: AABB3): void
26
46
  }
@@ -13,6 +13,7 @@ import { aabb3_intersects_line_segment } from "./aabb3_intersects_line_segment.j
13
13
  import { aabb3_intersects_ray } from "./aabb3_intersects_ray.js";
14
14
  import { aabb3_compute_surface_area } from "./aabb3_compute_surface_area.js";
15
15
  import { aabb3_intersects_frustum_array } from "./aabb3_intersects_frustum_array.js";
16
+ import { aabb3_matrix4_project } from "../../geom/3d/aabb/aabb3_matrix4_project.js";
16
17
 
17
18
 
18
19
  export class AABB3 {
@@ -504,17 +505,6 @@ export class AABB3 {
504
505
  return this.getExtentsZ() / 2;
505
506
  }
506
507
 
507
- /**
508
- * Get center position of the box
509
- * @param {Vector3} target where to write result
510
- */
511
- getCenter(target) {
512
- const x = (this.x0 + this.x1) / 2;
513
- const y = (this.y0 + this.y1) / 2;
514
- const z = (this.z0 + this.z1) / 2;
515
- target.set(x, y, z);
516
- }
517
-
518
508
  /**
519
509
  *
520
510
  * @param {Vector3} target
@@ -527,6 +517,42 @@ export class AABB3 {
527
517
  target.set(x, y, z);
528
518
  }
529
519
 
520
+ /**
521
+ *
522
+ * @returns {number}
523
+ */
524
+ getCenterX() {
525
+ return (this.x0 + this.x1) * 0.5;
526
+ }
527
+
528
+ /**
529
+ *
530
+ * @returns {number}
531
+ */
532
+ getCenterY() {
533
+ return (this.y0 + this.y1) * 0.5;
534
+ }
535
+
536
+ /**
537
+ *
538
+ * @returns {number}
539
+ */
540
+ getCenterZ() {
541
+ return (this.z0 + this.z1) * 0.5;
542
+ }
543
+
544
+ /**
545
+ * Get center position of the box
546
+ * @param {Vector3} target where to write result
547
+ */
548
+ getCenter(target) {
549
+ const x = this.getCenterX();
550
+ const y = this.getCenterY();
551
+ const z = this.getCenterZ();
552
+ target.set(x, y, z);
553
+ }
554
+
555
+
530
556
  /**
531
557
  * Accepts ray description, first set of coordinates is origin (oX,oY,oZ) and second is direction (dX,dY,dZ). Algorithm from GraphicsGems by Andrew Woo
532
558
  * @param oX
@@ -736,7 +762,7 @@ export class AABB3 {
736
762
  * @param {number[]|Float32Array|Float64Array} result
737
763
  * @param {number} offset
738
764
  */
739
- writeToArray(result, offset) {
765
+ writeToArray(result, offset = 0) {
740
766
  assert.isNonNegativeInteger(offset, 'offset');
741
767
 
742
768
  result[offset] = this.x0;
@@ -906,6 +932,21 @@ export class AABB3 {
906
932
  return aabb3_intersects_frustum_array(x0, y0, z0, x1, y1, z1, frustum);
907
933
  }
908
934
 
935
+ /**
936
+ *
937
+ * @param {number[]|ArrayLike<number>|Float32Array} matrix
938
+ */
939
+ applyMatrix4(matrix) {
940
+ const a = [];
941
+ const b = [];
942
+
943
+ this.writeToArray(a, 0);
944
+
945
+ aabb3_matrix4_project(b, a, matrix);
946
+
947
+ this.readFromArray(b, 0);
948
+ }
949
+
909
950
  /**
910
951
  *
911
952
  * @param {number} v
@@ -206,3 +206,22 @@ test('readFrom/writeTo Array consistency', () => {
206
206
  expect(s.y1).toEqual(5);
207
207
  expect(s.z1).toEqual(6);
208
208
  });
209
+
210
+
211
+ test("getExtentsX", () => {
212
+ expect(new AABB3(0, 0, 0, 0, 0, 0).getExtentsX()).toBe(0);
213
+ expect(new AABB3(0, 0, 0, 1, 0, 0).getExtentsX()).toBeCloseTo(1);
214
+ expect(new AABB3(-1, 0, 0, 1, 0, 0).getExtentsX()).toBeCloseTo(2);
215
+ });
216
+
217
+ test("getExtentsY", () => {
218
+ expect(new AABB3(0, 0, 0, 0, 0, 0).getExtentsY()).toBe(0);
219
+ expect(new AABB3(0, 0, 0, 0, 1, 0).getExtentsY()).toBeCloseTo(1);
220
+ expect(new AABB3(0, -1, 0, 0, 1, 0).getExtentsY()).toBeCloseTo(2);
221
+ });
222
+
223
+ test("getExtentsZ", () => {
224
+ expect(new AABB3(0, 0, 0, 0, 0, 0).getExtentsZ()).toBe(0);
225
+ expect(new AABB3(0, 0, 0, 0, 0, 1).getExtentsZ()).toBeCloseTo(1);
226
+ expect(new AABB3(0, 0, -1, 0, 0, 1).getExtentsZ()).toBeCloseTo(2);
227
+ });
@@ -35,11 +35,25 @@ const CAPACITY_GROW_MULTIPLIER = 1.2;
35
35
  const CAPACITY_GROW_MIN_STEP = 64;
36
36
 
37
37
  /**
38
+ * How many words are used for a single NODE in the tree
39
+ * One "word" is 4 bytes for the sake of alignment
38
40
  * @readonly
39
41
  * @type {number}
40
42
  */
41
43
  const ELEMENT_WORD_COUNT = 10;
42
44
 
45
+ /**
46
+ * How many nodes can be stored in the newly constructed tree before allocation needs to take place
47
+ * @readonly
48
+ * @type {number}
49
+ */
50
+ const INITIAL_CAPACITY = 128;
51
+
52
+ /**
53
+ * Bounding Volume Hierarchy implementation. Stores unsigned integer values at leaves, these are typically IDs or Index values.
54
+ * Highly optimized both in terms of memory usage and CPU. Most of the code inlined. No allocation are performed during usage (except for growing the tree capacity).
55
+ * RAM usage: 40 bytes per node. Compared with V8s per-object allocation size of 80 bytes (see https://blog.dashlane.com/how-is-data-stored-in-v8-js-engine-memory)
56
+ */
43
57
  export class ExplicitBinaryBoundingVolumeHierarchy {
44
58
  constructor() {
45
59
  /**
@@ -47,7 +61,7 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
47
61
  * @type {ArrayBuffer}
48
62
  * @private
49
63
  */
50
- this.__data_buffer = new ArrayBuffer(0);
64
+ this.__data_buffer = new ArrayBuffer(INITIAL_CAPACITY * ELEMENT_WORD_COUNT * 4);
51
65
 
52
66
  /**
53
67
  *
@@ -64,28 +78,34 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
64
78
  this.__data_uint32 = new Uint32Array(this.__data_buffer);
65
79
 
66
80
  /**
67
- *
81
+ * How many nodes are currently reserved, this will grow automatically through {@link #allocate_node} method usage
68
82
  * @type {number}
69
83
  * @private
70
84
  */
71
- this.__capacity = 0;
85
+ this.__capacity = INITIAL_CAPACITY;
72
86
 
73
87
  /**
74
- *
88
+ * Number of used nodes. These are either live nodes, or node sitting in the {@link #__free} pool
75
89
  * @type {number}
76
90
  * @private
77
91
  */
78
92
  this.__size = 0;
79
93
 
80
94
  /**
81
- *
95
+ * Indices of released nodes. Nodes are pulled from here first if available, before the whole tree gets resized
82
96
  * @type {number[]}
83
97
  * @private
84
98
  */
85
99
  this.__free = [];
100
+ /**
101
+ * Pointer into __free array that's used as a stack, so this pointer represents top of the stack
102
+ * @type {number}
103
+ * @private
104
+ */
105
+ this.__free_pointer = 0;
86
106
 
87
107
  /**
88
- *
108
+ * Root node of the hierarchy
89
109
  * @type {number}
90
110
  * @private
91
111
  */
@@ -134,12 +154,19 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
134
154
  * @returns {number}
135
155
  */
136
156
  allocate_node() {
137
- const free = this.__free;
138
-
139
157
  let result;
140
- if (free.length > 0) {
158
+ const free_stack_top = this.__free_pointer;
159
+
160
+ if (free_stack_top > 0) {
161
+
141
162
  // nodes in the free pool
142
- result = free.pop();
163
+
164
+ const free_index = free_stack_top - 1;
165
+
166
+ result = this.__free[free_index];
167
+
168
+ this.__free_pointer = free_index;
169
+
143
170
  } else {
144
171
 
145
172
  result = this.__size;
@@ -180,13 +207,14 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
180
207
  }
181
208
 
182
209
  /**
183
- * Release memory used by the node
210
+ * Release memory used by the node back into the pool
211
+ * NOTE: Make sure that the node is not "live" (not attached to the hierarchy), otherwise this operation may corrupt the tree
184
212
  * @param {number} id
185
213
  */
186
214
  release_node(id) {
187
215
  assert.isNonNegativeInteger(id, 'id');
188
216
  // no reset required as that's handled in the allocation method
189
- this.__free.push(id);
217
+ this.__free[this.__free_pointer++] = id;
190
218
  }
191
219
 
192
220
  /**
@@ -561,19 +589,21 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
561
589
 
562
590
  const uint32 = this.__data_uint32;
563
591
 
564
- while (index !== NULL_NODE) {
592
+ do {
565
593
  index = this.balance(index);
566
594
 
567
- const child1 = uint32[index * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
568
- const child2 = uint32[index * ELEMENT_WORD_COUNT + COLUMN_CHILD_2];
595
+ const address = index * ELEMENT_WORD_COUNT;
596
+
597
+ const child1 = uint32[address + COLUMN_CHILD_1];
598
+ const child2 = uint32[address + COLUMN_CHILD_2];
569
599
 
570
600
  assert.notEqual(child1, NULL_NODE, 'child1 is null');
571
601
  assert.notEqual(child2, NULL_NODE, 'child2 is null');
572
602
 
573
603
  this.node_set_combined_aabb(index, child1, child2);
574
604
 
575
- index = uint32[index * ELEMENT_WORD_COUNT + COLUMN_PARENT];
576
- }
605
+ index = uint32[address + COLUMN_PARENT];
606
+ } while (index !== NULL_NODE)
577
607
  }
578
608
 
579
609
  /**
@@ -895,4 +925,12 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
895
925
 
896
926
  return i - destination_offset;
897
927
  }
928
+
929
+ /**
930
+ * Sort live nodes in the traversal order.
931
+ * This makes most queries have linear access, resulting in near-optimal cache usage
932
+ */
933
+ sort_for_traversal_depth_first() {
934
+ throw new Error('Not Implemented');
935
+ }
898
936
  }
@@ -1,3 +1,5 @@
1
+ const hypot = Math.hypot;
2
+
1
3
  /**
2
4
  *
3
5
  * @param {number[]} m
@@ -10,17 +12,19 @@ export function decompose_matrix_4_array(m, position, rotation, scale) {
10
12
  const m12 = m[1];
11
13
  const m13 = m[2];
12
14
 
15
+ const scale_x = hypot(m11, m12, m13);
16
+
13
17
  const m21 = m[4];
14
18
  const m22 = m[5];
15
19
  const m23 = m[6];
16
20
 
21
+ const scale_y = hypot(m21, m22, m23);
22
+
17
23
  const m31 = m[8];
18
24
  const m32 = m[9];
19
25
  const m33 = m[10];
20
26
 
21
- const scale_x = Math.hypot(m11, m12, m13);
22
- const scale_y = Math.hypot(m21, m22, m23);
23
- const scale_z = Math.hypot(m31, m32, m33);
27
+ const scale_z = hypot(m31, m32, m33);
24
28
 
25
29
  // extract rotation
26
30
  const is1 = 1 / scale_x;
@@ -1047,7 +1047,9 @@ class Quaternion {
1047
1047
  }
1048
1048
 
1049
1049
  /**
1050
- *
1050
+ * This algorithm comes from "Quaternion Calculus and Fast Animation",
1051
+ * Ken Shoemake, 1987 SIGGRAPH course notes
1052
+ * @see https://gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Geometry/Quaternion.h#L813
1051
1053
  * @param {number} m11
1052
1054
  * @param {number} m12
1053
1055
  * @param {number} m13
@@ -1066,39 +1068,51 @@ class Quaternion {
1066
1068
 
1067
1069
  if (trace > 0) {
1068
1070
 
1069
- s = 0.5 / Math.sqrt(trace + 1.0);
1071
+ s = Math.sqrt(trace + 1.0);
1072
+
1073
+ w = 0.5 * s;
1074
+
1075
+ s = 0.5 / s;
1070
1076
 
1071
- w = 0.25 / s;
1072
1077
  x = (m32 - m23) * s;
1073
1078
  y = (m13 - m31) * s;
1074
1079
  z = (m21 - m12) * s;
1075
1080
 
1076
1081
  } else if (m11 > m22 && m11 > m33) {
1077
1082
 
1078
- s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
1083
+ s = Math.sqrt(1.0 + m11 - m22 - m33);
1084
+
1085
+ x = 0.5 * s;
1086
+
1087
+ s = 0.5 / s;
1079
1088
 
1080
- w = (m32 - m23) / s;
1081
- x = 0.25 * s;
1082
- y = (m12 + m21) / s;
1083
- z = (m13 + m31) / s;
1089
+ w = (m32 - m23) * s;
1090
+ y = (m12 + m21) * s;
1091
+ z = (m13 + m31) * s;
1084
1092
 
1085
1093
  } else if (m22 > m33) {
1086
1094
 
1087
- s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
1095
+ s = Math.sqrt(1.0 + m22 - m11 - m33);
1088
1096
 
1089
- w = (m13 - m31) / s;
1090
- x = (m12 + m21) / s;
1091
- y = 0.25 * s;
1092
- z = (m23 + m32) / s;
1097
+ y = 0.5 * s;
1098
+
1099
+ s = 0.5 / s;
1100
+
1101
+ w = (m13 - m31) * s;
1102
+ x = (m12 + m21) * s;
1103
+ z = (m23 + m32) * s;
1093
1104
 
1094
1105
  } else {
1095
1106
 
1096
- s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
1107
+ s = Math.sqrt(1.0 + m33 - m11 - m22);
1108
+
1109
+ z = 0.5 * s;
1110
+
1111
+ s = 0.5 / s;
1097
1112
 
1098
- w = (m21 - m12) / s;
1099
- x = (m13 + m31) / s;
1100
- y = (m23 + m32) / s;
1101
- z = 0.25 * s;
1113
+ w = (m21 - m12) * s;
1114
+ x = (m13 + m31) * s;
1115
+ y = (m23 + m32) * s;
1102
1116
 
1103
1117
  }
1104
1118
 
@@ -77,6 +77,8 @@ class Vector3 {
77
77
  const oldZ = this.z;
78
78
 
79
79
  if (x !== oldX || y !== oldY || z !== oldZ) {
80
+ // change has occurred
81
+
80
82
  this.x = x;
81
83
  this.y = y;
82
84
  this.z = z;
@@ -942,6 +944,15 @@ class Vector3 {
942
944
  input[offset + 2]
943
945
  );
944
946
  }
947
+
948
+ /**
949
+ *
950
+ * @param {number} value
951
+ * @returns {Vector3}
952
+ */
953
+ static fromScalar(value) {
954
+ return new Vector3(value, value, value);
955
+ }
945
956
  }
946
957
 
947
958
  Vector3.prototype.lengthSq = Vector3.prototype.lengthSqr;
@@ -171,12 +171,15 @@ export class Transform {
171
171
  * @param {Transform} other
172
172
  */
173
173
  copy(other) {
174
+ // prevent matrix from being overriden
175
+ this.clearFlag(TransformFlags.AutomaticChangeDetection);
176
+
177
+ this.matrix.set(other.matrix);
178
+
174
179
  this.position.copy(other.position);
175
180
  this.rotation.copy(other.rotation);
176
181
  this.scale.copy(other.scale);
177
182
 
178
- this.matrix.set(other.matrix);
179
-
180
183
  this.flags = other.flags;
181
184
  }
182
185
 
@@ -238,21 +238,16 @@ export class ShadedGeometry {
238
238
  const gbb = this.geometry.boundingBox;
239
239
 
240
240
  const min = gbb.min;
241
+
242
+ scratch_aabb3_array[0] = min.x;
243
+ scratch_aabb3_array[1] = min.y;
244
+ scratch_aabb3_array[2] = min.z;
245
+
241
246
  const max = gbb.max;
242
247
 
243
- const x0 = min.x;
244
- const y0 = min.y;
245
- const z0 = min.z;
246
- const x1 = max.x;
247
- const y1 = max.y;
248
- const z1 = max.z;
249
-
250
- scratch_aabb3_array[0] = x0;
251
- scratch_aabb3_array[1] = y0;
252
- scratch_aabb3_array[2] = z0;
253
- scratch_aabb3_array[3] = x1;
254
- scratch_aabb3_array[4] = y1;
255
- scratch_aabb3_array[5] = z1;
248
+ scratch_aabb3_array[3] = max.x;
249
+ scratch_aabb3_array[4] = max.y;
250
+ scratch_aabb3_array[5] = max.z;
256
251
 
257
252
  aabb3_matrix4_project(this.__bvh_aabb, scratch_aabb3_array, this.transform);
258
253
  }
@@ -53,6 +53,13 @@ export class SGMeshSystem extends System {
53
53
  * @private
54
54
  */
55
55
  this.__wait_queue_process_index = 0;
56
+
57
+ /**
58
+ * Cache THREE.js objects as we don't use them directly, so we can just keep 1 copy instead of calling {@link Asset#create} every time
59
+ * @type {Map<Asset, THREE.Object3D>}
60
+ * @private
61
+ */
62
+ this.__asset_object_cache = new Map();
56
63
  }
57
64
 
58
65
  /**
@@ -88,11 +95,28 @@ export class SGMeshSystem extends System {
88
95
 
89
96
  if (asset.boundingBox !== undefined) {
90
97
  mesh.__initial_bounds.copy(asset.boundingBox);
98
+
99
+ if (asset.has_root_transform) {
100
+ // TODO potentially move this to the GLTF Asset Loader to skip matrix operations here
101
+ mesh.__initial_bounds.applyMatrix4(asset.root_transform.matrix);
102
+ }
103
+
91
104
  } else {
92
105
  console.error(`No bounds for asset: ${asset.description}`);
93
106
  }
94
107
 
95
- let entity_node = three_object_to_entity_composition(asset.create());
108
+ let object;
109
+ const cached_object = this.__asset_object_cache.get(asset);
110
+
111
+ if (cached_object !== undefined) {
112
+ object = cached_object;
113
+ } else {
114
+ object = asset.create();
115
+
116
+ this.__asset_object_cache.set(asset, object);
117
+ }
118
+
119
+ let entity_node = three_object_to_entity_composition(object);
96
120
 
97
121
  if (asset.has_root_transform) {
98
122
  // apply root transform
@@ -230,12 +254,12 @@ export class SGMeshSystem extends System {
230
254
  continue;
231
255
  }
232
256
 
257
+ waiting.splice(index, 1);
258
+
233
259
  const transform = dataset.getComponent(entity, Transform);
234
260
 
235
261
  this.process(entity, mesh, transform);
236
262
 
237
- waiting.splice(index, 1);
238
-
239
263
  }
240
264
 
241
265
  this.__wait_queue_process_index = pointer;
@@ -17,6 +17,14 @@ import Highlight from "../../highlight/Highlight.js";
17
17
  import { ShadedGeometryHighlightSystem } from "../../highlight/system/ShadedGeometryHighlightSystem.js";
18
18
  import Quaternion from "../../../../../core/geom/Quaternion.js";
19
19
  import { seededRandom } from "../../../../../core/math/random/seededRandom.js";
20
+ import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
21
+ import { OrbitingBehavior } from "../../../../../../model/game/util/behavior/OrbitingBehavior.js";
22
+ import Vector2 from "../../../../../core/geom/Vector2.js";
23
+ import { GizmoRenderingPlugin } from "../../../render/gizmo/GizmoRenderingPlugin.js";
24
+ import { Gizmo } from "../../../render/gizmo/Gizmo.js";
25
+ import { AABB3 } from "../../../../../core/bvh2/aabb3/AABB3.js";
26
+ import { MicronRenderPlugin } from "../../../micron/plugin/MicronRenderPlugin.js";
27
+ import { randomFloatBetween } from "../../../../../core/math/random/randomFloatBetween.js";
20
28
 
21
29
  new EngineHarness().initialize({
22
30
  configuration(config, engine) {
@@ -30,23 +38,113 @@ new EngineHarness().initialize({
30
38
  new ShadedGeometryHighlightSystem(engine)
31
39
  );
32
40
 
33
- config.addLoader(GameAssetType.ModelGLTF_JSON, new GLTFAssetLoader());
41
+ config.addPlugin(GizmoRenderingPlugin);
42
+ config.addPlugin(MicronRenderPlugin);
43
+
44
+ const gltfAssetLoader = new GLTFAssetLoader();
45
+
46
+ config.addLoader(GameAssetType.ModelGLTF_JSON, gltfAssetLoader);
47
+ config.addLoader(GameAssetType.ModelGLTF, gltfAssetLoader);
34
48
  }
35
49
  }).then(main);
36
50
 
51
+ /**
52
+ *
53
+ * @param {EntityComponentDataset} ecd
54
+ * @param {Engine} engine
55
+ */
56
+ function make_grid(ecd, engine) {
57
+ const random = seededRandom(1);
58
+
59
+ const GRID_SIZE = 20;
60
+ const GRID_FREQUENCY_SIZE = 7;
61
+
62
+ for (let i = 0; i < GRID_SIZE; i++) {
63
+ for (let j = 0; j < GRID_SIZE; j++) {
64
+ const quaternion = new Quaternion();
65
+
66
+ quaternion.__setFromEuler(0, random() * Math.PI, 0);
67
+
68
+ const center = new Vector3(i * 8, 0, j * 8);
69
+
70
+ const transform = Transform.fromJSON({
71
+ position: center,
72
+ rotation: quaternion,
73
+ scale: randomFloatBetween(random, 0.5, 2)
74
+ });
75
+
76
+ const orbitingBehavior = OrbitingBehavior.from({
77
+ center: center,
78
+ rate: 1,
79
+ radius: 5
80
+ });
81
+
82
+ const ctx = {
83
+ time: Math.sin(i * Math.PI / (GRID_FREQUENCY_SIZE - 1)) + Math.cos(j * Math.PI / (GRID_FREQUENCY_SIZE - 1))
84
+ };
85
+
86
+ const color = [random(), random(), random(), 1];
87
+
88
+ engine.graphics.on.preRender.add(() => {
89
+
90
+ const bounds = new AABB3();
91
+ mesh.getUntransformedBoundingBox(bounds);
92
+ bounds.applyMatrix4(transform.matrix);
93
+
94
+ // mesh.getBoundingBox(bounds);
95
+
96
+ Gizmo.color = color;
97
+
98
+ Gizmo.draw_wire_cube(
99
+ [
100
+ bounds.getCenterX(), bounds.getCenterY(), bounds.getCenterZ()
101
+ ],
102
+ [
103
+ bounds.getExtentsX(), bounds.getExtentsY(), bounds.getExtentsZ()
104
+ ]
105
+ );
106
+ });
107
+
108
+
109
+ // const mesh = SGMesh.fromURL("data/models/RTS_Buildings_Humans/18/Building_R_18_out/Building_R_18.gltf");
110
+ const mesh = SGMesh.fromURL("data/models/samples/transform-hierarchy.glb");
111
+ new EntityBuilder()
112
+ .add(mesh)
113
+ .add(transform)
114
+ .add(BehaviorComponent.fromOne(
115
+ orbitingBehavior
116
+ // RepeatBehavior.from(
117
+ // new ActionBehavior((t) => {
118
+ // ctx.time += t;
119
+ //
120
+ // const f = smoothStep(0, 1, pingpong(ctx.time / 2.5, 1));
121
+ //
122
+ // transform.position.set(center.x, center.y + f * 5, center.z);
123
+ //
124
+ // })
125
+ // )
126
+ ))
127
+ .build(ecd);
128
+ }
129
+ }
130
+ }
131
+
37
132
  /**
38
133
  *
39
134
  * @param {Engine} engine
40
135
  */
41
136
  function main(engine) {
42
137
  EngineHarness.buildBasics({
43
- engine
138
+ engine,
139
+ terrainResolution: 1,
140
+ terrainSize: new Vector2(100, 100)
44
141
  });
45
142
 
46
143
  const ecd = engine.entityManager.dataset;
47
144
 
48
145
  // const mesh = SGMesh.fromURL("data/models/RTS_Buildings_Humans/18/Building_R_18_out/Building_R_18.gltf");
49
- const mesh = SGMesh.fromURL("moicon/ztest_object_many_pieces/model.gltf");
146
+ // const mesh = SGMesh.fromURL("moicon/ztest_object_many_pieces/model.gltf");
147
+ const mesh = SGMesh.fromURL("moicon/2022_04_20__Micron_from_Philippe/model.gltf");
50
148
  // const mesh = Mesh.fromURL("moicon/ztest_object_many_pieces/model.gltf");
51
149
 
52
150
  new EntityBuilder()
@@ -74,25 +172,6 @@ function main(engine) {
74
172
  });
75
173
  })
76
174
  // .build(ecd);
77
- const r = seededRandom(1);
78
-
79
- for (let i = 0; i < 10; i++) {
80
- for (let j = 0; j < 10; j++) {
81
- const quaternion = new Quaternion();
82
-
83
- quaternion.__setFromEuler(0, r() * Math.PI, 0);
84
175
 
85
- new EntityBuilder()
86
- .add(SGMesh.fromURL("data/models/RTS_Buildings_Humans/18/Building_R_18_out/Building_R_18.gltf"))
87
- .add(Transform.fromJSON({
88
- position: {
89
- x: i * 8,
90
- y: 0,
91
- z: j * 8
92
- },
93
- rotation: quaternion
94
- }))
95
- .build(ecd);
96
- }
97
- }
176
+ make_grid(ecd, engine);
98
177
  }
@@ -3,6 +3,9 @@ import {
3
3
  DynamicDrawUsage,
4
4
  InstancedBufferAttribute,
5
5
  InstancedBufferGeometry,
6
+ Line,
7
+ LineSegments,
8
+ Mesh,
6
9
  MeshDepthMaterial,
7
10
  RGBADepthPacking
8
11
  } from 'three';
@@ -13,6 +16,7 @@ import { typed_array_copy } from "../../../../core/collection/array/typed/typed_
13
16
  import { rewriteMaterial } from "./rewriteMaterial.js";
14
17
  import { max3 } from "../../../../core/math/max3.js";
15
18
  import { min2 } from "../../../../core/math/min2.js";
19
+ import { DrawMode } from "../../ecs/mesh-v2/DrawMode.js";
16
20
 
17
21
  export class InstancedMeshGroup {
18
22
  /**
@@ -111,7 +115,64 @@ export class InstancedMeshGroup {
111
115
  this.indices = [];
112
116
  this.references = [];
113
117
 
118
+ /**
119
+ *
120
+ * @type {DrawMode}
121
+ * @private
122
+ */
123
+ this.__draw_mode = DrawMode.Triangles;
124
+
114
125
  this.mesh = ThreeFactory.createMesh();
126
+
127
+ this.use_color = true;
128
+ }
129
+
130
+ __build_mesh() {
131
+
132
+ let new_object;
133
+ switch (this.__draw_mode) {
134
+ case DrawMode.Triangles:
135
+ new_object = new Mesh(this.mesh.geometry, this.mesh.material);
136
+ break;
137
+ case DrawMode.Lines:
138
+ new_object = new Line(this.mesh.geometry, this.mesh.material);
139
+ break;
140
+ case DrawMode.LineSegments:
141
+ new_object = new LineSegments(this.mesh.geometry, this.mesh.material);
142
+ break;
143
+
144
+ default:
145
+ throw new Error(`Unsupported DrawMode '${this.__draw_mode}'`);
146
+ }
147
+
148
+ new_object.matrixAutoUpdate = false;
149
+ new_object.frustumCulled = false;
150
+ new_object.matrixWorldNeedsUpdate = false;
151
+
152
+ if (this.mesh !== null) {
153
+ new_object.castShadow = this.mesh.castShadow;
154
+ new_object.receiveShadow = this.mesh.receiveShadow;
155
+
156
+ new_object.customDepthMaterial = this.mesh.customDepthMaterial;
157
+ }
158
+
159
+ this.mesh = new_object;
160
+ }
161
+
162
+ /**
163
+ *
164
+ * @param {DrawMode} v
165
+ */
166
+ set draw_mode(v) {
167
+ if (v === this.__draw_mode) {
168
+ // no change
169
+ return;
170
+ }
171
+
172
+ this.__draw_mode = v;
173
+
174
+ this.__build_mesh();
175
+
115
176
  }
116
177
 
117
178
  /**
@@ -128,6 +189,10 @@ export class InstancedMeshGroup {
128
189
  if (this.__attributeTransform !== null) {
129
190
  this.__attributeTransform.setUsage(v);
130
191
  }
192
+
193
+ if (this.__attributeColor !== null) {
194
+ this.__attributeColor.setUsage(v);
195
+ }
131
196
  }
132
197
 
133
198
  /**
@@ -254,10 +319,41 @@ export class InstancedMeshGroup {
254
319
  this.__attributeTransformArray.set(transform, index * 16);
255
320
  }
256
321
 
322
+ /**
323
+ *
324
+ * @param {number} index
325
+ * @param {number[]|ArrayLike<number>|Float32Array} color RGBA color in uint8 format (0...255), LDR
326
+ */
327
+ setColorAt(index, color) {
328
+ this.__attributeColorArray.set(color, index * 4);
329
+ }
330
+
331
+ /**
332
+ *
333
+ * @param {number} index
334
+ * @param {number} r
335
+ * @param {number} g
336
+ * @param {number} b
337
+ * @param {number} a
338
+ */
339
+ setColorByComponentAt(index, r, g, b, a) {
340
+ const color_array = this.__attributeColorArray;
341
+
342
+ const i4 = index * 4;
343
+
344
+ color_array[i4] = r;
345
+ color_array[i4 + 1] = g;
346
+ color_array[i4 + 2] = b;
347
+ color_array[i4 + 3] = a;
348
+ }
349
+
257
350
  requestAttributeUpdate() {
258
351
  this.__attributeTransform.needsUpdate = true;
259
- }
260
352
 
353
+ if (this.__attributeColor !== null) {
354
+ this.__attributeColor.needsUpdate = true;
355
+ }
356
+ }
261
357
 
262
358
  /**
263
359
  * Swap position in attribute arrays of two elements
@@ -356,6 +452,13 @@ export class InstancedMeshGroup {
356
452
  a_transform.array.copyWithin(index * 16, lastIndex * 16, lastIndex * 16 + 16);
357
453
  a_transform.needsUpdate = true;
358
454
 
455
+ const a_color = this.__attributeColor;
456
+
457
+ if (a_color !== null) {
458
+ a_color.array.copyWithin(index * 4, lastIndex * 4, lastIndex * 4 + 4);
459
+ a_color.needsUpdate = true;
460
+ }
461
+
359
462
 
360
463
  //update moved reference index
361
464
  const movedReference = references[lastIndex];
@@ -422,6 +525,30 @@ export class InstancedMeshGroup {
422
525
  //add attributes to newly created geometry
423
526
  geometry.setAttribute("instanceMatrix", this.__attributeTransform);
424
527
 
528
+ if (this.use_color) {
529
+
530
+ // color attribute
531
+ const newColorArray = new Uint8Array(this.capacity * 4);
532
+
533
+ if (this.__attributeColor !== null) {
534
+ const oldColorArray = this.__attributeColor.array;
535
+ typed_array_copy(oldColorArray, newColorArray);
536
+
537
+ if (newColorArray.length > oldColorArray.length) {
538
+ newColorArray.fill(255, oldColorArray.length);
539
+ }
540
+ }
541
+
542
+ this.__attributeColorArray = newColorArray;
543
+ this.__attributeColor = new InstancedBufferAttribute(newColorArray, 4);
544
+ this.__attributeColor.normalized = true;
545
+ this.__attributeColor.setUsage(this.__instanceUsage);
546
+
547
+ geometry.setAttribute("instanceColor", this.__attributeColor);
548
+ } else {
549
+ this.__attributeColorArray = null;
550
+ this.__attributeColor = null;
551
+ }
425
552
 
426
553
  this.mesh.geometry = geometry;
427
554
  }
@@ -1,5 +1,9 @@
1
1
  const SHADER_PREAMBLE = `
2
+ #define USE_COLOR_ALPHA
3
+ #define USE_INSTANCING_COLOR
4
+
2
5
  attribute mat4 instanceMatrix;
6
+ attribute vec4 instanceColor;
3
7
  \n`;
4
8
 
5
9
  export function rewriteMaterial(shader) {
@@ -28,7 +32,22 @@ export function rewriteMaterial(shader) {
28
32
  vec3 objectTangent = vec3( tangent.xyz );
29
33
  #endif
30
34
  `
31
- );
35
+ )
36
+ .replace(
37
+ '#include <color_vertex>',
38
+ `
39
+ #if defined( USE_COLOR_ALPHA )
40
+ vColor = instanceColor;
41
+ #else
42
+ vColor = instanceColor.xyz;
43
+ #endif
44
+ `
45
+ );
32
46
 
33
47
  shader.vertexShader = newVertexShader;
48
+
49
+ shader.fragmentShader = `
50
+ #define USE_COLOR_ALPHA
51
+ #define USE_INSTANCING_COLOR
52
+ `+shader.fragmentShader;
34
53
  }
@@ -2,6 +2,9 @@ import { ConicRay } from "../../../../core/geom/ConicRay.js";
2
2
  import { vec4 } from "gl-matrix";
3
3
  import { assert } from "../../../../core/assert.js";
4
4
 
5
+
6
+ const EMPTY_ARRAY_UINT8 = new Uint8Array(0);
7
+
5
8
  /**
6
9
  * Patches are organized into a tree hierarchy
7
10
  */
@@ -97,7 +100,7 @@ export class MicronGeometryPatch {
97
100
  *
98
101
  * @type {Uint8Array|Uint16Array|null}
99
102
  */
100
- this.face_indices = null;
103
+ this.face_indices = EMPTY_ARRAY_UINT8;
101
104
 
102
105
  /**
103
106
  * Bounding cone of all of the face normals
@@ -1,5 +1,17 @@
1
1
  import { deserialize_geometry_collection, serialize_geometry_collection } from "./geometry_collection_serialization.js";
2
2
  import { BinaryBuffer } from "../../../../../../core/binary/BinaryBuffer.js";
3
+ import { MicronGeometry } from "../../MicronGeometry.js";
4
+ import { MicronGeometryPatch } from "../../MicronGeometryPatch.js";
5
+
6
+ function sample_geo() {
7
+
8
+ const geometry = new MicronGeometry();
9
+
10
+ geometry.root = new MicronGeometryPatch();
11
+ geometry.patches.push(geometry.root);
12
+
13
+ return geometry;
14
+ }
3
15
 
4
16
  test('serialize empty', () => {
5
17
  const bb = new BinaryBuffer();
@@ -22,3 +34,18 @@ test('serialize/deserialize empty', () => {
22
34
 
23
35
  expect(destination.length).toBe(0);
24
36
  });
37
+
38
+ test('serialize/deserialize 1', () => {
39
+ const bb = new BinaryBuffer();
40
+
41
+ const geometry = sample_geo();
42
+
43
+ serialize_geometry_collection([geometry], bb);
44
+
45
+ bb.position = 0;
46
+
47
+ const destination = [];
48
+ deserialize_geometry_collection(destination, bb);
49
+
50
+ expect(destination.length).toBe(1);
51
+ });
@@ -1,5 +1,25 @@
1
1
  import { EnginePlugin } from "../../../plugin/EnginePlugin.js";
2
+ import { Gizmo } from "./Gizmo.js";
2
3
 
3
4
  export class GizmoRenderingPlugin extends EnginePlugin {
5
+ async startup() {
6
+ this.__layer = this.engine.graphics.layers.create('debug');
7
+
8
+ this.__layer.buildVisibleSet = (destination, destination_offset, view) => {
9
+ return Gizmo.collect(destination, destination_offset, view);
10
+ };
11
+
12
+ this.engine.graphics.on.postRender.add(Gizmo.clear, Gizmo);
13
+
14
+ return super.startup();
15
+ }
16
+
17
+ async shutdown() {
18
+ this.engine.graphics.layers.remove(this.__layer);
19
+
20
+ this.engine.graphics.on.postRender.remove(Gizmo.clear, Gizmo);
21
+
22
+ return super.shutdown();
23
+ }
4
24
 
5
25
  }
@@ -1,10 +1,13 @@
1
1
  import { array_copy } from "../../../../core/collection/array/copyArray.js";
2
2
  import { assert } from "../../../../core/assert.js";
3
3
  import { InstancedMeshGroup } from "../../geometry/instancing/InstancedMeshGroup.js";
4
- import { BoxBufferGeometry, MeshBasicMaterial } from "three";
4
+ import { BoxBufferGeometry, LineBasicMaterial, MeshBasicMaterial, OctahedronBufferGeometry } from "three";
5
5
  import { compose_matrix4_array } from "../../../../core/geom/3d/compose_matrix4_array.js";
6
6
  import Vector3 from "../../../../core/geom/Vector3.js";
7
7
  import Quaternion from "../../../../core/geom/Quaternion.js";
8
+ import { makeHelperBoxGeometry } from "../../../../editor/process/symbolic/makeHelperBoxGeometry.js";
9
+ import { DrawMode } from "../../ecs/mesh-v2/DrawMode.js";
10
+ import { makeHelperSphereGeometry } from "../../../../editor/process/symbolic/makeHelperSphereGeometry.js";
8
11
 
9
12
  /**
10
13
  *
@@ -12,6 +15,27 @@ import Quaternion from "../../../../core/geom/Quaternion.js";
12
15
  */
13
16
  const scratch_m4 = [];
14
17
 
18
+ /**
19
+ *
20
+ * @param {InstancedMeshGroup} group
21
+ * @param {number[]} matrix
22
+ * @param {number[]} color
23
+ */
24
+ function add_instance(group, matrix, color) {
25
+ const i = group.count;
26
+
27
+ group.setCount(i + 1);
28
+ group.setTransformAt(i, matrix);
29
+
30
+ group.setColorByComponentAt(
31
+ i,
32
+ Math.round(color[0] * 255),
33
+ Math.round(color[1] * 255),
34
+ Math.round(color[2] * 255),
35
+ Math.round(color[3] * 255)
36
+ );
37
+ }
38
+
15
39
  export class GizmoShapeRenderingInterface {
16
40
  constructor() {
17
41
 
@@ -23,6 +47,50 @@ export class GizmoShapeRenderingInterface {
23
47
  this.__color = [1, 1, 1, 1];
24
48
 
25
49
  this.__boxes_solid = InstancedMeshGroup.from(new BoxBufferGeometry(), new MeshBasicMaterial());
50
+ this.__boxes_wire = InstancedMeshGroup.from(makeHelperBoxGeometry(), new LineBasicMaterial());
51
+
52
+ this.__boxes_wire.draw_mode = DrawMode.LineSegments;
53
+
54
+ this.__sphere_solid = InstancedMeshGroup.from(new OctahedronBufferGeometry(1, 10), new MeshBasicMaterial());
55
+ this.__sphere_wire = InstancedMeshGroup.from(makeHelperSphereGeometry(), new LineBasicMaterial());
56
+
57
+ this.__sphere_wire.draw_mode = DrawMode.Lines;
58
+ }
59
+
60
+ __get_groups() {
61
+ return [
62
+ this.__boxes_wire,
63
+ this.__boxes_solid,
64
+ this.__sphere_wire,
65
+ this.__sphere_solid
66
+ ];
67
+ }
68
+
69
+ /**
70
+ *
71
+ * @param {THREE.Object3D[]} destination
72
+ * @param {number} destination_offset
73
+ * @param {number[]} frustum
74
+ */
75
+ collect(destination, destination_offset, frustum) {
76
+ let i = destination_offset;
77
+
78
+ const groups = this.__get_groups();
79
+
80
+ for (let j = 0; j < groups.length; j++) {
81
+ destination[i++] = groups[j].mesh;
82
+ }
83
+
84
+ return i - destination_offset;
85
+ }
86
+
87
+ clear() {
88
+ const groups = this.__get_groups();
89
+
90
+ for (let j = 0; j < groups.length; j++) {
91
+ groups[j].setCount(0);
92
+ }
93
+
26
94
  }
27
95
 
28
96
  /**
@@ -44,10 +112,7 @@ export class GizmoShapeRenderingInterface {
44
112
  */
45
113
  draw_solid_cube(center, size) {
46
114
  compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromArray(size));
47
- const i = this.__boxes_solid.count;
48
-
49
- this.__boxes_solid.setCount(i + 1);
50
- this.__boxes_solid.setTransformAt(i, scratch_m4);
115
+ add_instance(this.__boxes_solid, scratch_m4, this.__color);
51
116
  }
52
117
 
53
118
  /**
@@ -56,7 +121,8 @@ export class GizmoShapeRenderingInterface {
56
121
  * @param {number[]} size
57
122
  */
58
123
  draw_wire_cube(center, size) {
59
-
124
+ compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromArray(size));
125
+ add_instance(this.__boxes_wire, scratch_m4, this.__color);
60
126
  }
61
127
 
62
128
  /**
@@ -65,7 +131,7 @@ export class GizmoShapeRenderingInterface {
65
131
  * @param {number[]} to
66
132
  */
67
133
  draw_line(from, to) {
68
-
134
+ throw new Error('Not Implemented');
69
135
  }
70
136
 
71
137
 
@@ -75,7 +141,8 @@ export class GizmoShapeRenderingInterface {
75
141
  * @param {number} radius
76
142
  */
77
143
  draw_solid_sphere(center, radius) {
78
-
144
+ compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromScalar(radius));
145
+ add_instance(this.__sphere_solid, scratch_m4, this.__color);
79
146
  }
80
147
 
81
148
  /**
@@ -83,8 +150,9 @@ export class GizmoShapeRenderingInterface {
83
150
  * @param {number[]} center
84
151
  * @param {number} radius
85
152
  */
86
- draw_wire_sphere() {
87
-
153
+ draw_wire_sphere(center, radius) {
154
+ compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromScalar(radius));
155
+ add_instance(this.__sphere_wire, scratch_m4, this.__color);
88
156
  }
89
157
  }
90
158
 
@@ -4,8 +4,12 @@ import { compareNumbersAscending } from "../../../../core/function/Functions.js"
4
4
  test('set is sorted after insert, push 1,2', () => {
5
5
  const set = new IncrementalDeltaSet(compareNumbersAscending);
6
6
 
7
+ set.initializeUpdate();
8
+
7
9
  set.push(1);
8
- set.push(2);
10
+ set.push(2)
11
+
12
+ set.finalizeUpdate();
9
13
 
10
14
  expect(set.elements[0]).toBe(1);
11
15
  expect(set.elements[1]).toBe(2);
@@ -14,9 +18,13 @@ test('set is sorted after insert, push 1,2', () => {
14
18
  test('set is sorted after insert, push 2,1', () => {
15
19
  const set = new IncrementalDeltaSet(compareNumbersAscending);
16
20
 
21
+ set.initializeUpdate();
22
+
17
23
  set.push(2);
18
24
  set.push(1);
19
25
 
26
+ set.finalizeUpdate();
27
+
20
28
  expect(set.elements[0]).toBe(1);
21
29
  expect(set.elements[1]).toBe(2);
22
30
  });
@@ -24,10 +32,14 @@ test('set is sorted after insert, push 2,1', () => {
24
32
  test('set is sorted after insert, push 1,2,3', () => {
25
33
  const set = new IncrementalDeltaSet(compareNumbersAscending);
26
34
 
35
+ set.initializeUpdate();
36
+
27
37
  set.push(1);
28
38
  set.push(2);
29
39
  set.push(3);
30
40
 
41
+ set.finalizeUpdate();
42
+
31
43
  expect(set.elements[0]).toBe(1);
32
44
  expect(set.elements[1]).toBe(2);
33
45
  expect(set.elements[2]).toBe(3);
@@ -36,10 +48,14 @@ test('set is sorted after insert, push 1,2,3', () => {
36
48
  test('set is sorted after insert, push 3,2,1', () => {
37
49
  const set = new IncrementalDeltaSet(compareNumbersAscending);
38
50
 
51
+ set.initializeUpdate();
52
+
39
53
  set.push(3);
40
54
  set.push(2);
41
55
  set.push(1);
42
56
 
57
+ set.finalizeUpdate();
58
+
43
59
  expect(set.elements[0]).toBe(1);
44
60
  expect(set.elements[1]).toBe(2);
45
61
  expect(set.elements[2]).toBe(3);
@@ -5,7 +5,7 @@ import { assert } from "../../../../core/assert.js";
5
5
  export class ActionBehavior extends Behavior {
6
6
  /**
7
7
  *
8
- * @param {function} action
8
+ * @param {function(timeDelta:number)} action
9
9
  * @param {*} [context]
10
10
  */
11
11
  constructor(action, context) {
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.37.13",
8
+ "version": "2.37.16",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",