Implemented interpolation using ChatGPT

This commit is contained in:
Maximilian Giller 2025-09-21 17:13:47 +02:00
parent 5970b70d7e
commit 1ff76c27b2
2 changed files with 108 additions and 24 deletions

View file

@ -1,5 +1,6 @@
from decimal import Decimal from decimal import Decimal
import logging import logging
import math
from models import ( from models import (
EngineFixture, EngineFixture,
@ -67,27 +68,18 @@ class Engine:
continue continue
still_running = True still_running = True
# Move keyframes forward # Move track forward
t.step(step_size) t.step(step_size)
if t.current is None: if t.current is None:
raise BaseException(f"Track has no current keyframes.") raise BaseException(f"Track has no current keyframe.")
# Interpolate over keyframes of track # Interpolate and update value
for k in t.current: interpolated_value = self.get_current_value(t)
next_value: KeyframeValue = None
if t.next:
try:
next_value = t.next.get(k.parameter).value
except:
pass # next_value already None by default
interpolated_value = self.interpolate_value(k.value, next_value, k.interpolation)
# Update values of relevant fixtures # Update values of relevant fixtures
for f in self.fixtures.values(): for f in self.fixtures.values():
if f.fixture.id in t.track.fixture_ids: if f.fixture.id in t.track.fixture_ids:
f.set(k.parameter, interpolated_value) f.set(t.track.parameter, interpolated_value)
# Update running state # Update running state
self.is_running = still_running self.is_running = still_running
@ -100,11 +92,97 @@ class Engine:
api = self.apis[f.fixture.api] api = self.apis[f.fixture.api]
# TODO: Pass update to API # TODO: Pass update to API
def get_current_value(self, track: EngineTrack) -> KeyframeValue:
"""Interpolate between current keyframes of track and return the interpolated value."""
current = track.current
if current is None:
raise BaseException(
f"Track has no current keyframe. Cannot interpolate values."
)
next = track.next
if next is None:
return current.frame.value
t = current.delta.copy_abs() / (
current.delta.copy_abs() + next.delta.copy_abs()
)
return self.interpolate_value(
current.frame.value, next.frame.value, t, current.frame.interpolation
)
def interpolate_value( def interpolate_value(
self, current: KeyframeValue, next: KeyframeValue, interpolation: Interpolation self,
) -> KeyframeValue: start: KeyframeValue,
"""Interpolate between two keyframe values and return the interpolated value.""" end: KeyframeValue,
cur_frame: Keyframe = current.get(parameter) t: Decimal,
if interpolation: Interpolation,
# TODO ):
return current.get(parameter).value """CHATGPT. Interpolate between two numeric values with given interpolation type.
t is normalized between 0.0 (start) and 1.0 (end)."""
if not isinstance(start, (int, float)) or not isinstance(end, (int, float)):
logging.warning(
f"Interpolation [{interpolation}] only supported for int/float. "
f"Got {type(start)}{start}, {type(end)}{end}. Falling back to STEP."
)
return end # default STEP behavior
# Clamp t for safety
t = max(Decimal.from_float(0), min(Decimal.from_float(1), t))
match interpolation:
case Interpolation.STEP:
return end
case Interpolation.LINEAR:
f = t
case Interpolation.EASE_IN:
f = t * t
case Interpolation.EASE_OUT:
f = 1 - (1 - t) * (1 - t)
case Interpolation.EASE_IN_OUT:
f = 2 * t * t if t < 0.5 else 1 - pow(-2 * t + 2, 2) / 2
case Interpolation.EXPONENTIAL:
f = pow(2, 10 * (t - 1)) if t > 0 else 0
case Interpolation.LOGARITHMIC:
f = math.log10(9 * t + 1) # maps 0→0, 1→1 smoothly
case Interpolation.SINE:
f = 0.5 - 0.5 * math.cos(Decimal.from_float(math.pi) * t)
case Interpolation.SMOOTHSTEP:
f = t * t * (3 - 2 * t)
case Interpolation.BOUNCE:
f = self.bounce_out(float(t))
case Interpolation.ELASTIC:
f = self.elastic_out(t)
case _:
logging.warning(
f"Unhandled interpolation type [{interpolation}]. Using STEP."
)
return end
raise BaseException("Unreachable code reached.")
def bounce_out(self, t: float) -> float:
"""CHATGPT. Bounce easing function (out)."""
n1, d1 = 7.5625, 2.75
if t < 1 / d1:
return n1 * t * t
elif t < 2 / d1:
t -= 1.5 / d1
return n1 * t * t + 0.75
elif t < 2.5 / d1:
t -= 2.25 / d1
return n1 * t * t + 0.9375
else:
t -= 2.625 / d1
return n1 * t * t + 0.984375
def elastic_out(self, t: Decimal) -> float:
"""CHATGPT. Elastic easing function (out)."""
c4 = (2 * math.pi) / 3
if t == 0:
return 0
if t == 1:
return 1
return pow(2, -10 * t) * math.sin((float(t) * 10 - 0.75) * c4) + 1

View file

@ -151,6 +151,12 @@ class Interpolation(Enum):
EASE_IN_OUT = "ease-in-out" EASE_IN_OUT = "ease-in-out"
EASE_IN = "ease-in" EASE_IN = "ease-in"
EASE_OUT = "ease-out" EASE_OUT = "ease-out"
EXPONENTIAL = "exponential"
LOGARITHMIC = "logarithmic"
SINE = "sine"
SMOOTHSTEP = "smoothstep"
BOUNCE = "bounce"
ELASTIC = "elastic"
@dataclass_json @dataclass_json