def points_random_3d(count, range_x=(-10.0, 10.0), range_y=(-10.0, 10.0), range_z=(-10.0, 10.0), seed=None) -> VAO: """ Generates random positions inside a confied box. Args: count (int): Number of points to generate Keyword Args: range_x (tuple): min-max range for x axis: Example (-10.0. 10.0) range_y (tuple): min-max range for y axis: Example (-10.0. 10.0) range_z (tuple): min-max range for z axis: Example (-10.0. 10.0) seed (int): The random seed Returns: A :py:class:`demosys.opengl.vao.VAO` instance """ random.seed(seed) def gen(): for _ in range(count): yield random.uniform(*range_x) yield random.uniform(*range_y) yield random.uniform(*range_z) data = numpy.fromiter(gen(), count=count * 3, dtype=numpy.float32) vao = VAO("geometry:points_random_3d", mode=moderngl.POINTS) vao.buffer(data, '3f', ['in_position']) return vao
def __init__(self, area, text_lines=None, aspect_ratio=1.0): """ :param area: (x, y) Text area size (number of characters) :param size: Text size :param text: Initial text lines """ super().__init__() self.area = area self._text_lines = text_lines self._projection_bytes = None self._aspect_ratio = 1.0 self.aspect_ratio = aspect_ratio self._vao = None self._config = self.get_data('demosys.program.font_meta') self._texture = self.get_texture('demosys.text.font_texture') self._program = self.get_program('demosys.text.program_writer_2d') self._string_buffer = None self._init(FontMeta(self._config)) self._string_buffer = self.ctx.buffer(reserve=self.area[0] * 4 * self.area[1]) self._string_buffer.clear(chunk=b'\32') pos = self.ctx.buffer(data=bytes([0] * 4 * 3)) self._vao = VAO("textwriter", mode=moderngl.POINTS) self._vao.buffer(pos, '3f', 'in_position') self._vao.buffer(self._string_buffer, '1u', 'in_char_id', per_instance=True) self.text_lines = self._text_lines
def init_particles(self): count = 50000 area = 100.0 speed = 5.0 def gen(): for _ in range(count): # Position yield random.uniform(-area, area) yield random.uniform(-area, area) yield random.uniform(-area, area) # Velocity yield random.uniform(-speed, speed) yield random.uniform(-speed, speed) yield random.uniform(-speed, speed) data1 = numpy.fromiter(gen(), count=count * 6, dtype=numpy.float32) data2 = numpy.fromiter(gen(), count=count * 6, dtype=numpy.float32) self.pos1 = self.ctx.buffer(data1.tobytes()) self.particles1 = VAO("particles1", mode=mgl.POINTS) self.particles1.buffer(self.pos1, '3f 3f', ['in_position', 'in_velocity']) self.pos2 = self.ctx.buffer(data2.tobytes()) self.particles2 = VAO("particles2", mode=mgl.POINTS) self.particles2.buffer(self.pos2, '3f 3f', ['in_position', 'in_velocity']) # Set initial start buffers self.particles = self.particles1 self.pos = self.pos2
def load_vertex_buffer(self, fd, material, length): buffer_format, attributes, mesh_attributes = translate_buffer_format(material.vertex_format) vao = VAO(material.name, mode=moderngl.TRIANGLES) # buffer = context.ctx().buffer(fd.read(length)) vao.buffer(fd.read(length), buffer_format, attributes) setattr(material, 'vao', vao) setattr(material, 'buffer_format', buffer_format) setattr(material, 'attributes', attributes) setattr(material, 'mesh_attributes', mesh_attributes)
def _create_vao(self): data = numpy.array([ 0.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 1.0, 1.0, 0.0, -2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 1.0, 1.0, 2.0, -2.0, 0.0, 1.0, 0.0, ], dtype=numpy.float32) vao = VAO("textrenderer", mode=moderngl.TRIANGLES) vao.buffer(data, '3f 2f', ['in_position', 'in_uv']) return vao
def load(self, materials): name_map = { 'POSITION': 'in_position', 'NORMAL': 'in_normal', 'TEXCOORD_0': 'in_uv', 'TANGENT': 'in_tangent', 'JOINTS_0': 'in_joints', 'WEIGHTS_0': 'in_heights', 'COLOR_0': 'in_color0', } meshes = [] # Read all primitives as separate meshes for now # According to the spec they can have different materials and vertex format for primitive in self.primitives: vao = VAO(self.name, mode=primitive.mode or moderngl.TRIANGLES) # Index buffer component_type, index_vbo = self.load_indices(primitive) if index_vbo is not None: vao.index_buffer(context.ctx().buffer(index_vbo.tobytes()), index_element_size=component_type.size) attributes = {} vbos = self.prepare_attrib_mapping(primitive) for vbo_info in vbos: dtype, buffer = vbo_info.create() vao.buffer( buffer, " ".join([ "{}{}".format(attr[1], DTYPE_BUFFER_TYPE[dtype]) for attr in vbo_info.attributes ]), [name_map[attr[0]] for attr in vbo_info.attributes], ) for attr in vbo_info.attributes: attributes[attr[0]] = { 'name': name_map[attr[0]], 'components': attr[1], 'type': vbo_info.component_type.value, } bbox_min, bbox_max = self.get_bbox(primitive) meshes.append( Mesh( self.name, vao=vao, attributes=attributes, material=materials[primitive.material] if primitive.material is not None else None, bbox_min=bbox_min, bbox_max=bbox_max, )) return meshes
def test_create(self): shader = self.load_program("vf_pos.glsl") vao = VAO("test") vao.buffer( self.ctx.buffer(reserve=12), '3f', "in_position", ) vao.render(shader)
def test_transform(self): shader = self.load_program("v_write_1.glsl") vao = VAO("transform", mode=moderngl.POINTS) vao.buffer( self.ctx.buffer(reserve=12), '1u', 'in_val', ) result = self.ctx.buffer(reserve=12) vao.transform(shader, result) self.assertEqual( result.read(), b'\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00', )
class TextWriter2D(BaseText): runnable = False def __init__(self, area, text_lines=None, aspect_ratio=1.0): """ :param area: (x, y) Text area size (number of characters) :param size: Text size :param text: Initial text lines """ super().__init__() self.area = area self._text_lines = text_lines self._projection_bytes = None self._aspect_ratio = 1.0 self.aspect_ratio = aspect_ratio self._vao = None self._config = self.get_data('demosys.program.font_meta') self._texture = self.get_texture('demosys.text.font_texture') self._program = self.get_program('demosys.text.program_writer_2d') self._string_buffer = None self._init(FontMeta(self._config)) self._string_buffer = self.ctx.buffer(reserve=self.area[0] * 4 * self.area[1]) self._string_buffer.clear(chunk=b'\32') pos = self.ctx.buffer(data=bytes([0] * 4 * 3)) self._vao = VAO("textwriter", mode=moderngl.POINTS) self._vao.buffer(pos, '3f', 'in_position') self._vao.buffer(self._string_buffer, '1u', 'in_char_id', per_instance=True) self.text_lines = self._text_lines @property def text_lines(self): return self._text_lines @text_lines.setter def text_lines(self, value): self._text_lines = value self._string_buffer.clear(chunk=b'\32') for i, line in enumerate(self._text_lines): self.set_text_line(i, line) @property def aspect_ratio(self): return self._aspect_ratio @aspect_ratio.setter def aspect_ratio(self, value): self._aspect_ratio = value self._projection_bytes = matrix44.create_orthogonal_projection_matrix( -self.aspect_ratio, # left self.aspect_ratio, # right -1.0, # bottom 1.0, # top -100.0, # near 100.0, # far dtype=numpy.float32, ).tobytes() def set_text_line(self, line, text): if line >= self.area[1]: return self._string_buffer.clear(size=self.area[0] * 4, offset=self.area[0] * 4 * line, chunk=b'\32') self._string_buffer.write( numpy.fromiter(self._translate_string( text.encode('iso-8859-1', errors='replace'), self.area[0]), dtype=numpy.uint32).tobytes(), offset=(self.area[0] * 4) * line, ) def draw(self, pos, length=-1, size=1.0): if length < 0: length = self.area[0] * self.area[1] elif length > self.area[0] * self.area[1]: length = self.area[0] * self.area[1] csize = ( self._meta.character_width / self._meta.character_height * size, 1.0 * size, ) cpos = ( pos[0] - self._aspect_ratio + csize[0] / 2, -pos[1] + 1.0 - csize[1] / 2, ) self._texture.use(location=0) self._program["m_proj"].write(self._projection_bytes) self._program["text_pos"].value = cpos self._program["font_texture"].value = 0 self._program["char_size"].value = csize self._program["line_length"].value = self.area[0] self._vao.render(self._program, instances=length)
def load(self): """Deferred loading""" path = self.find_scene(self.meta.path) if not path: raise ValueError("Scene '{}' not found".format(self.meta.path)) if path.suffix == '.bin': path = path.parent / path.stem data = pywavefront.Wavefront(str(path), create_materials=True, cache=True) scene = Scene(self.meta.resolved_path) texture_cache = {} for _, mat in data.materials.items(): mesh = Mesh(mat.name) # Traditional loader if mat.vertices: buffer_format, attributes, mesh_attributes = translate_buffer_format(mat.vertex_format) vbo = numpy.array(mat.vertices, dtype='f4') vao = VAO(mat.name, mode=moderngl.TRIANGLES) vao.buffer(vbo, buffer_format, attributes) mesh.vao = vao for attrs in mesh_attributes: mesh.add_attribute(*attrs) # Binary cache loader elif hasattr(mat, 'vao'): mesh = Mesh(mat.name) mesh.vao = mat.vao for attrs in mat.mesh_attributes: mesh.add_attribute(*attrs) else: # Empty continue scene.meshes.append(mesh) mesh.material = Material(mat.name) scene.materials.append(mesh.material) mesh.material.color = mat.diffuse if mat.texture: # A texture can be referenced multiple times, so we need to cache loaded ones texture = texture_cache.get(mat.texture.path) if not texture: print("Loading:", mat.texture.path) texture = textures.load(TextureDescription( label=mat.texture.path, path=mat.texture.path, mipmap=True, )) texture_cache[mat.texture.path] = texture mesh.material.mat_texture = MaterialTexture( texture=texture, sampler=None, ) node = Node(mesh=mesh) scene.root_nodes.append(node) # Not supported yet for obj # self.calc_scene_bbox() scene.prepare() return scene
def sphere(radius=0.5, sectors=32, rings=16) -> VAO: """ Creates a sphere. Keyword Args: radius (float): Radius or the sphere rings (int): number or horizontal rings sectors (int): number of vertical segments Returns: A :py:class:`demosys.opengl.vao.VAO` instance """ R = 1.0 / (rings - 1) S = 1.0 / (sectors - 1) vertices = [0] * (rings * sectors * 3) normals = [0] * (rings * sectors * 3) uvs = [0] * (rings * sectors * 2) v, n, t = 0, 0, 0 for r in range(rings): for s in range(sectors): y = math.sin(-math.pi / 2 + math.pi * r * R) x = math.cos(2 * math.pi * s * S) * math.sin(math.pi * r * R) z = math.sin(2 * math.pi * s * S) * math.sin(math.pi * r * R) uvs[t] = s * S uvs[t + 1] = r * R vertices[v] = x * radius vertices[v + 1] = y * radius vertices[v + 2] = z * radius normals[n] = x normals[n + 1] = y normals[n + 2] = z t += 2 v += 3 n += 3 indices = [0] * rings * sectors * 6 i = 0 for r in range(rings - 1): for s in range(sectors - 1): indices[i] = r * sectors + s indices[i + 1] = (r + 1) * sectors + (s + 1) indices[i + 2] = r * sectors + (s + 1) indices[i + 3] = r * sectors + s indices[i + 4] = (r + 1) * sectors + s indices[i + 5] = (r + 1) * sectors + (s + 1) i += 6 vbo_vertices = numpy.array(vertices, dtype=numpy.float32) vbo_normals = numpy.array(normals, dtype=numpy.float32) vbo_uvs = numpy.array(uvs, dtype=numpy.float32) vbo_elements = numpy.array(indices, dtype=numpy.uint32) vao = VAO("sphere", mode=mlg.TRIANGLES) # VBOs vao.buffer(vbo_vertices, '3f', ['in_position']) vao.buffer(vbo_normals, '3f', ['in_normal']) vao.buffer(vbo_uvs, '2f', ['in_uv']) vao.index_buffer(vbo_elements, index_element_size=4) return vao
class FeedbackEffect(effect.Effect): def __init__(self): self.feedback = self.get_program("transform") self.program = self.get_program("billboards") self.texture = self.get_texture("particle") # VAOs representing the two different buffer bindings self.particles1 = None self.particles2 = None self.particles = None # VBOs for each position buffer self.pos1 = None self.pos2 = None self.pos = None self.init_particles() def draw(self, time, frametime, target): self.ctx.disable(mgl.DEPTH_TEST) self.ctx.enable(mgl.BLEND) self.ctx.blend_func = mgl.SRC_ALPHA, mgl.ONE_MINUS_SRC_ALPHA m_proj = self.create_projection(90.0, 1.0, 1000.0) # Rotate and translate m_mv = self.create_transformation(rotation=(time * 0.0, time * 0, time * 0), translation=(0.0, 0.0, -40.0)) # Apply the rotation and translation from the system camera m_mv = matrix44.multiply(m_mv, self.sys_camera.view_matrix) gravity_pos = vector3.create( math.sin(time) * 5, math.cos(time) * 5, math.sin(time / 3) * 5) gravity_force = math.cos(time / 2) * 3.0 + 3.0 # gravity_force = 2.0 # Transform positions self.feedback["gravity_pos"].write(gravity_pos.astype('f4').tobytes()) self.feedback["gravity_force"].value = gravity_force self.feedback["timedelta"].value = frametime self.particles.transform(self.feedback, self.pos) # Draw particles self.program["m_proj"].write(m_proj.astype('f4').tobytes()) self.program["m_mv"].write(m_mv.astype('f4').tobytes()) self.texture.use(location=0) self.program["texture0"].value = 0 self.particles.render(self.program) # Swap buffers self.pos = self.pos1 if self.pos == self.pos2 else self.pos2 self.particles = self.particles1 if self.particles == self.particles2 else self.particles2 def init_particles(self): count = 50000 area = 100.0 speed = 5.0 def gen(): for _ in range(count): # Position yield random.uniform(-area, area) yield random.uniform(-area, area) yield random.uniform(-area, area) # Velocity yield random.uniform(-speed, speed) yield random.uniform(-speed, speed) yield random.uniform(-speed, speed) data1 = numpy.fromiter(gen(), count=count * 6, dtype=numpy.float32) data2 = numpy.fromiter(gen(), count=count * 6, dtype=numpy.float32) self.pos1 = self.ctx.buffer(data1.tobytes()) self.particles1 = VAO("particles1", mode=mgl.POINTS) self.particles1.buffer(self.pos1, '3f 3f', ['in_position', 'in_velocity']) self.pos2 = self.ctx.buffer(data2.tobytes()) self.particles2 = VAO("particles2", mode=mgl.POINTS) self.particles2.buffer(self.pos2, '3f 3f', ['in_position', 'in_velocity']) # Set initial start buffers self.particles = self.particles1 self.pos = self.pos2
def plane_xz(size=(10, 10), resolution=(10, 10)) -> VAO: """ Generates a plane on the xz axis of a specific size and resolution. Normals and texture coordinates are also included. Args: size: (x, y) tuple resolution: (x, y) tuple Returns: A :py:class:`demosys.opengl.vao.VAO` instance """ sx, sz = size rx, rz = resolution dx, dz = sx / rx, sz / rz # step ox, oz = -sx / 2, -sz / 2 # start offset def gen_pos(): for z in range(rz): for x in range(rx): yield ox + x * dx yield 0 yield oz + z * dz def gen_uv(): for z in range(rz): for x in range(rx): yield x / (rx - 1) yield 1 - z / (rz - 1) def gen_normal(): for _ in range(rx * rz): yield 0.0 yield 1.0 yield 0.0 def gen_index(): for z in range(rz - 1): for x in range(rx - 1): # quad poly left yield z * rz + x + 1 yield z * rz + x yield z * rz + x + rx # quad poly right yield z * rz + x + 1 yield z * rz + x + rx yield z * rz + x + rx + 1 pos_data = numpy.fromiter(gen_pos(), dtype=numpy.float32) uv_data = numpy.fromiter(gen_uv(), dtype=numpy.float32) normal_data = numpy.fromiter(gen_normal(), dtype=numpy.float32) index_data = numpy.fromiter(gen_index(), dtype=numpy.uint32) vao = VAO("plane_xz", mode=moderngl.TRIANGLES) vao.buffer(pos_data, '3f', ['in_position']) vao.buffer(uv_data, '2f', ['in_uv']) vao.buffer(normal_data, '3f', ['in_normal']) vao.index_buffer(index_data, index_element_size=4) return vao
def bbox(width=1.0, height=1.0, depth=1.0): """ Generates a bounding box with (0.0, 0.0, 0.0) as the center. This is simply a box with ``LINE_STRIP`` as draw mode. Keyword Args: width (float): Width of the box height (float): Height of the box depth (float): Depth of the box Returns: A :py:class:`demosys.opengl.vao.VAO` instance """ width, height, depth = width / 2.0, height / 2.0, depth / 2.0 pos = numpy.array([ width, -height, depth, width, height, depth, -width, -height, depth, width, height, depth, -width, height, depth, -width, -height, depth, width, -height, -depth, width, height, -depth, width, -height, depth, width, height, -depth, width, height, depth, width, -height, depth, width, -height, -depth, width, -height, depth, -width, -height, depth, width, -height, -depth, -width, -height, depth, -width, -height, -depth, -width, -height, depth, -width, height, depth, -width, height, -depth, -width, -height, depth, -width, height, -depth, -width, -height, -depth, width, height, -depth, width, -height, -depth, -width, -height, -depth, width, height, -depth, -width, -height, -depth, -width, height, -depth, width, height, -depth, -width, height, -depth, width, height, depth, -width, height, -depth, -width, height, depth, width, height, depth, ], dtype=numpy.float32) vao = VAO("geometry:cube", mode=moderngl.LINE_STRIP) vao.buffer(pos, '3f', ["in_position"]) return vao
def cube(width, height, depth, center=(0.0, 0.0, 0.0), normals=True, uvs=True) -> VAO: """ Creates a cube VAO with normals and texture coordinates Args: width (float): Width of the cube height (float): Height of the cube depth (float): Depth of the cube Keyword Args: center: center of the cube as a 3-component tuple normals: (bool) Include normals uvs: (bool) include uv coordinates Returns: A :py:class:`demosys.opengl.vao.VAO` instance """ width, height, depth = width / 2.0, height / 2.0, depth / 2.0 pos = numpy.array([ center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, ], dtype=numpy.float32) if normals: normal_data = numpy.array([ -0, 0, 1, -0, 0, 1, -0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, ], dtype=numpy.float32) if uvs: uvs_data = numpy.array([ 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0 ], dtype=numpy.float32) vao = VAO("geometry:cube") # Add buffers vao.buffer(pos, '3f', ['in_position']) if normals: vao.buffer(normal_data, '3f', ['in_normal']) if uvs: vao.buffer(uvs_data, '2f', ['in_uv']) return vao
def quad_2d(width, height, xpos=0.0, ypos=0.0) -> VAO: """ Creates a 2D quad VAO using 2 triangles with normals and texture coordinates. Args: width (float): Width of the quad height (float): Height of the quad Keyword Args: xpos (float): Center position x ypos (float): Center position y Returns: A :py:class:`demosys.opengl.vao.VAO` instance. """ pos = numpy.array([ xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos - width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos + height / 2.0, 0.0, ], dtype=numpy.float32) normals = numpy.array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, ], dtype=numpy.float32) uvs = numpy.array([ 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ], dtype=numpy.float32) vao = VAO("geometry:quad", mode=moderngl.TRIANGLES) vao.buffer(pos, '3f', ["in_position"]) vao.buffer(normals, '3f', ["in_normal"]) vao.buffer(uvs, '2f', ["in_uv"]) return vao