@woosh/meep-engine 2.165.7 → 2.167.0

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 (146) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/package.json +1 -1
  3. package/src/CORRECTNESS_HUNT_2026_07_24.md +142 -0
  4. package/src/core/binary/BinaryBuffer.js +1 -1
  5. package/src/core/binary/EncodingBinaryBuffer.d.ts.map +1 -1
  6. package/src/core/binary/EncodingBinaryBuffer.js +3 -1
  7. package/src/core/cache/Cache.d.ts.map +1 -1
  8. package/src/core/cache/Cache.js +0 -4
  9. package/src/core/cache/CacheElement.d.ts.map +1 -1
  10. package/src/core/cache/CacheElement.js +4 -0
  11. package/src/core/cache/FrequencySketch.d.ts +0 -21
  12. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  13. package/src/core/cache/FrequencySketch.js +66 -65
  14. package/src/core/cache/wtinylfu/CacheElementWTinylfu.d.ts +17 -0
  15. package/src/core/cache/wtinylfu/CacheElementWTinylfu.d.ts.map +1 -0
  16. package/src/core/cache/wtinylfu/CacheElementWTinylfu.js +18 -0
  17. package/src/core/cache/wtinylfu/CacheRegionType.d.ts +10 -0
  18. package/src/core/cache/wtinylfu/CacheRegionType.d.ts.map +1 -0
  19. package/src/core/cache/wtinylfu/CacheRegionType.js +10 -0
  20. package/src/core/cache/wtinylfu/CacheRegionWTinylfu.d.ts +66 -0
  21. package/src/core/cache/wtinylfu/CacheRegionWTinylfu.d.ts.map +1 -0
  22. package/src/core/cache/wtinylfu/CacheRegionWTinylfu.js +183 -0
  23. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts +279 -0
  24. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -0
  25. package/src/core/cache/wtinylfu/CacheWTinylfu.js +807 -0
  26. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +243 -0
  27. package/src/core/cache/wtinylfu/wtinylfuDecisions.d.ts +80 -0
  28. package/src/core/cache/wtinylfu/wtinylfuDecisions.d.ts.map +1 -0
  29. package/src/core/cache/wtinylfu/wtinylfuDecisions.js +102 -0
  30. package/src/core/collection/RingBuffer.d.ts.map +1 -1
  31. package/src/core/collection/RingBuffer.js +15 -3
  32. package/src/core/collection/map/HashMap.d.ts +1 -1
  33. package/src/core/collection/map/HashMap.d.ts.map +1 -1
  34. package/src/core/collection/map/HashMap.js +5 -17
  35. package/src/core/geom/2d/line/line2_compute_segment_point_distance_sqr.d.ts.map +1 -1
  36. package/src/core/geom/2d/line/line2_compute_segment_point_distance_sqr.js +5 -0
  37. package/src/core/geom/ConicRay.d.ts.map +1 -1
  38. package/src/core/geom/ConicRay.js +9 -2
  39. package/src/core/geom/Quaternion.d.ts.map +1 -1
  40. package/src/core/geom/Quaternion.js +7 -3
  41. package/src/core/math/build_gaussian_kernel_1d.d.ts +2 -2
  42. package/src/core/math/build_gaussian_kernel_1d.d.ts.map +1 -1
  43. package/src/core/math/build_gaussian_kernel_1d.js +4 -2
  44. package/src/core/math/build_gaussian_kernel_2d.d.ts +2 -2
  45. package/src/core/math/build_gaussian_kernel_2d.d.ts.map +1 -1
  46. package/src/core/math/build_gaussian_kernel_2d.js +4 -2
  47. package/src/core/math/gaussian.d.ts +2 -2
  48. package/src/core/math/gaussian.js +2 -2
  49. package/src/core/math/spline/spline3_bezier_bounds.d.ts.map +1 -1
  50. package/src/core/math/spline/spline3_bezier_bounds.js +10 -3
  51. package/src/core/math/spline/spline3_bezier_to_hermite.d.ts +8 -7
  52. package/src/core/math/spline/spline3_bezier_to_hermite.d.ts.map +1 -1
  53. package/src/core/math/spline/spline3_bezier_to_hermite.js +13 -12
  54. package/src/core/math/spline/spline3_hermite_bounds.d.ts.map +1 -1
  55. package/src/core/math/spline/spline3_hermite_bounds.js +10 -3
  56. package/src/core/math/spline/spline3_hermite_bounds_t.d.ts.map +1 -1
  57. package/src/core/math/spline/spline3_hermite_bounds_t.js +115 -109
  58. package/src/core/math/spline/spline3_hermite_to_bezier.d.ts +2 -0
  59. package/src/core/math/spline/spline3_hermite_to_bezier.d.ts.map +1 -1
  60. package/src/core/math/spline/spline3_hermite_to_bezier.js +2 -0
  61. package/src/core/math/statistics/computeStatisticalMean.js +1 -1
  62. package/src/core/math/statistics/gaussian_amplitude.d.ts.map +1 -1
  63. package/src/core/math/statistics/gaussian_amplitude.js +3 -1
  64. package/src/engine/animation/clip/ecd_bind_animation_curve.d.ts.map +1 -1
  65. package/src/engine/animation/clip/ecd_bind_animation_curve.js +10 -1
  66. package/src/engine/animation/clip/hemisphere_align_quaternion_values.d.ts +20 -0
  67. package/src/engine/animation/clip/hemisphere_align_quaternion_values.d.ts.map +1 -0
  68. package/src/engine/animation/clip/hemisphere_align_quaternion_values.js +37 -0
  69. package/src/engine/animation/keyed2/AnimationTrackPlayback.d.ts.map +1 -1
  70. package/src/engine/animation/keyed2/AnimationTrackPlayback.js +5 -1
  71. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  72. package/src/engine/ecs/EntityComponentDataset.js +5 -21
  73. package/src/engine/ecs/fow/FogOfWar.d.ts +22 -0
  74. package/src/engine/ecs/fow/FogOfWar.d.ts.map +1 -1
  75. package/src/engine/ecs/fow/FogOfWar.js +202 -54
  76. package/src/engine/ecs/fow/FogOfWarEdgeMode.d.ts +16 -0
  77. package/src/engine/ecs/fow/FogOfWarEdgeMode.d.ts.map +1 -0
  78. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +39 -0
  79. package/src/engine/ecs/fow/FogOfWarSystem.d.ts.map +1 -1
  80. package/src/engine/ecs/fow/FogOfWarSystem.js +1 -0
  81. package/src/engine/ecs/fow/serialization/FogOfWarSerializationAdapter.d.ts.map +1 -1
  82. package/src/engine/ecs/fow/serialization/FogOfWarSerializationAdapter.js +3 -0
  83. package/src/engine/ecs/fow/shader/FogOfWarRenderer.d.ts +7 -0
  84. package/src/engine/ecs/fow/shader/FogOfWarRenderer.d.ts.map +1 -1
  85. package/src/engine/ecs/fow/shader/FogOfWarRenderer.js +155 -145
  86. package/src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.d.ts.map +1 -1
  87. package/src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.js +33 -8
  88. package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.d.ts.map +1 -1
  89. package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.js +3 -1
  90. package/src/engine/graphics/GraphicsEngine.d.ts +5 -1
  91. package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
  92. package/src/engine/graphics/GraphicsEngine.js +14 -3
  93. package/src/engine/graphics/composit/CompositLayer.d.ts.map +1 -1
  94. package/src/engine/graphics/composit/CompositLayer.js +258 -255
  95. package/src/engine/graphics/composit/LayerCompositer.d.ts +12 -0
  96. package/src/engine/graphics/composit/LayerCompositer.d.ts.map +1 -1
  97. package/src/engine/graphics/composit/LayerCompositer.js +19 -10
  98. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.d.ts +1 -1
  99. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.d.ts.map +1 -1
  100. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.js +10 -4
  101. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.d.ts +6 -0
  102. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.d.ts.map +1 -1
  103. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.js +21 -0
  104. package/src/engine/graphics/ecs/path/ribbon/RibbonPathBuilder.d.ts.map +1 -1
  105. package/src/engine/graphics/ecs/path/ribbon/RibbonPathBuilder.js +5 -23
  106. package/src/engine/graphics/ecs/path/tube/build/estimatePathViaIterativeIntegral.d.ts.map +1 -1
  107. package/src/engine/graphics/ecs/path/tube/build/estimatePathViaIterativeIntegral.js +6 -4
  108. package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
  109. package/src/engine/graphics/ecs/water/WaterSystem.js +9 -3
  110. package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.d.ts +12 -0
  111. package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.d.ts.map +1 -1
  112. package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.js +46 -9
  113. package/src/engine/graphics/particles/particular/engine/ParticularEngine.d.ts +5 -0
  114. package/src/engine/graphics/particles/particular/engine/ParticularEngine.d.ts.map +1 -1
  115. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +8 -0
  116. package/src/engine/graphics/particles/particular/engine/shader/MaterialRecord.d.ts +7 -0
  117. package/src/engine/graphics/particles/particular/engine/shader/MaterialRecord.d.ts.map +1 -1
  118. package/src/engine/graphics/particles/particular/engine/shader/MaterialRecord.js +8 -0
  119. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.d.ts +10 -2
  120. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.d.ts.map +1 -1
  121. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +42 -10
  122. package/src/engine/graphics/render/buffer/FrameBuffer.d.ts.map +1 -1
  123. package/src/engine/graphics/render/buffer/FrameBuffer.js +5 -2
  124. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts +13 -1
  125. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  126. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +40 -7
  127. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.d.ts.map +1 -1
  128. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js +10 -4
  129. package/src/engine/graphics/render/forward_plus/LightManager.d.ts.map +1 -1
  130. package/src/engine/graphics/render/forward_plus/LightManager.js +1235 -1232
  131. package/src/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.d.ts +5 -0
  132. package/src/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.d.ts.map +1 -1
  133. package/src/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +11 -10
  134. package/src/engine/graphics/trail/tube/TubeX.d.ts.map +1 -1
  135. package/src/engine/graphics/trail/tube/TubeX.js +557 -555
  136. package/src/engine/graphics/trail/x/RibbonXPlugin.d.ts +12 -1
  137. package/src/engine/graphics/trail/x/RibbonXPlugin.d.ts.map +1 -1
  138. package/src/engine/graphics/trail/x/RibbonXPlugin.js +15 -8
  139. package/src/engine/input/ecs/systems/InputControllerSystem.d.ts.map +1 -1
  140. package/src/engine/input/ecs/systems/InputControllerSystem.js +9 -6
  141. package/src/engine/navigation/ecs/components/Path.d.ts +5 -2
  142. package/src/engine/navigation/ecs/components/Path.d.ts.map +1 -1
  143. package/src/engine/navigation/ecs/components/Path.js +10 -4
  144. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +1 -1
  145. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  146. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
