@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
@@ -194,7 +194,7 @@ export class Deque {
194
194
 
195
195
  /**
196
196
  *
197
- * @param {E} e
197
+ * @param {T} e
198
198
  * @returns {number}
199
199
  * @private
200
200
  */
@@ -7,7 +7,37 @@ export class Color {
7
7
  g: number
8
8
  b: number
9
9
 
10
- onChanged: Signal
10
+ readonly onChanged: Signal<number, number, number, number, number, number>
11
11
 
12
- setRGB(r: number, g: number, b: number)
12
+ parse(text: string): void
13
+
14
+ setRGB(r: number, g: number, b: number): void
15
+
16
+ setHSL(h: number, s: number, l: number): void
17
+
18
+ setHCL(h: number, c: number, l: number): void
19
+
20
+ setHSI(h: number, s: number, i: number): void
21
+
22
+ setHSV(h: number, s: number, v: number): void
23
+
24
+ getHSV(): { h: number, s: number, v: number }
25
+
26
+ toUint(): number
27
+
28
+ fromUint(v: number): void
29
+
30
+ equals(other: Color): boolean
31
+
32
+ hash(): number
33
+
34
+ copy(other: Color): void
35
+
36
+ clone(): Color
37
+
38
+ fromJSON(json: any): void
39
+
40
+ toJSON(): any
41
+
42
+ writeToArray(destination: ArrayLike<number>, destination_offset: number): void
13
43
  }
@@ -10,7 +10,7 @@ import { computeHashFloat } from "../math/hash/computeHashFloat.js";
10
10
  import { rgb2hsv } from "./rgb2hsv.js";
11
11
  import { rgb2uint24 } from "./rgb2uint24.js";
12
12
 
13
- class Color {
13
+ export class Color {
14
14
  /**
15
15
  *
16
16
  * @param {number} r value from 0 to 1
@@ -37,6 +37,10 @@ class Color {
37
37
  */
38
38
  this.b = b;
39
39
 
40
+ /**
41
+ * @readonly
42
+ * @type {Signal<number,number,number,number,number,number>}
43
+ */
40
44
  this.onChanged = new Signal();
41
45
  }
42
46
 
@@ -384,30 +388,39 @@ class Color {
384
388
  this.setRGB(r, g, b);
385
389
  }
386
390
 
391
+ /**
392
+ *
393
+ * @param {string} str CSS-style string
394
+ * @returns {void}
395
+ */
387
396
  parse(str) {
388
397
  const c = parseColor(str);
389
398
 
390
399
  this.setRGB(c[0] / 255, c[1] / 255, c[2] / 255);
391
400
  }
392
- }
393
401
 
394
- /**
395
- *
396
- * @param {number} r
397
- * @param {number} g
398
- * @param {number} b
399
- * @returns {Color}
400
- */
401
- Color.fromRGB = function (r, g, b) {
402
- return new Color(r, g, b);
403
- };
402
+ /**
403
+ *
404
+ * @param {number} r
405
+ * @param {number} g
406
+ * @param {number} b
407
+ * @returns {Color}
408
+ */
409
+ static fromRGB(r, g, b) {
410
+ return new Color(r, g, b);
411
+ }
404
412
 
405
- Color.parse = function (str) {
406
- const r = new Color();
407
413
 
408
- r.parse(str);
414
+ /**
415
+ *
416
+ * @param {string} str
417
+ * @return {Color}
418
+ */
419
+ static parse(str) {
420
+ const r = new Color();
409
421
 
410
- return r;
411
- };
422
+ r.parse(str);
412
423
 
413
- export { Color };
424
+ return r;
425
+ };
426
+ }
@@ -10,6 +10,8 @@ export default class AABB2 {
10
10
 
11
11
  set(x0: number, y0: number, x1: number, y1: number): void
12
12
 
13
+ copy(other: AABB2): void
14
+
13
15
  setBoundsUnordered(x0: number, y0: number, x1: number, y1: number): void
14
16
 
15
17
  getHeight(): number
@@ -1003,6 +1003,11 @@ Vector3.typeName = "Vector3";
1003
1003
  Vector3._dot = v3_dot;
1004
1004
 
1005
1005
 
1006
+ /**
1007
+ *
1008
+ * @param {{x:number, y:number, z:number}} v
1009
+ * @return {number}
1010
+ */
1006
1011
  function v3Length(v) {
1007
1012
  return v3_length(v.x, v.y, v.z);
1008
1013
  }
@@ -1016,7 +1021,6 @@ function v3Length(v) {
1016
1021
  * @param {number} fraction
1017
1022
  */
1018
1023
  function v3Lerp(result, a, b, fraction) {
1019
-
1020
1024
  v3_lerp(result, a.x, a.y, a.z, b.x, b.y, b.z, fraction);
1021
1025
  }
1022
1026
 
@@ -213,6 +213,28 @@ export class ActionProcessor {
213
213
 
214
214
  }
215
215
 
216
+ /**
217
+ *
218
+ * @param {M} descriptor
219
+ * @returns {number[]}
220
+ * @private
221
+ */
222
+ __find_mark_indices_by_description(descriptor) {
223
+ const result = [];
224
+
225
+ const marks = this.marks;
226
+ const mark_count = marks.length;
227
+ for (let i = 0; i < mark_count; i++) {
228
+ const mark = marks[i];
229
+
230
+ if (mark.description === descriptor) {
231
+ result.push(i);
232
+ }
233
+ }
234
+
235
+ return result;
236
+ }
237
+
216
238
  /**
217
239
  * Navigate to a specific mark back or forward
218
240
  * @param {M} mark_descriptor specific mark description
@@ -221,7 +243,7 @@ export class ActionProcessor {
221
243
  */
222
244
  navigateTo(mark_descriptor) {
223
245
  // find all matching marks
224
- const matching_marks = this.marks.filter(m => m.description === mark_descriptor);
246
+ const matching_marks = this.__find_mark_indices_by_description(mark_descriptor);
225
247
 
226
248
  if (matching_marks.length === 0) {
227
249
  // not found
@@ -232,9 +254,15 @@ export class ActionProcessor {
232
254
  throw new Error(`Attempting to navigate to a non-unique mark descriptor '${mark_descriptor}'. Mark descriptor must be unique, instead ${matching_marks.length} matches were found`);
233
255
  }
234
256
 
235
- const match = matching_marks[0];
257
+ const match_index = matching_marks[0];
236
258
 
237
- const target = match.index;
259
+ let target;
260
+ if (match_index + 1 >= this.marks.length) {
261
+ // mark is already at the end, go to the end of the history
262
+ target = this.history.length;
263
+ } else {
264
+ target = this.marks[match_index + 1].index;
265
+ }
238
266
 
239
267
  let temp_cursor = this.cursor;
240
268
 
@@ -29,7 +29,7 @@ test('navigateTo backwards', () => {
29
29
 
30
30
  expect(ap.navigateTo('a')).toBe(true);
31
31
 
32
- expect(ctx.value).toBe(0);
32
+ expect(ctx.value).toBe(1);
33
33
  });
34
34
 
35
35
  test('navigateTo forward', () => {
@@ -1,4 +1,5 @@
1
1
  import { inverseLerp } from "../../../core/math/inverseLerp.js";
2
+ import { lerp } from "../../../core/math/lerp.js";
2
3
 
3
4
  /**
4
5
  *
@@ -32,12 +33,18 @@ function evaluate(t, keyframe0, keyframe1) {
32
33
  export class AnimationCurve {
33
34
  constructor() {
34
35
  /**
35
- *
36
+ * @readonly
36
37
  * @type {Keyframe[]}
37
38
  */
38
39
  this.keys = [];
39
40
  }
40
41
 
42
+
43
+ /**
44
+ *
45
+ * @param {number} t
46
+ * @return {number}
47
+ */
41
48
  evaluate(t) {
42
49
  const keys = this.keys;
43
50
  const key_count = keys.length;
@@ -69,4 +76,36 @@ export class AnimationCurve {
69
76
  return keys[key_count - 1].value;
70
77
  }
71
78
 
79
+
80
+ /**
81
+ *
82
+ * @param {number} index
83
+ * @param {number} weight
84
+ */
85
+ smoothTangents(index, weight) {
86
+ const keys = this.keys;
87
+
88
+ const key_main = keys[index];
89
+
90
+ const has_previous = index > 0;
91
+ if (has_previous) {
92
+ const key_previous = keys[index - 1];
93
+
94
+ key_main.inTangent = key_main.value - key_previous.value;
95
+ }
96
+
97
+ const has_next = index < keys.length - 1;
98
+ if (has_next) {
99
+ const key_next = keys[index + 1];
100
+
101
+ key_main.outTangent = key_next.value - key_main.value;
102
+ }
103
+
104
+ if (has_previous && has_next) {
105
+ const average = lerp(key_main.inTangent, key_main.outTangent, 0.5);
106
+ key_main.inTangent = average;
107
+ key_main.outTangent = average;
108
+ }
109
+
110
+ }
72
111
  }
@@ -24,4 +24,23 @@ export class Keyframe {
24
24
  */
25
25
  this.outTangent = 0;
26
26
  }
27
+
28
+ /**
29
+ *
30
+ * @param {number} time
31
+ * @param {number} value
32
+ * @param {number} inTangent
33
+ * @param {number} outTangent
34
+ * @return {Keyframe}
35
+ */
36
+ static from(time, value, inTangent = 0, outTangent = 0) {
37
+ const r = new Keyframe();
38
+
39
+ r.time = time;
40
+ r.value = value;
41
+ r.inTangent = inTangent;
42
+ r.outTangent = outTangent;
43
+
44
+ return r;
45
+ }
27
46
  }
@@ -0,0 +1,35 @@
1
+ //
2
+
3
+
4
+ /**
5
+ * Sample entire animation curve into a flat array with discrete equal intervals
6
+ * @param {number[]|Float32Array} out
7
+ * @param {number} out_offset
8
+ * @param {number} sample_count
9
+ * @param {AnimationCurve} curve
10
+ */
11
+ export function animation_curve_to_float_array(out, curve, out_offset = 0, sample_count = out.length) {
12
+ const keyframes = curve.keys;
13
+ const keyframe_count = keyframes.length;
14
+
15
+ if (keyframe_count === 0) {
16
+ // not data
17
+ out.fill(0, out_offset, out_offset + sample_count);
18
+ return;
19
+ }
20
+
21
+ const key_first = keyframes[0];
22
+ const key_last = keyframes[keyframe_count - 1];
23
+
24
+ const time_start = key_first.time;
25
+ const time_end = key_last.time;
26
+ const time_span = time_end - time_start;
27
+
28
+ for (let i = 0; i < sample_count; i++) {
29
+ const f = i / (sample_count - 1);
30
+
31
+ const t = time_start + (f * time_span);
32
+
33
+ out[i + out_offset] = curve.evaluate(t);
34
+ }
35
+ }
@@ -0,0 +1,50 @@
1
+ //
2
+
3
+ import { assert } from "../../../../core/assert.js";
4
+ import { lerp } from "../../../../core/math/lerp.js";
5
+
6
+ /**
7
+ *
8
+ * @param {number[]|Float32Array} output
9
+ * @param {number} output_offset
10
+ * @param {number[]|Float32Array} input
11
+ * @param {number} input_offset
12
+ * @param {number} input_size
13
+ */
14
+ export function downsample_float_array(output, output_offset, input, input_offset, input_size) {
15
+ assert.isNonNegativeInteger(output_offset, 'output_offset');
16
+ assert.isNonNegativeInteger(input_offset, 'input_offset');
17
+ assert.isNonNegativeInteger(input_size, 'input_size');
18
+
19
+ const target_samples = input_size / 2;
20
+
21
+ if (!Number.isInteger(target_samples)) {
22
+ throw new Error(`input_size(=${input_size}) is not divisible by 2`);
23
+ }
24
+
25
+ if (target_samples === 1) {
26
+ // special case, take average
27
+ output[output_offset] = (
28
+ input[input_offset] + input[input_offset + 1]
29
+ ) / 2;
30
+
31
+ return;
32
+ }
33
+
34
+ let i = 0;
35
+ for (; i < target_samples; i++) {
36
+
37
+ const f = i / (target_samples - 1);
38
+
39
+ const input_sample_offset = input_offset + i * 2;
40
+
41
+ const s0 = input[input_sample_offset];
42
+ const s1 = input[input_sample_offset + 1];
43
+
44
+ const sample_value = lerp(s0, s1, f);
45
+
46
+ // TODO implement decision scheme that would pick between min/max/average
47
+
48
+ output[output_offset + i] = sample_value;
49
+ }
50
+ }
@@ -0,0 +1,147 @@
1
+ //
2
+
3
+
4
+ import { max2 } from "../../../../core/math/max2.js";
5
+ import { lerp } from "../../../../core/math/lerp.js";
6
+ import { min3 } from "../../../../core/math/min3.js";
7
+ import { max3 } from "../../../../core/math/max3.js";
8
+ import { downsample_float_array } from "./downsample_float_array.js";
9
+ import { array_copy } from "../../../../core/collection/array/copyArray.js";
10
+ import { isPowerOfTwo } from "../../../../core/math/isPowerOrTwo.js";
11
+
12
+ /**
13
+ *
14
+ * @param {number[]} input
15
+ * @param {number} input_offset
16
+ * @param {number} input_size
17
+ * @param {number} f
18
+ * @return {number}
19
+ */
20
+ function array_sampler_linear(input, input_offset, input_size, f) {
21
+ const t = f * (input_size - 1);
22
+
23
+ const fraction = t % 1;
24
+
25
+ if (fraction === 0) {
26
+ return input[input_offset + t];
27
+ }
28
+
29
+ const i0 = Math.floor(t);
30
+ const i1 = i0 + 1;
31
+
32
+ const s0 = input[input_offset + i0];
33
+ const s1 = input[input_offset + i1];
34
+
35
+ return lerp(s0, s1, fraction);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @param {number[]|Float32Array} input0
41
+ * @param {number} input0_offset
42
+ * @param {number} input0_size
43
+ * @param {number[]|Float32Array} input1
44
+ * @param {number} input1_offset
45
+ * @param {number} input1_size
46
+ */
47
+ function compute_float_array_curve_linear_sampling_relative_error(
48
+ input0, input0_offset, input0_size,
49
+ input1, input1_offset, input1_size
50
+ ) {
51
+
52
+ const largest_size = max2(input0_size, input1_size);
53
+
54
+ let max_error = 0;
55
+
56
+ let min = Number.POSITIVE_INFINITY;
57
+ let max = Number.NEGATIVE_INFINITY;
58
+
59
+ for (let i = 0; i < largest_size; i++) {
60
+ const f = i / (largest_size - 1);
61
+
62
+ const s0 = array_sampler_linear(input0, input0_offset, input0_size, f);
63
+ const s1 = array_sampler_linear(input1, input1_offset, input1_size, f);
64
+
65
+ const error = Math.abs(s0 - s1);
66
+
67
+ if (error > max_error) {
68
+ max_error = error;
69
+ }
70
+
71
+ min = min3(min, s0, s1);
72
+ max = max3(max, s0, s1);
73
+ }
74
+
75
+ const span = max - min;
76
+
77
+ if (span === 0) {
78
+ // avoid division by 0
79
+ return 0;
80
+ }
81
+
82
+ return max_error / span;
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param {number[]} output
88
+ * @param {number} output_offset
89
+ * @param {number[]} input
90
+ * @param {number} input_offset
91
+ * @param {number} input_size
92
+ * @param {number} [error]
93
+ * @return {number}
94
+ */
95
+ export function downsample_float_array_curve_by_error(output, output_offset, input, input_offset, input_size, error = 0.03) {
96
+ if (!isPowerOfTwo(input_size)) {
97
+ throw new Error(`Input size must be a power of two, instead was '${input_size}'`);
98
+ }
99
+
100
+ const buffer_0 = new Float32Array(input_size >>> 1); // first step needs half the size of the original buffer
101
+ const buffer_1 = new Float32Array(input_size >>> 2); // next step needs quarter
102
+
103
+ let current = input;
104
+ let current_offset = input_offset;
105
+ let current_size = input_size;
106
+
107
+ let next = buffer_0;
108
+ let next_offset = 0;
109
+ let next_size = buffer_0.length;
110
+
111
+ let current_error = 0;
112
+
113
+ while (current_error <= error && current_size > 1) {
114
+ downsample_float_array(next, next_offset, current, current_offset, current_size);
115
+
116
+ current_error = compute_float_array_curve_linear_sampling_relative_error(
117
+ input, input_offset, input_size,
118
+ next, next_offset, next_size
119
+ );
120
+
121
+ if (current_error > error) {
122
+ // reached best possible reduction
123
+ break;
124
+ }
125
+
126
+ current = next;
127
+ current_offset = next_offset;
128
+ current_size = next_size;
129
+
130
+ // perform swap
131
+ if (next === buffer_0) {
132
+ next = buffer_1;
133
+ } else {
134
+ next = buffer_0;
135
+ }
136
+
137
+ next_offset = 0;
138
+ // next will be half the current size
139
+ next_size = current_size >>> 1;
140
+ }
141
+
142
+
143
+ // copy current buffer to output
144
+ array_copy(current, current_offset, output, output_offset, current_size);
145
+
146
+ return current_size;
147
+ }
@@ -0,0 +1,47 @@
1
+ import { downsample_float_array_curve_by_error } from "./downsample_float_array_curve_by_error.js";
2
+
3
+ test('1 constant to 1', () => {
4
+ const output = [];
5
+
6
+ const sample_count = downsample_float_array_curve_by_error(output, 0, [1], 0, 1, 0.1);
7
+
8
+ expect(sample_count).toBe(1);
9
+ expect(output[0]).toBe(1);
10
+ });
11
+
12
+ test('4 constants to 1', () => {
13
+ const output = [];
14
+
15
+ const sample_count = downsample_float_array_curve_by_error(output, 0, [1, 1, 1, 1], 0, 4, 0.1);
16
+
17
+ expect(sample_count).toBe(1);
18
+ expect(output[0]).toBe(1);
19
+ });
20
+
21
+ test('2 constants to 1 with error = 0', () => {
22
+ const output = [];
23
+
24
+ const sample_count = downsample_float_array_curve_by_error(output, 0, [1, 1], 0, 2, 0);
25
+
26
+ expect(sample_count).toBe(1);
27
+ expect(output[0]).toBe(1);
28
+ });
29
+
30
+ test('2 different constants to 2', () => {
31
+ const output = [];
32
+
33
+ const sample_count = downsample_float_array_curve_by_error(output, 0, [1, 2], 0, 2, 0.1);
34
+
35
+ expect(sample_count).toBe(2);
36
+ expect(output).toEqual([1, 2]);
37
+ });
38
+
39
+ test('4 to 2, linear scale compression', () => {
40
+ const output = [];
41
+
42
+ const sample_count = downsample_float_array_curve_by_error(output, 0, [1, 2, 3, 4], 0, 4, 0.1);
43
+
44
+ expect(sample_count).toBe(2);
45
+ expect(output[0]).toBeCloseTo(1);
46
+ expect(output[1]).toBeCloseTo(4);
47
+ });