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,451 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The Camera requires some prior knowledge of a fun piece of math called Quaternions.
|
|
3
|
+
|
|
4
|
+
They are a 4D "imaginary" number that inherently represents rotations in 3D space without the
|
|
5
|
+
need of 3D rotation matrices (which are ugly!)*, and are pretty intuitive to use.
|
|
6
|
+
|
|
7
|
+
* https://github.com/moble/quaternion/wiki/Euler-angles-are-horrible
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Great resources for understanding Quaternions:
|
|
11
|
+
|
|
12
|
+
• "Quaternions and 3d rotation, explained interactively" by 3blue1brown
|
|
13
|
+
- https://www.youtube.com/watch?v=d4EgbgTm0Bg
|
|
14
|
+
|
|
15
|
+
• "Visualizing quaternions (4d numbers) with stereographic projection" by 3blue1brown
|
|
16
|
+
- https://www.youtube.com/watch?v=zjMuIxRvygQ
|
|
17
|
+
|
|
18
|
+
• "Visualizing quaternion, an explorable video series" by Ben Eater and 3blue1brown
|
|
19
|
+
- https://eater.net/quaternions
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Useful resources on Linear Algebra and Coordinate Systems:
|
|
23
|
+
|
|
24
|
+
• "The Essence of Linear Algebra" by 3blue1brown
|
|
25
|
+
- https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab
|
|
26
|
+
|
|
27
|
+
• "here, have a coordinate system chart~" by @FreyaHolmer
|
|
28
|
+
- https://twitter.com/FreyaHolmer/status/1325556229410861056
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import math
|
|
32
|
+
from typing import Iterable, Self, Tuple, Union
|
|
33
|
+
|
|
34
|
+
import numpy
|
|
35
|
+
import quaternion
|
|
36
|
+
from attr import define
|
|
37
|
+
|
|
38
|
+
from Broken import BrokenEnum, clamp, log
|
|
39
|
+
from Broken.Types import Degrees
|
|
40
|
+
from ShaderFlow import SHADERFLOW
|
|
41
|
+
from ShaderFlow.Message import ShaderMessage
|
|
42
|
+
from ShaderFlow.Module import ShaderModule
|
|
43
|
+
from ShaderFlow.Modules.Dynamics import DynamicNumber, ShaderDynamics
|
|
44
|
+
from ShaderFlow.Modules.Keyboard import ShaderKeyboard
|
|
45
|
+
from ShaderFlow.Variable import ShaderVariable
|
|
46
|
+
|
|
47
|
+
# -------------------------------------------------------------------------------------------------|
|
|
48
|
+
|
|
49
|
+
Quaternion = quaternion.quaternion
|
|
50
|
+
Vector3D = numpy.ndarray
|
|
51
|
+
_dtype = numpy.float32
|
|
52
|
+
|
|
53
|
+
class GlobalBasis:
|
|
54
|
+
Origin = numpy.array((0, 0, 0), dtype=_dtype)
|
|
55
|
+
Null = numpy.array((0, 0, 0), dtype=_dtype)
|
|
56
|
+
X = numpy.array((1, 0, 0), dtype=_dtype)
|
|
57
|
+
Y = numpy.array((0, 1, 0), dtype=_dtype)
|
|
58
|
+
Z = numpy.array((0, 0, 1), dtype=_dtype)
|
|
59
|
+
|
|
60
|
+
# -------------------------------------------------------------------------------------------------|
|
|
61
|
+
|
|
62
|
+
class CameraProjection(BrokenEnum):
|
|
63
|
+
"""
|
|
64
|
+
# Perspective
|
|
65
|
+
Project from a Plane A at the position to a Plane B at a distance of one
|
|
66
|
+
- The plane is always perpendicular to the camera's direction
|
|
67
|
+
- Plane A is multiplied by isometric, Plane B by Zoom
|
|
68
|
+
|
|
69
|
+
# VirtualReality
|
|
70
|
+
Two halves of the screen, one for each eye, with a separation between them
|
|
71
|
+
|
|
72
|
+
# Equirectangular
|
|
73
|
+
The "360°" videos we see on platforms like YouTube, it's a simples sphere projected to the
|
|
74
|
+
screen where X defines the azimuth and Y the inclination, ranging such that they sweep the sphere
|
|
75
|
+
"""
|
|
76
|
+
Perspective = 0
|
|
77
|
+
VirtualReality = 1
|
|
78
|
+
Equirectangular = 2
|
|
79
|
+
|
|
80
|
+
class CameraMode(BrokenEnum):
|
|
81
|
+
"""
|
|
82
|
+
How to deal with Rotations and actions on 3D or 2D space
|
|
83
|
+
- FreeCamera: Apply quaternion rotation and don't care of roll changing the "UP" direction
|
|
84
|
+
- Camera2D: Fixed direction, drag moves position on the plane of the screen, becomes isometric
|
|
85
|
+
- Spherical: Always correct such that the camera orthonormal base is pointing "UP"
|
|
86
|
+
"""
|
|
87
|
+
FreeCamera = 0
|
|
88
|
+
Camera2D = 1
|
|
89
|
+
Spherical = 2
|
|
90
|
+
|
|
91
|
+
# -------------------------------------------------------------------------------------------------|
|
|
92
|
+
|
|
93
|
+
class Algebra:
|
|
94
|
+
|
|
95
|
+
def rotate_vector(vector: Vector3D, R: Quaternion) -> Vector3D:
|
|
96
|
+
"""
|
|
97
|
+
Applies a Quaternion rotation to a vector.
|
|
98
|
+
|
|
99
|
+
• Permalink: https://github.com/moble/quaternion/blob/2286c479016097b156682eddaf927036c192c22e/src/quaternion/__init__.py#L654
|
|
100
|
+
|
|
101
|
+
As numpy-quaternion documentation says, we should avoid quaternion.rotate_vectors
|
|
102
|
+
when we don't have multiple vectors to rotate, and we mean a lot of vectors.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
vector (Vector3D): Vector to rotate
|
|
106
|
+
R (Quaternion): Rotation quaternion
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Vector3D: Rotated vector
|
|
110
|
+
"""
|
|
111
|
+
# Potential speed gains, need to verify
|
|
112
|
+
# if sum(quaternion.as_float_array(R)[1:]) < 1e-6:
|
|
113
|
+
# return vector
|
|
114
|
+
return quaternion.as_vector_part(R * quaternion.quaternion(0, *vector) * R.conjugate())
|
|
115
|
+
|
|
116
|
+
def quaternion(axis: Vector3D, angle: Degrees) -> Quaternion:
|
|
117
|
+
"""Builds a quaternion that represents an rotation around an axis for an angle"""
|
|
118
|
+
theta = math.radians(angle/2)
|
|
119
|
+
return Quaternion(math.cos(theta), *(math.sin(theta)*axis))
|
|
120
|
+
|
|
121
|
+
def angle(A: Vector3D, B: Vector3D) -> Degrees:
|
|
122
|
+
"""
|
|
123
|
+
Returns the angle between two vectors by the linear algebra formula:
|
|
124
|
+
• Theta(A, B) = arccos( (A·B) / (|A|*|B|) )
|
|
125
|
+
• Safe for zero vector norm divisions
|
|
126
|
+
• Clips the arccos domain to [-1, 1] to avoid NaNs
|
|
127
|
+
"""
|
|
128
|
+
A, B = DynamicNumber.extract(A, B)
|
|
129
|
+
|
|
130
|
+
# Avoid zero divisions
|
|
131
|
+
if not (LA := numpy.linalg.norm(A)):
|
|
132
|
+
return 0.0
|
|
133
|
+
if not (LB := numpy.linalg.norm(B)):
|
|
134
|
+
return 0.0
|
|
135
|
+
|
|
136
|
+
# Inner cosine; avoid NaNs
|
|
137
|
+
cos = numpy.clip(numpy.dot(A, B)/(LA*LB), -1, 1)
|
|
138
|
+
return numpy.degrees(numpy.arccos(cos))
|
|
139
|
+
|
|
140
|
+
def unit_vector(vector: Vector3D) -> Vector3D:
|
|
141
|
+
"""Returns the unit vector of a given vector, safely"""
|
|
142
|
+
if (factor := numpy.linalg.norm(vector)):
|
|
143
|
+
return vector/factor
|
|
144
|
+
return vector
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def safe(
|
|
148
|
+
*vector: Union[numpy.ndarray, Tuple[float], float, int],
|
|
149
|
+
dimensions: int=3,
|
|
150
|
+
dtype: numpy.dtype=_dtype
|
|
151
|
+
) -> numpy.ndarray:
|
|
152
|
+
"""
|
|
153
|
+
Returns a safe numpy array from a given vector, with the correct dimensions and dtype
|
|
154
|
+
"""
|
|
155
|
+
return numpy.array(vector, dtype=dtype).reshape(dimensions)
|
|
156
|
+
|
|
157
|
+
# -------------------------------------------------------------------------------------------------|
|
|
158
|
+
|
|
159
|
+
@define
|
|
160
|
+
class ShaderCamera(ShaderModule):
|
|
161
|
+
name: str = "iCamera"
|
|
162
|
+
mode: CameraMode = CameraMode.Camera2D.field()
|
|
163
|
+
projection: CameraProjection = CameraProjection.Perspective.field()
|
|
164
|
+
separation: ShaderDynamics = None
|
|
165
|
+
rotation: ShaderDynamics = None
|
|
166
|
+
position: ShaderDynamics = None
|
|
167
|
+
up: ShaderDynamics = None
|
|
168
|
+
zoom: ShaderDynamics = None
|
|
169
|
+
isometric: ShaderDynamics = None
|
|
170
|
+
orbital: ShaderDynamics = None
|
|
171
|
+
dolly: ShaderDynamics = None
|
|
172
|
+
|
|
173
|
+
def __post__(self):
|
|
174
|
+
self.position = ShaderDynamics(scene=self.scene,
|
|
175
|
+
name=f"{self.name}Position", real=True,
|
|
176
|
+
frequency=7, zeta=1, response=1,
|
|
177
|
+
value=numpy.copy(GlobalBasis.Origin)
|
|
178
|
+
)
|
|
179
|
+
self.separation = ShaderDynamics(scene=self.scene,
|
|
180
|
+
name=f"{self.name}VRSeparation", real=True,
|
|
181
|
+
frequency=0.5, zeta=1, response=0, value=0.1
|
|
182
|
+
)
|
|
183
|
+
self.rotation = ShaderDynamics(scene=self.scene,
|
|
184
|
+
name=f"{self.name}Rotation", real=True,
|
|
185
|
+
frequency=5, zeta=1, response=0,
|
|
186
|
+
value=Quaternion(1, 0, 0, 0)
|
|
187
|
+
)
|
|
188
|
+
self.up = ShaderDynamics(scene=self.scene,
|
|
189
|
+
name=f"{self.name}UP", real=True,
|
|
190
|
+
frequency=1, zeta=1, response=0,
|
|
191
|
+
value=numpy.copy(GlobalBasis.Y)
|
|
192
|
+
)
|
|
193
|
+
self.zoom = ShaderDynamics(scene=self.scene,
|
|
194
|
+
name=f"{self.name}Zoom", real=True,
|
|
195
|
+
frequency=3, zeta=1, response=0, value=1
|
|
196
|
+
)
|
|
197
|
+
self.isometric = ShaderDynamics(scene=self.scene,
|
|
198
|
+
name=f"{self.name}Isometric", real=True,
|
|
199
|
+
frequency=1, zeta=1, response=0, value=0
|
|
200
|
+
)
|
|
201
|
+
self.orbital = ShaderDynamics(scene=self.scene,
|
|
202
|
+
name=f"{self.name}Orbital", real=True,
|
|
203
|
+
frequency=1, zeta=1, response=0, value=0
|
|
204
|
+
)
|
|
205
|
+
self.dolly = ShaderDynamics(scene=self.scene,
|
|
206
|
+
name=f"{self.name}Dolly", real=True,
|
|
207
|
+
frequency=1, zeta=1, response=0, value=0
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def pipeline(self) -> Iterable[ShaderVariable]:
|
|
211
|
+
yield ShaderVariable("uniform", "int", f"{self.name}Mode", value=self.mode.value)
|
|
212
|
+
yield ShaderVariable("uniform", "int", f"{self.name}Projection", value=self.projection.value)
|
|
213
|
+
yield ShaderVariable("uniform", "vec3", f"{self.name}X", value=self.base_x)
|
|
214
|
+
yield ShaderVariable("uniform", "vec3", f"{self.name}Y", value=self.base_y)
|
|
215
|
+
yield ShaderVariable("uniform", "vec3", f"{self.name}Z", value=self.base_z)
|
|
216
|
+
|
|
217
|
+
def includes(self) -> Iterable[str]:
|
|
218
|
+
yield SHADERFLOW.RESOURCES.SHADERS_INCLUDE/"Camera.glsl"
|
|
219
|
+
|
|
220
|
+
# ---------------------------------------------------------------------------------------------|
|
|
221
|
+
# Actions with vectors
|
|
222
|
+
|
|
223
|
+
def move(self, *direction: Vector3D, absolute: bool=False) -> Self:
|
|
224
|
+
"""
|
|
225
|
+
Move the camera in a direction relative to the camera's position
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
direction: Direction to move
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Self: Fluent interface
|
|
232
|
+
"""
|
|
233
|
+
if not absolute:
|
|
234
|
+
self.position.target += Algebra.safe(direction)
|
|
235
|
+
else:
|
|
236
|
+
self.position.target = Algebra.safe(direction)
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def rotate(self, direction: Vector3D=GlobalBasis.Null, angle: Degrees=0.0) -> Self:
|
|
240
|
+
"""
|
|
241
|
+
Adds a cumulative rotation to the camera. Use "look" for absolute rotation
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
direction: Perpendicular axis to rotate around, following the right-hand rule
|
|
245
|
+
angle: Angle to rotate
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Self: Fluent interface
|
|
249
|
+
"""
|
|
250
|
+
self.rotation.target = Algebra.quaternion(direction, angle) * self.rotation.target
|
|
251
|
+
self.rotation.target /= numpy.linalg.norm(quaternion.as_float_array(self.rotation.target))
|
|
252
|
+
return self
|
|
253
|
+
|
|
254
|
+
def rotate2d(self, angle: Degrees=0.0) -> Self:
|
|
255
|
+
"""Aligns the UP vector rotated on FORWARD direction. Same math angle on a cartesian plane"""
|
|
256
|
+
target = Algebra.rotate_vector(self.up.value, Algebra.quaternion(self.base_z_target, angle))
|
|
257
|
+
return self.align(self.base_y_target, target)
|
|
258
|
+
|
|
259
|
+
def align(self, A: Vector3D, B: Vector3D, angle: Degrees=0) -> Self:
|
|
260
|
+
"""
|
|
261
|
+
Rotate the camera as if we were to align these two vectors
|
|
262
|
+
"""
|
|
263
|
+
A, B = DynamicNumber.extract(A, B)
|
|
264
|
+
return self.rotate(
|
|
265
|
+
Algebra.unit_vector(numpy.cross(A, B)),
|
|
266
|
+
Algebra.angle(A, B) - angle
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def look(self, *target: Vector3D) -> Self:
|
|
270
|
+
"""
|
|
271
|
+
Rotate the camera to look at some target point
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
target: Target point to look at
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Self: Fluent interface
|
|
278
|
+
"""
|
|
279
|
+
return self.align(self.base_z_target, Algebra.safe(target) - self.position.target)
|
|
280
|
+
|
|
281
|
+
# ---------------------------------------------------------------------------------------------|
|
|
282
|
+
# Bases and directions
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def base_x(self) -> Vector3D:
|
|
286
|
+
return Algebra.rotate_vector(GlobalBasis.X, self.rotation.value)
|
|
287
|
+
@property
|
|
288
|
+
def base_x_target(self) -> Vector3D:
|
|
289
|
+
return Algebra.rotate_vector(GlobalBasis.X, self.rotation.target)
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def base_y(self) -> Vector3D:
|
|
293
|
+
return Algebra.rotate_vector(GlobalBasis.Y, self.rotation.value)
|
|
294
|
+
@property
|
|
295
|
+
def base_y_target(self) -> Vector3D:
|
|
296
|
+
return Algebra.rotate_vector(GlobalBasis.Y, self.rotation.target)
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def base_z(self) -> Vector3D:
|
|
300
|
+
return Algebra.rotate_vector(GlobalBasis.Z, self.rotation.value)
|
|
301
|
+
@property
|
|
302
|
+
def base_z_target(self) -> Vector3D:
|
|
303
|
+
return Algebra.rotate_vector(GlobalBasis.Z, self.rotation.target)
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def x(self) -> float:
|
|
307
|
+
return self.position.value[0]
|
|
308
|
+
@x.setter
|
|
309
|
+
def x(self, value: float):
|
|
310
|
+
self.position.target[0] = value
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def y(self) -> float:
|
|
314
|
+
return self.position.value[1]
|
|
315
|
+
@y.setter
|
|
316
|
+
def y(self, value: float):
|
|
317
|
+
self.position.target[1] = value
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def z(self) -> float:
|
|
321
|
+
return self.position.value[2]
|
|
322
|
+
@z.setter
|
|
323
|
+
def z(self, value: float):
|
|
324
|
+
self.position.target[2] = value
|
|
325
|
+
|
|
326
|
+
# ---------------------------------------------------------------------------------------------|
|
|
327
|
+
# Interaction
|
|
328
|
+
|
|
329
|
+
def update(self):
|
|
330
|
+
dt = abs(self.scene.dt or self.scene.rdt)
|
|
331
|
+
|
|
332
|
+
# Movement on keys
|
|
333
|
+
move = numpy.copy(GlobalBasis.Null)
|
|
334
|
+
|
|
335
|
+
# WASD Shift Spacebar movement
|
|
336
|
+
if self.mode == CameraMode.Camera2D:
|
|
337
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.W): move += GlobalBasis.Y
|
|
338
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.A): move -= GlobalBasis.X
|
|
339
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.S): move -= GlobalBasis.Y
|
|
340
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.D): move += GlobalBasis.X
|
|
341
|
+
else:
|
|
342
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.W): move += GlobalBasis.Z
|
|
343
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.A): move -= GlobalBasis.X
|
|
344
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.S): move -= GlobalBasis.Z
|
|
345
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.D): move += GlobalBasis.X
|
|
346
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.SPACE): move += GlobalBasis.Y
|
|
347
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.LEFT_SHIFT): move -= GlobalBasis.Y
|
|
348
|
+
|
|
349
|
+
if move.any():
|
|
350
|
+
move = Algebra.rotate_vector(move, self.rotation.target)
|
|
351
|
+
self.move(2 * Algebra.unit_vector(move) * dt / self.zoom.value)
|
|
352
|
+
|
|
353
|
+
# Rotation on Q and E
|
|
354
|
+
rotate = numpy.copy(GlobalBasis.Null)
|
|
355
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.Q): rotate += GlobalBasis.Z
|
|
356
|
+
if self.scene.keyboard(ShaderKeyboard.Keys.E): rotate -= GlobalBasis.Z
|
|
357
|
+
if rotate.any(): self.rotate(Algebra.rotate_vector(rotate, self.rotation.target), 45*dt)
|
|
358
|
+
|
|
359
|
+
# Alignment with the "UP" direction
|
|
360
|
+
if self.mode == CameraMode.Spherical:
|
|
361
|
+
self.align(self.base_x_target, self.up, 90)
|
|
362
|
+
|
|
363
|
+
# Isometric on T and G
|
|
364
|
+
self.apply_isometric(+0.5*self.scene.keyboard(ShaderKeyboard.Keys.T)*dt)
|
|
365
|
+
self.apply_isometric(-0.5*self.scene.keyboard(ShaderKeyboard.Keys.G)*dt)
|
|
366
|
+
|
|
367
|
+
def apply_isometric(self, value: float, absolute: bool=False) -> None:
|
|
368
|
+
if value == 0:
|
|
369
|
+
return
|
|
370
|
+
if not absolute:
|
|
371
|
+
self.isometric.target += value
|
|
372
|
+
else:
|
|
373
|
+
self.isometric.target = value
|
|
374
|
+
self.isometric.target = clamp(self.isometric.target, 0, 1)
|
|
375
|
+
|
|
376
|
+
def apply_zoom(self, value: float) -> None:
|
|
377
|
+
# Note: Need to separate multiply and divide to return to the original value
|
|
378
|
+
if value > 0:
|
|
379
|
+
self.zoom.target *= (1 + value)
|
|
380
|
+
else:
|
|
381
|
+
self.zoom.target /= (1 - value)
|
|
382
|
+
|
|
383
|
+
def handle(self, message: ShaderMessage):
|
|
384
|
+
|
|
385
|
+
# Movement on Drag
|
|
386
|
+
if any([
|
|
387
|
+
isinstance(message, ShaderMessage.Mouse.Position) and self.scene.exclusive,
|
|
388
|
+
isinstance(message, ShaderMessage.Mouse.Drag)
|
|
389
|
+
]):
|
|
390
|
+
if not (self.scene.mouse_buttons[1] or self.scene.exclusive):
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
# Rotate around the camera basis itself
|
|
394
|
+
if (self.mode == CameraMode.FreeCamera):
|
|
395
|
+
self.rotate(direction=self.base_y/self.zoom.value, angle= message.du*100)
|
|
396
|
+
self.rotate(direction=self.base_x/self.zoom.value, angle=-message.dv*100)
|
|
397
|
+
|
|
398
|
+
# Rotate relative to the XY plane
|
|
399
|
+
elif (self.mode == CameraMode.Camera2D):
|
|
400
|
+
move = (message.du*GlobalBasis.X) + (message.dv*GlobalBasis.Y)
|
|
401
|
+
move = Algebra.rotate_vector(move, self.rotation.target)
|
|
402
|
+
self.move(move*(1 if self.scene.exclusive else -1)/self.zoom.value)
|
|
403
|
+
|
|
404
|
+
elif (self.mode == CameraMode.Spherical):
|
|
405
|
+
up = 1 if (Algebra.angle(self.base_y_target, self.up) < 90) else -1
|
|
406
|
+
self.rotate(direction=self.up*up /self.zoom.value, angle= message.du*100)
|
|
407
|
+
self.rotate(direction=self.base_x/self.zoom.value, angle=-message.dv*100)
|
|
408
|
+
|
|
409
|
+
# Wheel Scroll Zoom
|
|
410
|
+
elif isinstance(message, ShaderMessage.Mouse.Scroll):
|
|
411
|
+
self.apply_zoom(0.05*message.dy)
|
|
412
|
+
|
|
413
|
+
# Camera alignments and modes
|
|
414
|
+
elif isinstance(message, ShaderMessage.Keyboard.Press) and (message.action == 1):
|
|
415
|
+
|
|
416
|
+
# Switch camera modes
|
|
417
|
+
for _ in range(1):
|
|
418
|
+
if (message.key == ShaderKeyboard.Keys.NUMBER_1):
|
|
419
|
+
self.mode = CameraMode.FreeCamera
|
|
420
|
+
elif (message.key == ShaderKeyboard.Keys.NUMBER_2):
|
|
421
|
+
self.align(self.base_x_target, GlobalBasis.X)
|
|
422
|
+
self.align(self.base_y_target, GlobalBasis.Y)
|
|
423
|
+
self.mode = CameraMode.Camera2D
|
|
424
|
+
self.position.target[2] = 0
|
|
425
|
+
self.isometric.target = 0
|
|
426
|
+
self.zoom.target = 1
|
|
427
|
+
elif (message.key == ShaderKeyboard.Keys.NUMBER_3):
|
|
428
|
+
self.mode = CameraMode.Spherical
|
|
429
|
+
else: break
|
|
430
|
+
else:
|
|
431
|
+
log.info(f"{self.who} • Set mode to {self.mode}")
|
|
432
|
+
|
|
433
|
+
# What is "UP", baby don't hurt me
|
|
434
|
+
for _ in range(1):
|
|
435
|
+
if (message.key == ShaderKeyboard.Keys.I):
|
|
436
|
+
self.up.target = GlobalBasis.X
|
|
437
|
+
elif (message.key == ShaderKeyboard.Keys.J):
|
|
438
|
+
self.up.target = GlobalBasis.Y
|
|
439
|
+
elif (message.key == ShaderKeyboard.Keys.K):
|
|
440
|
+
self.up.target = GlobalBasis.Z
|
|
441
|
+
else: break
|
|
442
|
+
else:
|
|
443
|
+
log.info(f"{self.who} • Set up to {self.up.target}")
|
|
444
|
+
self.align(self.base_z_target, self.up.target)
|
|
445
|
+
self.align(self.base_y_target, self.up.target, 90)
|
|
446
|
+
self.align(self.base_x_target, self.up.target, 90)
|
|
447
|
+
|
|
448
|
+
# Switch Projection
|
|
449
|
+
if (message.key == ShaderKeyboard.Keys.P):
|
|
450
|
+
self.projection = next(self.projection)
|
|
451
|
+
log.info(f"{self.who} • Set projection to {self.projection}")
|
|
File without changes
|