basilisk-engine 0.0.1__py3-none-any.whl → 0.0.3__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 (62) hide show
  1. basilisk/bsk_assets/__init__.py +0 -0
  2. basilisk/collisions/__init__.py +0 -0
  3. basilisk/collisions/broad/__init__.py +0 -0
  4. basilisk/collisions/broad/broad_aabb.py +96 -0
  5. basilisk/collisions/broad/broad_bvh.py +102 -0
  6. basilisk/collisions/collider.py +75 -0
  7. basilisk/collisions/collider_handler.py +163 -0
  8. basilisk/collisions/narrow/__init__.py +0 -0
  9. basilisk/collisions/narrow/epa.py +86 -0
  10. basilisk/collisions/narrow/gjk.py +66 -0
  11. basilisk/collisions/narrow/helper.py +23 -0
  12. basilisk/draw/__init__.py +0 -0
  13. basilisk/draw/draw.py +101 -0
  14. basilisk/draw/draw_handler.py +208 -0
  15. basilisk/draw/font_renderer.py +28 -0
  16. basilisk/generic/__init__.py +0 -0
  17. basilisk/generic/abstract_bvh.py +16 -0
  18. basilisk/generic/collisions.py +26 -0
  19. basilisk/generic/input_validation.py +28 -0
  20. basilisk/generic/math.py +7 -0
  21. basilisk/generic/matrices.py +34 -0
  22. basilisk/generic/meshes.py +73 -0
  23. basilisk/generic/quat.py +119 -0
  24. basilisk/generic/quat_methods.py +8 -0
  25. basilisk/generic/vec3.py +112 -0
  26. basilisk/input/__init__.py +0 -0
  27. basilisk/input/mouse.py +60 -0
  28. basilisk/mesh/__init__.py +0 -0
  29. basilisk/mesh/built-in/__init__.py +0 -0
  30. basilisk/mesh/cube.py +20 -0
  31. basilisk/mesh/mesh.py +216 -0
  32. basilisk/mesh/mesh_from_data.py +48 -0
  33. basilisk/mesh/model.py +272 -0
  34. basilisk/mesh/narrow_aabb.py +81 -0
  35. basilisk/mesh/narrow_bvh.py +84 -0
  36. basilisk/mesh/narrow_primative.py +24 -0
  37. basilisk/nodes/__init__.py +0 -0
  38. basilisk/nodes/node.py +508 -0
  39. basilisk/nodes/node_handler.py +94 -0
  40. basilisk/physics/__init__.py +0 -0
  41. basilisk/physics/physics_body.py +36 -0
  42. basilisk/physics/physics_engine.py +37 -0
  43. basilisk/render/__init__.py +0 -0
  44. basilisk/render/batch.py +85 -0
  45. basilisk/render/camera.py +166 -0
  46. basilisk/render/chunk.py +85 -0
  47. basilisk/render/chunk_handler.py +139 -0
  48. basilisk/render/frame.py +182 -0
  49. basilisk/render/image.py +76 -0
  50. basilisk/render/image_handler.py +119 -0
  51. basilisk/render/light.py +97 -0
  52. basilisk/render/light_handler.py +54 -0
  53. basilisk/render/material.py +196 -0
  54. basilisk/render/material_handler.py +123 -0
  55. basilisk/render/shader_handler.py +95 -0
  56. basilisk/render/sky.py +118 -0
  57. basilisk/shaders/__init__.py +0 -0
  58. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/METADATA +1 -1
  59. basilisk_engine-0.0.3.dist-info/RECORD +65 -0
  60. basilisk_engine-0.0.1.dist-info/RECORD +0 -8
  61. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/WHEEL +0 -0
  62. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,76 @@
