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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue