@woosh/meep-engine 2.36.0 → 2.36.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/core/collection/queue/Deque.js +1 -1
  2. package/core/color/Color.d.ts +32 -2
  3. package/core/color/Color.js +31 -18
  4. package/core/geom/AABB2.d.ts +2 -0
  5. package/core/geom/Vector3.js +5 -1
  6. package/core/process/undo/ActionProcessor.js +31 -3
  7. package/core/process/undo/ActionProcessor.spec.js +1 -1
  8. package/engine/animation/curve/AnimationCurve.js +40 -1
  9. package/engine/animation/curve/Keyframe.js +19 -0
  10. package/engine/animation/curve/compression/animation_curve_to_float_array.js +35 -0
  11. package/engine/animation/curve/compression/downsample_float_array.js +50 -0
  12. package/engine/animation/curve/compression/downsample_float_array_curve_by_error.js +147 -0
  13. package/engine/animation/curve/compression/downsample_float_array_curve_by_error.spec.js +47 -0
  14. package/engine/animation/curve/compression/prototypeCurveCompression.js +154 -0
  15. package/engine/animation/curve/draw/build_curve_editor.js +316 -0
  16. package/engine/animation/curve/draw/build_plot_entity_from_array.js +47 -0
  17. package/engine/animation/curve/draw/build_tangent_editor.js +115 -0
  18. package/engine/animation/curve/draw/draw_grid.js +27 -0
  19. package/engine/animation/curve/draw/draw_label.js +17 -0
  20. package/engine/animation/curve/draw/plot_array.js +76 -0
  21. package/engine/animation/curve/draw/plot_data.js +49 -0
  22. package/engine/ecs/parent/EntityNode.d.ts +7 -3
  23. package/engine/ecs/parent/EntityNode.js +21 -0
  24. package/engine/ecs/parent/entity_node_compute_bounding_box.js +41 -0
  25. package/engine/graphics/ecs/highlight/HighlightDefinition.d.ts +11 -1
  26. package/engine/graphics/ecs/highlight/HighlightDefinition.js +18 -8
  27. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -1
  28. package/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +122 -0
  29. package/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js +148 -0
  30. package/engine/graphics/ecs/mesh-v2/render/adapters/InstancedRendererAdapter.js +2 -0
  31. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.d.ts +4 -0
  32. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +34 -3
  33. package/engine/graphics/micron/format/serialization/collection/geometry_collection_serialization.spec.js +24 -0
  34. package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +10 -0
  35. package/engine/graphics/micron/prototypeVirtualGeometry.js +5 -3
  36. package/engine/graphics/micron/render/v1/MicronRenderContext.js +0 -6
  37. package/engine/graphics/render/forward_plus/LightManager.js +8 -6
  38. package/engine/graphics/render/forward_plus/data/channelCountToThreIntegerTextureType.js +3 -3
  39. package/engine/graphics/texture/channelCountToThreeTextureFormat.js +2 -2
  40. package/engine/ui/DraggableAspect.js +4 -1
  41. package/package.json +1 -1
