shaderflow 0.4.0.dev1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shaderflow might be problematic. Click here for more details.

Files changed (51) hide show
  1. ShaderFlow/Common/Imgui.py +0 -0
  2. ShaderFlow/Common/Notes.py +134 -0
  3. ShaderFlow/Common/__init__.py +0 -0
  4. ShaderFlow/Message.py +147 -0
  5. ShaderFlow/Module.py +162 -0
  6. ShaderFlow/Modules/Audio.py +431 -0
  7. ShaderFlow/Modules/Bouncing.py +94 -0
  8. ShaderFlow/Modules/Camera.py +451 -0
  9. ShaderFlow/Modules/Depth.py +0 -0
  10. ShaderFlow/Modules/Dynamics.py +288 -0
  11. ShaderFlow/Modules/Frametimer.py +79 -0
  12. ShaderFlow/Modules/Keyboard.py +43 -0
  13. ShaderFlow/Modules/Noise.py +80 -0
  14. ShaderFlow/Modules/OpticalFlow.py +1 -0
  15. ShaderFlow/Modules/Piano.py +362 -0
  16. ShaderFlow/Modules/Spectrogram.py +315 -0
  17. ShaderFlow/Modules/Video.py +234 -0
  18. ShaderFlow/Modules/Waveform.py +100 -0
  19. ShaderFlow/Modules/__init__.py +0 -0
  20. ShaderFlow/Resources/Images/ShaderFlow.png +0 -0
  21. ShaderFlow/Resources/Images/ShaderFlow.svg +1 -0
  22. ShaderFlow/Resources/Scenes/Examples/Demo.py +284 -0
  23. ShaderFlow/Resources/Scenes/Examples/GLSL/Bars.frag +20 -0
  24. ShaderFlow/Resources/Scenes/Examples/GLSL/Bouncing.frag +50 -0
  25. ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Simulation.glsl +49 -0
  26. ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Visuals.glsl +31 -0
  27. ShaderFlow/Resources/Scenes/Examples/GLSL/MotionBlur.frag +20 -0
  28. ShaderFlow/Resources/Scenes/Examples/GLSL/Multipass.frag +43 -0
  29. ShaderFlow/Resources/Scenes/Examples/GLSL/RayMarch.frag +59 -0
  30. ShaderFlow/Resources/Scenes/Examples/GLSL/ShaderToy.frag +65 -0
  31. ShaderFlow/Resources/Scenes/Examples/GLSL/Video.frag +5 -0
  32. ShaderFlow/Resources/Scenes/Examples/GLSL/Visualizer.frag +75 -0
  33. ShaderFlow/Resources/Scenes/Examples/GLSL/Waveform.frag +18 -0
  34. ShaderFlow/Resources/Shaders/Fragment/Default.glsl +65 -0
  35. ShaderFlow/Resources/Shaders/Fragment/Final.glsl +7 -0
  36. ShaderFlow/Resources/Shaders/Fragment/Loading.frag +40 -0
  37. ShaderFlow/Resources/Shaders/Fragment/Missing.glsl +23 -0
  38. ShaderFlow/Resources/Shaders/Include/Camera.glsl +166 -0
  39. ShaderFlow/Resources/Shaders/Include/Complex.glsl +0 -0
  40. ShaderFlow/Resources/Shaders/Include/ShaderFlow.glsl +444 -0
  41. ShaderFlow/Resources/Shaders/Vertex/Default.glsl +21 -0
  42. ShaderFlow/Scene.py +1124 -0
  43. ShaderFlow/Shader.py +391 -0
  44. ShaderFlow/Texture.py +528 -0
  45. ShaderFlow/Variable.py +128 -0
  46. ShaderFlow/__init__.py +11 -0
  47. ShaderFlow/__main__.py +22 -0
  48. shaderflow-0.4.0.dev1.dist-info/METADATA +79 -0
  49. shaderflow-0.4.0.dev1.dist-info/RECORD +51 -0
  50. shaderflow-0.4.0.dev1.dist-info/WHEEL +5 -0
  51. shaderflow-0.4.0.dev1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,288 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from ast import Tuple
