@woosh/meep-engine 2.49.8 → 2.49.9

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 (58) hide show
  1. package/package.json +1 -1
  2. package/src/core/math/bessel_i0.spec.js +43 -0
  3. package/src/core/math/bessel_j0.js +30 -0
  4. package/src/core/math/hash/murmur3_32.spec.js +8 -0
  5. package/src/core/math/hash/squirrel3.spec.js +16 -0
  6. package/src/core/math/interval/NumericInterval.js +1 -0
  7. package/src/core/math/{bessel_i0.js → modified_bessel_i0.js} +5 -2
  8. package/src/core/math/noise/{create_noise_2d.js → create_simplex_noise_2d.js} +2 -2
  9. package/src/core/math/noise/create_simplex_noise_2d.spec.js +21 -0
  10. package/src/core/math/normalizeArrayVector.spec.js +15 -0
  11. package/src/core/math/physics/irradiance/interpolate_irradiance_linear.spec.js +20 -0
  12. package/src/core/math/physics/irradiance/interpolate_irradiance_lograrithmic.js +4 -4
  13. package/src/core/math/physics/irradiance/interpolate_irradiance_lograrithmic.spec.js +18 -0
  14. package/src/core/math/physics/irradiance/interpolate_irradiance_smith.js +1 -1
  15. package/src/core/math/physics/irradiance/interpolate_irradiance_smith.spec.js +20 -0
  16. package/src/core/math/random/seededRandomMersenneTwister.spec.js +10 -0
  17. package/src/core/math/spline/spline_bezier3.js +1 -1
  18. package/src/core/math/spline/spline_bezier3_bounds.js +2 -1
  19. package/src/core/math/spline/spline_bezier3_bounds.spec.js +37 -0
  20. package/src/core/math/statistics/computeSampleSize_Cochran.spec.js +12 -0
  21. package/src/core/math/statistics/computeStatisticalPartialMedian.js +4 -0
  22. package/src/core/math/statistics/computeStatisticalPartialMedian.spec.js +13 -0
  23. package/src/engine/achievements/Achievement.spec.js +21 -0
  24. package/src/engine/animation/curve/compression/prototypeCurveCompression.js +2 -2
  25. package/src/engine/animation/curve/compression/{animation_curve_to_float_array.js → sample_animation_curve_to_float_array.js} +10 -3
  26. package/src/engine/animation/curve/compression/sample_animation_curve_to_float_array.spec.js +29 -0
  27. package/src/engine/animation/curve/draw/build_curve_editor.js +3 -3
  28. package/src/engine/development/performance/RingBufferMetric.js +1 -1
  29. package/src/engine/ecs/animation/Animation.js +2 -180
  30. package/src/engine/ecs/animation/AnimationClip.js +132 -0
  31. package/src/engine/ecs/animation/AnimationClip.spec.js +5 -0
  32. package/src/engine/ecs/animation/AnimationClipFlag.js +7 -0
  33. package/src/engine/ecs/animation/AnimationFlags.js +8 -0
  34. package/src/engine/ecs/animation/AnimationSerializationAdapter.js +32 -0
  35. package/src/engine/ecs/systems/AnimationSystem.js +3 -1
  36. package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
  37. package/src/engine/graphics/ecs/animation/AnimationControllerSystem.js +2 -1
  38. package/src/engine/graphics/ecs/path/testPathDisplaySystem.js +1 -1
  39. package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
  40. package/src/engine/graphics/load_and_set_cubemap_v0.js +1 -1
  41. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
  42. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
  43. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
  44. package/src/engine/graphics/texture/sampler/filter/kaiser_1.js +8 -4
  45. package/src/engine/graphics/texture/sampler/filter/kaiser_bessel_window.js +2 -0
  46. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js +11 -4
  47. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.spec.js +30 -0
  48. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +18 -2
  49. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.spec.js +17 -0
  50. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +2 -2
  51. package/src/generation/grid/GridData.js +0 -60
  52. /package/src/engine/ecs/{animation → ik}/IKMath.js +0 -0
  53. /package/src/engine/ecs/{animation → ik}/IKProblem.js +0 -0
  54. /package/src/engine/ecs/{animation → ik}/IKSolver.js +0 -0
  55. /package/src/engine/ecs/{animation → ik}/InverseKinematics.js +0 -0
  56. /package/src/engine/ecs/{animation → ik}/InverseKinematicsSystem.js +0 -0
  57. /package/src/engine/ecs/{animation → ik}/OneBoneSurfaceAlignmentSolver.js +0 -0
  58. /package/src/engine/ecs/{animation → ik}/TwoBoneInverseKinematicsSolver.js +0 -0
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "description": "Fully featured ECS game engine written in JavaScript",
6
6
  "type": "module",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.49.8",
