UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

110 lines (83 loc) 3.85 kB
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; }