basilisk-engine 0.0.9__py3-none-any.whl → 0.1.1__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.

Files changed (61) hide show
  1. basilisk/__init__.py +3 -1
  2. basilisk/collisions/broad/broad_aabb.py +8 -1
  3. basilisk/collisions/broad/broad_bvh.py +38 -2
  4. basilisk/collisions/collider.py +20 -10
  5. basilisk/collisions/collider_handler.py +97 -32
  6. basilisk/collisions/narrow/contact_manifold.py +91 -0
  7. basilisk/collisions/narrow/dataclasses.py +27 -0
  8. basilisk/collisions/narrow/deprecated.py +47 -0
  9. basilisk/collisions/narrow/epa.py +21 -15
  10. basilisk/collisions/narrow/gjk.py +15 -14
  11. basilisk/collisions/narrow/graham_scan.py +25 -0
  12. basilisk/collisions/narrow/helper.py +14 -7
  13. basilisk/collisions/narrow/line_intersections.py +107 -0
  14. basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
  15. basilisk/draw/draw_handler.py +7 -5
  16. basilisk/engine.py +28 -6
  17. basilisk/generic/abstract_custom.py +134 -0
  18. basilisk/generic/collisions.py +47 -2
  19. basilisk/generic/quat.py +84 -65
  20. basilisk/generic/vec3.py +99 -67
  21. basilisk/input/mouse.py +3 -3
  22. basilisk/mesh/cube.py +20 -2
  23. basilisk/mesh/mesh.py +69 -54
  24. basilisk/mesh/mesh_from_data.py +106 -21
  25. basilisk/mesh/narrow_aabb.py +10 -1
  26. basilisk/mesh/narrow_bvh.py +9 -1
  27. basilisk/nodes/node.py +211 -101
  28. basilisk/nodes/node_handler.py +58 -33
  29. basilisk/particles/__init__.py +0 -0
  30. basilisk/particles/particle_handler.py +55 -0
  31. basilisk/particles/particle_renderer.py +87 -0
  32. basilisk/physics/impulse.py +113 -0
  33. basilisk/physics/physics_body.py +10 -2
  34. basilisk/physics/physics_engine.py +2 -3
  35. basilisk/render/batch.py +3 -1
  36. basilisk/render/camera.py +35 -1
  37. basilisk/render/chunk.py +19 -4
  38. basilisk/render/chunk_handler.py +39 -23
  39. basilisk/render/image.py +1 -1
  40. basilisk/render/image_handler.py +17 -14
  41. basilisk/render/light_handler.py +16 -11
  42. basilisk/render/material.py +38 -14
  43. basilisk/render/material_handler.py +31 -18
  44. basilisk/render/shader.py +110 -0
  45. basilisk/render/shader_handler.py +20 -35
  46. basilisk/render/sky.py +8 -5
  47. basilisk/scene.py +116 -33
  48. basilisk/shaders/batch.frag +40 -11
  49. basilisk/shaders/batch.vert +14 -7
  50. basilisk/shaders/geometry.frag +9 -0
  51. basilisk/shaders/geometry.vert +42 -0
  52. basilisk/shaders/normal.frag +60 -0
  53. basilisk/shaders/normal.vert +97 -0
  54. basilisk/shaders/particle.frag +72 -0
  55. basilisk/shaders/particle.vert +85 -0
  56. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
  57. basilisk_engine-0.1.1.dist-info/RECORD +95 -0
  58. basilisk/shaders/image.png +0 -0
  59. basilisk_engine-0.0.9.dist-info/RECORD +0 -78
  60. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
  61. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/top_level.txt +0 -0
@@ -41,7 +41,7 @@ class Material():
41
41
  clearcoat_gloss: float
42
42
  """The glossiness of the clearcoat layer. 0 For a satin appearance, 1 for a gloss appearance"""
43
43
 