8
+ "version": "2.49.9",
9
9
  "main": "build/meep.module.js",
10
10
  "module": "build/meep.module.js",
11
11
  "exports": {
@@ -0,0 +1,43 @@
1
+ import { bessel_j0 } from "./bessel_j0.js";
2
+
3
+ test("sanity", () => {
4
+ const v = bessel_j0(0);
5
+
6
+ expect(typeof v).toBe("number");
7
+ expect(v).not.toBeNaN();
8
+
9
+ });
10
+
11
+ test("sweep through low positive values", () => {
12
+ // https://www.wolframalpha.com/input?i=besselj%5B0%2Cx%5D
13
+
14
+ expect(bessel_j0(0)).toBeCloseTo(1, 1);
15
+ expect(bessel_j0(0.1)).toBeCloseTo(0.997, 1);
16
+ expect(bessel_j0(0.2)).toBeCloseTo(0.990, 1);
17
+ expect(bessel_j0(0.3)).toBeCloseTo(0.977, 1);
18
+ expect(bessel_j0(0.4)).toBeCloseTo(0.960, 1);
19
+ expect(bessel_j0(0.5)).toBeCloseTo(0.938, 1);
20
+ expect(bessel_j0(0.6)).toBeCloseTo(0.912, 1);
21
+ expect(bessel_j0(0.7)).toBeCloseTo(0.881, 1);
22
+ expect(bessel_j0(0.8)).toBeCloseTo(0.846, 1);
23
+ expect(bessel_j0(0.9)).toBeCloseTo(0.807, 1);
24
+ expect(bessel_j0(1)).toBeCloseTo(0.765, 1);
25
+ expect(bessel_j0(2)).toBeCloseTo(0.223, 1);
26
+
27
+ });
28
+
29
+ test("roots", () => {
30
+
31
+ expect(bessel_j0(2.40482555769577)).toBeCloseTo(0, 1);
32
+ expect(bessel_j0(-2.40482555769577)).toBeCloseTo(0, 1);
33
+
34
+ expect(bessel_j0(5.52007811028631)).toBeCloseTo(0, 1);
35
+ expect(bessel_j0(-5.52007811028631)).toBeCloseTo(0, 1);
36
+
37
+ expect(bessel_j0(8.65372791291101)).toBeCloseTo(0, 1);
38
+ expect(bessel_j0(-8.65372791291101)).toBeCloseTo(0, 1);
39
+
40
+ expect(bessel_j0(11.7915344390143)).toBeCloseTo(0, 1);
41
+ expect(bessel_j0(-11.7915344390143)).toBeCloseTo(0, 1);
42
+
43
+ });
@@ -0,0 +1,30 @@
1
+ import { assert } from "../assert.js";
2
+
3
+ const q = 0.7172491568;
4
+ const p0 = 0.6312725339;
5
+ const ps0 = 0.4308049446;
6
+ const p1 = 0.3500347951;
7
+ const ps1 = 0.4678202347;
8
+ const p2 = -0.06207747907;
9
+ const ps2 = 0.04253832927;
10
+ // const lamb4 = (lamb * lamb) * (lamb * lamb);
11
+ const lamb4 = 0.559840650625;
12
+
13
+ /**
14
+ * Bessel first order function of zeroth order
15
+ * @see https://link.springer.com/article/10.1007/s40314-020-01238-z
16
+ * @see https://www.shadertoy.com/view/Wt3czM
17
+ * @param {number} x
18
+ * @return {number}
19
+ */
20
+ export function bessel_j0(x) {
21
+ assert.notNaN(x, 'x');
22
+
23
+ const xx = x * x;
24
+
25
+ const t0 = Math.sqrt(1.0 + lamb4 * xx);
26
+ const t1 = Math.sqrt(t0);
27
+
28
+ return xx === 0.0 ? 1.0 : 1.0 / (t1 * (1.0 + q * xx)) * ((p0 + p1 * xx + p2 * t0) * Math.cos(x) + ((ps0 + ps1 * xx) * t0 + ps2 * xx) * (Math.sin(x) / x));
29
+
30
+ }
@@ -0,0 +1,8 @@
1
+ import { murmur3_32 } from "./murmur3_32.js";
2
+
3
+ test("hashing [0] with 0 seed", () => {
4
+ const hash = murmur3_32(new Uint32Array([0]), 0);
5
+ expect(typeof hash).toBe("number");
6
+ expect(Number.isInteger(hash)).toBe(true);
7
+ expect(hash).not.toBeNaN();
8
+ });
@@ -0,0 +1,16 @@
1
+ import { squirrel3 } from "./squirrel3.js";
2
+
3
+ function expect_integer(v) {
4
+
5
+ expect(typeof v).toBe("number");
6
+ expect(Number.isInteger(v)).toBe(true);
7
+ expect(v).not.toBeNaN();
8
+ }
9
+
10
+ test("sanity check", () => {
11
+ expect_integer(squirrel3(0));
12
+ expect_integer(squirrel3(1));
13
+ expect_integer(squirrel3(-1));
14
+
15
+ expect(squirrel3(0)).not.toEqual(squirrel3(1));
16
+ });
@@ -111,6 +111,7 @@ export class NumericInterval {
111
111
 
112
112
  /**
113
113
  * Whether min and max are the same
114
+ * In other words if span is 0
114
115
  * @returns {boolean}
115
116
  */
116
117
  isExact() {
@@ -1,14 +1,16 @@
1
1
  import { assert } from "../assert.js";
2
2
 
3
+ // const lamb = 0.865;
4
+
3
5
  /**
4
- * Modified bessel function of the first kind
6
+ * Modified bessel function of the first kind zeroth order
5
7
  * @see https://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
6
8
  * @see https://computergraphics.stackexchange.com/questions/6393/kaiser-windowed-sinc-filter-for-mip-mapping
7
9
  * @see https://github.com/terifan/ImageResampler/blob/dbc212ce6aaa769bf3c9623cb6ead58ffd51d76c/src/org/terifan/image_resampler/FilterFactory.java
8
10
  * @param {number} x
9
11
  * @returns {number}
10
12
  */
11
- export function bessel_i0(x) {
13
+ export function modified_bessel_i0(x) {
12
14
  assert.notNaN(x, 'x');
13
15
 
14
16
  let sum = 1.0;
@@ -24,3 +26,4 @@ export function bessel_i0(x) {
24
26
 
25
27
  return sum;
26
28
  }
29
+
@@ -58,9 +58,9 @@ const grad4 = /*#__PURE__*/ new Float64Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1,
58
58
  /**
59
59
  * Creates a 2D noise function
60
60
  * @param random the random function that will be used to build the permutation table
61
- * @returns {function(x:number, y:number):number}
61
+ * @returns {function(x:number, y:number):number} producing values in range -1 .. 1
62
62
  */
63
- export function createNoise2D(random = Math.random) {
63
+ export function create_simplex_noise_2d(random = Math.random) {
64
64
  // allocate continuous chunk of memory
65
65
  const buffer = new ArrayBuffer(512 * 2);
66
66
 
@@ -0,0 +1,21 @@
1
+ import { create_simplex_noise_2d } from "./create_simplex_noise_2d.js";
2
+
3
+ test("sample 10 points in 0..1 range", () => {
4
+ const noise2d = create_simplex_noise_2d(() => 0);
5
+
6
+ const COUNT = 10;
7
+ for (let i = 0; i < COUNT; i++) {
8
+ const u = i / (COUNT - 1);
9
+ for (let j = 0; j < COUNT; j++) {
10
+ const v = j / (COUNT - 1);
11
+
12
+ const value = noise2d(u, v);
13
+
14
+ expect(typeof value).toBe('number');
15
+ expect(value).not.toBeNaN();
16
+
17
+ expect(value).toBeGreaterThanOrEqual(-1);
18
+ expect(value).toBeLessThanOrEqual(1);
19
+ }
20
+ }
21
+ });
@@ -0,0 +1,15 @@
1
+ import { normalizeArrayVector } from "./normalizeArrayVector.js";
2
+
3
+ test("1d vector normalization", () => {
4
+ const v = [7];
5
+
6
+ normalizeArrayVector(v, v, 1);
7
+
8
+ expect(v[0]).toBeCloseTo(1);
9
+
10
+ v[0] = -13;
11
+
12
+ normalizeArrayVector(v, v, 1);
13
+
14
+ expect(v[0]).toBeCloseTo(-1);
15
+ });
@@ -0,0 +1,20 @@
1
+ import { interpolate_irradiance_linear } from "./interpolate_irradiance_linear.js";
2
+
3
+ test("sanity check", () => {
4
+
5
+ expect(interpolate_irradiance_linear(3, 3, 3)).toBe(1);
6
+
7
+ expect(interpolate_irradiance_linear(3, 3, 7)).toBe(1);
8
+
9
+ expect(interpolate_irradiance_linear(7, 3, 7)).toBe(0);
10
+
11
+ expect(interpolate_irradiance_linear(0, 3, 7)).toBe(1);
12
+
13
+ expect(interpolate_irradiance_linear(11, 3, 7)).toBe(0);
14
+
15
+ const mid = interpolate_irradiance_linear(5, 3, 7);
16
+
17
+ expect(mid).toBeGreaterThan(0);
18
+ expect(mid).toBeLessThan(1);
19
+
20
+ });
@@ -1,19 +1,19 @@
1
1
  import { inverseLerp } from "../../inverseLerp.js";
2
2
 
3
3
  /**
4
- *
4
+ * Approximates logarithmic decay, except it goes to 0 at max
5
+ * Estimation is done using a 5th degree polynomial
5
6
  * @param {number} distance
6
7
  * @param {number} min
7
8
  * @param {number} max
8
9
  * @return {number}
9
10
  */
10
11
  export function interpolate_irradiance_lograrithmic(distance, min, max) {
11
- if (distance >= max) {
12
- return 0;
13
- }
14
12
 
15
13
  if (distance <= min) {
16
14
  return 1;
15
+ } else if (distance >= max) {
16
+ return 0;
17
17
  }
18
18
 
19
19
  const x = inverseLerp(min, max, distance);
@@ -0,0 +1,18 @@
1
+ import { interpolate_irradiance_lograrithmic } from "./interpolate_irradiance_lograrithmic.js";
2
+
3
+ test("sanity check", () => {
4
+ expect(interpolate_irradiance_lograrithmic(3, 3, 3)).toBe(1);
5
+
6
+ expect(interpolate_irradiance_lograrithmic(3, 3, 7)).toBe(1);
7
+
8
+ expect(interpolate_irradiance_lograrithmic(7, 3, 7)).toBe(0);
9
+
10
+ expect(interpolate_irradiance_lograrithmic(0, 3, 7)).toBe(1);
11
+
12
+ expect(interpolate_irradiance_lograrithmic(11, 3, 7)).toBe(0);
13
+
14
+ const mid = interpolate_irradiance_lograrithmic(5, 3, 7);
15
+
16
+ expect(mid).toBeGreaterThan(0);
17
+ expect(mid).toBeLessThan(1);
18
+ });
@@ -8,7 +8,7 @@
8
8
  */
9
9
  export function interpolate_irradiance_smith(distance, min, max) {
10
10
 
11
- if (distance < min) {
11
+ if (distance <= min) {
12
12
  return 1;
13
13
  } else if (distance > max) {
14
14
  return 0;
@@ -0,0 +1,20 @@
1
+ import { interpolate_irradiance_smith } from "./interpolate_irradiance_smith.js";
2
+
3
+ test("sanity check", () => {
4
+
5
+ expect(interpolate_irradiance_smith(3, 3, 3)).toBe(1);
6
+
7
+ expect(interpolate_irradiance_smith(3, 3, 7)).toBe(1);
8
+
9
+ expect(interpolate_irradiance_smith(7, 3, 7)).toBe(0);
10
+
11
+ expect(interpolate_irradiance_smith(0, 3, 7)).toBe(1);
12
+
13
+ expect(interpolate_irradiance_smith(11, 3, 7)).toBe(0);
14
+
15
+ const mid = interpolate_irradiance_smith(5, 3, 7);
16
+
17
+ expect(mid).toBeGreaterThan(0);
18
+ expect(mid).toBeLessThan(1);
19
+
20
+ });
@@ -0,0 +1,10 @@
1
+ import { seededRandomMersenneTwister } from "./seededRandomMersenneTwister.js";
2
+
3
+ test("sanity", () => {
4
+ const rng = seededRandomMersenneTwister(0);
5
+
6
+ const v0 = rng();
7
+
8
+ expect(v0).toBeLessThanOrEqual(1);
9
+ expect(v0).toBeGreaterThanOrEqual(0);
10
+ });
@@ -9,7 +9,7 @@
9
9
  * @param {number} p3 end point
10
10
  * @returns {number}
11
11
  */
12
- function spline_bezier3(t, p0, p1, p2, p3) {
12
+ export function spline_bezier3(t, p0, p1, p2, p3) {
13
13
  // first we compute necessary factors for each point
14
14
  const nt = 1 - t;
15
15
 
@@ -1,6 +1,7 @@
1
1
  import { min2 } from "../min2.js";
2
2
  import { max2 } from "../max2.js";
3
3
  import { assert } from "../../assert.js";
4
+ import { spline_bezier3 } from './spline_bezier3'
4
5
 
5
6
  /**
6
7
  * Compute bounds of a 3-rd degree bezier curve
@@ -15,7 +16,7 @@ import { assert } from "../../assert.js";
15
16
  * @param {number} p2
16
17
  * @param {number} p3
17
18
  */
18
- function spline_bezier3_bounds(result, result_offset, result_stride, p0, p1, p2, p3) {
19
+ export function spline_bezier3_bounds(result, result_offset, result_stride, p0, p1, p2, p3) {
19
20
  assert.greaterThan(result_stride, 0, 'result_stride must be greater than 0');
20
21
  assert.isInteger(result_stride, 'result_stride');
21
22
 
@@ -0,0 +1,37 @@
1
+ import { spline_bezier3_bounds } from "./spline_bezier3_bounds.js";
2
+
3
+ test("spline of 0 length", () => {
4
+ const bounds = [];
5
+
6
+ spline_bezier3_bounds(
7
+ bounds, 0, 1,
8
+ 0, 0, 0, 0
9
+ );
10
+
11
+ expect(bounds[0]).toBeCloseTo(0);
12
+ expect(bounds[1]).toBeCloseTo(0);
13
+ });
14
+
15
+ test("line-shaped spline of", () => {
16
+ const bounds = [];
17
+
18
+ spline_bezier3_bounds(
19
+ bounds, 0, 1,
20
+ -2, -1, 1, 2
21
+ );
22
+
23
+ expect(bounds[0]).toBeCloseTo(-2);
24
+ expect(bounds[1]).toBeCloseTo(2);
25
+ });
26
+
27
+ test("overshooting control point spline of", () => {
28
+ const bounds = [];
29
+
30
+ spline_bezier3_bounds(
31
+ bounds, 0, 1,
32
+ 0, -1, 1, 0
33
+ );
34
+
35
+ expect(bounds[0]).toBeLessThan(0);
36
+ expect(bounds[1]).toBeGreaterThan(0);
37
+ });
@@ -0,0 +1,12 @@
1
+ import { computeSampleSize_Cochran } from "./computeSampleSize_Cochran.js";
2
+
3
+ test("sanity for 95% confidence", () => {
4
+ function execute(error) {
5
+ return Math.floor(computeSampleSize_Cochran(1.95996, 0.5, error))
6
+ }
7
+
8
+ expect(execute(0.05)).toEqual(384);
9
+ expect(execute(0.035)).toEqual(783);
10
+ expect(execute(0.025)).toEqual(1536);
11
+ expect(execute(0.01)).toEqual(9603);
12
+ });
@@ -1,4 +1,5 @@
1
1
  import { compareNumbersAscending } from "../../function/Functions.js";
2
+ import { assert } from "../../assert.js";
2
3
 
3
4
  /**
4
5
  *
@@ -8,6 +9,9 @@ import { compareNumbersAscending } from "../../function/Functions.js";
8
9
  * @returns {number}
9
10
  */
10
11
  export function computeStatisticalPartialMedian(values, start, end) {
12
+ assert.isArrayLike(values, 'values');
13
+ assert.lessThan(end, values.length);
14
+
11
15
  const copy = values.slice();
12
16
 
13
17
  copy.sort(compareNumbersAscending);
@@ -0,0 +1,13 @@
1
+ import { computeStatisticalPartialMedian } from "./computeStatisticalPartialMedian.js";
2
+
3
+ test("single item", () => {
4
+ expect(computeStatisticalPartialMedian([7], 0, 0)).toEqual(7);
5
+ });
6
+
7
+ test("[1,2,1]", () => {
8
+ expect(computeStatisticalPartialMedian([1,2,1], 0, 2)).toEqual(1);
9
+ });
10
+
11
+ test("[7,6,2,4,1]", () => {
12
+ expect(computeStatisticalPartialMedian([7,6,2,4,1], 0, 4)).toEqual(4);
13
+ });
@@ -0,0 +1,21 @@
1
+ import { Achievement } from "./Achievement.js";
2
+
3
+ test("constructor does not throw", () => {
4
+ expect(() => new Achievement()).not.toThrow();
5
+ });
6
+
7
+ test("fromJSON correctness", () => {
8
+ const achievement = new Achievement();
9
+
10
+ achievement.fromJSON({
11
+ id: "x",
12
+ condition: "hello == kitty",
13
+ icon: "y",
14
+ secret: true
15
+ });
16
+
17
+ expect(achievement.id).toEqual("x");
18
+ expect(achievement.condition).toEqual("hello == kitty");
19
+ expect(achievement.icon).toEqual("y");
20
+ expect(achievement.secret).toEqual(true);
21
+ });
@@ -4,7 +4,7 @@ import { Keyframe } from "../Keyframe.js";
4
4
  import { EngineConfiguration } from "../../../EngineConfiguration.js";
5
5
  import GUIElementSystem from "../../../ecs/gui/GUIElementSystem.js";
6
6
  import ViewportPositionSystem from "../../../ecs/gui/position/ViewportPositionSystem.js";
7
- import { animation_curve_to_float_array } from "./animation_curve_to_float_array.js";
7
+ import { sample_animation_curve_to_float_array } from "./sample_animation_curve_to_float_array.js";
8
8
  import { downsample_float_array_curve_by_error } from "./downsample_float_array_curve_by_error.js";
9
9
  import { build_plot_entity_from_array } from "../draw/build_plot_entity_from_array.js";
10
10
 
@@ -28,7 +28,7 @@ async function init() {
28
28
 
29
29
  function build_error_scale(ecd, curve) {
30
30
  const curve_data = [];
31
- animation_curve_to_float_array(curve_data, curve, 0, 256);
31
+ sample_animation_curve_to_float_array(curve_data, 0, curve, 256);
32
32
 
33
33
 
34
34
  build_plot_entity_from_array({
@@ -5,10 +5,15 @@
5
5
  * Sample entire animation curve into a flat array with discrete equal intervals
6
6
  * @param {number[]|Float32Array} out
7
7
  * @param {number} out_offset
8
- * @param {number} sample_count
9
8
  * @param {AnimationCurve} curve
9
+ * @param {number} sample_count
10
10
  */
11
- export function animation_curve_to_float_array(out, curve, out_offset = 0, sample_count = out.length) {
11
+ export function sample_animation_curve_to_float_array(
12
+ out,
13
+ out_offset,
14
+ curve,
15
+ sample_count = out.length
16
+ ) {
12
17
  const keyframes = curve.keys;
13
18
  const keyframe_count = keyframes.length;
14
19
 
@@ -25,8 +30,10 @@ export function animation_curve_to_float_array(out, curve, out_offset = 0, sampl
25
30
  const time_end = key_last.time;
26
31
  const time_span = time_end - time_start;
27
32
 
33
+ const multiplier = sample_count > 1 ? 1 / (sample_count - 1) : 0;
34
+
28
35
  for (let i = 0; i < sample_count; i++) {
29
- const f = i / (sample_count - 1);
36
+ const f = i * multiplier;
30
37
 
31
38
  const t = time_start + (f * time_span);
32
39
 
@@ -0,0 +1,29 @@
1
+ import { AnimationCurve } from "../AnimationCurve.js";
2
+ import { sample_animation_curve_to_float_array } from "./sample_animation_curve_to_float_array.js";
3
+ import { Keyframe } from "../Keyframe.js";
4
+
5
+ test("0 samples should produce valid result", () => {
6
+
7
+ const curve = new AnimationCurve();
8
+
9
+ const result = new Float32Array(0);
10
+
11
+ expect(() => sample_animation_curve_to_float_array(result, 0, curve, 0)).not.toThrow();
12
+ });
13
+
14
+ test("3 samples on a line", () => {
15
+
16
+
17
+ const curve = new AnimationCurve();
18
+ curve.add(Keyframe.from(-1, -1))
19
+ curve.add(Keyframe.from(1, 7))
20
+
21
+ curve.smoothAllTangents();
22
+
23
+ const result = new Float32Array(3);
24
+ sample_animation_curve_to_float_array(result, 0, curve, 3);
25
+
26
+ expect(result[0]).toBeCloseTo(-1);
27
+ expect(result[1]).toBeCloseTo(3);
28
+ expect(result[2]).toBeCloseTo(7);
29
+ });
@@ -1,10 +1,10 @@
1
1
  import Vector2 from "../../../../core/geom/Vector2.js";
2
2
  import EmptyView from "../../../../view/elements/EmptyView.js";
3
3
  import { CanvasView } from "../../../../view/elements/CanvasView.js";
4
- import AABB2 from "../../../../core/geom/AABB2.js";
4
+ import AABB2 from "../../../../core/geom/2d/aabb/AABB2.js";
5
5
  import Signal from "../../../../core/events/signal/Signal.js";
6
6
  import ObservedValue from "../../../../core/model/ObservedValue.js";
7
- import { animation_curve_to_float_array } from "../compression/animation_curve_to_float_array.js";
7
+ import { sample_animation_curve_to_float_array } from "../compression/sample_animation_curve_to_float_array.js";
8
8
  import { plot_data } from "./plot_data.js";
9
9
  import { DraggableAspect } from "../../../ui/DraggableAspect.js";
10
10
  import { min2 } from "../../../../core/math/min2.js";
@@ -123,7 +123,7 @@ export function build_curve_editor({
123
123
  const width = graph.size.x;
124
124
  const data = new Float32Array(width);
125
125
 
126
- animation_curve_to_float_array(data, curve, 0, data.length);
126
+ sample_animation_curve_to_float_array(data, 0, curve, data.length);
127
127
 
128
128
  plot_data({
129
129
  data,
@@ -58,7 +58,7 @@ export class RingBufferMetric extends AbstractMetric {
58
58
  } else {
59
59
 
60
60
  result.mean = computeStatisticalMean(array, 0, data_count);
61
- result.median = computeStatisticalPartialMedian(array, 0, data_count);
61
+ result.median = computeStatisticalPartialMedian(array, 0, data_count - 1);
62
62
  result.max = computeArrayMax(array, 0, data_count);
63
63
  result.min = computeArrayMin(array, 0, data_count);
64
64