Implemented interpolation using ChatGPT
This commit is contained in:
parent
5970b70d7e
commit
1ff76c27b2
2 changed files with 108 additions and 24 deletions
126
src/engine.py
126
src/engine.py
|
@ -1,5 +1,6 @@
|
|||
from decimal import Decimal
|
||||
import logging
|
||||
import math
|
||||
|
||||
from models import (
|
||||
EngineFixture,
|
||||
|
@ -67,27 +68,18 @@ class Engine:
|
|||
continue
|
||||
still_running = True
|
||||
|
||||
# Move keyframes forward
|
||||
# Move track forward
|
||||
t.step(step_size)
|
||||
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
|
||||
for k in t.current:
|
||||
|
||||
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)
|
||||
# Interpolate and update value
|
||||
interpolated_value = self.get_current_value(t)
|
||||
|
||||
# Update values of relevant fixtures
|
||||
for f in self.fixtures.values():
|
||||
if f.fixture.id in t.track.fixture_ids:
|
||||
f.set(k.parameter, interpolated_value)
|
||||
# Update values of relevant fixtures
|
||||
for f in self.fixtures.values():
|
||||
if f.fixture.id in t.track.fixture_ids:
|
||||
f.set(t.track.parameter, interpolated_value)
|
||||
|
||||
# Update running state
|
||||
self.is_running = still_running
|
||||
|
@ -100,11 +92,97 @@ class Engine:
|
|||
api = self.apis[f.fixture.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(
|
||||
self, current: KeyframeValue, next: KeyframeValue, interpolation: Interpolation
|
||||
) -> KeyframeValue:
|
||||
"""Interpolate between two keyframe values and return the interpolated value."""
|
||||
cur_frame: Keyframe = current.get(parameter)
|
||||
if
|
||||
# TODO
|
||||
return current.get(parameter).value
|
||||
self,
|
||||
start: KeyframeValue,
|
||||
end: KeyframeValue,
|
||||
t: Decimal,
|
||||
interpolation: Interpolation,
|
||||
):
|
||||
"""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
|
||||
|
|
|
@ -151,6 +151,12 @@ class Interpolation(Enum):
|
|||
EASE_IN_OUT = "ease-in-out"
|
||||
EASE_IN = "ease-in"
|
||||
EASE_OUT = "ease-out"
|
||||
EXPONENTIAL = "exponential"
|
||||
LOGARITHMIC = "logarithmic"
|
||||
SINE = "sine"
|
||||
SMOOTHSTEP = "smoothstep"
|
||||
BOUNCE = "bounce"
|
||||
ELASTIC = "elastic"
|
||||
|
||||
|
||||
@dataclass_json
|
||||
|
|
Loading…
Reference in a new issue