5
+ from copy import deepcopy
6
+ from ctypes import Union
7
+ from math import pi, tau
8
+ from numbers import Number
9
+ from typing import Iterable, Optional, Self
10
+
11
+ import numpy
12
+ from attr import define, field
13
+
14
+ from ShaderFlow.Module import ShaderModule
15
+ from ShaderFlow.Variable import ShaderVariable
16
+
17
+ # Fixme: Move to Broken when ought to be used somewhere else?
18
+
19
+ @define(slots=False)
20
+ class DynamicNumber(Number):
21
+ """
22
+ Simulate on time domain a progressive second order system
23
+
24
+ # Sources:
25
+ - https://www.youtube.com/watch?v=KPoeNZZ6H4s <- Math mostly took from here, thanks @t3ssel8r
26
+ - https://en.wikipedia.org/wiki/Semi-implicit_Euler_method
27
+ - Control System classes on my university which I got 6/10 final grade
28
+
29
+ This is a Python-port of the video's math, with custom implementation and extras
30
+ """
31
+
32
+ def _convert(self, value):
33
+ return numpy.array(value, dtype=getattr(value, "dtype", self.dtype))
34
+
35
+ def _set_target(self, attribute, value):
36
+ target = self._convert(value)
37
+ if (target.shape != self.value.shape):
38
+ self.initial = deepcopy(target)
39
+ self.value = deepcopy(target)
40
+ return target
41
+
42
+ value: Union[numpy.dtype, numpy.ndarray] = field(default=0)
43
+ """The current value of the system. Prefer explicitly using it over the object itself"""
44
+
45
+ target: Union[numpy.dtype, numpy.ndarray] = field(default=None, on_setattr=_set_target)
46
+ """The target value the system is trying to reach, modeled by the parameters"""
47
+
48
+ dtype: numpy.dtype = field(default=numpy.float32)
49
+ """Data type of the NumPy vectorized data"""
50
+
51
+ initial: Union[numpy.dtype, numpy.ndarray] = field(default=None)
52
+ """Initial value of the system, defaults to first value set"""
53
+
54
+ def __attrs_post_init__(self):
55
+ self.value = self._convert(self.value)
56
+ self.target = self._convert(self.target if (self.target is not None) else self.value)
57
+ self.initial = deepcopy(self.value)
58
+
59
+ frequency: float = 1.0
60
+ """Natural frequency of the system in Hertz, "the speed the system responds to a change in input".
61
+ Also, the frequency it tends to vibrate at, doesn't affect shape of the resulting motion"""
62
+
63
+ zeta: float = 1.0
64
+ """Damping coefficient, z=0 vibration never dies, z=1 is the critical limit where the system
65
+ does not overshoot, z>1 increases this effect and the system takes longer to settle"""
66
+
67
+ response: float = 0.0
68
+ """Defines the initial response "time" of the system, when r=1 the system responds instantly
69
+ to changes on the input, when r=0 the system takes a bit to respond (smoothstep like), when r<0
70
+ the system "anticipates" motion"""
71
+
72
+ precision: float = 1e-6
73
+ """If |target - value| < precision, the system stops updating to save computation"""
74
+
75
+ integral: float = 0.0
76
+ """Integral of the system, the sum of all values over time"""
77
+
78
+ derivative: float = 0.0
79
+ """Derivative of the system, the rate of change of the value in ($unit/second)"""
80
+
81
+ acceleration: float = 0.0
82
+ """Acceleration of the system, the rate of change of the derivative in ($unit/second^2)"""
83
+
84
+ instant: bool = False
85
+ """Update the system immediately to the target value, """
86
+
87
+ @property
88
+ def k1(self) -> float:
89
+ """Y velocity coefficient"""
90
+ return self.zeta/(pi * self.frequency)
91
+
92
+ @property
93
+ def k2(self) -> float:
94
+ """Y acceleration coefficient"""
95
+ return 1/(self.radians*self.radians)
96
+
97
+ @property
98
+ def k3(self) -> float:
99
+ """X velocity coefficient"""
100
+ return (self.response * self.zeta) / (tau * self.frequency)
101
+
102
+ @property
103
+ def radians(self) -> float:
104
+ """Natural resonance frequency in radians per second"""
105
+ return tau * self.frequency
106
+
107
+ @property
108
+ def damping(self) -> float:
109
+ """Damping ratio of some sort"""
110
+ return self.radians * (abs(self.zeta*self.zeta - 1.0))**0.5
111
+
112
+ previous: float = 0
113
+ """Previous target value"""
114
+
115
+ def next(self, target: Number=None, dt: float=1.0) -> Number:
116
+ """
117
+ Update the system to the next time step, optionally with a new target value
118
+ # Fixme: There is a HUGE potential for speed gains if we don't create many temporary ndarray
119
+
120
+ Args:
121
+ target: Next target value to reach, None for previous
122
+ dt: Time delta since last update
123
+
124
+ Returns:
125
+ The system's self.value
126
+ """
127
+ if not dt:
128
+ return self.value
129
+
130
+ # Update target
131
+ if (target is not None):
132
+ self.target = target
133
+
134
+ # Instant mode
135
+ if self.instant:
136
+ self.value = self.target*1
137
+ self.integral += self.value * dt
138
+ self.derivative = 0
139
+ self.acceleration = 0
140
+ return self.value
141
+
142
+ # Optimization: Do not compute if value within precision to target
143
+ if abs(numpy.sum(self.target - self.value)) < self.precision:
144
+ self.integral += self.value * dt
145
+ return self.value
146
+
147
+ # "Estimate velocity"
148
+ velocity = (self.target - self.previous)/dt
149
+ self.previous = self.target
150
+
151
+ # "Clamp k2 to stable values without jitter"
152
+ if (self.radians*dt < self.zeta):
153
+ k2 = max(self.k1*dt, self.k2, 0.5*(self.k1+dt)*dt)
154
+ k1 = self.k1
155
+
156
+ # "Use pole matching when the system is very fast"
157
+ else:
158
+ t1 = math.exp(-1 * self.zeta * self.radians * dt)
159
+ a1 = 2 * t1 * (math.cos if self.zeta <= 1 else math.cosh)(self.damping*dt)
160
+ t2 = 1/(1 + t1*t1 - a1) * dt
161
+ k1 = t2 * (1 - t1*t1)
162
+ k2 = t2 * dt
163
+
164
+ # Integrate values
165
+ self.value += (self.derivative * dt)
166
+ self.acceleration = (self.target + self.k3*velocity - self.value - k1*self.derivative)/k2
167
+ self.derivative += (self.acceleration * dt)
168
+ self.integral += (self.value * dt)
169
+ return self.value
170
+
171
+ def set(self, value: Number):
172
+ """Force the system to a new value"""
173
+ self.target = deepcopy(value)
174
+ self.value = deepcopy(value)
175
+
176
+ def reset(self, instant: bool=False):
177
+ """Reset the system to its initial state"""
178
+ self.target = deepcopy(self.initial)
179
+ if instant:
180
+ self.value = deepcopy(self.initial)
181
+ self.integral = 0
182
+ self.derivative = 0
183
+ self.acceleration = 0
184
+ self.previous = 0
185
+
186
+ @staticmethod
187
+ def extract(*objects: Union[Number, Self]) -> Tuple[Number]:
188
+ """Extract the values from DynamicNumbers objects or return the same object"""
189
+ return tuple(obj.value if isinstance(obj, DynamicNumber) else obj for obj in objects)
190
+
191
+ # # Number implementation
192
+
193
+ def __str__(self) -> str:
194
+ return str(self.value)
195
+
196
+ def __float__(self) -> float:
197
+ return self.value
198
+
199
+ def __int__(self) -> int:
200
+ return int(self.value)
201
+
202
+ def __mul__(self, other) -> Number:
203
+ return self.value * other
204
+
205
+ def __rmul__(self, other) -> Self:
206
+ return self * other
207
+
208
+ def __add__(self, other) -> Self:
209
+ return self.value + other
210
+
211
+ def __radd__(self, other) -> Self:
212
+ return self + other
213
+
214
+ def __sub__(self, other) -> Self:
215
+ return self.value - other
216
+
217
+ def __rsub__(self, other) -> Self:
218
+ return self - other
219
+
220
+ def __truediv__(self, other) -> Self:
221
+ return self.value / other
222
+
223
+ def __rtruediv__(self, other) -> Self:
224
+ return self / other
225
+
226
+ def __floordiv__(self, other) -> Self:
227
+ return self.value // other
228
+
229
+ def __rfloordiv__(self, other) -> Self:
230
+ return self // other
231
+
232
+ def __mod__(self, other) -> Self:
233
+ return self.value % other
234
+
235
+ def __rmod__(self, other) -> Self:
236
+ return self % other
237
+
238
+ def __pow__(self, other) -> Self:
239
+ return self.value ** other
240
+
241
+ def __rpow__(self, other) -> Self:
242
+ return self ** other
243
+
244
+ # -------------------------------------------------------------------------------------------------|
245
+
246
+ @define
247
+ class ShaderDynamics(ShaderModule, DynamicNumber):
248
+ name: str = "iShaderDynamics"
249
+ real: bool = False
250
+
251
+ def __post__(self):
252
+ DynamicNumber.__attrs_post_init__(self)
253
+
254
+ def setup(self):
255
+ self.reset(instant=self.scene.rendering)
256
+
257
+ def update(self):
258
+ # Note: |dt| as backwards in time the system is unstable
259
+ dt = abs(self.scene.rdt if self.real else self.scene.dt)
260
+ self.next(dt=dt)
261
+
262
+ @property
263
+ def type(self) -> Optional[str]:
264
+
265
+ if isinstance(self.value, int):
266
+ return "int"
267
+
268
+ elif isinstance(self.value, float):
269
+ return "float"
270
+
271
+ shape = self.value.shape
272
+
273
+ if len(shape) == 0:
274
+ return "float"
275
+
276
+ return {
277
+ 2: "vec2",
278
+ 3: "vec3",
279
+ 4: "vec4",
280
+ }.get(shape[0], None)
281
+
282
+ def pipeline(self) -> Iterable[ShaderVariable]:
283
+ if not self.type:
284
+ return
285
+ yield ShaderVariable("uniform", self.type, f"{self.name}", self.value)
286
+ yield ShaderVariable("uniform", self.type, f"{self.name}Integral", self.integral)
287
+ yield ShaderVariable("uniform", self.type, f"{self.name}Derivative", self.derivative)
288
+
@@ -0,0 +1,79 @@
1
+ from collections import deque
2
+ from typing import Deque
3
+
4
+ import imgui
5
+ import numpy
6
+ from attr import Factory, define
7
+
8
+ from ShaderFlow.Module import ShaderModule
9
+
10
+
11
+ @define
12
+ class ShaderFrametimer(ShaderModule):
13
+ frametimes: Deque[float] = Factory(deque)
14
+ history: float = 2
15
+
16
+ @property
17
+ def length(self) -> int:
18
+ return max(int(self.history * self.scene.fps), 10)
19
+
20
+ # Framerate manipulation
21
+
22
+ def update(self):
23
+ self.frametimes.append(self.scene.rdt)
24
+ while len(self.frametimes) > self.length:
25
+ self.frametimes.popleft()
26
+
27
+ def percent(self, percent: float=1) -> float:
28
+ cut = int(len(self.frametimes) * (percent/100))
29
+ return numpy.sort(self.frametimes)[-cut:]
30
+
31
+ def __safe__(self, value):
32
+ return value if value < 1e8 else 0
33
+
34
+ # # Frametimes
35
+
36
+ def frametime_average(self, percent: float=100) -> float:
37
+ frametimes = self.percent(percent)
38
+ return sum(frametimes) / (len(frametimes) + 1e-9)
39
+
40
+ @property
41
+ def frametime_maximum(self) -> float:
42
+ return max(self.frametimes)
43
+
44
+ @property
45
+ def frametime_minimum(self) -> float:
46
+ return min(self.frametimes)
47
+
48
+ # # Framerates
49
+
50
+ def framerate_average(self, percent: float=100) -> float:
51
+ return self.__safe__(1.0 / (self.frametime_average(percent) + 1e-9))
52
+
53
+ @property
54
+ def framerate_maximum(self) -> float:
55
+ return self.__safe__(1.0 / (self.frametime_minimum + 1e-9))
56
+
57
+ @property
58
+ def framerate_minimum(self) -> float:
59
+ return self.__safe__(1.0 / (self.frametime_maximum + 1e-9))
60
+
61
+ # ShaderFlow
62
+
63
+ def ui(self):
64
+ imgui.plot_lines(
65
+ (
66
+ f"Target {self.scene.fps:7.3f} fps\n"
67
+ f"Average {self.framerate_average(100):7.3f} fps\n"
68
+ # f"Low 10% {self.framerate_average(10):7.3f} fps\n"
69
+ # f"Low 1% {self.framerate_average(1):7.3f} fps\n"
70
+ f"Maximum {self.framerate_maximum:7.3f} fps\n"
71
+ f"Minimum {self.framerate_minimum:7.3f} fps\n"
72
+ ),
73
+ numpy.array(self.frametimes, dtype=numpy.float32),
74
+ scale_min = 0,
75
+ graph_size = (0, 70)
76
+ )
77
+
78
+ if (state := imgui.input_float("History (Seconds)", self.history, 0.5, 0.5, "%.2f"))[0]:
79
+ self.history = max(0, state[1])
@@ -0,0 +1,43 @@
1
+ import functools
2
+ from typing import Dict, Iterable, Union
3
+
4
+ from attr import Factory, define
5
+ from moderngl_window.context.base import BaseKeys as ModernglKeys
6
+
7
+ from ShaderFlow.Message import ShaderMessage
8
+ from ShaderFlow.Module import ShaderModule
9
+ from ShaderFlow.Variable import ShaderVariable
10
+
11
+
12
+ @functools.lru_cache(maxsize=None)
13
+ def __camel__(name: str) -> str:
14
+ # Convert stuff like (NUMPAD_9 -> Numpad9) and (Home -> Home)
15
+ return "".join([word.capitalize() for word in name.split("_")])
16
+
17
+ @define
18
+ class ShaderKeyboard(ShaderModule):
19
+ Keys = None
20
+ DirKeys = None
21
+
22
+ _pressed: Dict[int, bool] = Factory(dict)
23
+
24
+ @staticmethod
25
+ def set_keymap(keymap: ModernglKeys) -> None:
26
+ ShaderKeyboard.DirKeys = {key: getattr(keymap, key) for key in dir(keymap) if not key.startswith("_")}
27
+ ShaderKeyboard.Keys = keymap
28
+
29
+ def pressed(self, key: Union[int, ModernglKeys]=None) -> bool:
30
+ return self._pressed.setdefault(key, False)
31
+
32
+ def __call__(self, *a, **k) -> bool:
33
+ return self.pressed(*a, **k)
34
+
35
+ def pipeline(self) -> Iterable[ShaderVariable]:
36
+ return
37
+ for name, key in ShaderKeyboard.DirKeys.items():
38
+ yield ShaderVariable("uniform", "bool", f"iKey{__camel__(name)}", self._pressed.setdefault(key, False))
39
+
40
+ def handle(self, message: ShaderMessage):
41
+ if isinstance(message, ShaderMessage.Keyboard.Press):
42
+ self._pressed[message.key] = (message.action != ShaderKeyboard.Keys.ACTION_RELEASE)
43
+
@@ -0,0 +1,80 @@
1
+ import functools
2
+ import random
3
+ from typing import Iterable
4
+
5
+ import numpy
6
+ import opensimplex
7
+ from attr import Factory, define
8
+
9
+ from ShaderFlow.Module import ShaderModule
10
+ from ShaderFlow.Variable import ShaderVariable
11
+
12
+
13
+ @define
14
+ class ShaderNoise(ShaderModule):
15
+ name: str = "Noise"
16
+ seed: int = Factory(functools.partial(random.randint, 0, 10000))
17
+
18
+ # TODO: Convert these to BrokenSecondOrderShaderDynamics?
19
+
20
+ # Maximum amplitude (roughly)
21
+ amplitude: float = 1
22
+ frequency: float = 1
23
+ octaves: int = 1
24
+ roughness: float = 1
25
+
26
+ # # Number of dimensions for this noise
27
+
28
+ __dimensions__: int = 1
29
+
30
+ @property
31
+ def dimensions(self):
32
+ return self.__dimensions__
33
+
34
+ @dimensions.setter
35
+ def dimensions(self, value):
36
+ self.__dimensions__ = value
37
+ self.__simplex__ = [
38
+ opensimplex.OpenSimplex(seed=self.seed + dimension*1000)
39
+ for dimension in range(self.dimensions)
40
+ ]
41
+
42
+ @property
43
+ def type(self) -> str:
44
+ return {
45
+ 1: "float",
46
+ 2: "vec2",
47
+ 3: "vec3",
48
+ }[self.dimensions]
49
+
50
+ # Noise generator
51
+ __simplex__: opensimplex.OpenSimplex = None
52
+
53
+ def __init__(self, dimensions: int=1, *args, **kwargs):
54
+ self.__attrs_init__(*args, **kwargs)
55
+ self.dimensions = dimensions
56
+
57
+ def at(self, x: float=0, y: float=0, z: float=0) -> float:
58
+ """Internal function to return a noise value for three dimensions"""
59
+ noise = numpy.zeros(self.dimensions, dtype=numpy.float32)
60
+
61
+ for dimension in range(self.dimensions):
62
+ for octave in range(self.octaves):
63
+
64
+ # The "position velocity" due frequency of this octave
65
+ # · One octave up, double the frequency
66
+ # · Scale linearly with self.frequency
67
+ k = (2**octave) * self.frequency
68
+
69
+ # Amplitude of this octave so noise remains bounded
70
+ # · Double the octave, half the amplitude
71
+ amplitude = self.amplitude * (self.roughness)**octave
72
+
73
+ # Sum this octave's noise to the total
74
+ # Fixme: x=0, y=0, z=0 yields the same noise for all octaves
75
+ noise[dimension] += self.__simplex__[dimension].noise3(x*k, y*k, z*k) * amplitude
76
+
77
+ return noise
78
+
79
+ def pipeline(self) -> Iterable[ShaderVariable]:
80
+ yield ShaderVariable("uniform", self.type, f"i{self.name}", self.at(self.scene.time))
@@ -0,0 +1 @@
1
+