@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
110 lines (83 loc) • 3.85 kB
JavaScript
import { assert } from "../../../core/assert.js";
import AABB2 from "../../../core/geom/2d/aabb/AABB2.js";
import { animation_curve_compute_aabb } from "./animation_curve_compute_aabb.js";
import { evaluate_two_key_curve } from "./evaluate_two_key_curve.js";
const bounds = new AABB2();
const NORMALIZED_CHECK_DISTANCE = 0.07;
/**
* Compute the value difference in the curve if the `middle` key between `previous` and `next` is removed
* @param {Keyframe} key_middle
* @param {Keyframe} key_previous
* @param {Keyframe} key_next
* @return {number} value delta if the middle frame is removed, the higher this value - the more important the middle keyframe is
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
function compute_keyframe_value_effect(
key_middle,
key_previous,
key_next
) {
// check if this key contributes to the shape
const v1_actual = evaluate_two_key_curve(key_middle.time, key_previous, key_next);
const v1_expected = key_middle.value;
const v1_error = Math.abs(v1_actual - v1_expected);
// sample just before the current frame
const v0_time = key_middle.time - (key_middle.time - key_previous.time) * NORMALIZED_CHECK_DISTANCE;
const v0_actual = evaluate_two_key_curve(v0_time, key_previous, key_next);
const v0_expected = evaluate_two_key_curve(v0_time, key_previous, key_middle);
const v0_error = Math.abs(v0_actual - v0_expected);
// sample just after the current frame
const v2_time = key_middle.time + (key_next.time - key_middle.time) * NORMALIZED_CHECK_DISTANCE;
const v2_actual = evaluate_two_key_curve(v2_time, key_previous, key_next);
const v2_expected = evaluate_two_key_curve(v2_time, key_middle, key_next);
const v2_error = Math.abs(v2_actual - v2_expected);
return Math.max(v0_error, v1_error, v2_error);
}
/**
* Will remove keyframes that do not affect the shape of the curve significantly.
* The error tolerance determines the significance degree.
* Intended to reduce the complexity of a curve to improve performance and lower memory usage
* @param {AnimationCurve} curve The curve to optimize. The curve is modified in place.
* @param {number} [error_tolerance] how much of a loss to accept, this is relative to normalized value bounds of the curve
* @returns {number} number of removed keys, 0 if curve was unchanged
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export function animation_curve_optimize(
curve,
error_tolerance = 1e-3
) {
assert.lessThan(error_tolerance, 1, 'error_tolerance must be less than 1');
animation_curve_compute_aabb(bounds, curve);
const absolute_error_tolerance = bounds.height * error_tolerance;
let key_count = curve.length;
const keyframes = curve.keys;
const initial_key_count = key_count;
for (let i = 1; i < key_count; i++) {
const key_previous = keyframes[i - 1];
const key_current = keyframes[i];
let should_remove = false;
if (key_current.equals(key_previous)) {
// adds no semantic value
should_remove = true;
}
if (!should_remove && i < key_count - 1) {
const key_next = keyframes[i + 1];
const max_error = compute_keyframe_value_effect(key_current, key_previous, key_next);
if (max_error <= absolute_error_tolerance) {
// does not significantly affect the curve shape
should_remove = true;
}
}
if (should_remove) {
curve.remove(key_current);
i--;
key_count--;
}
}
// number of removed keys
return initial_key_count - key_count;
}