pyrender-maintained 1.0.0__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.
- pyrender/__init__.py +24 -0
- pyrender/camera.py +435 -0
- pyrender/constants.py +149 -0
- pyrender/font.py +272 -0
- pyrender/light.py +382 -0
- pyrender/material.py +705 -0
- pyrender/mesh.py +328 -0
- pyrender/node.py +263 -0
- pyrender/offscreen.py +160 -0
- pyrender/platforms/__init__.py +6 -0
- pyrender/platforms/base.py +73 -0
- pyrender/platforms/egl.py +219 -0
- pyrender/platforms/osmesa.py +59 -0
- pyrender/platforms/pyglet_platform.py +90 -0
- pyrender/primitive.py +489 -0
- pyrender/renderer.py +1328 -0
- pyrender/sampler.py +102 -0
- pyrender/scene.py +585 -0
- pyrender/shader_program.py +283 -0
- pyrender/texture.py +259 -0
- pyrender/trackball.py +216 -0
- pyrender/utils.py +115 -0
- pyrender/version.py +1 -0
- pyrender/viewer.py +1157 -0
- pyrender_maintained-1.0.0.dist-info/METADATA +55 -0
- pyrender_maintained-1.0.0.dist-info/RECORD +29 -0
- pyrender_maintained-1.0.0.dist-info/WHEEL +5 -0
- pyrender_maintained-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyrender_maintained-1.0.0.dist-info/top_level.txt +1 -0
pyrender/font.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Font texture loader and processor.
|
|
2
|
+
|
|
3
|
+
Author: Matthew Matl
|
|
4
|
+
"""
|
|
5
|
+
import freetype
|
|
6
|
+
import numpy as np
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import OpenGL
|
|
10
|
+
from OpenGL.GL import *
|
|
11
|
+
|
|
12
|
+
from .constants import TextAlign, FLOAT_SZ
|
|
13
|
+
from .texture import Texture
|
|
14
|
+
from .sampler import Sampler
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FontCache(object):
|
|
18
|
+
"""A cache for fonts.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, font_dir=None):
|
|
22
|
+
self._font_cache = {}
|
|
23
|
+
self.font_dir = font_dir
|
|
24
|
+
if self.font_dir is None:
|
|
25
|
+
base_dir, _ = os.path.split(os.path.realpath(__file__))
|
|
26
|
+
self.font_dir = os.path.join(base_dir, 'fonts')
|
|
27
|
+
|
|
28
|
+
def get_font(self, font_name, font_pt):
|
|
29
|
+
# If it's a file, load it directly, else, try to load from font dir.
|
|
30
|
+
if os.path.isfile(font_name):
|
|
31
|
+
font_filename = font_name
|
|
32
|
+
_, font_name = os.path.split(font_name)
|
|
33
|
+
font_name, _ = os.path.split(font_name)
|
|
34
|
+
else:
|
|
35
|
+
font_filename = os.path.join(self.font_dir, font_name) + '.ttf'
|
|
36
|
+
|
|
37
|
+
cid = OpenGL.contextdata.getContext()
|
|
38
|
+
key = (cid, font_name, int(font_pt))
|
|
39
|
+
|
|
40
|
+
if key not in self._font_cache:
|
|
41
|
+
self._font_cache[key] = Font(font_filename, font_pt)
|
|
42
|
+
return self._font_cache[key]
|
|
43
|
+
|
|
44
|
+
def clear(self):
|
|
45
|
+
for key in self._font_cache:
|
|
46
|
+
self._font_cache[key].delete()
|
|
47
|
+
self._font_cache = {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Character(object):
|
|
51
|
+
"""A single character, with its texture and attributes.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, texture, size, bearing, advance):
|
|
55
|
+
self.texture = texture
|
|
56
|
+
self.size = size
|
|
57
|
+
self.bearing = bearing
|
|
58
|
+
self.advance = advance
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Font(object):
|
|
62
|
+
"""A font object.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
font_file : str
|
|
67
|
+
The file to load the font from.
|
|
68
|
+
font_pt : int
|
|
69
|
+
The height of the font in pixels.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, font_file, font_pt=40):
|
|
73
|
+
self.font_file = font_file
|
|
74
|
+
self.font_pt = int(font_pt)
|
|
75
|
+
self._face = freetype.Face(font_file)
|
|
76
|
+
self._face.set_pixel_sizes(0, font_pt)
|
|
77
|
+
self._character_map = {}
|
|
78
|
+
|
|
79
|
+
for i in range(0, 128):
|
|
80
|
+
|
|
81
|
+
# Generate texture
|
|
82
|
+
face = self._face
|
|
83
|
+
face.load_char(chr(i))
|
|
84
|
+
buf = face.glyph.bitmap.buffer
|
|
85
|
+
src = (np.array(buf) / 255.0).astype(np.float32)
|
|
86
|
+
src = src.reshape((face.glyph.bitmap.rows,
|
|
87
|
+
face.glyph.bitmap.width))
|
|
88
|
+
tex = Texture(
|
|
89
|
+
sampler=Sampler(
|
|
90
|
+
magFilter=GL_LINEAR,
|
|
91
|
+
minFilter=GL_LINEAR,
|
|
92
|
+
wrapS=GL_CLAMP_TO_EDGE,
|
|
93
|
+
wrapT=GL_CLAMP_TO_EDGE
|
|
94
|
+
),
|
|
95
|
+
source=src,
|
|
96
|
+
source_channels='R',
|
|
97
|
+
)
|
|
98
|
+
character = Character(
|
|
99
|
+
texture=tex,
|
|
100
|
+
size=np.array([face.glyph.bitmap.width,
|
|
101
|
+
face.glyph.bitmap.rows]),
|
|
102
|
+
bearing=np.array([face.glyph.bitmap_left,
|
|
103
|
+
face.glyph.bitmap_top]),
|
|
104
|
+
advance=face.glyph.advance.x
|
|
105
|
+
)
|
|
106
|
+
self._character_map[chr(i)] = character
|
|
107
|
+
|
|
108
|
+
self._vbo = None
|
|
109
|
+
self._vao = None
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def font_file(self):
|
|
113
|
+
"""str : The file the font was loaded from.
|
|
114
|
+
"""
|
|
115
|
+
return self._font_file
|
|
116
|
+
|
|
117
|
+
@font_file.setter
|
|
118
|
+
def font_file(self, value):
|
|
119
|
+
self._font_file = value
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def font_pt(self):
|
|
123
|
+
"""int : The height of the font in pixels.
|
|
124
|
+
"""
|
|
125
|
+
return self._font_pt
|
|
126
|
+
|
|
127
|
+
@font_pt.setter
|
|
128
|
+
def font_pt(self, value):
|
|
129
|
+
self._font_pt = int(value)
|
|
130
|
+
|
|
131
|
+
def _add_to_context(self):
|
|
132
|
+
|
|
133
|
+
self._vao = glGenVertexArrays(1)
|
|
134
|
+
glBindVertexArray(self._vao)
|
|
135
|
+
self._vbo = glGenBuffers(1)
|
|
136
|
+
glBindBuffer(GL_ARRAY_BUFFER, self._vbo)
|
|
137
|
+
glBufferData(GL_ARRAY_BUFFER, FLOAT_SZ * 6 * 4, None, GL_DYNAMIC_DRAW)
|
|
138
|
+
glEnableVertexAttribArray(0)
|
|
139
|
+
glVertexAttribPointer(
|
|
140
|
+
0, 4, GL_FLOAT, GL_FALSE, 4 * FLOAT_SZ, ctypes.c_void_p(0)
|
|
141
|
+
)
|
|
142
|
+
glBindVertexArray(0)
|
|
143
|
+
|
|
144
|
+
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
|
145
|
+
for c in self._character_map:
|
|
146
|
+
ch = self._character_map[c]
|
|
147
|
+
if not ch.texture._in_context():
|
|
148
|
+
ch.texture._add_to_context()
|
|
149
|
+
|
|
150
|
+
def _remove_from_context(self):
|
|
151
|
+
for c in self._character_map:
|
|
152
|
+
ch = self._character_map[c]
|
|
153
|
+
ch.texture.delete()
|
|
154
|
+
if self._vao is not None:
|
|
155
|
+
glDeleteVertexArrays(1, [self._vao])
|
|
156
|
+
glDeleteBuffers(1, [self._vbo])
|
|
157
|
+
self._vao = None
|
|
158
|
+
self._vbo = None
|
|
159
|
+
|
|
160
|
+
def _in_context(self):
|
|
161
|
+
return self._vao is not None
|
|
162
|
+
|
|
163
|
+
def _bind(self):
|
|
164
|
+
glBindVertexArray(self._vao)
|
|
165
|
+
|
|
166
|
+
def _unbind(self):
|
|
167
|
+
glBindVertexArray(0)
|
|
168
|
+
|
|
169
|
+
def delete(self):
|
|
170
|
+
self._unbind()
|
|
171
|
+
self._remove_from_context()
|
|
172
|
+
|
|
173
|
+
def render_string(self, text, x, y, scale=1.0,
|
|
174
|
+
align=TextAlign.BOTTOM_LEFT):
|
|
175
|
+
"""Render a string to the current view buffer.
|
|
176
|
+
|
|
177
|
+
Note
|
|
178
|
+
----
|
|
179
|
+
Assumes correct shader program already bound w/ uniforms set.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
text : str
|
|
184
|
+
The text to render.
|
|
185
|
+
x : int
|
|
186
|
+
Horizontal pixel location of text.
|
|
187
|
+
y : int
|
|
188
|
+
Vertical pixel location of text.
|
|
189
|
+
scale : int
|
|
190
|
+
Scaling factor for text.
|
|
191
|
+
align : int
|
|
192
|
+
One of the TextAlign options which specifies where the ``x``
|
|
193
|
+
and ``y`` parameters lie on the text. For example,
|
|
194
|
+
:attr:`.TextAlign.BOTTOM_LEFT` means that ``x`` and ``y`` indicate
|
|
195
|
+
the position of the bottom-left corner of the textbox.
|
|
196
|
+
"""
|
|
197
|
+
glActiveTexture(GL_TEXTURE0)
|
|
198
|
+
glEnable(GL_BLEND)
|
|
199
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
200
|
+
glDisable(GL_DEPTH_TEST)
|
|
201
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
|
202
|
+
self._bind()
|
|
203
|
+
|
|
204
|
+
# Determine width and height of text relative to x, y
|
|
205
|
+
width = 0.0
|
|
206
|
+
height = 0.0
|
|
207
|
+
for c in text:
|
|
208
|
+
ch = self._character_map[c]
|
|
209
|
+
height = max(height, ch.bearing[1] * scale)
|
|
210
|
+
width += (ch.advance >> 6) * scale
|
|
211
|
+
|
|
212
|
+
# Determine offsets based on alignments
|
|
213
|
+
xoff = 0
|
|
214
|
+
yoff = 0
|
|
215
|
+
if align == TextAlign.BOTTOM_RIGHT:
|
|
216
|
+
xoff = -width
|
|
217
|
+
elif align == TextAlign.BOTTOM_CENTER:
|
|
218
|
+
xoff = -width / 2.0
|
|
219
|
+
elif align == TextAlign.TOP_LEFT:
|
|
220
|
+
yoff = -height
|
|
221
|
+
elif align == TextAlign.TOP_RIGHT:
|
|
222
|
+
yoff = -height
|
|
223
|
+
xoff = -width
|
|
224
|
+
elif align == TextAlign.TOP_CENTER:
|
|
225
|
+
yoff = -height
|
|
226
|
+
xoff = -width / 2.0
|
|
227
|
+
elif align == TextAlign.CENTER:
|
|
228
|
+
xoff = -width / 2.0
|
|
229
|
+
yoff = -height / 2.0
|
|
230
|
+
elif align == TextAlign.CENTER_LEFT:
|
|
231
|
+
yoff = -height / 2.0
|
|
232
|
+
elif align == TextAlign.CENTER_RIGHT:
|
|
233
|
+
xoff = -width
|
|
234
|
+
yoff = -height / 2.0
|
|
235
|
+
|
|
236
|
+
x += xoff
|
|
237
|
+
y += yoff
|
|
238
|
+
|
|
239
|
+
ch = None
|
|
240
|
+
for c in text:
|
|
241
|
+
ch = self._character_map[c]
|
|
242
|
+
xpos = x + ch.bearing[0] * scale
|
|
243
|
+
ypos = y - (ch.size[1] - ch.bearing[1]) * scale
|
|
244
|
+
w = ch.size[0] * scale
|
|
245
|
+
h = ch.size[1] * scale
|
|
246
|
+
|
|
247
|
+
vertices = np.array([
|
|
248
|
+
[xpos, ypos, 0.0, 0.0],
|
|
249
|
+
[xpos + w, ypos, 1.0, 0.0],
|
|
250
|
+
[xpos + w, ypos + h, 1.0, 1.0],
|
|
251
|
+
[xpos + w, ypos + h, 1.0, 1.0],
|
|
252
|
+
[xpos, ypos + h, 0.0, 1.0],
|
|
253
|
+
[xpos, ypos, 0.0, 0.0],
|
|
254
|
+
], dtype=np.float32)
|
|
255
|
+
|
|
256
|
+
ch.texture._bind()
|
|
257
|
+
|
|
258
|
+
glBindBuffer(GL_ARRAY_BUFFER, self._vbo)
|
|
259
|
+
glBufferData(
|
|
260
|
+
GL_ARRAY_BUFFER, FLOAT_SZ * 6 * 4, vertices, GL_DYNAMIC_DRAW
|
|
261
|
+
)
|
|
262
|
+
# TODO MAKE THIS MORE EFFICIENT, lgBufferSubData is broken
|
|
263
|
+
# glBufferSubData(
|
|
264
|
+
# GL_ARRAY_BUFFER, 0, 6 * 4 * FLOAT_SZ,
|
|
265
|
+
# np.ascontiguousarray(vertices.flatten)
|
|
266
|
+
# )
|
|
267
|
+
glDrawArrays(GL_TRIANGLES, 0, 6)
|
|
268
|
+
x += (ch.advance >> 6) * scale
|
|
269
|
+
|
|
270
|
+
self._unbind()
|
|
271
|
+
if ch:
|
|
272
|
+
ch.texture._unbind()
|
pyrender/light.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""Punctual light sources as defined by the glTF 2.0 KHR extension at
|
|
2
|
+
https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
|
|
3
|
+
|
|
4
|
+
Author: Matthew Matl
|
|
5
|
+
"""
|
|
6
|
+
import abc
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from OpenGL.GL import *
|
|
10
|
+
|
|
11
|
+
from .utils import format_color_vector
|
|
12
|
+
from .texture import Texture
|
|
13
|
+
from .constants import SHADOW_TEX_SZ
|
|
14
|
+
from .camera import OrthographicCamera, PerspectiveCamera
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Light(abc.ABC):
|
|
18
|
+
"""Base class for all light objects.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
color : (3,) float
|
|
23
|
+
RGB value for the light's color in linear space.
|
|
24
|
+
intensity : float
|
|
25
|
+
Brightness of light. The units that this is defined in depend on the
|
|
26
|
+
type of light. Point and spot lights use luminous intensity in candela
|
|
27
|
+
(lm/sr), while directional lights use illuminance in lux (lm/m2).
|
|
28
|
+
name : str, optional
|
|
29
|
+
Name of the light.
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self,
|
|
32
|
+
color=None,
|
|
33
|
+
intensity=None,
|
|
34
|
+
name=None):
|
|
35
|
+
|
|
36
|
+
if color is None:
|
|
37
|
+
color = np.ones(3)
|
|
38
|
+
if intensity is None:
|
|
39
|
+
intensity = 1.0
|
|
40
|
+
|
|
41
|
+
self.name = name
|
|
42
|
+
self.color = color
|
|
43
|
+
self.intensity = intensity
|
|
44
|
+
self._shadow_camera = None
|
|
45
|
+
self._shadow_texture = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def name(self):
|
|
49
|
+
"""str : The user-defined name of this object.
|
|
50
|
+
"""
|
|
51
|
+
return self._name
|
|
52
|
+
|
|
53
|
+
@name.setter
|
|
54
|
+
def name(self, value):
|
|
55
|
+
if value is not None:
|
|
56
|
+
value = str(value)
|
|
57
|
+
self._name = value
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def color(self):
|
|
61
|
+
"""(3,) float : The light's color.
|
|
62
|
+
"""
|
|
63
|
+
return self._color
|
|
64
|
+
|
|
65
|
+
@color.setter
|
|
66
|
+
def color(self, value):
|
|
67
|
+
self._color = format_color_vector(value, 3)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def intensity(self):
|
|
71
|
+
"""float : The light's intensity in candela or lux.
|
|
72
|
+
"""
|
|
73
|
+
return self._intensity
|
|
74
|
+
|
|
75
|
+
@intensity.setter
|
|
76
|
+
def intensity(self, value):
|
|
77
|
+
self._intensity = float(value)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def shadow_texture(self):
|
|
81
|
+
""":class:`.Texture` : A texture used to hold shadow maps for this light.
|
|
82
|
+
"""
|
|
83
|
+
return self._shadow_texture
|
|
84
|
+
|
|
85
|
+
@shadow_texture.setter
|
|
86
|
+
def shadow_texture(self, value):
|
|
87
|
+
if self._shadow_texture is not None:
|
|
88
|
+
if self._shadow_texture._in_context():
|
|
89
|
+
self._shadow_texture.delete()
|
|
90
|
+
self._shadow_texture = value
|
|
91
|
+
|
|
92
|
+
@abc.abstractmethod
|
|
93
|
+
def _generate_shadow_texture(self, size=None):
|
|
94
|
+
"""Generate a shadow texture for this light.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
size : int, optional
|
|
99
|
+
Size of texture map. Must be a positive power of two.
|
|
100
|
+
"""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
@abc.abstractmethod
|
|
104
|
+
def _get_shadow_camera(self, scene_scale):
|
|
105
|
+
"""Generate and return a shadow mapping camera for this light.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
scene_scale : float
|
|
110
|
+
Length of scene's bounding box diagonal.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
camera : :class:`.Camera`
|
|
115
|
+
The camera used to render shadowmaps for this light.
|
|
116
|
+
"""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DirectionalLight(Light):
|
|
121
|
+
"""Directional lights are light sources that act as though they are
|
|
122
|
+
infinitely far away and emit light in the direction of the local -z axis.
|
|
123
|
+
This light type inherits the orientation of the node that it belongs to;
|
|
124
|
+
position and scale are ignored except for their effect on the inherited
|
|
125
|
+
node orientation. Because it is at an infinite distance, the light is
|
|
126
|
+
not attenuated. Its intensity is defined in lumens per metre squared,
|
|
127
|
+
or lux (lm/m2).
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
color : (3,) float, optional
|
|
132
|
+
RGB value for the light's color in linear space. Defaults to white
|
|
133
|
+
(i.e. [1.0, 1.0, 1.0]).
|
|
134
|
+
intensity : float, optional
|
|
135
|
+
Brightness of light, in lux (lm/m^2). Defaults to 1.0
|
|
136
|
+
name : str, optional
|
|
137
|
+
Name of the light.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self,
|
|
141
|
+
color=None,
|
|
142
|
+
intensity=None,
|
|
143
|
+
name=None):
|
|
144
|
+
super(DirectionalLight, self).__init__(
|
|
145
|
+
color=color,
|
|
146
|
+
intensity=intensity,
|
|
147
|
+
name=name,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _generate_shadow_texture(self, size=None):
|
|
151
|
+
"""Generate a shadow texture for this light.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
size : int, optional
|
|
156
|
+
Size of texture map. Must be a positive power of two.
|
|
157
|
+
"""
|
|
158
|
+
if size is None:
|
|
159
|
+
size = SHADOW_TEX_SZ
|
|
160
|
+
self.shadow_texture = Texture(width=size, height=size,
|
|
161
|
+
source_channels='D', data_format=GL_FLOAT)
|
|
162
|
+
|
|
163
|
+
def _get_shadow_camera(self, scene_scale):
|
|
164
|
+
"""Generate and return a shadow mapping camera for this light.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
scene_scale : float
|
|
169
|
+
Length of scene's bounding box diagonal.
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
camera : :class:`.Camera`
|
|
174
|
+
The camera used to render shadowmaps for this light.
|
|
175
|
+
"""
|
|
176
|
+
return OrthographicCamera(
|
|
177
|
+
znear=0.01 * scene_scale,
|
|
178
|
+
zfar=10 * scene_scale,
|
|
179
|
+
xmag=scene_scale,
|
|
180
|
+
ymag=scene_scale
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class PointLight(Light):
|
|
185
|
+
"""Point lights emit light in all directions from their position in space;
|
|
186
|
+
rotation and scale are ignored except for their effect on the inherited
|
|
187
|
+
node position. The brightness of the light attenuates in a physically
|
|
188
|
+
correct manner as distance increases from the light's position (i.e.
|
|
189
|
+
brightness goes like the inverse square of the distance). Point light
|
|
190
|
+
intensity is defined in candela, which is lumens per square radian (lm/sr).
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
color : (3,) float
|
|
195
|
+
RGB value for the light's color in linear space.
|
|
196
|
+
intensity : float
|
|
197
|
+
Brightness of light in candela (lm/sr).
|
|
198
|
+
range : float
|
|
199
|
+
Cutoff distance at which light's intensity may be considered to
|
|
200
|
+
have reached zero. If None, the range is assumed to be infinite.
|
|
201
|
+
name : str, optional
|
|
202
|
+
Name of the light.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self,
|
|
206
|
+
color=None,
|
|
207
|
+
intensity=None,
|
|
208
|
+
range=None,
|
|
209
|
+
name=None):
|
|
210
|
+
super(PointLight, self).__init__(
|
|
211
|
+
color=color,
|
|
212
|
+
intensity=intensity,
|
|
213
|
+
name=name,
|
|
214
|
+
)
|
|
215
|
+
self.range = range
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def range(self):
|
|
219
|
+
"""float : The cutoff distance for the light.
|
|
220
|
+
"""
|
|
221
|
+
return self._range
|
|
222
|
+
|
|
223
|
+
@range.setter
|
|
224
|
+
def range(self, value):
|
|
225
|
+
if value is not None:
|
|
226
|
+
value = float(value)
|
|
227
|
+
if value <= 0:
|
|
228
|
+
raise ValueError('Range must be > 0')
|
|
229
|
+
self._range = value
|
|
230
|
+
self._range = value
|
|
231
|
+
|
|
232
|
+
def _generate_shadow_texture(self, size=None):
|
|
233
|
+
"""Generate a shadow texture for this light.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
size : int, optional
|
|
238
|
+
Size of texture map. Must be a positive power of two.
|
|
239
|
+
"""
|
|
240
|
+
raise NotImplementedError('Shadows not implemented for point lights')
|
|
241
|
+
|
|
242
|
+
def _get_shadow_camera(self, scene_scale):
|
|
243
|
+
"""Generate and return a shadow mapping camera for this light.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
scene_scale : float
|
|
248
|
+
Length of scene's bounding box diagonal.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
camera : :class:`.Camera`
|
|
253
|
+
The camera used to render shadowmaps for this light.
|
|
254
|
+
"""
|
|
255
|
+
raise NotImplementedError('Shadows not implemented for point lights')
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class SpotLight(Light):
|
|
259
|
+
"""Spot lights emit light in a cone in the direction of the local -z axis.
|
|
260
|
+
The angle and falloff of the cone is defined using two numbers, the
|
|
261
|
+
``innerConeAngle`` and ``outerConeAngle``.
|
|
262
|
+
As with point lights, the brightness
|
|
263
|
+
also attenuates in a physically correct manner as distance increases from
|
|
264
|
+
the light's position (i.e. brightness goes like the inverse square of the
|
|
265
|
+
distance). Spot light intensity refers to the brightness inside the
|
|
266
|
+
``innerConeAngle`` (and at the location of the light) and is defined in
|
|
267
|
+
candela, which is lumens per square radian (lm/sr). A spot light's position
|
|
268
|
+
and orientation are inherited from its node transform. Inherited scale does
|
|
269
|
+
not affect cone shape, and is ignored except for its effect on position
|
|
270
|
+
and orientation.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
color : (3,) float
|
|
275
|
+
RGB value for the light's color in linear space.
|
|
276
|
+
intensity : float
|
|
277
|
+
Brightness of light in candela (lm/sr).
|
|
278
|
+
range : float
|
|
279
|
+
Cutoff distance at which light's intensity may be considered to
|
|
280
|
+
have reached zero. If None, the range is assumed to be infinite.
|
|
281
|
+
innerConeAngle : float
|
|
282
|
+
Angle, in radians, from centre of spotlight where falloff begins.
|
|
283
|
+
Must be greater than or equal to ``0`` and less
|
|
284
|
+
than ``outerConeAngle``. Defaults to ``0``.
|
|
285
|
+
outerConeAngle : float
|
|
286
|
+
Angle, in radians, from centre of spotlight where falloff ends.
|
|
287
|
+
Must be greater than ``innerConeAngle`` and less than or equal to
|
|
288
|
+
``PI / 2.0``. Defaults to ``PI / 4.0``.
|
|
289
|
+
name : str, optional
|
|
290
|
+
Name of the light.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def __init__(self,
|
|
294
|
+
color=None,
|
|
295
|
+
intensity=None,
|
|
296
|
+
range=None,
|
|
297
|
+
innerConeAngle=0.0,
|
|
298
|
+
outerConeAngle=(np.pi / 4.0),
|
|
299
|
+
name=None):
|
|
300
|
+
super(SpotLight, self).__init__(
|
|
301
|
+
name=name,
|
|
302
|
+
color=color,
|
|
303
|
+
intensity=intensity,
|
|
304
|
+
)
|
|
305
|
+
self.outerConeAngle = outerConeAngle
|
|
306
|
+
self.innerConeAngle = innerConeAngle
|
|
307
|
+
self.range = range
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def innerConeAngle(self):
|
|
311
|
+
"""float : The inner cone angle in radians.
|
|
312
|
+
"""
|
|
313
|
+
return self._innerConeAngle
|
|
314
|
+
|
|
315
|
+
@innerConeAngle.setter
|
|
316
|
+
def innerConeAngle(self, value):
|
|
317
|
+
if value < 0.0 or value > self.outerConeAngle:
|
|
318
|
+
raise ValueError('Invalid value for inner cone angle')
|
|
319
|
+
self._innerConeAngle = float(value)
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def outerConeAngle(self):
|
|
323
|
+
"""float : The outer cone angle in radians.
|
|
324
|
+
"""
|
|
325
|
+
return self._outerConeAngle
|
|
326
|
+
|
|
327
|
+
@outerConeAngle.setter
|
|
328
|
+
def outerConeAngle(self, value):
|
|
329
|
+
if value < 0.0 or value > np.pi / 2.0 + 1e-9:
|
|
330
|
+
raise ValueError('Invalid value for outer cone angle')
|
|
331
|
+
self._outerConeAngle = float(value)
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def range(self):
|
|
335
|
+
"""float : The cutoff distance for the light.
|
|
336
|
+
"""
|
|
337
|
+
return self._range
|
|
338
|
+
|
|
339
|
+
@range.setter
|
|
340
|
+
def range(self, value):
|
|
341
|
+
if value is not None:
|
|
342
|
+
value = float(value)
|
|
343
|
+
if value <= 0:
|
|
344
|
+
raise ValueError('Range must be > 0')
|
|
345
|
+
self._range = value
|
|
346
|
+
self._range = value
|
|
347
|
+
|
|
348
|
+
def _generate_shadow_texture(self, size=None):
|
|
349
|
+
"""Generate a shadow texture for this light.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
size : int, optional
|
|
354
|
+
Size of texture map. Must be a positive power of two.
|
|
355
|
+
"""
|
|
356
|
+
if size is None:
|
|
357
|
+
size = SHADOW_TEX_SZ
|
|
358
|
+
self.shadow_texture = Texture(width=size, height=size,
|
|
359
|
+
source_channels='D', data_format=GL_FLOAT)
|
|
360
|
+
|
|
361
|
+
def _get_shadow_camera(self, scene_scale):
|
|
362
|
+
"""Generate and return a shadow mapping camera for this light.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
scene_scale : float
|
|
367
|
+
Length of scene's bounding box diagonal.
|
|
368
|
+
|
|
369
|
+
Returns
|
|
370
|
+
-------
|
|
371
|
+
camera : :class:`.Camera`
|
|
372
|
+
The camera used to render shadowmaps for this light.
|
|
373
|
+
"""
|
|
374
|
+
return PerspectiveCamera(
|
|
375
|
+
znear=0.01 * scene_scale,
|
|
376
|
+
zfar=10 * scene_scale,
|
|
377
|
+
yfov=np.clip(2 * self.outerConeAngle + np.pi / 16.0, 0.0, np.pi),
|
|
378
|
+
aspectRatio=1.0
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
__all__ = ['Light', 'DirectionalLight', 'SpotLight', 'PointLight']
|