@@ -1,12 +1,14 @@
1
+ import { assert } from "../assert.js";
1
2
  import { gaussian } from "./gaussian.js";
2
3
 
3
4
  /**
4
- *
5
+ * Build a normalized 1-dimensional gaussian kernel, taps sum to 1.
5
6
  * @param {Float32Array} kernel
6
7
  * @param {number} sample_count
7
- * @param {number} sigma
8
+ * @param {number} sigma standard deviation, must be greater than 0. A sigma of 0 makes every tap `NaN`, as the centre tap evaluates `exp(-0/0)`
8
9
  */
9
10
  export function build_gaussian_kernel_1d(kernel, sample_count, sigma) {
11
+ assert.greaterThan(sigma, 0, 'sigma');
10
12
 
11
13
  const half_width = (sample_count - 1) * 0.5;
12
14
 
@@ -3,8 +3,8 @@
3
3
  * @param {number[]|Float32Array} result
4
4
  * @param {number} samplesX
5
5
  * @param {number} samplesY
6
- * @param {number} sigma_x
7
- * @param {number} sigma_y
6
+ * @param {number} sigma_x standard deviation along X, must be greater than 0. A sigma of 0 makes every tap `NaN`, as the centre tap evaluates `exp(-0/0)`
7
+ * @param {number} sigma_y standard deviation along Y, must be greater than 0
8
8
  */
9
9
  export function build_gaussian_kernel_2d(result: number[] | Float32Array, samplesX: number, samplesY: number, sigma_x: number, sigma_y: number): void;
10
10
  //# sourceMappingURL=build_gaussian_kernel_2d.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build_gaussian_kernel_2d.d.ts","sourceRoot":"","sources":["../../../../src/core/math/build_gaussian_kernel_2d.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,iDANW,MAAM,EAAE,GAAC,YAAY,YACrB,MAAM,YACN,MAAM,WACN,MAAM,WACN,MAAM,QA6ChB"}
1
+ {"version":3,"file":"build_gaussian_kernel_2d.d.ts","sourceRoot":"","sources":["../../../../src/core/math/build_gaussian_kernel_2d.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,iDANW,MAAM,EAAE,GAAC,YAAY,YACrB,MAAM,YACN,MAAM,WACN,MAAM,WACN,MAAM,QA+ChB"}
@@ -6,8 +6,8 @@ import { gaussian } from "./gaussian.js";
6
6
  * @param {number[]|Float32Array} result
7
7
  * @param {number} samplesX
8
8
  * @param {number} samplesY
9
- * @param {number} sigma_x
10
- * @param {number} sigma_y
9
+ * @param {number} sigma_x standard deviation along X, must be greater than 0. A sigma of 0 makes every tap `NaN`, as the centre tap evaluates `exp(-0/0)`
10
+ * @param {number} sigma_y standard deviation along Y, must be greater than 0
11
11
  */
12
12
  export function build_gaussian_kernel_2d(
13
13
  result,
@@ -16,6 +16,8 @@ export function build_gaussian_kernel_2d(
16
16
  ) {
17
17
  assert.isNonNegativeInteger(samplesX, 'samplesX');
18
18
  assert.isNonNegativeInteger(samplesY, 'samplesY');
19
+ assert.greaterThan(sigma_x, 0, 'sigma_x');
20
+ assert.greaterThan(sigma_y, 0, 'sigma_y');
19
21
 
20
22
  const half_samples_x = (samplesX - 1) * 0.5;
21
23
  const half_samples_y = (samplesY - 1) * 0.5;
@@ -2,9 +2,9 @@
2
2
  * Un-normalized gaussian: `exp(−distance² / (2·sigma²))`.
3
3
  * This is not the standard gaussian — it lacks the normalization factor;
4
4
  * the standard gaussian would be weighted by `1 / √(2π·sigma²)`.
5
- * @param {number} sigma Standard deviation. Controls the "width" of the bell curve: larger sigma → wider curve, slower falloff with distance.
5
+ * @param {number} sigma Standard deviation. Controls the "width" of the bell curve: larger sigma → wider curve, slower falloff with distance. Must be greater than 0; at 0 the exponent is a division by zero and the result is `NaN` for `distance === 0`.
6
6
  * @param {number} distance Distance from the mean (the point at which to evaluate the curve). At 0 the result is 1; it decays toward 0 as |distance| grows.
7
- * @returns {number} Ranges from 0 to 1 (exclusive of 0, inclusive of 1 at distance 0)
7
+ * @returns {number} Ranges from 0 to 1, inclusive of 1 at distance 0. Non-zero in exact arithmetic, but `Math.exp` underflows to exactly 0 once `distance/sigma` is large enough (around 27 standard deviations), so callers must not assume a non-zero result.
8
8
  */
9
9
  export function gaussian(sigma: number, distance: number): number;
10
10
  //# sourceMappingURL=gaussian.d.ts.map
@@ -2,9 +2,9 @@
2
2
  * Un-normalized gaussian: `exp(−distance² / (2·sigma²))`.
3
3
  * This is not the standard gaussian — it lacks the normalization factor;
4
4
  * the standard gaussian would be weighted by `1 / √(2π·sigma²)`.
5
- * @param {number} sigma Standard deviation. Controls the "width" of the bell curve: larger sigma → wider curve, slower falloff with distance.
5
+ * @param {number} sigma Standard deviation. Controls the "width" of the bell curve: larger sigma → wider curve, slower falloff with distance. Must be greater than 0; at 0 the exponent is a division by zero and the result is `NaN` for `distance === 0`.
6
6
  * @param {number} distance Distance from the mean (the point at which to evaluate the curve). At 0 the result is 1; it decays toward 0 as |distance| grows.
7
- * @returns {number} Ranges from 0 to 1 (exclusive of 0, inclusive of 1 at distance 0)
7
+ * @returns {number} Ranges from 0 to 1, inclusive of 1 at distance 0. Non-zero in exact arithmetic, but `Math.exp` underflows to exactly 0 once `distance/sigma` is large enough (around 27 standard deviations), so callers must not assume a non-zero result.
8
8
  */
9
9
  export function gaussian(sigma, distance) {
10
10
  const d2 = distance * distance;
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_bezier_bounds.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_bezier_bounds.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AACH,8CARW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAuEhB"}
1
+ {"version":3,"file":"spline3_bezier_bounds.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_bezier_bounds.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,8CARW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA6EhB"}
@@ -1,5 +1,6 @@
1
1
  import { assert } from "../../assert.js";
2
2
  import { max2 } from "../max2.js";
3
+ import { max3 } from "../max3.js";
3
4
  import { min2 } from "../min2.js";
4
5
  import { spline3_bezier } from "./spline3_bezier.js";
5
6
 
@@ -27,9 +28,14 @@ export function spline3_bezier_bounds(result, result_offset, result_stride, p0,
27
28
  let min = min2(p0, p3);
28
29
  let max = max2(p0, p3);
29
30
 
30
- if (Math.abs(a) < 1e-12) {
31
+ // `a`, `b` and `c` all scale linearly with the curve's coordinates, so the degeneracy test has to
32
+ // be relative to them. An absolute threshold silently treats an entire small-coordinate curve as
33
+ // degenerate and reports the endpoints as the bounds, missing interior extrema.
34
+ const epsilon = max3(Math.abs(a), Math.abs(b), Math.abs(c)) * 1e-12;
31
35
 
32
- if (Math.abs(b) >= 1e-12) {
36
+ if (Math.abs(a) <= epsilon) {
37
+
38
+ if (Math.abs(b) > epsilon) {
33
39
  const t = -c / b;
34
40
 
35
41
  if (0 < t && t < 1) {
@@ -49,10 +55,11 @@ export function spline3_bezier_bounds(result, result_offset, result_stride, p0,
49
55
  } else {
50
56
 
51
57
  const b2ac = b * b - 4 * c * a;
52
- const sqrtb2ac = Math.sqrt(b2ac);
53
58
 
54
59
  if (b2ac >= 0) {
55
60
 
61
+ const sqrtb2ac = Math.sqrt(b2ac);
62
+
56
63
  const t1 = (-b + sqrtb2ac) / (2 * a);
57
64
 
58
65
  if (0 < t1 && t1 < 1) {
@@ -1,14 +1,15 @@
1
1
  /**
2
- * 1D
3
- * Cubic spline.
4
- * The result is [v0, m0, v1, m1] where X are values are M are tangents.
2
+ * One-dimensional cubic curve conversion from bezier to Hermite
3
+ * The result is `[p0, p1, m0, m1]` where P are positions and M are tangents, matching the argument
4
+ * order taken by {@link spline3_hermite} and the layout written by {@link spline3_hermite_subdivide}.
5
+ * The result can therefore be fed straight back into {@link spline3_hermite_to_bezier}.
5
6
  * @param {Float32Array|number[]} result where to put results
6
7
  * @param {number} result_offset offset to start writing at in result array
7
8
  * @param {number} result_stride how far to step between each written value. Necessary for N-dimensional curves where we want to interleave multiple curves together, such as for color or quaternions
8
- * @param {number} p0
9
- * @param {number} p1
10
- * @param {number} p2
11
- * @param {number} p3
9
+ * @param {number} p0 first bezier control point
10
+ * @param {number} p1 second bezier control point
11
+ * @param {number} p2 third bezier control point
12
+ * @param {number} p3 fourth bezier control point
12
13
  * @see spline3_hermite_to_bezier
13
14
  */
14
15
  export function spline3_bezier_to_hermite(result: Float32Array | number[], result_offset: number, result_stride: number, p0: number, p1: number, p2: number, p3: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_bezier_to_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_bezier_to_hermite.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,kDATW,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAsBhB"}
1
+ {"version":3,"file":"spline3_bezier_to_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_bezier_to_hermite.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,kDATW,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAsBhB"}
@@ -1,14 +1,15 @@
1
1
  /**
2
- * 1D
3
- * Cubic spline.
4
- * The result is [v0, m0, v1, m1] where X are values are M are tangents.
2
+ * One-dimensional cubic curve conversion from bezier to Hermite
3
+ * The result is `[p0, p1, m0, m1]` where P are positions and M are tangents, matching the argument
4
+ * order taken by {@link spline3_hermite} and the layout written by {@link spline3_hermite_subdivide}.
5
+ * The result can therefore be fed straight back into {@link spline3_hermite_to_bezier}.
5
6
  * @param {Float32Array|number[]} result where to put results
6
7
  * @param {number} result_offset offset to start writing at in result array
7
8
  * @param {number} result_stride how far to step between each written value. Necessary for N-dimensional curves where we want to interleave multiple curves together, such as for color or quaternions
8
- * @param {number} p0
9
- * @param {number} p1
10
- * @param {number} p2
11
- * @param {number} p3
9
+ * @param {number} p0 first bezier control point
10
+ * @param {number} p1 second bezier control point
11
+ * @param {number} p2 third bezier control point
12
+ * @param {number} p3 fourth bezier control point
12
13
  * @see spline3_hermite_to_bezier
13
14
  */
14
15
  export function spline3_bezier_to_hermite(
@@ -22,12 +23,12 @@ export function spline3_bezier_to_hermite(
22
23
 
23
24
  const TENSION_FACTOR = 3;
24
25
 
25
- const v0 = (p1 - p0) * TENSION_FACTOR;
26
- const v1 = (p3 - p2) * TENSION_FACTOR;
26
+ const m0 = (p1 - p0) * TENSION_FACTOR;
27
+ const m1 = (p3 - p2) * TENSION_FACTOR;
27
28
 
28
29
  result[result_offset + 0 * result_stride] = p0;
29
- result[result_offset + 1 * result_stride] = v0;
30
- result[result_offset + 2 * result_stride] = p3;
31
- result[result_offset + 3 * result_stride] = v1;
30
+ result[result_offset + 1 * result_stride] = p3;
31
+ result[result_offset + 2 * result_stride] = m0;
32
+ result[result_offset + 3 * result_stride] = m1;
32
33
 
33
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_bounds.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_bounds.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,+CAXW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAqFhB"}
1
+ {"version":3,"file":"spline3_hermite_bounds.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_bounds.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH,+CAXW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA2FhB"}
@@ -1,5 +1,6 @@
1
1
  import { assert } from "../../assert.js";
2
2
  import { max2 } from "../max2.js";
3
+ import { max3 } from "../max3.js";
3
4
  import { min2 } from "../min2.js";
4
5
  import { spline3_hermite } from "./spline3_hermite.js";
5
6
 
@@ -40,9 +41,14 @@ export function spline3_hermite_bounds(
40
41
  let min = min2(p0, p1);
41
42
  let max = max2(p0, p1);
42
43
 
43
- if (Math.abs(a) < 1e-12) {
44
+ // `a`, `b` and `c` all scale linearly with the curve's coordinates, so the degeneracy test has to
45
+ // be relative to them. An absolute threshold silently treats an entire small-coordinate curve as
46
+ // degenerate and reports the endpoints as the bounds, missing interior extrema.
47
+ const epsilon = max3(Math.abs(a), Math.abs(b), Math.abs(c)) * 1e-12;
44
48
 
45
- if (Math.abs(b) >= 1e-12) {
49
+ if (Math.abs(a) <= epsilon) {
50
+
51
+ if (Math.abs(b) > epsilon) {
46
52
  const t = -c / b;
47
53
 
48
54
  if (0 < t && t < 1) {
@@ -62,10 +68,11 @@ export function spline3_hermite_bounds(
62
68
  } else {
63
69
 
64
70
  const b2ac = b * b - 4 * c * a;
65
- const sqrtb2ac = Math.sqrt(b2ac);
66
71
 
67
72
  if (b2ac >= 0) {
68
73
 
74
+ const sqrtb2ac = Math.sqrt(b2ac);
75
+
69
76
  const t1 = (-b + sqrtb2ac) / (2 * a);
70
77
 
71
78
  if (0 < t1 && t1 < 1) {
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_bounds_t.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_bounds_t.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iDAXW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAyFhB"}
1
+ {"version":3,"file":"spline3_hermite_bounds_t.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_bounds_t.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iDAXW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA8FhB"}
@@ -1,109 +1,115 @@
1
- import { assert } from "../../assert.js";
2
- import { spline3_hermite } from "./spline3_hermite.js";
3
-
4
- /**
5
- * Compute the parameter values `t` at which a cubic Hermite spline reaches its
6
- * minimum and maximum on `[0, 1]`. `t_at_min` is written to
7
- * `result[result_offset]`, `t_at_max` to `result[result_offset + result_stride]`.
8
- * Both values lie in `[0, 1]`.
9
- *
10
- * Companion to {@link spline3_hermite_bounds}, which writes the values; this
11
- * one writes the parameters at which those values are reached. Either function
12
- * does its own critical-point search call whichever you actually need.
13
- *
14
- * @param {number[]|Float32Array|Float64Array} result
15
- * @param {number} result_offset
16
- * @param {number} result_stride
17
- * @param {number} p0
18
- * @param {number} p1
19
- * @param {number} m0
20
- * @param {number} m1
21
- *
22
- * @author Alex Goldring
23
- * @copyright Company Named Limited (c) 2026
24
- */
25
- export function spline3_hermite_bounds_t(
26
- result,
27
- result_offset,
28
- result_stride,
29
- p0, p1, m0, m1
30
- ) {
31
- assert.greaterThan(result_stride, 0, 'result_stride must be greater than 0');
32
- assert.isInteger(result_stride, 'result_stride');
33
-
34
- // Derivative of the Hermite polynomial:
35
- // 3 (m0 + m1 + 2 p0 - 2 p1) - 2 t (2 m0 + m1 + 3 p0 - 3 p1) + m0
36
- const a = 3 * (m0 + m1 + 2 * p0 - 2 * p1);
37
- const b = -2 * (2 * m0 + m1 + 3 * p0 - 3 * p1);
38
- const c = m0;
39
-
40
- let min, max, t_at_min, t_at_max;
41
- if (p0 <= p1) {
42
- min = p0; t_at_min = 0;
43
- max = p1; t_at_max = 1;
44
- } else {
45
- min = p1; t_at_min = 1;
46
- max = p0; t_at_max = 0;
47
- }
48
-
49
- if (Math.abs(a) < 1e-12) {
50
-
51
- if (Math.abs(b) >= 1e-12) {
52
- const t = -c / b;
53
-
54
- if (0 < t && t < 1) {
55
- const value = spline3_hermite(t, p0, p1, m0, m1);
56
-
57
- if (value < min) {
58
- min = value;
59
- t_at_min = t;
60
- }
61
- if (value > max) {
62
- max = value;
63
- t_at_max = t;
64
- }
65
- }
66
- }
67
-
68
- } else {
69
-
70
- const b2ac = b * b - 4 * c * a;
71
-
72
- if (b2ac >= 0) {
73
- const sqrtb2ac = Math.sqrt(b2ac);
74
-
75
- const t1 = (-b + sqrtb2ac) / (2 * a);
76
-
77
- if (0 < t1 && t1 < 1) {
78
- const value = spline3_hermite(t1, p0, p1, m0, m1);
79
-
80
- if (value < min) {
81
- min = value;
82
- t_at_min = t1;
83
- }
84
- if (value > max) {
85
- max = value;
86
- t_at_max = t1;
87
- }
88
- }
89
-
90
- const t2 = (-b - sqrtb2ac) / (2 * a);
91
-
92
- if (0 < t2 && t2 < 1) {
93
- const value = spline3_hermite(t2, p0, p1, m0, m1);
94
-
95
- if (value < min) {
96
- min = value;
97
- t_at_min = t2;
98
- }
99
- if (value > max) {
100
- max = value;
101
- t_at_max = t2;
102
- }
103
- }
104
- }
105
- }
106
-
107
- result[result_offset] = t_at_min;
108
- result[result_offset + result_stride] = t_at_max;
109
- }
1
+ import { assert } from "../../assert.js";
2
+ import { max3 } from "../max3.js";
3
+ import { spline3_hermite } from "./spline3_hermite.js";
4
+
5
+ /**
6
+ * Compute the parameter values `t` at which a cubic Hermite spline reaches its
7
+ * minimum and maximum on `[0, 1]`. `t_at_min` is written to
8
+ * `result[result_offset]`, `t_at_max` to `result[result_offset + result_stride]`.
9
+ * Both values lie in `[0, 1]`.
10
+ *
11
+ * Companion to {@link spline3_hermite_bounds}, which writes the values; this
12
+ * one writes the parameters at which those values are reached. Either function
13
+ * does its own critical-point search — call whichever you actually need.
14
+ *
15
+ * @param {number[]|Float32Array|Float64Array} result
16
+ * @param {number} result_offset
17
+ * @param {number} result_stride
18
+ * @param {number} p0
19
+ * @param {number} p1
20
+ * @param {number} m0
21
+ * @param {number} m1
22
+ *
23
+ * @author Alex Goldring
24
+ * @copyright Company Named Limited (c) 2026
25
+ */
26
+ export function spline3_hermite_bounds_t(
27
+ result,
28
+ result_offset,
29
+ result_stride,
30
+ p0, p1, m0, m1
31
+ ) {
32
+ assert.greaterThan(result_stride, 0, 'result_stride must be greater than 0');
33
+ assert.isInteger(result_stride, 'result_stride');
34
+
35
+ // Derivative of the Hermite polynomial:
36
+ // 3 (m0 + m1 + 2 p0 - 2 p1) - 2 t (2 m0 + m1 + 3 p0 - 3 p1) + m0
37
+ const a = 3 * (m0 + m1 + 2 * p0 - 2 * p1);
38
+ const b = -2 * (2 * m0 + m1 + 3 * p0 - 3 * p1);
39
+ const c = m0;
40
+
41
+ let min, max, t_at_min, t_at_max;
42
+ if (p0 <= p1) {
43
+ min = p0; t_at_min = 0;
44
+ max = p1; t_at_max = 1;
45
+ } else {
46
+ min = p1; t_at_min = 1;
47
+ max = p0; t_at_max = 0;
48
+ }
49
+
50
+ // `a`, `b` and `c` all scale linearly with the curve's coordinates, so the degeneracy test has to
51
+ // be relative to them. An absolute threshold silently treats an entire small-coordinate curve as
52
+ // degenerate and reports the endpoints as the extrema, missing interior ones.
53
+ const epsilon = max3(Math.abs(a), Math.abs(b), Math.abs(c)) * 1e-12;
54
+
55
+ if (Math.abs(a) <= epsilon) {
56
+
57
+ if (Math.abs(b) > epsilon) {
58
+ const t = -c / b;
59
+
60
+ if (0 < t && t < 1) {
61
+ const value = spline3_hermite(t, p0, p1, m0, m1);
62
+
63
+ if (value < min) {
64
+ min = value;
65
+ t_at_min = t;
66
+ }
67
+ if (value > max) {
68
+ max = value;
69
+ t_at_max = t;
70
+ }
71
+ }
72
+ }
73
+
74
+ } else {
75
+
76
+ const b2ac = b * b - 4 * c * a;
77
+
78
+ if (b2ac >= 0) {
79
+ const sqrtb2ac = Math.sqrt(b2ac);
80
+
81
+ const t1 = (-b + sqrtb2ac) / (2 * a);
82
+
83
+ if (0 < t1 && t1 < 1) {
84
+ const value = spline3_hermite(t1, p0, p1, m0, m1);
85
+
86
+ if (value < min) {
87
+ min = value;
88
+ t_at_min = t1;
89
+ }
90
+ if (value > max) {
91
+ max = value;
92
+ t_at_max = t1;
93
+ }
94
+ }
95
+
96
+ const t2 = (-b - sqrtb2ac) / (2 * a);
97
+
98
+ if (0 < t2 && t2 < 1) {
99
+ const value = spline3_hermite(t2, p0, p1, m0, m1);
100
+
101
+ if (value < min) {
102
+ min = value;
103
+ t_at_min = t2;
104
+ }
105
+ if (value > max) {
106
+ max = value;
107
+ t_at_max = t2;
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ result[result_offset] = t_at_min;
114
+ result[result_offset + result_stride] = t_at_max;
115
+ }
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * One-dimensional cubic curve conversion from Hermite to bezier
3
+ * The result is the 4 bezier control points in order. {@link spline3_bezier_to_hermite} inverts this,
4
+ * and writes its Hermite as `[p0, p1, m0, m1]`, so its output can be fed straight back in here.
3
5
  * @param {Float32Array|number[]} result where to put results
4
6
  * @param {number} result_offset offset to start writing at in result array
5
7
  * @param {number} result_stride how far to step between each written value. Necessary for N-dimensional curves where we want to interleave multiple curves together, such as for color or quaternions
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_to_bezier.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_to_bezier.js"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,kDATW,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAsBhB"}
1
+ {"version":3,"file":"spline3_hermite_to_bezier.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_to_bezier.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,kDATW,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,iBACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAsBhB"}
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * One-dimensional cubic curve conversion from Hermite to bezier
3
+ * The result is the 4 bezier control points in order. {@link spline3_bezier_to_hermite} inverts this,
4
+ * and writes its Hermite as `[p0, p1, m0, m1]`, so its output can be fed straight back in here.
3
5
  * @param {Float32Array|number[]} result where to put results
4
6
  * @param {number} result_offset offset to start writing at in result array
5
7
  * @param {number} result_stride how far to step between each written value. Necessary for N-dimensional curves where we want to interleave multiple curves together, such as for color or quaternions
@@ -14,7 +14,7 @@ export function computeStatisticalMean(values, start = 0, end = values.length) {
14
14
 
15
15
  const sample_count = end - start;
16
16
 
17
- for (let i = 0; i < end; i++) {
17
+ for (let i = start; i < end; i++) {
18
18
  const value = values[i];
19
19
 
20
20
  total += value;
@@ -1 +1 @@
1
- {"version":3,"file":"gaussian_amplitude.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/statistics/gaussian_amplitude.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,6CAJW,MAAM,sBACN,MAAM,GACJ,MAAM,CAMlB"}
1
+ {"version":3,"file":"gaussian_amplitude.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/statistics/gaussian_amplitude.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,6CAJW,MAAM,sBACN,MAAM,GACJ,MAAM,CAQlB"}
@@ -9,5 +9,7 @@ import { gaussian } from "../gaussian.js";
9
9
  export function gaussian_amplitude(variance, distance_from_mean) {
10
10
  // Adapted from https://github.com/Kitware/VTK/blob/af928016f4041ad8a6a193d032355b909721fc95/Common/Core/vtkMath.cxx#L339
11
11
 
12
- return 1.0 / Math.sqrt(2.0 * Math.PI * variance) * gaussian(variance, distance_from_mean);
12
+ // gaussian() takes a standard deviation and squares it internally, so pass sqrt(variance) to get
13
+ // the exponent -distance^2 / (2 * variance) that the 1/sqrt(2*PI*variance) normalization expects.
14
+ return 1.0 / Math.sqrt(2.0 * Math.PI * variance) * gaussian(Math.sqrt(variance), distance_from_mean);
13
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ecd_bind_animation_curve.d.ts","sourceRoot":"","sources":["../../../../../src/engine/animation/clip/ecd_bind_animation_curve.js"],"names":[],"mappings":"AAoGA;;;;GAIG;AACH,2DAFW,mBAAmB,wBAgC7B;8BArI6B,oBAAoB;qCACb,2BAA2B"}
1
+ {"version":3,"file":"ecd_bind_animation_curve.d.ts","sourceRoot":"","sources":["../../../../../src/engine/animation/clip/ecd_bind_animation_curve.js"],"names":[],"mappings":"AA6GA;;;;GAIG;AACH,2DAFW,mBAAmB,wBAgC7B;8BA9I6B,oBAAoB;qCACb,2BAA2B"}
@@ -7,6 +7,7 @@ import { AnimationTrack } from "./AnimationTrack.js";
7
7
  import { AnimationTrackBinding } from "./AnimationTrackBinding.js";
8
8
  import { bind_property_writer } from "./bind_property_writer.js";
9
9
  import { curve_from_track_data } from "./curve_from_track_data.js";
10
+ import { hemisphere_align_quaternion_values } from "./hemisphere_align_quaternion_values.js";
10
11
 
11
12
 
12
13
  /**
@@ -44,7 +45,7 @@ function track_to_interpolation(track) {
44
45
  */
45
46
  function convert_three_track_to_curves(track) {
46
47
 
47
- const values = track.values;
48
+ let values = track.values;
48
49
  const times = track.times;
49
50
 
50
51
  const key_count = times.length;
@@ -58,6 +59,14 @@ function convert_three_track_to_curves(track) {
58
59
  // figure out interpolation type
59
60
  const interpolation = track_to_interpolation(track);
60
61
 
62
+ // A quaternion track is interpolated below as four independent per-component curves; without hemisphere
63
+ // alignment a pair of adjacent keys with a negative dot product interpolates the long way around. Negate
64
+ // keys into a common hemisphere first so the component nlerp takes the short arc. (Discrete needs no
65
+ // interpolation; CubicTangents packs tangents per key and is handled separately / not yet aligned.)
66
+ if (interpolation === AnimationInterpolationKind.Linear && track.ValueTypeName === 'quaternion') {
67
+ values = hemisphere_align_quaternion_values(values, key_count);
68
+ }
69
+
61
70
  const values_per_key = values.length / key_count;
62
71
 
63
72
  const component_count = interpolation === AnimationInterpolationKind.CubicTangents ? values_per_key / 3 : values_per_key;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Return a copy of a flat quaternion keyframe-value array (`[x0,y0,z0,w0, x1,y1,z1,w1, ...]`) in which every
3
+ * key has been negated as needed so that consecutive keys lie in the same hemisphere (their dot product is
4
+ * non-negative).
5
+ *
6
+ * A quaternion and its negation represent the same rotation, so this does not change the pose at any key. It
7
+ * does change what happens BETWEEN keys: the animation clip pipeline interpolates a quaternion track as four
8
+ * independent per-component curves (component-wise normalized-lerp), and without hemisphere alignment a pair of
9
+ * adjacent keys whose dot product is negative interpolates the long way around — spinning nearly 360 degrees and
10
+ * dipping through a near-degenerate quaternion at the midpoint. Aligning the keys first makes the component nlerp
11
+ * take the short arc, matching what three.js does at interpolation time via Quaternion.slerpFlat's sign flip.
12
+ *
13
+ * The input array is not mutated.
14
+ *
15
+ * @param {number[]|Float32Array|Float64Array} values flat quaternion components, 4 per key
16
+ * @param {number} key_count number of keyframes
17
+ * @returns {number[]|Float32Array|Float64Array} a copy with hemisphere-aligned keys
18
+ */
19
+ export function hemisphere_align_quaternion_values(values: number[] | Float32Array | Float64Array, key_count: number): number[] | Float32Array | Float64Array;
20
+ //# sourceMappingURL=hemisphere_align_quaternion_values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hemisphere_align_quaternion_values.d.ts","sourceRoot":"","sources":["../../../../../src/engine/animation/clip/hemisphere_align_quaternion_values.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,2DAJW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,aAClC,MAAM,GACJ,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,CAoB9C"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Return a copy of a flat quaternion keyframe-value array (`[x0,y0,z0,w0, x1,y1,z1,w1, ...]`) in which every
3
+ * key has been negated as needed so that consecutive keys lie in the same hemisphere (their dot product is
4
+ * non-negative).
5
+ *
6
+ * A quaternion and its negation represent the same rotation, so this does not change the pose at any key. It
7
+ * does change what happens BETWEEN keys: the animation clip pipeline interpolates a quaternion track as four
8
+ * independent per-component curves (component-wise normalized-lerp), and without hemisphere alignment a pair of
9
+ * adjacent keys whose dot product is negative interpolates the long way around — spinning nearly 360 degrees and
10
+ * dipping through a near-degenerate quaternion at the midpoint. Aligning the keys first makes the component nlerp
11
+ * take the short arc, matching what three.js does at interpolation time via Quaternion.slerpFlat's sign flip.
12
+ *
13
+ * The input array is not mutated.
14
+ *
15
+ * @param {number[]|Float32Array|Float64Array} values flat quaternion components, 4 per key
16
+ * @param {number} key_count number of keyframes
17
+ * @returns {number[]|Float32Array|Float64Array} a copy with hemisphere-aligned keys
18
+ */
19
+ export function hemisphere_align_quaternion_values(values, key_count) {
20
+ const out = values.slice();
21
+
22
+ for (let i = 1; i < key_count; i++) {
23
+ const p = (i - 1) * 4;
24
+ const c = i * 4;
25
+
26
+ const dot = out[p] * out[c] + out[p + 1] * out[c + 1] + out[p + 2] * out[c + 2] + out[p + 3] * out[c + 3];
27
+
28
+ if (dot < 0) {
29
+ out[c] = -out[c];
30
+ out[c + 1] = -out[c + 1];
31
+ out[c + 2] = -out[c + 2];
32
+ out[c + 3] = -out[c + 3];
33
+ }
34
+ }
35
+
36
+ return out;
37
+ }