basilisk-engine 0.0.1__py3-none-any.whl → 0.0.2__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 basilisk-engine might be problematic. Click here for more details.
- basilisk/bsk_assets/__init__.py +0 -0
- basilisk/collisions/__init__.py +0 -0
- basilisk/collisions/broad/__init__.py +0 -0
- basilisk/collisions/broad/broad_aabb.py +96 -0
- basilisk/collisions/broad/broad_bvh.py +102 -0
- basilisk/collisions/collider.py +75 -0
- basilisk/collisions/collider_handler.py +163 -0
- basilisk/collisions/narrow/__init__.py +0 -0
- basilisk/collisions/narrow/epa.py +86 -0
- basilisk/collisions/narrow/gjk.py +66 -0
- basilisk/collisions/narrow/helper.py +23 -0
- basilisk/draw/__init__.py +0 -0
- basilisk/draw/draw.py +101 -0
- basilisk/draw/draw_handler.py +208 -0
- basilisk/draw/font_renderer.py +28 -0
- basilisk/generic/__init__.py +0 -0
- basilisk/generic/abstract_bvh.py +16 -0
- basilisk/generic/collisions.py +26 -0
- basilisk/generic/input_validation.py +28 -0
- basilisk/generic/math.py +7 -0
- basilisk/generic/matrices.py +34 -0
- basilisk/generic/meshes.py +73 -0
- basilisk/generic/quat.py +119 -0
- basilisk/generic/quat_methods.py +8 -0
- basilisk/generic/vec3.py +112 -0
- basilisk/input/__init__.py +1 -0
- basilisk/input/mouse.py +60 -0
- basilisk/mesh/__init__.py +0 -0
- basilisk/mesh/built-in/__init__.py +0 -0
- basilisk/mesh/cube.py +20 -0
- basilisk/mesh/mesh.py +216 -0
- basilisk/mesh/mesh_from_data.py +48 -0
- basilisk/mesh/model.py +272 -0
- basilisk/mesh/narrow_aabb.py +81 -0
- basilisk/mesh/narrow_bvh.py +84 -0
- basilisk/mesh/narrow_primative.py +24 -0
- basilisk/nodes/__init__.py +0 -0
- basilisk/nodes/node.py +508 -0
- basilisk/nodes/node_handler.py +94 -0
- basilisk/physics/__init__.py +0 -0
- basilisk/physics/physics_body.py +36 -0
- basilisk/physics/physics_engine.py +37 -0
- basilisk/render/__init__.py +0 -0
- basilisk/render/batch.py +85 -0
- basilisk/render/camera.py +166 -0
- basilisk/render/chunk.py +85 -0
- basilisk/render/chunk_handler.py +139 -0
- basilisk/render/frame.py +182 -0
- basilisk/render/image.py +76 -0
- basilisk/render/image_handler.py +119 -0
- basilisk/render/light.py +97 -0
- basilisk/render/light_handler.py +54 -0
- basilisk/render/material.py +196 -0
- basilisk/render/material_handler.py +123 -0
- basilisk/render/shader_handler.py +95 -0
- basilisk/render/sky.py +118 -0
- basilisk/shaders/__init__.py +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/METADATA +1 -1
- basilisk_engine-0.0.2.dist-info/RECORD +65 -0
- basilisk_engine-0.0.1.dist-info/RECORD +0 -8
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/top_level.txt +0 -0
basilisk/nodes/node.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import glm
|
|
2
|
+
import numpy as np
|
|
3
|
+
from ..generic.vec3 import Vec3
|
|
4
|
+
from ..generic.quat import Quat
|
|
5
|
+
from ..generic.matrices import get_model_matrix
|
|
6
|
+
from ..mesh.mesh import Mesh
|
|
7
|
+
from ..mesh.cube import cube
|
|
8
|
+
from ..render.material import Material
|
|
9
|
+
from ..physics.physics_body import PhysicsBody
|
|
10
|
+
from ..collisions.collider import Collider
|
|
11
|
+
from ..render.chunk import Chunk
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Node():
|
|
15
|
+
position: Vec3
|
|
16
|
+
"""The position of the node in meters with swizzle xyz"""
|
|
17
|
+
scale: Vec3
|
|
18
|
+
"""The scale of the node in meters in each direction"""
|
|
19
|
+
rotation: Quat
|
|
20
|
+
"""The rotation of the node"""
|
|
21
|
+
forward: glm.vec3
|
|
22
|
+
"""The forward facing vector of the node"""
|
|
23
|
+
mesh: Mesh
|
|
24
|
+
"""The mesh of the node stored as a basilisk mesh object"""
|
|
25
|
+
material: Material
|
|
26
|
+
"""The mesh of the node stored as a basilisk material object"""
|
|
27
|
+
velocity: glm.vec3
|
|
28
|
+
"""The translational velocity of the node"""
|
|
29
|
+
rotational_velocity: glm.vec3
|
|
30
|
+
"""The rotational velocity of the node"""
|
|
31
|
+
physics: bool
|
|
32
|
+
"""Allows the node's movement to be affected by the physics engine and collisions"""
|
|
33
|
+
mass: float
|
|
34
|
+
"""The mass of the node in kg"""
|
|
35
|
+
collisions: bool
|
|
36
|
+
"""Gives the node collision with other nodes in the scene"""
|
|
37
|
+
collider: str
|
|
38
|
+
"""The collider type of the node. Can be either 'box' or 'mesh'"""
|
|
39
|
+
static_friction: float
|
|
40
|
+
"""Determines the friction of the node when still: recommended value 0.0 - 1.0"""
|
|
41
|
+
kinetic_friction: float
|
|
42
|
+
"""Determines the friction of the node when moving: recommended value 0.0 - 1.0"""
|
|
43
|
+
elasticity: float
|
|
44
|
+
"""Determines how bouncy an object is: recommended value 0.0 - 1.0"""
|
|
45
|
+
collision_group: str
|
|
46
|
+
"""Nodes of the same collision group do not collide with each other"""
|
|
47
|
+
name: str
|
|
48
|
+
"""The name of the node for reference"""
|
|
49
|
+
tags: list[str]
|
|
50
|
+
"""Tags are used to sort nodes into separate groups"""
|
|
51
|
+
static: bool
|
|
52
|
+
"""Objects that don't move should be marked as static"""
|
|
53
|
+
chunk: Chunk
|
|
54
|
+
"""""" # TODO Jonah description
|
|
55
|
+
children: list
|
|
56
|
+
"""List of nodes that this node is a parent of"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, node_handler,
|
|
59
|
+
position: glm.vec3=None,
|
|
60
|
+
scale: glm.vec3=None,
|
|
61
|
+
rotation: glm.quat=None,
|
|
62
|
+
forward: glm.vec3=None,
|
|
63
|
+
mesh: Mesh=None,
|
|
64
|
+
material: Material=None,
|
|
65
|
+
velocity: glm.vec3=None,
|
|
66
|
+
rotational_velocity: glm.vec3=None,
|
|
67
|
+
physics: bool=False,
|
|
68
|
+
mass: float=None,
|
|
69
|
+
collisions: bool=False,
|
|
70
|
+
collider: str=None,
|
|
71
|
+
static_friction: float=None,
|
|
72
|
+
kinetic_friction: float=None,
|
|
73
|
+
elasticity: float=None,
|
|
74
|
+
collision_group : float=None,
|
|
75
|
+
name: str='',
|
|
76
|
+
tags: list[str]=None,
|
|
77
|
+
static: bool=True
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Basilisk node object.
|
|
81
|
+
Contains mesh data, translation, material, physics, collider, and descriptive information.
|
|
82
|
+
Base building block for populating a Basilisk scene.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
# parents
|
|
86
|
+
self.node_handler = node_handler
|
|
87
|
+
self.chunk = None
|
|
88
|
+
|
|
89
|
+
# lazy update variables
|
|
90
|
+
self.needs_geometric_center = True
|
|
91
|
+
self.needs_model_matrix = True
|
|
92
|
+
|
|
93
|
+
# node data
|
|
94
|
+
self.internal_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
|
|
95
|
+
self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
|
|
96
|
+
self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
|
|
97
|
+
self.forward = forward if forward else glm.vec3(1, 0, 0)
|
|
98
|
+
self.mesh = mesh if mesh else cube
|
|
99
|
+
self.material = material if material else None # TODO add default base material
|
|
100
|
+
self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
|
|
101
|
+
self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
|
|
102
|
+
|
|
103
|
+
# physics body
|
|
104
|
+
if physics: self.physics_body: PhysicsBody = self.node_handler.scene.physics_engine.add(mass = mass if mass else 1.0)
|
|
105
|
+
elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
|
|
106
|
+
else: self.physics_body = None
|
|
107
|
+
|
|
108
|
+
# collider
|
|
109
|
+
if collisions:
|
|
110
|
+
if not mesh: raise ValueError('Node: cannot collide if it doezsnt have a mesh')
|
|
111
|
+
self.collider: Collider = self.node_handler.scene.collider_handler.add(
|
|
112
|
+
node = self,
|
|
113
|
+
box_mesh = True if collider == 'box' else False,
|
|
114
|
+
static_friction = static_friction,
|
|
115
|
+
kinetic_friction = kinetic_friction,
|
|
116
|
+
elasticity = elasticity,
|
|
117
|
+
collision_group = collision_group
|
|
118
|
+
)
|
|
119
|
+
elif collider: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
|
|
120
|
+
elif static_friction: raise ValueError('Node: cannot have static friction if it does not allow collisions')
|
|
121
|
+
elif kinetic_friction: raise ValueError('Node: cannot have kinetic friction if it does not allow collisions')
|
|
122
|
+
elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
|
|
123
|
+
elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
|
|
124
|
+
else: self.collider = None
|
|
125
|
+
|
|
126
|
+
# information and recursion
|
|
127
|
+
self.name = name
|
|
128
|
+
self.tags = tags if tags else []
|
|
129
|
+
self.static = static and not (self.physics_body or self.velocity or self.rotational_velocity)
|
|
130
|
+
self.data_index = 0
|
|
131
|
+
self.children = []
|
|
132
|
+
|
|
133
|
+
# default callback functions for node transform
|
|
134
|
+
self.previous_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
|
|
135
|
+
self.previous_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
|
|
136
|
+
self.previous_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0) # TODO Do these need to be the callback class or can they just be glm?
|
|
137
|
+
|
|
138
|
+
self.position_updated = False
|
|
139
|
+
self.scale_updated = False
|
|
140
|
+
self.rotation_updated = False
|
|
141
|
+
|
|
142
|
+
# callback function to be added to the custom Vec3 and Quat classes
|
|
143
|
+
def position_callback():
|
|
144
|
+
print('position')
|
|
145
|
+
self.position_updated = True
|
|
146
|
+
|
|
147
|
+
def scale_callback():
|
|
148
|
+
print('scale')
|
|
149
|
+
self.scale_updated = True
|
|
150
|
+
|
|
151
|
+
def rotation_callback():
|
|
152
|
+
print('rotation')
|
|
153
|
+
self.rotation_updated = True
|
|
154
|
+
|
|
155
|
+
self.internal_position.callback = position_callback
|
|
156
|
+
self.internal_scale.callback = scale_callback
|
|
157
|
+
self.internal_rotation.callback = rotation_callback
|
|
158
|
+
|
|
159
|
+
def update(self, dt: float) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Updates the node's movement variables based on the delta time
|
|
162
|
+
"""
|
|
163
|
+
# update all hard-to-update variables
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# reset updates
|
|
167
|
+
self.position_updated = False
|
|
168
|
+
self.scale_updated = False
|
|
169
|
+
self.rotation_updated = False
|
|
170
|
+
|
|
171
|
+
self.position += dt * self.velocity
|
|
172
|
+
self.rotation += 0.5 * dt * self.rotation * glm.quat(0, *self.rotational_velocity)
|
|
173
|
+
self.rotation = glm.normalize(self.rotation)
|
|
174
|
+
self.scale = self.scale
|
|
175
|
+
|
|
176
|
+
if self.physics_body:
|
|
177
|
+
self.velocity += self.physics_body.get_delta_velocity(dt)
|
|
178
|
+
self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
|
|
179
|
+
|
|
180
|
+
def sync_data(self, dt: float) -> None: # TODO only needed for child nodes now
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
def get_nodes(self,
|
|
184
|
+
require_mesh: bool=False,
|
|
185
|
+
require_collider: bool=False,
|
|
186
|
+
require_physics_body: bool=False,
|
|
187
|
+
filter_material: Material=None,
|
|
188
|
+
filter_tags: list[str]=None
|
|
189
|
+
) -> list:
|
|
190
|
+
"""
|
|
191
|
+
Returns the nodes matching the required filters from this branch of the nodes
|
|
192
|
+
"""
|
|
193
|
+
# adds self to nodes list if it matches the criteria
|
|
194
|
+
nodes = []
|
|
195
|
+
|
|
196
|
+
if all([
|
|
197
|
+
(not require_mesh or self.mesh),
|
|
198
|
+
(not require_collider or self.collider),
|
|
199
|
+
(not require_physics_body or self.physics_body),
|
|
200
|
+
(not filter_material or self.material == filter_material),
|
|
201
|
+
(not filter_tags or all([tag in self.tags for tag in filter_tags]))
|
|
202
|
+
]):
|
|
203
|
+
nodes.append(self)
|
|
204
|
+
|
|
205
|
+
# adds children to nodes list if they match the criteria
|
|
206
|
+
for node in self.children: nodes.extend(node.get_nodes(require_mesh, require_collider, require_physics_body, filter_material, filter_tags))
|
|
207
|
+
return node
|
|
208
|
+
|
|
209
|
+
def adopt_child(self, node) -> None: # TODO determine the best way for the user to do this through the scene
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
def add_child(self) -> None: # TODO add node constructor
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
def apply_force(self, force: glm.vec3, dt: float) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Applies a force at the center of the node
|
|
218
|
+
"""
|
|
219
|
+
self.apply_offset_force(force, glm.vec3(0.0), dt)
|
|
220
|
+
|
|
221
|
+
def apply_offset_force(self, force: glm.vec3, offset: glm.vec3, dt: float) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Applies a force at the given offset
|
|
224
|
+
"""
|
|
225
|
+
# translation
|
|
226
|
+
assert self.physics_body, 'Node: Cannot apply a force to a node that doesn\'t have a physics body'
|
|
227
|
+
self.velocity = force / self.mass * dt
|
|
228
|
+
|
|
229
|
+
# rotation
|
|
230
|
+
torque = glm.cross(offset, force)
|
|
231
|
+
self.apply_torque(torque, dt)
|
|
232
|
+
|
|
233
|
+
def apply_torque(self, torque: glm.vec3, dt: float) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Applies a torque on the node
|
|
236
|
+
"""
|
|
237
|
+
assert self.physics_body, 'Node: Cannot apply a torque to a node that doesn\'t have a physics body'
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
# TODO change geometric variables into properties
|
|
241
|
+
def get_inverse_inertia(self) -> glm.mat3x3:
|
|
242
|
+
"""
|
|
243
|
+
Transforms the mesh inertia tensor and inverts it
|
|
244
|
+
"""
|
|
245
|
+
if not (self.mesh and self.physics_body): return None
|
|
246
|
+
inertia_tensor = self.mesh.get_inertia_tensor(self.scale)
|
|
247
|
+
|
|
248
|
+
# mass
|
|
249
|
+
if self.physics_body: inertia_tensor *= self.physics_body.mass
|
|
250
|
+
|
|
251
|
+
# rotation
|
|
252
|
+
rotation_matrix = glm.mat3_cast(self.rotation)
|
|
253
|
+
inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
|
|
254
|
+
|
|
255
|
+
return glm.inverse(inertia_tensor)
|
|
256
|
+
|
|
257
|
+
def get_data(self) -> np.ndarray:
|
|
258
|
+
"""
|
|
259
|
+
Gets the node batch data for chunk batching
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
# Get data from the mesh node
|
|
263
|
+
mesh_data = self.mesh.data
|
|
264
|
+
node_data = np.array([*self.position, *self.rotation, *self.scale, self.material.index])
|
|
265
|
+
|
|
266
|
+
# Create an array to hold the node's data
|
|
267
|
+
data = np.zeros(shape=(mesh_data.shape[0], 25), dtype='f4')
|
|
268
|
+
data[:,:14] = mesh_data
|
|
269
|
+
data[:,14:] = node_data
|
|
270
|
+
|
|
271
|
+
return data
|
|
272
|
+
|
|
273
|
+
def __repr__(self) -> str:
|
|
274
|
+
"""
|
|
275
|
+
Returns a string representation of the node
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
return f'<Bailisk Node | {self.name}, {self.mesh}, ({self.position})>'
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def position(self): return self.internal_position.data
|
|
282
|
+
@property
|
|
283
|
+
def scale(self): return self.internal_scale.data
|
|
284
|
+
@property
|
|
285
|
+
def rotation(self): return self.internal_rotation.data
|
|
286
|
+
@property
|
|
287
|
+
def forward(self): return self._forward
|
|
288
|
+
# TODO add property for Mesh
|
|
289
|
+
@property
|
|
290
|
+
def material(self): return self._material
|
|
291
|
+
@property
|
|
292
|
+
def velocity(self): return self._velocity
|
|
293
|
+
@property
|
|
294
|
+
def rotational_velocity(self): return self._rotational_velocity
|
|
295
|
+
@property
|
|
296
|
+
def mass(self):
|
|
297
|
+
if self.physics_body: return self.physics_body.mass
|
|
298
|
+
raise RuntimeError('Node: Cannot access the mass of a node that has no physics body')
|
|
299
|
+
@property
|
|
300
|
+
def static_friction(self):
|
|
301
|
+
if self.collider: return self.collider.static_friction
|
|
302
|
+
raise RuntimeError('Node: Cannot access the static friction of a node that has no collider')
|
|
303
|
+
@property
|
|
304
|
+
def kinetic_friction(self):
|
|
305
|
+
if self.collider: return self.collider.kinetic_friction
|
|
306
|
+
raise RuntimeError('Node: Cannot access the kinetic friction of a node that has no collider')
|
|
307
|
+
@property
|
|
308
|
+
def elasticity(self):
|
|
309
|
+
if self.collider: return self.collider.elasticity
|
|
310
|
+
raise RuntimeError('Node: Cannot access the elasticity of a node that has no collider')
|
|
311
|
+
@property
|
|
312
|
+
def collision_group(self):
|
|
313
|
+
if self.collider: return self.collider.collision_group
|
|
314
|
+
raise RuntimeError('Node: Cannot access the collision_group of a node that has no collider')
|
|
315
|
+
@property
|
|
316
|
+
def name(self): return self._name
|
|
317
|
+
@property
|
|
318
|
+
def tags(self): return self._tags
|
|
319
|
+
@property
|
|
320
|
+
def x(self): return self.internal_position.data.x
|
|
321
|
+
@property
|
|
322
|
+
def y(self): return self.internal_position.data.y
|
|
323
|
+
@property
|
|
324
|
+
def z(self): return self.internal_position.data.z
|
|
325
|
+
|
|
326
|
+
# TODO add descriptions in the class header
|
|
327
|
+
@property
|
|
328
|
+
def model_matrix(self):
|
|
329
|
+
if self.needs_model_matrix:
|
|
330
|
+
self._model_matrix = get_model_matrix(self.position, self.scale, self.rotation)
|
|
331
|
+
self.needs_model_matrix = False
|
|
332
|
+
return self._model_matrix
|
|
333
|
+
@property
|
|
334
|
+
def geometric_center(self): # assumes the node has a mesh
|
|
335
|
+
# if not self.mesh: raise RuntimeError('Node: Cannot retrieve geometric center if node does not have mesh')
|
|
336
|
+
if self.needs_geometric_center:
|
|
337
|
+
self._geometric_center = self.model_matrix * self.mesh.geometric_center
|
|
338
|
+
self.needs_geometric_center = False
|
|
339
|
+
return self._geometric_center
|
|
340
|
+
@property
|
|
341
|
+
def center_of_mass(self):
|
|
342
|
+
if not self.mesh: raise RuntimeError('Node: Cannot retrieve center of mass if node does not have mesh')
|
|
343
|
+
return self.model_matrix * self.mesh.center_of_mass
|
|
344
|
+
@property
|
|
345
|
+
def volume(self):
|
|
346
|
+
if not self.mesh: raise RuntimeError('Node: Cannot retrieve volume if node does not have mesh')
|
|
347
|
+
return self.mesh.volume * self.scale.x * self.scale.y * self.scale.z
|
|
348
|
+
|
|
349
|
+
@position.setter
|
|
350
|
+
def position(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
351
|
+
if isinstance(value, glm.vec3): self.internal_position.data = glm.vec3(value)
|
|
352
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
353
|
+
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
|
|
354
|
+
self.internal_position.data = glm.vec3(value)
|
|
355
|
+
else: raise TypeError(f'Node: Invalid position value type {type(value)}')
|
|
356
|
+
self.update_position()
|
|
357
|
+
|
|
358
|
+
@scale.setter
|
|
359
|
+
def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
360
|
+
if isinstance(value, glm.vec3): self.internal_scale.data = glm.vec3(value)
|
|
361
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
362
|
+
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
|
|
363
|
+
self.internal_scale.data = glm.vec3(value)
|
|
364
|
+
else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
|
|
365
|
+
self.update_scale()
|
|
366
|
+
|
|
367
|
+
@rotation.setter
|
|
368
|
+
def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
|
|
369
|
+
if isinstance(value, glm.quat) or isinstance(value, glm.vec4) or isinstance(value, glm.vec3): self.internal_rotation.data = glm.quat(value)
|
|
370
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
371
|
+
if len(value) == 3: self.internal_rotation.data = glm.quat(glm.vec3(*value))
|
|
372
|
+
elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
|
|
373
|
+
else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
|
|
374
|
+
else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
|
|
375
|
+
self.update_rotation()
|
|
376
|
+
|
|
377
|
+
def update_position(self): # TODO Could these be repeated in a Vec3/Quat Callback function?
|
|
378
|
+
"""
|
|
379
|
+
Updates the rotation
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
thresh = 0.01
|
|
383
|
+
cur = self.position
|
|
384
|
+
prev = self.previous_position
|
|
385
|
+
|
|
386
|
+
if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
|
|
387
|
+
self.chunk.node_update_callback(self)
|
|
388
|
+
self.previous_position = self.position
|
|
389
|
+
|
|
390
|
+
def update_scale(self):
|
|
391
|
+
"""
|
|
392
|
+
Updates the rotation
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
thresh = 0.01
|
|
396
|
+
cur = self.scale
|
|
397
|
+
prev = self.previous_scale
|
|
398
|
+
|
|
399
|
+
if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
|
|
400
|
+
self.chunk.node_update_callback(self)
|
|
401
|
+
self.previous_scale = self.scale
|
|
402
|
+
|
|
403
|
+
def update_rotation(self):
|
|
404
|
+
"""
|
|
405
|
+
Updates the rotation
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
thresh = 0.01
|
|
409
|
+
cur = self.rotation
|
|
410
|
+
prev = self.previous_rotation
|
|
411
|
+
|
|
412
|
+
if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh or abs(cur.w - prev.w) > thresh):
|
|
413
|
+
self.chunk.node_update_callback(self)
|
|
414
|
+
self.previous_rotation = self.rotation
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@forward.setter
|
|
418
|
+
def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
419
|
+
if isinstance(value, glm.vec3): self._forward = glm.vec3(value)
|
|
420
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
421
|
+
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for forward. Expected 3, got {len(value)}')
|
|
422
|
+
self._forward = glm.vec3(value)
|
|
423
|
+
else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
|
|
424
|
+
|
|
425
|
+
# TODO add setter for Mesh
|
|
426
|
+
|
|
427
|
+
@material.setter
|
|
428
|
+
def material(self, value: Material):
|
|
429
|
+
if isinstance(value, Material):
|
|
430
|
+
self._material = value
|
|
431
|
+
self.node_handler.scene.material_handler.add(value)
|
|
432
|
+
if not self.chunk: return
|
|
433
|
+
self.chunk.node_update_callback(self)
|
|
434
|
+
else: raise TypeError(f'Node: Invalid material value type {type(value)}')
|
|
435
|
+
|
|
436
|
+
@velocity.setter
|
|
437
|
+
def velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
438
|
+
if isinstance(value, glm.vec3): self._velocity = glm.vec3(value)
|
|
439
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
440
|
+
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for velocity. Expected 3, got {len(value)}')
|
|
441
|
+
self._velocity = glm.vec3(value)
|
|
442
|
+
else: raise TypeError(f'Node: Invalid velocity value type {type(value)}')
|
|
443
|
+
|
|
444
|
+
@rotational_velocity.setter
|
|
445
|
+
def rotational_velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
446
|
+
if isinstance(value, glm.vec3): self._rotational_velocity = glm.vec3(value)
|
|
447
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
448
|
+
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for rotational velocity. Expected 3, got {len(value)}')
|
|
449
|
+
self._rotational_velocity = glm.vec3(value)
|
|
450
|
+
else: raise TypeError(f'Node: Invalid rotational velocity value type {type(value)}')
|
|
451
|
+
|
|
452
|
+
@mass.setter
|
|
453
|
+
def mass(self, value: int | float):
|
|
454
|
+
if not self.physics_body: raise RuntimeError('Node: Cannot set the mass of a node that has no physics body')
|
|
455
|
+
if isinstance(value, int) or isinstance(value, float): self.physics_body.mass = value
|
|
456
|
+
else: raise TypeError(f'Node: Invalid mass value type {type(value)}')
|
|
457
|
+
|
|
458
|
+
@static_friction.setter
|
|
459
|
+
def static_friction(self, value: int | float):
|
|
460
|
+
if not self.collider: raise RuntimeError('Node: Cannot set the static friction of a node that has no physics body')
|
|
461
|
+
if isinstance(value, int) or isinstance(value, float): self.collider.static_friction = value
|
|
462
|
+
else: raise TypeError(f'Node: Invalid static friction value type {type(value)}')
|
|
463
|
+
|
|
464
|
+
@kinetic_friction.setter
|
|
465
|
+
def kinetic_friction(self, value: int | float):
|
|
466
|
+
if not self.collider: raise RuntimeError('Node: Cannot set the kinetic friction of a node that has no physics body')
|
|
467
|
+
if isinstance(value, int) or isinstance(value, float): self.collider.kinetic_friction = value
|
|
468
|
+
else: raise TypeError(f'Node: Invalid kinetic friction value type {type(value)}')
|
|
469
|
+
|
|
470
|
+
@elasticity.setter
|
|
471
|
+
def elasticity(self, value: int | float):
|
|
472
|
+
if not self.collider: raise RuntimeError('Node: Cannot set the elasticity of a node that has no physics body')
|
|
473
|
+
if isinstance(value, int) or isinstance(value, float): self.collider.elasticity = value
|
|
474
|
+
else: raise TypeError(f'Node: Invalid elasticity value type {type(value)}')
|
|
475
|
+
|
|
476
|
+
@collision_group.setter
|
|
477
|
+
def collision_group(self, value: str):
|
|
478
|
+
if not self.collider: raise RuntimeError('Node: Cannot set the collision gruop of a node that has no physics body')
|
|
479
|
+
if isinstance(value, str): self.collider.collision_group = value
|
|
480
|
+
else: raise TypeError(f'Node: Invalid collision group value type {type(value)}')
|
|
481
|
+
|
|
482
|
+
@name.setter
|
|
483
|
+
def name(self, value: str):
|
|
484
|
+
if isinstance(value, str): self._name = value
|
|
485
|
+
else: raise TypeError(f'Node: Invalid name value type {type(value)}')
|
|
486
|
+
|
|
487
|
+
@tags.setter
|
|
488
|
+
def tags(self, value: list[str]):
|
|
489
|
+
if isinstance(value, list) or isinstance(value, tuple):
|
|
490
|
+
for tag in value:
|
|
491
|
+
if not isinstance(tag, str): raise TypeError(f'Node: Invalid tag value in tags list of type {type(tag)}')
|
|
492
|
+
self._tags = value
|
|
493
|
+
else: raise TypeError(f'Node: Invalid tags value type {type(value)}')
|
|
494
|
+
|
|
495
|
+
@x.setter
|
|
496
|
+
def x(self, value: int | float):
|
|
497
|
+
if isinstance(value, int) or isinstance(value, float): self.internal_position.data.x = value
|
|
498
|
+
else: raise TypeError(f'Node: Invalid positional x value type {type(value)}')
|
|
499
|
+
|
|
500
|
+
@y.setter
|
|
501
|
+
def y(self, value: int | float):
|
|
502
|
+
if isinstance(value, int) or isinstance(value, float): self.internal_position.data.y = value
|
|
503
|
+
else: raise TypeError(f'Node: Invalid positional y value type {type(value)}')
|
|
504
|
+
|
|
505
|
+
@z.setter
|
|
506
|
+
def z(self, value: int | float):
|
|
507
|
+
if isinstance(value, int) or isinstance(value, float): self.internal_position.data.z = value
|
|
508
|
+
else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import glm
|
|
2
|
+
from .node import Node
|
|
3
|
+
from ..render.chunk_handler import ChunkHandler
|
|
4
|
+
from ..mesh.mesh import Mesh
|
|
5
|
+
from ..render.material import Material
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NodeHandler():
|
|
9
|
+
scene: ...
|
|
10
|
+
"""Back reference to the scene"""
|
|
11
|
+
nodes: list[Node]
|
|
12
|
+
"""The list of root nodes in the scene"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, scene):
|
|
15
|
+
"""
|
|
16
|
+
Contains all the nodes in the scene.
|
|
17
|
+
Handles chunking and batching of nodes
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
self.scene = scene
|
|
21
|
+
self.nodes = []
|
|
22
|
+
self.chunk_handler = ChunkHandler(scene)
|
|
23
|
+
|
|
24
|
+
def update(self):
|
|
25
|
+
"""
|
|
26
|
+
Updates the nodes and chunks in the scene
|
|
27
|
+
"""
|
|
28
|
+
for node in self.nodes: node.update(self.scene.engine.delta_time)
|
|
29
|
+
self.chunk_handler.update()
|
|
30
|
+
|
|
31
|
+
def render(self):
|
|
32
|
+
"""
|
|
33
|
+
Updates the node meshes in the scene
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
self.chunk_handler.render()
|
|
37
|
+
|
|
38
|
+
def add(self,
|
|
39
|
+
position: glm.vec3=None,
|
|
40
|
+
scale: glm.vec3=None,
|
|
41
|
+
rotation: glm.quat=None,
|
|
42
|
+
forward: glm.vec3=None,
|
|
43
|
+
mesh: Mesh=None,
|
|
44
|
+
material: Material=None,
|
|
45
|
+
velocity: glm.vec3=None,
|
|
46
|
+
rotational_velocity: glm.quat=None,
|
|
47
|
+
physics: bool=False,
|
|
48
|
+
mass: float=None,
|
|
49
|
+
collisions: bool=False,
|
|
50
|
+
collider: str=None,
|
|
51
|
+
static_friction: float=None,
|
|
52
|
+
kinetic_friction: float=None,
|
|
53
|
+
elasticity: float=None,
|
|
54
|
+
collision_group : float=None,
|
|
55
|
+
name: str='',
|
|
56
|
+
tags: list[str]=None,
|
|
57
|
+
static: bool=True
|
|
58
|
+
) -> Node:
|
|
59
|
+
"""
|
|
60
|
+
Adds a new node to the node handler
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
node = Node(self, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, collider, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
|
|
64
|
+
|
|
65
|
+
self.nodes.append(node)
|
|
66
|
+
self.chunk_handler.add(node)
|
|
67
|
+
|
|
68
|
+
return node
|
|
69
|
+
|
|
70
|
+
def get(self, name: str) -> Node: # TODO switch to full filter and adapt to search roots
|
|
71
|
+
"""
|
|
72
|
+
Returns the first node with the given traits
|
|
73
|
+
"""
|
|
74
|
+
for node in self.nodes:
|
|
75
|
+
if node.name == name: return node
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def get_all(self, name: str) -> Node:
|
|
79
|
+
"""
|
|
80
|
+
Returns all nodes with the given traits
|
|
81
|
+
"""
|
|
82
|
+
nodes = []
|
|
83
|
+
for node in self.nodes:
|
|
84
|
+
if node.name == name: nodes.append(node)
|
|
85
|
+
return nodes
|
|
86
|
+
|
|
87
|
+
def remove(self, node: Node) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Removes a node and all of its children from their handlers
|
|
90
|
+
"""
|
|
91
|
+
# TODO add support for recursive nodes
|
|
92
|
+
if node.physics_body: self.scene.physics_engine.remove(node.physics_body)
|
|
93
|
+
if node.collider: self.scene.collider_handler.remove(node.collider)
|
|
94
|
+
node.node_handler = None
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import glm
|
|
2
|
+
|
|
3
|
+
class PhysicsBody():
|
|
4
|
+
physics_engine: ...
|
|
5
|
+
"""Back reference to the parent physics engine"""
|
|
6
|
+
mass: float
|
|
7
|
+
"""The mass of the physics body in kg"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, physics_engine, mass:float=1.0) -> None:
|
|
10
|
+
self.physics_engine = physics_engine
|
|
11
|
+
self.mass = mass
|
|
12
|
+
|
|
13
|
+
def get_delta_velocity(self, dt: float) -> glm.vec3:
|
|
14
|
+
"""
|
|
15
|
+
Returns the chnage in translational velocity from constant accelerations and forces
|
|
16
|
+
"""
|
|
17
|
+
if not self.physics_engine: return None
|
|
18
|
+
|
|
19
|
+
dv = glm.vec3(0, 0, 0)
|
|
20
|
+
for acceleration in self.physics_engine.accelerations: dv += acceleration * dt
|
|
21
|
+
for force in self.physics_engine.forces: dv += force / self.mass * dt
|
|
22
|
+
return dv
|
|
23
|
+
|
|
24
|
+
def get_delta_rotational_velocity(self, dt: float) -> glm.quat:
|
|
25
|
+
"""
|
|
26
|
+
Returns the delta rotation quaternion from constant accelerations and forces
|
|
27
|
+
"""
|
|
28
|
+
if not self.physics_engine: return None
|
|
29
|
+
|
|
30
|
+
dw = glm.vec3(0, 0, 0)
|
|
31
|
+
for rotational_acceleration in self.physics_engine.rotational_accelerations: dw += rotational_acceleration * dt
|
|
32
|
+
# TODO add torques
|
|
33
|
+
return dw
|
|
34
|
+
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return f'<Physics Body| {self.mass}>'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import glm
|
|
2
|
+
from .physics_body import PhysicsBody
|
|
3
|
+
|
|
4
|
+
class PhysicsEngine():
|
|
5
|
+
physics_bodies: list[PhysicsBody]
|
|
6
|
+
"""Contains all the physics bodies controlled by this physics engine"""
|
|
7
|
+
accelerations: list[glm.vec3]
|
|
8
|
+
"""Contains constant accelerations to be applied to physics bodies"""
|
|
9
|
+
rotational_accelerations: list[glm.vec3]
|
|
10
|
+
"""Contains constant rotational accelerations to be applied to physics bodies"""
|
|
11
|
+
forces: list[glm.vec3]
|
|
12
|
+
"""Contains constant forces to be applied to physics bodies"""
|
|
13
|
+
torques: list[glm.vec3]
|
|
14
|
+
"""Contains constant rotational accelerations to be applied to physics bodies"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, accelerations: list[glm.vec3] = None, rotational_accelerations: list[glm.vec3] = None, forces: list[glm.vec3] = None, torques: list[glm.vec3] = None) -> None:
|
|
17
|
+
self.physics_bodies = []
|
|
18
|
+
self.accelerations = accelerations if accelerations else [glm.vec3(0, -1, 0)]
|
|
19
|
+
self.rotational_accelerations = rotational_accelerations if rotational_accelerations else []
|
|
20
|
+
self.forces = forces if forces else []
|
|
21
|
+
self.torques = torques if torques else []
|
|
22
|
+
|
|
23
|
+
def add(self, mass: float) -> PhysicsBody:
|
|
24
|
+
"""
|
|
25
|
+
Adds a physics body to the physics engine and returns it
|
|
26
|
+
"""
|
|
27
|
+
physics_body = PhysicsBody(self, mass)
|
|
28
|
+
self.physics_bodies.append(physics_body)
|
|
29
|
+
return physics_body
|
|
30
|
+
|
|
31
|
+
def remove(self, physics_body: PhysicsBody) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Removes the physics body from the physics engine and disconnects it from the engine
|
|
34
|
+
"""
|
|
35
|
+
if physics_body in self.physics_bodies:
|
|
36
|
+
self.physics_bodies.remove(physics_body)
|
|
37
|
+
physics_body.physics_engine = None
|
|
File without changes
|