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.
- ShaderFlow/Common/Imgui.py +0 -0
- ShaderFlow/Common/Notes.py +134 -0
- ShaderFlow/Common/__init__.py +0 -0
- ShaderFlow/Message.py +147 -0
- ShaderFlow/Module.py +162 -0
- ShaderFlow/Modules/Audio.py +431 -0
- ShaderFlow/Modules/Bouncing.py +94 -0
- ShaderFlow/Modules/Camera.py +451 -0
- ShaderFlow/Modules/Depth.py +0 -0
- ShaderFlow/Modules/Dynamics.py +288 -0
- ShaderFlow/Modules/Frametimer.py +79 -0
- ShaderFlow/Modules/Keyboard.py +43 -0
- ShaderFlow/Modules/Noise.py +80 -0
- ShaderFlow/Modules/OpticalFlow.py +1 -0
- ShaderFlow/Modules/Piano.py +362 -0
- ShaderFlow/Modules/Spectrogram.py +315 -0
- ShaderFlow/Modules/Video.py +234 -0
- ShaderFlow/Modules/Waveform.py +100 -0
- ShaderFlow/Modules/__init__.py +0 -0
- ShaderFlow/Resources/Images/ShaderFlow.png +0 -0
- ShaderFlow/Resources/Images/ShaderFlow.svg +1 -0
- ShaderFlow/Resources/Scenes/Examples/Demo.py +284 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Bars.frag +20 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Bouncing.frag +50 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Simulation.glsl +49 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Visuals.glsl +31 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/MotionBlur.frag +20 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Multipass.frag +43 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/RayMarch.frag +59 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/ShaderToy.frag +65 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Video.frag +5 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Visualizer.frag +75 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Waveform.frag +18 -0
- ShaderFlow/Resources/Shaders/Fragment/Default.glsl +65 -0
- ShaderFlow/Resources/Shaders/Fragment/Final.glsl +7 -0
- ShaderFlow/Resources/Shaders/Fragment/Loading.frag +40 -0
- ShaderFlow/Resources/Shaders/Fragment/Missing.glsl +23 -0
- ShaderFlow/Resources/Shaders/Include/Camera.glsl +166 -0
- ShaderFlow/Resources/Shaders/Include/Complex.glsl +0 -0
- ShaderFlow/Resources/Shaders/Include/ShaderFlow.glsl +444 -0
- ShaderFlow/Resources/Shaders/Vertex/Default.glsl +21 -0
- ShaderFlow/Scene.py +1124 -0
- ShaderFlow/Shader.py +391 -0
- ShaderFlow/Texture.py +528 -0
- ShaderFlow/Variable.py +128 -0
- ShaderFlow/__init__.py +11 -0
- ShaderFlow/__main__.py +22 -0
- shaderflow-0.4.0.dev1.dist-info/METADATA +79 -0
- shaderflow-0.4.0.dev1.dist-info/RECORD +51 -0
- shaderflow-0.4.0.dev1.dist-info/WHEEL +5 -0
- 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
|
+
|