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/render/image.py
ADDED
|
@@ -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]
|
basilisk/render/light.py
ADDED
|
@@ -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()
|