44
- def __init__(self, name: str=None, color: tuple=(255.0, 255.0, 255.0), texture: Image=None, normal: Image=None,
44
+ def __init__(self, name: str=None, color: tuple=(255.0, 255.0, 255.0), texture: Image=None, normal: Image=None, roughness_map: Image=None, ao_map: Image=None,
45
45
  roughness: float=0.7, subsurface: float=0.2, sheen: float=0.5, sheen_tint: float=0.5,
46
46
  anisotropic: float=0.0, specular: float=1.0, metallicness: float=0.0, specular_tint: float=0.0,
47
47
  clearcoat: float=0.5, clearcoat_gloss: float=0.25) -> None:
@@ -67,6 +67,8 @@ class Material():
67
67
  self.color = color
68
68
  self.texture = texture
69
69
  self.normal = normal
70
+ self.roughness_map = roughness_map
71
+ self.ao_map = ao_map
70
72
  self.roughness = roughness
71
73
  self.subsurface = subsurface
72
74
  self.sheen = sheen
@@ -96,6 +98,14 @@ class Material():
96
98
  if self.normal: data.extend([1, self.normal.index.x, self.normal.index.y])
97
99
  else: data.extend([0, 0, 0])
98
100
 
101
+ # Add roughness data
102
+ if self.roughness_map: data.extend([1, self.roughness_map.index.x, self.roughness_map.index.y])
103
+ else: data.extend([0, 0, 0])
104
+
105
+ # Add ao data
106
+ if self.ao_map: data.extend([1, self.ao_map.index.x, self.ao_map.index.y])
107
+ else: data.extend([0, 0, 0])
108
+
99
109
  return data
100
110
 
101
111
  def __repr__(self) -> str:
@@ -109,6 +119,10 @@ class Material():
109
119
  @property
110
120
  def normal(self): return self._normal
111
121
  @property
122
+ def roughness_map(self): return self._roughness_map
123
+ @property
124
+ def ao_map(self): return self._ao_map
125
+ @property
112
126
  def roughness(self): return self._roughness
113
127
  @property
114
128
  def subsurface(self): return self._subsurface
@@ -133,64 +147,74 @@ class Material():
133
147
  @color.setter
134
148
  def color(self, value: tuple | list | glm.vec3 | np.ndarray):
135
149
  self._color = validate_glm_vec3("Material", "color", value)
136
- if self.material_handler: self.material_handler.write()
150
+ if self.material_handler: self.material_handler.write(regenerate=True)
137
151
 
138
152
  @texture.setter
139
153
  def texture(self, value: Image | None):
140
154
  self._texture = validate_image("Material", "texture", value)
141
- if self.material_handler: self.material_handler.write()
155
+ if self.material_handler: self.material_handler.write(regenerate=True)
142
156
 
143
157
  @normal.setter
144
158
  def normal(self, value: Image | None):
145
159
  self._normal = validate_image("Material", "normal map", value)
146
- if self.material_handler: self.material_handler.write()
160
+ if self.material_handler: self.material_handler.write(regenerate=True)
161
+
162
+ @roughness_map.setter
163
+ def roughness_map(self, value: Image | None):
164
+ self._roughness_map = validate_image("Material", "roughness_map", value)
165
+ if self.material_handler: self.material_handler.write(regenerate=True)
166
+
167
+ @ao_map.setter
168
+ def ao_map(self, value: Image | None):
169
+ self._ao_map = validate_image("Material", "ao_map map", value)
170
+ if self.material_handler: self.material_handler.write(regenerate=True)
147
171
 
148
172
  @roughness.setter
149
173
  def roughness(self, value: float | int | glm.float32):
150
174
  self._roughness = validate_float("Material", "roughness", value)
151
- if self.material_handler: self.material_handler.write()
175
+ if self.material_handler: self.material_handler.write(regenerate=True)
152
176
 
153
177
  @subsurface.setter
154
178
  def subsurface(self, value: float | int | glm.float32):
155
179
  self._subsurface = validate_float("Material", "subsurface", value)
156
- if self.material_handler: self.material_handler.write()
180
+ if self.material_handler: self.material_handler.write(regenerate=True)
157
181
 
158
182
  @sheen.setter
159
183
  def sheen(self, value: float | int | glm.float32):
160
184
  self._sheen = validate_float("Material", "sheen", value)
161
- if self.material_handler: self.material_handler.write()
185
+ if self.material_handler: self.material_handler.write(regenerate=True)
162
186
 
163
187
  @sheen_tint.setter
164
188
  def sheen_tint(self, value: float | int | glm.float32):
165
189
  self._sheen_tint = validate_float("Material", "sheen tint", value)
166
- if self.material_handler: self.material_handler.write()
190
+ if self.material_handler: self.material_handler.write(regenerate=True)
167
191
 
168
192
  @anisotropic.setter
169
193
  def anisotropic(self, value: float | int | glm.float32):
170
194
  self._anisotropic = validate_float("Material", "anisotropic", value)
171
- if self.material_handler: self.material_handler.write()
195
+ if self.material_handler: self.material_handler.write(regenerate=True)
172
196
 
173
197
  @specular.setter
174
198
  def specular(self, value: float | int | glm.float32):
175
199
  self._specular = validate_float("Material", "specular", value)
176
- if self.material_handler: self.material_handler.write()
200
+ if self.material_handler: self.material_handler.write(regenerate=True)
177
201
 
178
202
  @metallicness.setter
179
203
  def metallicness(self, value: float | int | glm.float32):
180
204
  self._metallicness = validate_float("Material", "metallicness", value)
181
- if self.material_handler: self.material_handler.write()
205
+ if self.material_handler: self.material_handler.write(regenerate=True)
182
206
 
183
207
  @specular_tint.setter
184
208
  def specular_tint(self, value: float | int | glm.float32):
185
209
  self._specular_tint = validate_float("Material", "specular tint", value)
186
- if self.material_handler: self.material_handler.write()
210
+ if self.material_handler: self.material_handler.write(regenerate=True)
187
211
 
188
212
  @clearcoat.setter
189
213
  def clearcoat(self, value: float | int | glm.float32):
190
214
  self._clearcoat = validate_float("Material", "clearcoat", value)
191
- if self.material_handler: self.material_handler.write()
215
+ if self.material_handler: self.material_handler.write(regenerate=True)
192
216
 
193
217
  @clearcoat_gloss.setter
194
218
  def clearcoat_gloss(self, value: float | int | glm.float32):
195
219
  self._clearcoat_gloss = validate_float("Material", "clearcoat gloss", value)
196
- if self.material_handler: self.material_handler.write()
220
+ if self.material_handler: self.material_handler.write(regenerate=True)
@@ -39,18 +39,27 @@ class MaterialHandler():
39
39
  Adds the given material to the handler if it is not already present
40
40
  """
41
41
 
42
- # Check that the material is not already in the scene
43
- if material in self.materials: return None
44
- # Update the material's handler
45
- material.material_handler = self
46
- # Add images
47
- if material.texture: self.image_handler.add(material.texture)
48
- if material.normal: self.image_handler.add(material.normal)
49
-
50
- # Add the material
51
- self.materials.append(material)
42
+ write = False
43
+
44
+ if isinstance(material, Material): material = [material]
45
+
46
+ for mtl in material:
47
+ # Check that the material is not already in the scene
48
+ if mtl in self.materials: continue
49
+ # Update the material's handler
50
+ mtl.material_handler = self
51
+ # Add images
52
+ if mtl.texture: self.image_handler.add(mtl.texture)
53
+ if mtl.normal: self.image_handler.add(mtl.normal)
54
+
55
+ # Add the material
56
+ self.materials.append(mtl)
57
+
58
+ write = True
59
+
60
+
52
61
  # Write materials
53
- self.write()
62
+ if write: self.write(regenerate=True)
54
63
 
55
64
  def generate_material_texture(self) -> None:
56
65
  """
@@ -64,7 +73,7 @@ class MaterialHandler():
64
73
  if self.data_texture: self.data_texture.release()
65
74
 
66
75
  # Create empty texture data
67
- material_data = np.zeros(shape=(len(self.materials), 19), dtype="f4")
76
+ material_data = np.zeros(shape=(len(self.materials), 25), dtype="f4")
68
77
 
69
78
  # Get data from the materials
70
79
  for i, mtl in enumerate(self.materials):
@@ -75,17 +84,20 @@ class MaterialHandler():
75
84
  material_data = np.ravel(material_data)
76
85
  self.data_texture = self.ctx.texture((1, len(material_data)), components=1, dtype='f4', data=material_data)
77
86
 
78
- def write(self, shader_program: mgl.Program=None) -> None:
87
+ def write(self, regenerate=False) -> None:
79
88
  """
80
- Writes all material data to the given shader
89
+ Writes all material data to relavent shaders
81
90
  """
82
91
 
83
- if shader_program == None: shader_program = self.scene.shader_handler.programs['batch']
92
+ if regenerate: self.generate_material_texture()
84
93
 
85
- self.generate_material_texture()
94
+ if not self.data_texture: return
86
95
 
87
- shader_program[f'materialsTexture'] = 9
88
- self.data_texture.use(location=9)
96
+ for shader in self.engine.scene.shader_handler.shaders:
97
+ if 'materialsTexture' not in shader.uniforms: continue
98
+
99
+ shader.program['materialsTexture'] = 9
100
+ self.data_texture.use(location=9)
89
101
 
90
102
  def get(self, identifier: str | int) -> any:
91
103
  """
@@ -113,6 +125,7 @@ class MaterialHandler():
113
125
 
114
126
  self.base = Material('Base')
115
127
  self.materials.append(self.base)
128
+ self.generate_material_texture()
116
129
  self.write()
117
130
 
118
131
  def __del__(self) -> None:
@@ -0,0 +1,110 @@
1
+ import moderngl as mgl
2
+ import random
3
+
4
+ attribute_mappings = {
5
+ 'in_position' : [0, 1, 2],
6
+ 'in_uv' : [3, 4],
7
+ 'in_normal' : [5, 6, 7],
8
+ 'in_tangent' : [8, 9, 10],
9
+ 'in_bitangent' : [11, 12, 13],
10
+ 'obj_position' : [14, 15, 16],
11
+ 'obj_rotation' : [17, 18, 19, 20],
12
+ 'obj_scale' : [21, 22, 23],
13
+ 'obj_material' : [24],
14
+ }
15
+
16
+
17
+ class Shader:
18
+ program: mgl.Program=None
19
+ """Shader program for the vertex and fragment shader"""
20
+ vertex_shader: str
21
+ """String representation of the vertex shader"""
22
+ fragment_shader: str
23
+ """String representation of the vertex shader"""
24
+ uniforms: list[str]=[]
25
+ """List containg the names of all uniforms in the shader"""
26
+ attribute_indices: list[int]
27
+ """List of indices that map all possible shader attributes to the ones used byu the shader"""
28
+ fmt: str
29
+ """String representation of the format for building vaos"""
30
+ attributes: list[str]
31
+ """List representation of the attributes for building vaos"""
32
+
33
+ def __init__(self, engine, vert: str=None, frag: str=None) -> None:
34
+ """
35
+ Basilisk shader object. Contains shader program and shader attrbibute/uniform information
36
+ Args:
37
+ vert: str=None
38
+ Path to the vertex shader. Defaults to internal if none is given
39
+ frag: str=None
40
+ Path to the fragment shader. Defaults to internal if none is given
41
+ """
42
+
43
+ self.engine = engine
44
+ self.ctx = engine.ctx
45
+
46
+ # Default class attributes values
47
+ self.uniforms = []
48
+ self.attribute_indices = []
49
+ self.fmt = ''
50
+ self.attributes = []
51
+
52
+ # Default vertex and fragment shaders
53
+ if vert == None: vert = self.engine.root + '/shaders/batch.vert'
54
+ if frag == None: frag = self.engine.root + '/shaders/batch.frag'
55
+
56
+ # Read the shaders
57
+ with open(vert) as file:
58
+ self.vertex_shader = file.read()
59
+ with open(frag) as file:
60
+ self.fragment_shader = file.read()
61
+
62
+ # Hash value for references
63
+ if vert == None and frag == None:
64
+ self.hash = hash((self.vertex_shader, self.fragment_shader, 'default'))
65
+ else:
66
+ self.hash = hash((self.vertex_shader, self.fragment_shader))
67
+
68
+ # Create a string of all lines in both shaders
69
+ lines = f'{self.vertex_shader}\n{self.fragment_shader}'.split('\n')
70
+
71
+ # Parse through shader to find uniforms and attributes
72
+ for line in lines:
73
+ tokens = line.strip().split(' ')
74
+
75
+ # Add uniforms
76
+ if tokens[0] == 'uniform' and len(tokens) > 2:
77
+ self.uniforms.append(tokens[-1][:-1])
78
+
79
+ # Add attributes
80
+ if tokens[0] == 'layout' and len(tokens) > 2 and 'in' in line:
81
+ self.attributes.append(tokens[-1][:-1])
82
+
83
+ if tokens[-1][:-1] not in attribute_mappings: continue
84
+ indices = attribute_mappings[tokens[-1][:-1]]
85
+ self.attribute_indices.extend(indices)
86
+ self.fmt += f'{len(indices)}f '
87
+
88
+ # Create a program with shaders
89
+ self.program = self.ctx.program(vertex_shader=self.vertex_shader, fragment_shader=self.fragment_shader)
90
+
91
+ def set_main(self):
92
+ """
93
+ Selects a shader for use
94
+ """
95
+
96
+ self.engine.scene.shader_handler.add(self)
97
+ self.engine.scene.node_handler.chunk_handler.update_all()
98
+
99
+ def write(self, name: str, value) -> None:
100
+ """
101
+ Writes a uniform to the shader program
102
+ """
103
+
104
+ self.program[name].write(value)
105
+
106
+ def __del__(self) -> int:
107
+ if self.program: self.program.release()
108
+
109
+ def __hash__(self) -> int:
110
+ return self.hash
@@ -1,8 +1,6 @@
1
1
  import moderngl as mgl
2
2
  import glm
3
-
4
- # Predefined uniforms that do not change each frame
5
- single_frame_uniforms = ['m_proj']
3
+ from .shader import Shader
6
4
 
7
5
 
8
6
  class ShaderHandler:
@@ -12,10 +10,8 @@ class ShaderHandler:
12
10
  """Back reference to the parent scene"""
13
11
  ctx: mgl.Context
14
12
  """Back reference to the parent context"""
15
- programs: dict = {}
13
+ shaders: set
16
14
  """Dictionary containing all the shaders"""
17
- shader_uniforms: dict = {}
18
- """Dictionary all the uniforms present in a shader"""
19
15
  uniform_values: dict = {}
20
16
  """Dictionary containing uniform values"""
21
17
 
@@ -30,38 +26,27 @@ class ShaderHandler:
30
26
  self.ctx = scene.engine.ctx
31
27
 
32
28
  # Initalize dictionaries
33
- self.programs = {}
34
- self.shader_uniforms = {}
35
-
36
- self.load('batch', self.engine.root + '/shaders/batch.vert', self.engine.root + '/shaders/batch.frag')
37
- self.load('draw', self.engine.root + '/shaders/draw.vert', self.engine.root + '/shaders/draw.frag')
38
- self.load('sky', self.engine.root + '/shaders/sky.vert', self.engine.root + '/shaders/sky.frag')
29
+ self.shaders = set()
30
+ self.add(self.engine.shader)
39
31
 
40
- def load(self, name: str, vert_path: str, frag_path: str) -> None:
32
+ def add(self, shader: Shader) -> None:
41
33
  """
42
34
  Creates a shader program from a file name.
43
35
  Parses through shaders to identify uniforms and save for writting
44
36
  """
45
37
 
46
- # Read the shaders
47
- with open(vert_path) as file:
48
- vertex_shader = file.read()
49
- with open(frag_path) as file:
50
- fragment_shader = file.read()
51
-
52
- # Create blank list for uniforms
53
- self.shader_uniforms[name] = []
54
- # Create a list of all lines in both shaders
55
- lines = f'{vertex_shader}\n{fragment_shader}'.split('\n')
56
- # Parse through shader to find uniform variables
57
- for line in lines:
58
- tokens = line.strip().split(' ')
59
- if tokens[0] == 'uniform' and len(tokens) > 2:
60
- self.shader_uniforms[name].append(tokens[2][:-1])
61
38
 
62
- # Create a program with shaders
63
- program = self.ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
64
- self.programs[name] = program
39
+ if not shader: return None
40
+ if shader in self.shaders: return shader
41
+
42
+ self.shaders.add(shader)
43
+
44
+ if self.scene.material_handler:
45
+ self.scene.light_handler.write()
46
+ self.scene.material_handler.write()
47
+ self.scene.material_handler.image_handler.write()
48
+
49
+ return shader
65
50
 
66
51
  def get_uniforms_values(self) -> None:
67
52
  """
@@ -83,13 +68,13 @@ class ShaderHandler:
83
68
 
84
69
  self.get_uniforms_values()
85
70
  for uniform in self.uniform_values:
86
- for program in self.programs:
87
- if not uniform in self.shader_uniforms[program]: continue # Does not write uniforms not in the shader
88
- self.programs[program][uniform].write(self.uniform_values[uniform])
71
+ for shader in self.shaders:
72
+ if not uniform in shader.uniforms: continue # Does not write uniforms not in the shader
73
+ shader.write(uniform, self.uniform_values[uniform])
89
74
 
90
75
  def release(self) -> None:
91
76
  """
92
77
  Releases all shader programs in handler
93
78
  """
94
79
 
95
- [program.release() for program in self.programs.values()]
80
+ [shader.__del__() for shader in self.shaders]
basilisk/render/sky.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  from PIL import Image as PIL_Image
3
+ from .shader import Shader
3
4
 
4
5
  class Sky:
5
6
  texture_cube=None
@@ -26,11 +27,12 @@ class Sky:
26
27
 
27
28
  def write(self):
28
29
  # Write the texture cube to the sky shader
29
- self.program['skyboxTexture'] = 8
30
+ self.shader.program['skyboxTexture'] = 8
30
31
  self.texture_cube.use(location = 8)
31
32
 
32
- batch_program = self.scene.shader_handler.programs['batch']
33
- batch_program['skyboxTexture'] = 8
33
+ shader = self.scene.engine.shader
34
+ if 'skyboxTexture' not in shader.uniforms: return
35
+ shader.program['skyboxTexture'] = 8
34
36
  self.texture_cube.use(location = 8)
35
37
 
36
38
 
@@ -105,8 +107,9 @@ class Sky:
105
107
 
106
108
  # Create a renderable vao
107
109
  self.vbo = self.ctx.buffer(vertex_data)
108
- self.program = self.scene.shader_handler.programs['sky']
109
- self.vao = self.ctx.vertex_array(self.program, [(self.vbo, '3f', 'in_position')], skip_errors=True)
110
+ root = self.scene.engine.root
111
+ self.shader = self.scene.shader_handler.add(Shader(self.scene.engine, root + '/shaders/sky.vert', root + '/shaders/sky.frag'))
112
+ self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f', 'in_position')], skip_errors=True)
110
113
 
111
114
  def __del__(self):
112
115
  """
basilisk/scene.py CHANGED
@@ -12,7 +12,9 @@ from .collisions.collider_handler import ColliderHandler
12
12
  from .draw.draw_handler import DrawHandler
13
13
  from .render.sky import Sky
14
14
  from .render.frame import Frame
15
-
15
+ from .particles.particle_handler import ParticleHandler
16
+ from .nodes.node import Node
17
+ from .generic.collisions import moller_trumbore
16
18
 
17
19
  class Scene():
18
20
  engine: any
@@ -42,8 +44,10 @@ class Scene():
42
44
  Updates the physics and in the scene
43
45
  """
44
46
 
45
- self.camera.update()
46
47
  self.node_handler.update()
48
+ self.particle.update()
49
+ self.camera.update()
50
+ self.collider_handler.resolve_collisions()
47
51
 
48
52
  def render(self) -> None:
49
53
  """
@@ -52,39 +56,50 @@ class Scene():
52
56
 
53
57
  self.frame.use()
54
58
  self.shader_handler.write()
55
- self.sky.render()
59
+ if self.sky: self.sky.render()
56
60
  self.node_handler.render()
61
+ self.particle.render()
57
62
  self.draw_handler.render()
58
63
 
59
64
  if self.engine.headless: return
60
65
  self.frame.render()
61
66
 
62
- def add_node(self,
63
- position: glm.vec3=None,
64
- scale: glm.vec3=None,
65
- rotation: glm.quat=None,
66
- forward: glm.vec3=None,
67
- mesh: Mesh=None,
68
- material: Material=None,
69
- velocity: glm.vec3=None,
70
- rotational_velocity: glm.quat=None,
71
- physics: bool=False,
72
- mass: float=None,
73
- collisions: bool=False,
74
- collider: str=None,
75
- static_friction: float=None,
76
- kinetic_friction: float=None,
77
- elasticity: float=None,
78
- collision_group : float=None,
79
- name: str='',
80
- tags: list[str]=None,
81
- static: bool=True):
67
+ def add(self, bsk_object: ...) -> ...:
68
+ """
69
+ Adds an object to the scene. Can pass in any scene objects:
70
+ Argument overloads:
71
+ object: Node - Adds the given node to the scene.
72
+ """
82
73
 
83
- if material: self.material_handler.add(material)
84
- else: material = self.material_handler.base
74
+ if isinstance(bsk_object, type(None)):
75
+ # Considered well defined behavior
76
+ return
77
+ elif isinstance(bsk_object, Node):
78
+ # Add a node to the scene
79
+ return self.node_handler.add(bsk_object)
80
+ # Light
81
+
82
+ # Mesh
85
83
 
86
- return self.node_handler.add(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, collider, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
84
+ else:
85
+ raise ValueError(f'scene.add: Incompatable object add type {type(bsk_object)}')
87
86
 
87
+ return None
88
+
89
+ def remove(self, bsk_object):
90
+ """
91
+ Removes the given baskilsk object from the scene
92
+ """
93
+
94
+ if isinstance(bsk_object, type(None)):
95
+ # Considered well defined behavior
96
+ return
97
+ elif isinstance(bsk_object, Node):
98
+ self.node_handler.remove(bsk_object)
99
+ else:
100
+ raise ValueError(f'scene.remove: Incompatable object remove type {type(bsk_object)}')
101
+
102
+ return None
88
103
 
89
104
  def set_engine(self, engine: any) -> None:
90
105
  """
@@ -96,14 +111,84 @@ class Scene():
96
111
 
97
112
  self.camera = FreeCamera()
98
113
  self.shader_handler = ShaderHandler(self)
114
+ self.material_handler = MaterialHandler(self)
115
+ self.light_handler = LightHandler(self)
99
116
  self.physics_engine = PhysicsEngine()
100
117
  self.node_handler = NodeHandler(self)
118
+ self.particle = ParticleHandler(self)
101
119
  self.collider_handler = ColliderHandler(self)
102
- self.material_handler = MaterialHandler(self)
103
- self.light_handler = LightHandler(self)
104
120
  self.draw_handler = DrawHandler(self)
105
121
  self.frame = Frame(self)
106
122
  self.sky = Sky(self.engine)
123
+
124
+ def raycast(self, position: glm.vec3=None, forward: glm.vec3=None, max_distance: float=1e5, has_collisions: bool=None, has_physics: bool=None, tags: list[str]=[]) -> tuple[Node, glm.vec3]:
125
+ """
126
+ Ray cast from any posiiton and forward vector and returns the nearest node. If no position or forward is given, uses the scene camera's current position and forward
127
+ """
128
+ if not position: position = self.camera.position
129
+ if not forward: forward = self.camera.forward
130
+ forward = glm.normalize(forward)
131
+
132
+ # if we are filtering for collisions, use the broad BVH to improve performance
133
+ if has_collisions:
134
+ colliders = self.collider_handler.bvh.get_line_collided(position, forward)
135
+ nodes = [collider.node for collider in colliders]
136
+
137
+ def is_valid(node: Node) -> bool:
138
+ return all([
139
+ has_collisions is None or bool(node.collider) == has_collisions,
140
+ has_physics is None or bool(node.physics_body) == has_physics,
141
+ all(tag in node.tags for tag in tags)
142
+ ])
143
+
144
+ nodes: list[Node] = list(filter(lambda node: is_valid(node), nodes))
145
+
146
+ # if we are not filtering for collisions, filter nodes and
147
+ else: nodes = self.node_handler.get_all(collisions=has_collisions, physics=has_physics, tags=tags)
148
+
149
+ # determine closest node
150
+ best_distance, best_point, best_node = max_distance, None, None
151
+ position_two = position + forward
152
+ for node in nodes:
153
+
154
+ inv_mat = glm.inverse(node.model_matrix)
155
+ relative_position = inv_mat * position
156
+ relative_forward = glm.normalize(inv_mat * position_two - relative_position)
157
+
158
+ triangles = [node.mesh.indices[i] for i in node.mesh.get_line_collided(relative_position, relative_forward)]
159
+
160
+ for triangle in triangles:
161
+ intersection = moller_trumbore(relative_position, relative_forward, [node.mesh.points[i] for i in triangle])
162
+ if not intersection: continue
163
+ intersection = node.model_matrix * intersection
164
+ distance = glm.length(intersection - position)
165
+ if distance < best_distance:
166
+ best_distance = distance
167
+ best_point = intersection
168
+ best_node = node
169
+
170
+ return best_node, best_point
171
+
172
+ def raycast_mouse(self, position: tuple[int, int] | glm.vec2, max_distance: float=1e5, has_collisions: bool=None, has_pshyics: bool=None, tags: list[str]=[]) -> tuple[Node, glm.vec3]:
173
+ """
174
+ Ray casts from the mouse position with respect to the camera. Returns the nearest node that was clicked, if none was clicked, returns None.
175
+ """
176
+ # derive forward vector from mouse click position
177
+ position = glm.vec2(position)
178
+ inv_proj, inv_view = glm.inverse(self.camera.m_proj), glm.inverse(self.camera.m_view)
179
+ ndc = glm.vec4(2 * position[0] / self.engine.win_size[0] - 1, 1 - 2 * position[1] / self.engine.win_size[1], 1, 1)
180
+ point = inv_proj * ndc
181
+ point /= point.w
182
+ forward = glm.normalize(glm.vec3(inv_view * glm.vec4(point.x, point.y, point.z, 0)))
183
+
184
+ return self.raycast(
185
+ position=self.camera.position,
186
+ forward=forward,
187
+ max_distance=max_distance,
188
+ has_collisions=has_collisions,
189
+ has_physics=has_pshyics,
190
+ tags=tags
191
+ )
107
192
 
108
193
  @property
109
194
  def camera(self): return self._camera
@@ -120,9 +205,7 @@ class Scene():
120
205
 
121
206
  @sky.setter
122
207
  def sky(self, value: Sky):
123
- if not value: return
124
- if not isinstance(value, Sky):
125
- raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky')
208
+ if not isinstance(value, Sky) and not isinstance(value, type(None)):
209
+ raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
126
210
  self._sky = value
127
- self._sky.write()
128
-
211
+ if value: self._sky.write()