@@ -0,0 +1,76 @@
1
+ import { min2 } from "../../../../core/math/min2.js";
2
+ import { max2 } from "../../../../core/math/max2.js";
3
+
4
+ /**
5
+ *
6
+ * @param {number[]} data
7
+ * @return {number[]}
8
+ */
9
+ export function array_compute_min_max(data) {
10
+ const point_count = data.length;
11
+
12
+ let min = Number.POSITIVE_INFINITY;
13
+ let max = Number.NEGATIVE_INFINITY;
14
+ for (let i = 0; i < point_count; i++) {
15
+ min = min2(data[i], min);
16
+ max = max2(data[i], max);
17
+ }
18
+
19
+ return [min, max];
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {CanvasRenderingContext2D} ctx
25
+ * @param {number} width
26
+ * @param {number} height
27
+ * @param {number} x_offset
28
+ * @param {number} y_offset
29
+ * @param {number[]} data
30
+ * @param {number[]} data_range_y
31
+ */
32
+ export function plot_array(
33
+ ctx,
34
+ width,
35
+ height,
36
+ x_offset,
37
+ y_offset,
38
+ data,
39
+ data_range_y
40
+ ) {
41
+ const point_count = data.length;
42
+
43
+ if (data_range_y === undefined) {
44
+ data_range_y = array_compute_min_max(data)
45
+ }
46
+
47
+ const [min, max] = data_range_y;
48
+
49
+ const value_span = max - min;
50
+ const normalization_multiplier = value_span !== 0 ? 1 / value_span : 0;
51
+
52
+ ctx.beginPath();
53
+ if (point_count === 1) {
54
+ ctx.moveTo(x_offset, height + y_offset);
55
+ ctx.lineTo(x_offset + width, height + y_offset);
56
+ } else {
57
+
58
+ for (let i = 0; i < point_count; i++) {
59
+ const value = data[i];
60
+ const value_normalized = (value - min) * normalization_multiplier;
61
+
62
+ const v = 1 - value_normalized;
63
+ const u = i / (point_count - 1);
64
+
65
+ const x = u * width + x_offset;
66
+ const y = v * height + y_offset;
67
+
68
+ if (i === 0) {
69
+ ctx.moveTo(x, y);
70
+ } else {
71
+ ctx.lineTo(x, y);
72
+ }
73
+ }
74
+ }
75
+ ctx.stroke();
76
+ }
@@ -0,0 +1,49 @@
1
+ import { draw_grid } from "./draw_grid.js";
2
+ import { plot_array } from "./plot_array.js";
3
+ import Vector2 from "../../../../core/geom/Vector2.js";
4
+
5
+ /**
6
+ *
7
+ * @param {CanvasRenderingContext2D} ctx
8
+ * @param {number[]|Float32Array} data
9
+ * @param {Vector2} [margin]
10
+ * @param {number} width
11
+ * @param {number} height
12
+ * @param {number[]} [range_y]
13
+ */
14
+ export function plot_data({
15
+ ctx,
16
+ data,
17
+ margin = Vector2.zero,
18
+ width,
19
+ height,
20
+ range_y
21
+ }) {
22
+
23
+ ctx.fillStyle = '#222222';
24
+ ctx.fillRect(0, 0, width, height);
25
+
26
+ draw_grid({
27
+ ctx,
28
+ width,
29
+ height,
30
+ color: '#262626',
31
+ spacing: 32
32
+ });
33
+
34
+ draw_grid({
35
+ ctx,
36
+ width,
37
+ height,
38
+ color: '#303030',
39
+ spacing: 32,
40
+ offset_x: 16,
41
+ offset_y: 16
42
+ });
43
+
44
+ ctx.fillStyle = 'none';
45
+ ctx.strokeStyle = '#00ff00';
46
+ ctx.lineWidth = 1;
47
+ plot_array(ctx, width - margin.x * 2, height - margin.y * 2, margin.x, margin.y, data, range_y);
48
+
49
+ }
@@ -5,15 +5,19 @@ import {Transform} from "../transform/Transform";
5
5
  export class EntityNode {
6
6
  constructor(entity?: EntityBuilder)
7
7
 
8
- parent: EntityNode | null
9
- entity: EntityBuilder
8
+ readonly parent: EntityNode | null
9
+ readonly entity: EntityBuilder
10
10
 
11
- transform: Transform
11
+ readonly transform: Transform
12
+
13
+ readonly children: Readonly<EntityNode[]>
12
14
 
13
15
  addChild(node: EntityNode): boolean
14
16
 
15
17
  removeChild(node: EntityNode): boolean
16
18
 
19
+ traverseChildren(visitor: (EntityNode) => void, thisArg?: any): void
20
+
17
21
  build(ecd: EntityComponentDataset): void
18
22
 
19
23
  destroy(): void
@@ -41,6 +41,23 @@ export class EntityNode {
41
41
  this.__transform = new Transform();
42
42
  }
43
43
 
44
+ /**
45
+ *
46
+ * @param {(EntityNode) => *} visitor
47
+ * @param {*} [context]
48
+ */
49
+ traverseChildren(visitor, context) {
50
+ this.__children.forEach(visitor, context);
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @return {Readonly<EntityNode[]>}
56
+ */
57
+ get children() {
58
+ return this.__children;
59
+ }
60
+
44
61
  /**
45
62
  *
46
63
  * @private
@@ -162,6 +179,10 @@ export class EntityNode {
162
179
  }
163
180
  }
164
181
 
182
+ /**
183
+ *
184
+ * @return {EntityBuilder}
185
+ */
165
186
  get entity() {
166
187
  return this.__entity;
167
188
  }
@@ -0,0 +1,41 @@
1
+ import { ShadedGeometry } from "../../graphics/ecs/mesh-v2/ShadedGeometry.js";
2
+ import { AABB3 } from "../../../core/bvh2/aabb3/AABB3.js";
3
+
4
+ const scratch_aabb3 = new AABB3();
5
+
6
+ /**
7
+ *
8
+ * @param {AABB3} out
9
+ * @param {EntityNode} root
10
+ */
11
+ export function entity_node_expand_bounding_box_to_fit(out, root) {
12
+ const sg = root.entity.getComponent(ShadedGeometry);
13
+
14
+ if (sg !== null) {
15
+ sg.getBoundingBox(scratch_aabb3);
16
+
17
+ out.expandToFit(scratch_aabb3);
18
+ }
19
+
20
+ const children = root.children;
21
+ const child_count = children.length;
22
+
23
+ for (let i = 0; i < child_count; i++) {
24
+ const child = children[i];
25
+
26
+ entity_node_expand_bounding_box_to_fit(out, child);
27
+ }
28
+ }
29
+
30
+ /**
31
+ *
32
+ * @param {AABB3} out
33
+ * @param {EntityNode} root
34
+ */
35
+ export function entity_node_compute_bounding_box(out, root) {
36
+ // init bounds
37
+ out.setNegativelyInfiniteBounds();
38
+
39
+ // expand bounds to fit the tree
40
+ entity_node_expand_bounding_box_to_fit(out, root);
41
+ }
@@ -1,6 +1,16 @@
1
1
  import {Color} from "../../../../core/color/Color";
2
2
 
3
3
  export class HighlightDefinition {
4
- color: Color
4
+ readonly color: Color
5
5
  opacity: number
6
+
7
+ static rgba(r: number, g: number, b: number, a?: number): HighlightDefinition
8
+
9
+ equals(other: HighlightDefinition): boolean
10
+
11
+ hash(): number
12
+
13
+ fromJSON(json: any): void
14
+
15
+ toJSON(): any
6
16
  }
@@ -1,6 +1,5 @@
1
1
  import { Color } from "../../../../core/color/Color.js";
2
2
  import { assert } from "../../../../core/assert.js";
3
- import { COMPONENT_SERIALIZATION_TRANSIENT_FIELD } from "../../../ecs/storage/COMPONENT_SERIALIZATION_TRANSIENT_FIELD.js";
4
3
  import { computeHashIntegerArray } from "../../../../core/collection/array/computeHashIntegerArray.js";
5
4
  import { computeHashFloat } from "../../../../core/math/hash/computeHashFloat.js";
6
5
 
@@ -8,7 +7,7 @@ export class HighlightDefinition {
8
7
  constructor() {
9
8
 
10
9
  /**
11
- *
10
+ * @readonly
12
11
  * @type {Color}
13
12
  */
14
13
  this.color = new Color();
@@ -18,12 +17,6 @@ export class HighlightDefinition {
18
17
  * @type {number}
19
18
  */
20
19
  this.opacity = 1;
21
-
22
- /**
23
- *
24
- * @type {boolean}
25
- */
26
- this[COMPONENT_SERIALIZATION_TRANSIENT_FIELD] = false;
27
20
  }
28
21
 
29
22
  /**
@@ -73,4 +66,21 @@ export class HighlightDefinition {
73
66
 
74
67
  return r;
75
68
  }
69
+
70
+ /**
71
+ *
72
+ * @param {number} r
73
+ * @param {number} g
74
+ * @param {number} b
75
+ * @param {number} [a]
76
+ * @return {HighlightDefinition}
77
+ */
78
+ static rgba(r, g, b, a = 1) {
79
+ const result = new HighlightDefinition();
80
+
81
+ result.color.setRGB(r, g, b);
82
+ result.opacity = a;
83
+
84
+ return result;
85
+ }
76
86
  }
@@ -10,7 +10,9 @@ import { ShadedGeometryRendererContext } from "./render/ShadedGeometryRendererCo
10
10
  import { assert } from "../../../../core/assert.js";
11
11
  import { ray3_array_compose } from "../../../../core/geom/3d/ray/ray3_array_compose.js";
12
12
  import { ray3_array_apply_matrix4 } from "../../../../core/geom/3d/ray/ray3_array_apply_matrix4.js";
13
- import { ExplicitBinaryBoundingVolumeHierarchy } from "../../../../core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.js";
13
+ import {
14
+ ExplicitBinaryBoundingVolumeHierarchy
15
+ } from "../../../../core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.js";
14
16
  import { bvh_query_leaves_generic } from "../../../../core/bvh2/bvh3/bvh_query_leaves_generic.js";
15
17
  import { BVHQueryIntersectsRay } from "../../../../core/bvh2/bvh3/query/BVHQueryIntersectsRay.js";
16
18
 
@@ -157,6 +159,8 @@ export class ShadedGeometrySystem extends System {
157
159
 
158
160
  async startup(entityManager, readyCallback, errorCallback) {
159
161
 
162
+ this.entityManager = entityManager;
163
+
160
164
  const engine = this.__engine;
161
165
  const graphics = engine.graphics;
162
166
 
@@ -0,0 +1,122 @@
1
+ export const SGMeshFlags = {
2
+ CastShadow: 1,
3
+ ReceiveShadow: 2
4
+ };
5
+
6
+ export class SGMesh {
7
+ constructor() {
8
+ /**
9
+ *
10
+ * @type {string|null}
11
+ */
12
+ this.__url = null;
13
+
14
+ /**
15
+ *
16
+ * @type {EntityNode|null}
17
+ * @private
18
+ */
19
+ this.__node = null;
20
+
21
+ /**
22
+ *
23
+ * @type {number}
24
+ */
25
+ this.flags = 0;
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @returns {string|null}
31
+ */
32
+ get url() {
33
+ return this.__url;
34
+ }
35
+
36
+ /**
37
+ *
38
+ * @param {string|null} v
39
+ */
40
+ set url(v) {
41
+ if (v === this.__url) {
42
+ // no change
43
+ return;
44
+ }
45
+
46
+ this.__url = v;
47
+ }
48
+
49
+ /**
50
+ *
51
+ * @param {number|SGMeshFlags} flag
52
+ * @returns {void}
53
+ */
54
+ setFlag(flag) {
55
+ this.flags |= flag;
56
+ }
57
+
58
+ /**
59
+ *
60
+ * @param {number|SGMeshFlags} flag
61
+ * @returns {void}
62
+ */
63
+ clearFlag(flag) {
64
+ this.flags &= ~flag;
65
+ }
66
+
67
+ /**
68
+ *
69
+ * @param {number|SGMeshFlags} flag
70
+ * @param {boolean} value
71
+ */
72
+ writeFlag(flag, value) {
73
+ if (value) {
74
+ this.setFlag(flag);
75
+ } else {
76
+ this.clearFlag(flag);
77
+ }
78
+ }
79
+
80
+ /**
81
+ *
82
+ * @param {number|SGMeshFlags} flag
83
+ * @returns {boolean}
84
+ */
85
+ getFlag(flag) {
86
+ return (this.flags & flag) === flag;
87
+ }
88
+
89
+ /**
90
+ *
91
+ * @return {boolean}
92
+ */
93
+ get castShadow() {
94
+ return this.getFlag(SGMeshFlags.CastShadow);
95
+ }
96
+
97
+ /**
98
+ *
99
+ * @param {boolean} v
100
+ */
101
+ set castShadow(v) {
102
+ this.writeFlag(SGMeshFlags.CastShadow, v);
103
+ }
104
+
105
+ /**
106
+ *
107
+ * @return {boolean}
108
+ */
109
+ get receiveShadow() {
110
+ return this.getFlag(SGMeshFlags.ReceiveShadow);
111
+ }
112
+
113
+ /**
114
+ *
115
+ * @param {boolean} v
116
+ */
117
+ set receiveShadow(v) {
118
+ this.writeFlag(SGMeshFlags.ReceiveShadow, v);
119
+ }
120
+ }
121
+
122
+ SGMesh.typeName = "SGMesh";
@@ -0,0 +1,148 @@
1
+ import { System } from "../../../../ecs/System.js";
2
+ import { SGMesh } from "./SGMesh.js";
3
+ import { Transform } from "../../../../ecs/transform/Transform.js";
4
+ import { assetTypeByPath } from "../../mesh/assetTypeByPath.js";
5
+ import { three_object_to_entity_composition } from "../three_object_to_entity_composition.js";
6
+
7
+
8
+ /**
9
+ * @readonly
10
+ * @type {number}
11
+ */
12
+ const CYCLE_COUNT = 128;
13
+
14
+ export class SGMeshSystem extends System {
15
+ /**
16
+ *
17
+ * @param {Engine} engine
18
+ */
19
+ constructor(engine) {
20
+ super();
21
+
22
+ /**
23
+ *
24
+ * @type {Engine}
25
+ * @private
26
+ */
27
+ this.__engine = engine;
28
+
29
+ /**
30
+ *
31
+ * @type {AssetManager}
32
+ * @private
33
+ */
34
+ this.__assetManager = engine.assetManager;
35
+
36
+ this.dependencies = [SGMesh, Transform];
37
+
38
+ /**
39
+ *
40
+ * @type {number[]}
41
+ * @private
42
+ */
43
+ this.__wait_queue = [];
44
+ /**
45
+ *
46
+ * @type {number}
47
+ * @private
48
+ */
49
+ this.__wait_queue_process_index = 0;
50
+ }
51
+
52
+ /**
53
+ *
54
+ * @param {number} entity
55
+ * @param {SGMesh} mesh
56
+ * @param {Transform} transform
57
+ */
58
+ async process(entity, mesh, transform) {
59
+ const am = this.__assetManager;
60
+
61
+ const assetType = assetTypeByPath(component.url);
62
+
63
+ const asset = await am.promise(mesh.url, assetType);
64
+
65
+ // TODO check that entity still exists, as we get to this point asynchronously
66
+
67
+ const entity_node = three_object_to_entity_composition(asset.create());
68
+
69
+ mesh.__node = entity_node;
70
+
71
+ entity_node.build(this.entityManager.dataset);
72
+ }
73
+
74
+ /**
75
+ *
76
+ * @param {SGMesh} mesh
77
+ * @param {Transform} transform
78
+ * @param {number} entity
79
+ */
80
+ link(mesh, transform, entity) {
81
+ if (mesh.url === null) {
82
+ this.__wait_queue.push(entity);
83
+ return;
84
+ }
85
+
86
+ this.process(entity, mesh, transform);
87
+ }
88
+
89
+ /**
90
+ *
91
+ * @param {SGMesh} mesh
92
+ * @param {Transform} transform
93
+ * @param {number} entity
94
+ */
95
+ unlink(mesh, transform, entity) {
96
+ if (mesh.__node !== null) {
97
+ mesh.__node.destroy();
98
+
99
+ mesh.__node = null;
100
+ }
101
+
102
+ // check the queue
103
+ const i = this.__wait_queue.indexOf(entity);
104
+
105
+ if (i !== -1) {
106
+ this.__wait_queue.splice(i, 1);
107
+ }
108
+ }
109
+
110
+ update(time_delta) {
111
+
112
+ const waiting = this.__wait_queue;
113
+
114
+ let l = waiting.length;
115
+
116
+ if (l > 0) {
117
+
118
+ let pointer = this.__wait_queue_process_index;
119
+
120
+ const dataset = this.entityManager.dataset;
121
+
122
+ for (let i = 0; i < CYCLE_COUNT; i++) {
123
+
124
+ const index = pointer % l;
125
+
126
+ const entity = waiting[index];
127
+
128
+ const mesh = dataset.getComponent(entity, SGMesh);
129
+
130
+ if (mesh.url === null) {
131
+ continue;
132
+ }
133
+
134
+ const transform = dataset.getComponent(entity, Transform);
135
+
136
+ this.process(entity, mesh, transform);
137
+
138
+ waiting.splice(i, 1);
139
+
140
+ i--;
141
+ l--;
142
+ }
143
+
144
+ this.__wait_queue_process_index = pointer;
145
+
146
+ }
147
+ }
148
+ }
@@ -3,6 +3,7 @@ import { HashMap } from "../../../../../../core/collection/HashMap.js";
3
3
  import { InstancedMeshGroup } from "../../../../geometry/instancing/InstancedMeshGroup.js";
4
4
  import { ShadedGeometryFlags } from "../../ShadedGeometryFlags.js";
5
5
  import { DrawMode } from "../../DrawMode.js";
6
+ import { StreamDrawUsage } from "three";
6
7
 
7
8
  const INSTANCED_EQUALITY_FLAGS = ShadedGeometryFlags.CastShadow
8
9
  | ShadedGeometryFlags.ReceiveShadow
@@ -89,6 +90,7 @@ export class InstancedRendererAdapter extends AbstractRenderAdapter {
89
90
  }
90
91
 
91
92
  im = new InstancedMeshGroup();
93
+ im.instance_usage = StreamDrawUsage;
92
94
  im.shrinkFactor = 0; // prevent shrinking as we're essentially rebuilding this geometry frame
93
95
 
94
96
  im.setMaterial(sg.material);
@@ -0,0 +1,4 @@
1
+ import {Object3D} from "three";
2
+ import {EntityNode} from "../../../ecs/parent/EntityNode";
3
+
4
+ export function three_object_to_entity_composition(object: Object3D): EntityNode
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  BufferGeometry,
3
+ DynamicDrawUsage,
3
4
  InstancedBufferAttribute,
4
5
  InstancedBufferGeometry,
5
6
  MeshDepthMaterial,
6
- RGBADepthPacking,
7
- StreamDrawUsage
7
+ RGBADepthPacking
8
8
  } from 'three';
9
9
  import ThreeFactory from "../../three/ThreeFactory.js";
10
10
  import { StaticMaterialCache } from "../../../asset/loaders/material/StaticMaterialCache.js";
@@ -77,6 +77,13 @@ export class InstancedMeshGroup {
77
77
  */
78
78
  this.__attributeTransformArray = null;
79
79
 
80
+ /**
81
+ *
82
+ * @type {Usage}
83
+ * @private
84
+ */
85
+ this.__instanceUsage = DynamicDrawUsage;
86
+
80
87
  /**
81
88
  *
82
89
  * @type {Material}
@@ -90,6 +97,30 @@ export class InstancedMeshGroup {
90
97
  this.mesh = ThreeFactory.createMesh();
91
98
  }
92
99
 
100
+ /**
101
+ *
102
+ * @param {Usage} v
103
+ */
104
+ set instance_usage(v) {
105
+ if (this.__instanceUsage === v) {
106
+ return;
107
+ }
108
+
109
+ this.__instanceUsage = v;
110
+
111
+ if (this.__attributeTransform !== null) {
112
+ this.__attributeTransform.setUsage(v);
113
+ }
114
+ }
115
+
116
+ /**
117
+ *
118
+ * @return {Usage}
119
+ */
120
+ get instance_usage() {
121
+ return this.__instanceUsage;
122
+ }
123
+
93
124
  dispose() {
94
125
  if (this.__threeGeometry !== null) {
95
126
  this.__threeGeometry.dispose();
@@ -367,7 +398,7 @@ export class InstancedMeshGroup {
367
398
  //rewrite old attributes
368
399
  this.__attributeTransformArray = newTransformArray;
369
400
  this.__attributeTransform = new InstancedBufferAttribute(newTransformArray, 16);
370
- this.__attributeTransform.setUsage(StreamDrawUsage);
401
+ this.__attributeTransform.setUsage(this.__instanceUsage);
371
402
 
372
403
  //add attributes to newly created geometry
373
404
  geometry.setAttribute("instanceMatrix", this.__attributeTransform);
@@ -0,0 +1,24 @@
1
+ import { deserialize_geometry_collection, serialize_geometry_collection } from "./geometry_collection_serialization.js";
2
+ import { BinaryBuffer } from "../../../../../../core/binary/BinaryBuffer.js";
3
+
4
+ test('serialize empty', () => {
5
+ const bb = new BinaryBuffer();
6
+
7
+ expect(() => {
8
+ serialize_geometry_collection([], bb);
9
+ }).not.toThrow();
10
+
11
+ });
12
+
13
+ test('serialize/deserialize empty', () => {
14
+ const bb = new BinaryBuffer();
15
+
16
+ serialize_geometry_collection([], bb);
17
+
18
+ bb.position = 0;
19
+
20
+ const destination = [];
21
+ deserialize_geometry_collection(destination, bb);
22
+
23
+ expect(destination.length).toBe(0);
24
+ });