1
+ import os
2
+ import sys
3
+ import numpy as np
4
+ import moderngl as mgl
5
+ import glm
6
+ import pygame as pg
7
+ from PIL import Image as PIL_Image
8
+
9
+
10
+ texture_sizes = (128, 256, 512, 1024, 2048)
11
+
12
+
13
+ class Image():
14
+ name: str
15
+ """Name of the image"""
16
+ index: glm.ivec2
17
+ """Location of the image in the texture arrays"""
18
+ data: np.ndarray
19
+ """Array of the texture data"""
20
+ size: int
21
+ """The width and height in pixels of the image"""
22
+
23
+ def __init__(self, path: str | os.PathLike | pg.Surface) -> None:
24
+ """
25
+ A basilisk image object that contains a moderngl texture
26
+ Args:
27
+ path: str | os.PathLike | pg.Surface
28
+ The string path to the image. Can also read a pygame surface
29
+ """
30
+
31
+ # Check if the user is loading a pygame surface
32
+ if isinstance(path, pg.Surface):
33
+ self.from_pg_surface(path)
34
+ return
35
+
36
+ # Verify the path type
37
+ if not isinstance(path, str) and not isinstance(path, os.PathLike):
38
+ raise TypeError(f'Invalid path type: {type(path)}. Expected a string or os.PathLike')
39
+
40
+ # Get name from path
41
+ self.name = path.split('/')[-1].split('\\')[-1].split('.')[0]
42
+
43
+ # Set the texture
44
+ # Load image
45
+ img = PIL_Image.open(path).convert('RGBA')
46
+ # Set the size in one of the size buckets
47
+ size_buckets = texture_sizes
48
+ self.size = size_buckets[np.argmin(np.array([abs(size - img.size[0]) for size in size_buckets]))]
49
+ img = img.resize((self.size, self.size))
50
+ # Get the image data
51
+ self.data = img.tobytes()
52
+
53
+ # Default index value (to be set by image handler)
54
+ self.index = glm.ivec2(1, 1)
55
+
56
+ def from_pg_surface(self, surf: pg.Surface) -> None:
57
+ """
58
+ Loads a basilisk image from a pygame surface
59
+ Args:
60
+ """
61
+
62
+ # Set the size in one of the size buckets
63
+ size_buckets = texture_sizes
64
+ self.size = size_buckets[np.argmin(np.array([abs(size - surf.get_size()[0]) for size in size_buckets]))]
65
+ surf = pg.transform.scale(surf, (self.size, self.size)).convert_alpha()
66
+ # Get image data
67
+ self.data = pg.image.tobytes(surf, 'RGBA')
68
+
69
+ # Default index value (to be set by image handler)
70
+ self.index = glm.ivec2(1, 1)
71
+
72
+ def __repr__(self) -> str:
73
+ """
74
+ Returns a string representation of the object
75
+ """
76
+ return f'<Basilisk Image | {self.name}, ({self.size}x{self.size}), {sys.getsizeof(self.data) / 1024 / 1024:.2} mb>'
@@ -0,0 +1,119 @@
1
+ import moderngl as mgl
2
+ import glm
3
+ import numpy as np
4
+
5
+
6
+ texture_sizes = (128, 256, 512, 1024, 2048)
7
+
8
+
9
+ class ImageHandler():
10
+ engine: any
11
+ """Back refernce to the parent engine"""
12
+ scene: any
13
+ """Back refernce to the parent scene"""
14
+ ctx: mgl.Context
15
+ """Back reference to the Context used by the scene/engine"""
16
+ images: list
17
+ """List of basilisk Images containing all the loaded images given to the scene"""
18
+ texture_arrays: dict
19
+ """Dictionary of textures arrays for writting textures to GPU"""
20
+
21
+ def __init__(self, scene) -> None:
22
+ """
23
+ Container for all the basilisk image objects in the scene.
24
+ Handles the managment and writting of all image textures.
25
+ """
26
+
27
+ # Set back references
28
+ self.scene = scene
29
+ self.engine = scene.engine
30
+ self.ctx = scene.engine.ctx
31
+
32
+ self.images = []
33
+ self.texture_arrays = {size : [] for size in texture_sizes}
34
+
35
+ def add(self, image: any) -> None:
36
+ """
37
+ Adds an existing basilisk image object to the handler for writting
38
+ Args:
39
+ image: bsk.Image
40
+ The existing image that is to be added to the scene.
41
+ """
42
+
43
+ if image in self.images: return
44
+
45
+ self.images.append(image)
46
+ self.write(self.scene.shader_handler.programs['batch'])
47
+ self.write(self.scene.shader_handler.programs['draw'])
48
+
49
+ def generate_texture_array(self) -> None:
50
+ """
51
+ Generates texutre arrays for all the images. Updates the index of the image instance
52
+ """
53
+
54
+ # Release any existsing texture arrays
55
+ for texture_array in self.texture_arrays.values():
56
+ if not texture_array: continue
57
+ texture_array.release()
58
+
59
+ self.texture_arrays = {size : [] for size in texture_sizes}
60
+
61
+ for image in self.images:
62
+ # Add the image data to the array
63
+ self.texture_arrays[image.size].append(image.data)
64
+ # Update the image index
65
+ image.index = glm.ivec2(texture_sizes.index(image.size), len(self.texture_arrays[image.size]) - 1)
66
+
67
+
68
+ for size in self.texture_arrays:
69
+ # Get the rray data and attributes
70
+ array_data = np.array(self.texture_arrays[size])
71
+ dim = (size, size, len(self.texture_arrays[size]))
72
+
73
+ # Make the array
74
+ self.texture_arrays[size] = self.ctx.texture_array(size=dim, components=4, data=array_data)
75
+ # Texture OpenGl settings
76
+ self.texture_arrays[size].build_mipmaps()
77
+ self.texture_arrays[size].filter = (mgl.LINEAR_MIPMAP_LINEAR, mgl.LINEAR)
78
+ self.texture_arrays[size].anisotropy = 32.0
79
+
80
+ def write(self, shader_program: mgl.Program) -> None:
81
+ """
82
+ Writes all texture arrays to the given shader program
83
+ Args:
84
+ shader_program: mgl.Program:
85
+ Destination of the texture array write
86
+ """
87
+
88
+ self.generate_texture_array()
89
+
90
+ for i, size in enumerate(texture_sizes):
91
+ if not size in self.texture_arrays: continue
92
+ shader_program[f'textureArrays[{i}].array'] = i + 3
93
+ self.texture_arrays[size].use(location=i+3)
94
+
95
+ def get(self, identifier: str | int) -> any:
96
+ """
97
+ Gets the basilisk image with the given name or index
98
+ Args:
99
+ identifier: str | int
100
+ The name string or index of the desired image
101
+ """
102
+
103
+ # Simply use index if given
104
+ if isinstance(identifier, int): return self.images[identifier]
105
+
106
+ # Else, search the list for an image with the given name
107
+ for image in self.images:
108
+ if image.name != identifier: continue
109
+ return image
110
+
111
+ # No matching image found
112
+ return None
113
+
114
+ def __del__(self):
115
+ """
116
+ Deallocates all texture arrays
117
+ """
118
+
119
+ # [texture_array.release() for texture_array in self.texture_arrays]
@@ -0,0 +1,97 @@
1
+ import glm
2
+ import numpy as np
3
+
4
+
5
+ class Light():
6
+ light_handler: ...
7
+ """Back reference to the parent light handler"""
8
+ intensity: float
9
+ """The brightness of the light"""
10
+ color: glm.vec3
11
+ """The color of the light"""
12
+
13
+ def __init__(self, light_handler, intensity: float=1.0, color: tuple=(255, 255, 255)):
14
+ """
15
+ Abstract light class for Basilisk Engine.
16
+ Cannot be added to a scene.
17
+ """
18
+
19
+ # Back References
20
+ self.light_handler = light_handler
21
+
22
+ # Light attributes
23
+ self.intensity = intensity
24
+ self.color = color
25
+
26
+ @property
27
+ def intensity(self): return self._intensity
28
+ @property
29
+ def color(self): return self._color
30
+
31
+ @intensity.setter
32
+ def intensity(self, value: float | int):
33
+ if isinstance(value, float) or isinstance(value, int):
34
+ self._intensity = value
35
+ else:
36
+ raise TypeError(f"Light: Invalid intensity value type {type(value)}. Expected float or int")
37
+ self.light_handler.write()
38
+
39
+ @color.setter
40
+ def color(self, value: tuple | list | glm.vec3 | np.ndarray):
41
+ if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
42
+ if len(value) != 3: raise ValueError(f"Light: Invalid number of values for color. Expected 3 values, got {len(value)} values")
43
+ self._color = glm.vec3(value)
44
+ elif isinstance(value, glm.vec3):
45
+ self._color = glm.vec3(value)
46
+ else:
47
+ raise TypeError(f"Light: Invalid color value type {type(value)}. Expected tuple, list, glm.vec3, or numpy array")
48
+ self.light_handler.write()
49
+
50
+ class DirectionalLight(Light):
51
+ direction: glm.vec3
52
+ """The direction that the light is applied to objects"""
53
+ ambient: float
54
+ """Base value of light that is applied at all locations, regardless of direction"""
55
+
56
+ def __init__(self, light_handler, direction: tuple=(1.5, -2.0, 1.0), intensity:float=1.0, color: tuple=(255, 255, 255), ambient: float=0.0):
57
+ """
58
+ Diractional/Global light for Basilisk Engine.
59
+ Has same intensity and direction everywhere.
60
+ Args:
61
+ direction: tuple
62
+ The direction that the light is applied to objects
63
+ intensity: float
64
+ The brightness of the light
65
+ color: tuple
66
+ The color of the light
67
+ ambient: float
68
+ Base value of light that is applied at all locations, regardless of direction
69
+ """
70
+
71
+ super().__init__(light_handler, intensity, color)
72
+ self.direction = direction
73
+ self.ambient = ambient
74
+
75
+ @property
76
+ def direction(self): return self._direction
77
+ @property
78
+ def ambient(self): return self._ambient
79
+
80
+ @direction.setter
81
+ def direction(self, value: tuple | list | glm.vec3 | np.ndarray):
82
+ if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
83
+ if len(value) != 3: raise ValueError(f"Light: Invalid number of values for direction. Expected 3 values, got {len(value)} values")
84
+ self._direction = glm.normalize(glm.vec3(value))
85
+ elif isinstance(value, glm.vec3):
86
+ self._direction = glm.normalize(glm.vec3(value))
87
+ else:
88
+ raise TypeError(f"Light: Invalid direction value type {type(value)}. Expected tuple, list, glm.vec3, or numpy array")
89
+ self.light_handler.write()
90
+
91
+ @ambient.setter
92
+ def ambient(self, value: float | int):
93
+ if isinstance(value, float) or isinstance(value, int):
94
+ self._ambient = value
95
+ else:
96
+ raise TypeError(f"Light: Invalid ambient value type {type(value)}. Expected float or int")
97
+ self.light_handler.write()
@@ -0,0 +1,54 @@
1
+ import moderngl as mgl
2
+ import glm
3
+ from ..render.light import DirectionalLight
4
+
5
+
6
+ class LightHandler():
7
+ engine: ...
8
+ """Back reference to the parent engine"""
9
+ scene: ...
10
+ """Back reference to the parent scene"""
11
+ ctx: mgl.Context
12
+ """Back reference to the parent context"""
13
+ directional_light: DirectionalLight
14
+ """The directional light of the scene"""
15
+ point_lights: list
16
+ """List of all the point lights in the scene"""
17
+
18
+ def __init__(self, scene) -> None:
19
+ """
20
+ Handles all the lights in a Basilisk scene.
21
+ """
22
+
23
+ # Back references
24
+ self.scene = scene
25
+ self.engine = scene.engine
26
+ self.ctx = scene.engine.ctx
27
+
28
+ # Intialize light variables
29
+ self.directional_lights = None
30
+ self.directional_lights = [DirectionalLight(self, direction=dir, intensity=intensity) for dir, intensity in zip(((1, -1, 1), (-.1, 3, -.1)), (1, .05))]
31
+ self.point_lights = []
32
+
33
+ # Initalize uniforms
34
+ self.write()
35
+
36
+ def write(self, program: mgl.Program=None, directional=True, point=False) -> None:
37
+ """
38
+ Writes all the lights in a scene to the given shader program
39
+ """
40
+
41
+ if not program: program = self.scene.shader_handler.programs['batch']
42
+
43
+ if directional and self.directional_lights:
44
+
45
+ program['numDirLights'].write(glm.int32(len(self.directional_lights)))
46
+
47
+ for i, light in enumerate(self.directional_lights):
48
+ program[f'dirLights[{i}].direction'].write(light.direction)
49
+ program[f'dirLights[{i}].intensity'].write(glm.float32(light.intensity))
50
+ program[f'dirLights[{i}].color' ].write(light.color / 255.0)
51
+ program[f'dirLights[{i}].ambient' ].write(glm.float32(light.ambient))
52
+
53
+ if point:
54
+ ...
@@ -0,0 +1,196 @@
1
+ import glm
2
+ import numpy as np
3
+ from ..render.image import Image
4
+ from ..generic.input_validation import *
5
+
6
+ class Material():
7
+ material_handler: ...
8
+ """Back reference to the parent material handler"""
9
+ name: str = None
10
+ """Name of the material"""
11
+ index: 0
12
+ """Index of the material in the material uniform"""
13
+
14
+ # Base Material Attributes
15
+ color: glm.vec3 = glm.vec3(255.0, 255.0, 255.0)
16
+ """Color multiplier of the material"""
17
+ texture: Image = None
18
+ """Key/name of the texture image in the image handler. If the image given does not exist, it must be loaded"""
19
+ normal: Image = None
20
+ """Key/name of the normal image in the image handler. If the image given does not exist, it must be loaded"""
21
+
22
+ # PBR Material Attributes
23
+ roughness: float
24
+ """The roughness of the material, controlls both the specular and diffuse response"""
25
+ subsurface: float
26
+ """Amount of subsurface scattering the material exhibits. Value in range [0, 1]. Lerps between diffuse and subsurface lobes"""
27
+ sheen: float
28
+ """Amount of sheen the material exhibits. Additive lobe"""
29
+ sheen_tint: float
30
+ """Amount that the sheen is tinted to the base color. Set to white by default"""
31
+ anisotropic: float
32
+ """The amount of anisotropic behaviour the materials specular lobe exhibits"""
33
+ specular: float
34
+ """The strength of the specular lobe of the material"""
35
+ metallicness: float
36
+ """The metallicness of the material, which dictates how much light the surfaces diffuses"""
37
+ specular_tint: float
38
+ """Amount that the specular is tinted to the base color. Set to white by default"""
39
+ clearcoat: float
40
+ """Amount of clearcoat the material exhibits. Additive lobe"""
41
+ clearcoat_gloss: float
42
+ """The glossiness of the clearcoat layer. 0 For a satin appearance, 1 for a gloss appearance"""
43
+
44
+ def __init__(self, name: str=None, color: tuple=(255.0, 255.0, 255.0), texture: Image=None, normal: Image=None,
45
+ roughness: float=0.7, subsurface: float=0.2, sheen: float=0.5, sheen_tint: float=0.5,
46
+ anisotropic: float=0.0, specular: float=1.0, metallicness: float=0.0, specular_tint: float=0.0,
47
+ clearcoat: float=0.5, clearcoat_gloss: float=0.25) -> None:
48
+ """
49
+ Basilisk Material object. Contains the data and images references used by the material.
50
+ Args:
51
+ name: str
52
+ Identifier to be used by user
53
+ color: tuple
54
+ Base color of the material. Applies to textures as well
55
+ texture: Basilisk Image
56
+ The albedo map (color texture) of the material
57
+ normal: Basilisk Image
58
+ The normal map of the material.
59
+ """
60
+
61
+ # Set handler only when used by a scene
62
+ self.material_handler = None
63
+
64
+ self.index = 0
65
+
66
+ self.name = name if name else ''
67
+ self.color = color
68
+ self.texture = texture
69
+ self.normal = normal
70
+ self.roughness = roughness
71
+ self.subsurface = subsurface
72
+ self.sheen = sheen
73
+ self.sheen_tint = sheen_tint
74
+ self.anisotropic = anisotropic
75
+ self.specular = specular
76
+ self.metallicness = metallicness
77
+ self.specular_tint = specular_tint
78
+ self.clearcoat = clearcoat
79
+ self.clearcoat_gloss = clearcoat_gloss
80
+
81
+ def get_data(self) -> list:
82
+ """
83
+ Returns a list containing all the gpu data in the material.
84
+ Used by the material handler
85
+ """
86
+
87
+ # Add color and PBR data
88
+ data = [self.color.x / 255.0, self.color.y / 255.0, self.color.z / 255.0, self.roughness, self.subsurface, self.sheen,
89
+ self.sheen_tint, self.anisotropic, self.specular, self.metallicness, self.specular_tint, self.clearcoat, self.clearcoat_gloss]
90
+
91
+ # Add texture data
92
+ if self.texture: data.extend([1, self.texture.index.x, self.texture.index.y])
93
+ else: data.extend([0, 0, 0])
94
+
95
+ # Add normal data
96
+ if self.normal: data.extend([1, self.normal.index.x, self.normal.index.y])
97
+ else: data.extend([0, 0, 0])
98
+
99
+ return data
100
+
101
+ def __repr__(self) -> str:
102
+ return f'<Basilisk Material | {self.name}, ({self.color.x}, {self.color.y}, {self.color.z}), {self.texture}>'
103
+
104
+
105
+ @property
106
+ def color(self): return self._color
107
+ @property
108
+ def texture(self): return self._texture
109
+ @property
110
+ def normal(self): return self._normal
111
+ @property
112
+ def roughness(self): return self._roughness
113
+ @property
114
+ def subsurface(self): return self._subsurface
115
+ @property
116
+ def sheen(self): return self._sheen
117
+ @property
118
+ def sheen_tint(self): return self._sheen_tint
119
+ @property
120
+ def anisotropic(self): return self._anisotropic
121
+ @property
122
+ def specular(self): return self._specular
123
+ @property
124
+ def metallicness(self): return self._metallicness
125
+ @property
126
+ def specular_tint(self): return self._specular_tint
127
+ @property
128
+ def clearcoat(self): return self._clearcoat
129
+ @property
130
+ def clearcoat_gloss(self): return self._clearcoat_gloss
131
+
132
+
133
+ @color.setter
134
+ def color(self, value: tuple | list | glm.vec3 | np.ndarray):
135
+ self._color = validate_glm_vec3("Material", "color", value)
136
+ if self.material_handler: self.material_handler.write()
137
+
138
+ @texture.setter
139
+ def texture(self, value: Image | None):
140
+ self._texture = validate_image("Material", "texture", value)
141
+ if self.material_handler: self.material_handler.write()
142
+
143
+ @normal.setter
144
+ def normal(self, value: Image | None):
145
+ self._normal = validate_image("Material", "normal map", value)
146
+ if self.material_handler: self.material_handler.write()
147
+
148
+ @roughness.setter
149
+ def roughness(self, value: float | int | glm.float32):
150
+ self._roughness = validate_float("Material", "roughness", value)
151
+ if self.material_handler: self.material_handler.write()
152
+
153
+ @subsurface.setter
154
+ def subsurface(self, value: float | int | glm.float32):
155
+ self._subsurface = validate_float("Material", "subsurface", value)
156
+ if self.material_handler: self.material_handler.write()
157
+
158
+ @sheen.setter
159
+ def sheen(self, value: float | int | glm.float32):
160
+ self._sheen = validate_float("Material", "sheen", value)
161
+ if self.material_handler: self.material_handler.write()
162
+
163
+ @sheen_tint.setter
164
+ def sheen_tint(self, value: float | int | glm.float32):
165
+ self._sheen_tint = validate_float("Material", "sheen tint", value)
166
+ if self.material_handler: self.material_handler.write()
167
+
168
+ @anisotropic.setter
169
+ def anisotropic(self, value: float | int | glm.float32):
170
+ self._anisotropic = validate_float("Material", "anisotropic", value)
171
+ if self.material_handler: self.material_handler.write()
172
+
173
+ @specular.setter
174
+ def specular(self, value: float | int | glm.float32):
175
+ self._specular = validate_float("Material", "specular", value)
176
+ if self.material_handler: self.material_handler.write()
177
+
178
+ @metallicness.setter
179
+ def metallicness(self, value: float | int | glm.float32):
180
+ self._metallicness = validate_float("Material", "metallicness", value)
181
+ if self.material_handler: self.material_handler.write()
182
+
183
+ @specular_tint.setter
184
+ def specular_tint(self, value: float | int | glm.float32):
185
+ self._specular_tint = validate_float("Material", "specular tint", value)
186
+ if self.material_handler: self.material_handler.write()
187
+
188
+ @clearcoat.setter
189
+ def clearcoat(self, value: float | int | glm.float32):
190
+ self._clearcoat = validate_float("Material", "clearcoat", value)
191
+ if self.material_handler: self.material_handler.write()
192
+
193
+ @clearcoat_gloss.setter
194
+ def clearcoat_gloss(self, value: float | int | glm.float32):
195
+ self._clearcoat_gloss = validate_float("Material", "clearcoat gloss", value)
196
+ if self.material_handler: self.material_handler.write()
@@ -0,0 +1,123 @@
1
+ import moderngl as mgl
2
+ from ..render.image_handler import ImageHandler
3
+ from ..render.material import Material
4
+ import numpy as np
5
+
6
+
7
+ class MaterialHandler():
8
+ engine: ...
9
+ """Back reference to the parent engine"""
10
+ scene: ...
11
+ """Back reference to the parent scene"""
12
+ ctx: mgl.Context
13
+ """Back reference to the parent context"""
14
+ materials: list[Material]
15
+ """List containing all the materials in the scene"""
16
+ data_texture: mgl.Texture
17
+ """ModernGL texture containing all the material data for materials in the scene"""
18
+
19
+ def __init__(self, scene) -> None:
20
+ """
21
+ Handles all the materials introduced to a scene.
22
+ Writes material information to the GPU
23
+ """
24
+
25
+ # Back references
26
+ self.scene = scene
27
+ self.engine = scene.engine
28
+ self.ctx = scene.engine.ctx
29
+
30
+ # Initialize data
31
+ self.materials = []
32
+ self.data_texture = None
33
+ self.set_base()
34
+
35
+ self.image_handler = ImageHandler(scene)
36
+
37
+ def add(self, material: Material) -> None:
38
+ """
39
+ Adds the given material to the handler if it is not already present
40
+ """
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)
52
+ # Write materials
53
+ self.write()
54
+
55
+ def generate_material_texture(self) -> None:
56
+ """
57
+ Generates the texture that is used to write material data to the GPU
58
+ """
59
+
60
+ # Check that there are materials to write
61
+ if len(self.materials) == 0: return
62
+
63
+ # Release existing data texture
64
+ if self.data_texture: self.data_texture.release()
65
+
66
+ # Create empty texture data
67
+ material_data = np.zeros(shape=(len(self.materials), 19), dtype="f4")
68
+
69
+ # Get data from the materials
70
+ for i, mtl in enumerate(self.materials):
71
+ mtl.index = i
72
+ material_data[i] = mtl.get_data()
73
+
74
+ # Create texture from data
75
+ material_data = np.ravel(material_data)
76
+ self.data_texture = self.ctx.texture((1, len(material_data)), components=1, dtype='f4', data=material_data)
77
+
78
+ def write(self, shader_program: mgl.Program=None) -> None:
79
+ """
80
+ Writes all material data to the given shader
81
+ """
82
+
83
+ if shader_program == None: shader_program = self.scene.shader_handler.programs['batch']
84
+
85
+ self.generate_material_texture()
86
+
87
+ shader_program[f'materialsTexture'] = 9
88
+ self.data_texture.use(location=9)
89
+
90
+ def get(self, identifier: str | int) -> any:
91
+ """
92
+ Gets the basilisk material with the given name or index
93
+ Args:
94
+ identifier: str | int
95
+ The name string or index of the desired material
96
+ """
97
+
98
+ # Simply use index if given
99
+ if isinstance(identifier, int): return self.materials[identifier]
100
+
101
+ # Else, search the list for an image material the given name
102
+ for material in self.materials:
103
+ if material.name != identifier: continue
104
+ return material
105
+
106
+ # No matching material found
107
+ return None
108
+
109
+ def set_base(self):
110
+ """
111
+ Creates a base material
112
+ """
113
+
114
+ self.base = Material('Base')
115
+ self.materials.append(self.base)
116
+ self.write()
117
+
118
+ def __del__(self) -> None:
119
+ """
120
+ Releases the material data texture
121
+ """
122
+
123
+ if self.data_texture: self.data_texture.release()