-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathellipse.py
More file actions
99 lines (80 loc) · 3.39 KB
/
ellipse.py
File metadata and controls
99 lines (80 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""
Ellipse Module
==============
Draws ellipses (ovals) with configurable semi-axes.
Can grow/shrink over time.
"""
import numpy as np
from fractions import Fraction
from math import pi, sin
from main import TransformModule
class EllipseModule(TransformModule):
"""
Ellipse generator.
Configuration:
radius_x: Semi-major axis (horizontal radius)
radius_y: Semi-minor axis (vertical radius)
end_radius_x: Ending horizontal radius (for animation)
end_radius_y: Ending vertical radius (for animation)
decay: Exponential decay rate (0 = no decay)
cycles: Number of times around the ellipse
rotation: Ellipse rotation in degrees
start_x, start_y: Center position
"""
def _load_config(self):
"""Load ellipse configuration."""
self.radius_x = self._getfloat('radius_x', 50.0)
self.radius_y = self._getfloat('radius_y', 30.0)
self.end_radius_x = self._getfloat('end_radius_x', self.radius_x)
self.end_radius_y = self._getfloat('end_radius_y', self.radius_y)
self.cycles = self._getfloat('cycles', 1.0)
self.lobe = self._getfloat('lobe', 0.0)
self.lobe_n = self._getfloat('lobe_n', 1.0)
self.decay = self._getfloat('decay', 0.0)
self.rotation_deg = self._getfloat('rotation', 0.0)
self.end_rotation_deg = self._getfloat('end_rotation', self.rotation_deg)
self.rotation_rad = self.rotation_deg * pi / 180
self.end_rotation_rad = self.end_rotation_deg * pi / 180
def transform(self, z: complex, t: float) -> complex:
"""
Generate point on ellipse at time t.
With cycles > 1, draws the ellipse multiple times.
Combined with transforms, creates moiré effects.
"""
# Normalize t to [0,1] for global interpolation
period = float(self._pipeline_period)
t_norm = t / period if period > 0 else t
# Convert to position within cycles
t_in_cycles = t_norm * self.cycles
# Position within current cycle [0, 1)
t_frac = t_in_cycles % 1.0
# Interpolate radii (global t — oscillation varies across repetitions)
rx = self._interpolate(self.radius_x, self.end_radius_x, t_norm, 'radius_x')
ry = self._interpolate(self.radius_y, self.end_radius_y, t_norm, 'radius_y')
# Apply exponential decay
if self.decay > 0:
decay_factor = np.exp(-self.decay * t_in_cycles)
rx *= decay_factor
ry *= decay_factor
# Angle for this single ellipse (one full revolution per cycle)
angle = t_frac * 2 * pi
# Per-revolution lobe: radii vary within each revolution
if self.lobe != 0:
s = self.lobe * sin(angle * self.lobe_n)
rx += s
ry += s
# Point on ellipse (before rotation)
x = rx * np.cos(angle)
y = ry * np.sin(angle)
point = x + 1j * y
# Apply rotation (interpolated for drift)
rot = self._interpolate(self.rotation_rad, self.end_rotation_rad, t_norm, 'rotation')
point *= np.exp(1j * rot)
return z + point
@property
def natural_period(self) -> Fraction:
"""Period based on cycles."""
return Fraction(self.cycles).limit_denominator(1000)
def __repr__(self):
return f"EllipseModule(rx={self.radius_x}, ry={self.radius_y})"