def load(self) -> Scene: """Loads and stl scene/file Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) file_obj = str(path) if file_obj.endswith('.gz'): file_obj = gzip.GzipFile(file_obj) stl_mesh = trimesh.load(file_obj, file_type='stl') scene = Scene(self.meta.resolved_path) scene_mesh = Mesh("mesh") scene_mesh.material = Material("default") vao = VAO("mesh", mode=moderngl.TRIANGLES) vao.buffer(numpy.array(stl_mesh.vertices, dtype='f4'), '3f', ['in_position']) vao.buffer(numpy.array(stl_mesh.vertex_normals, dtype='f4'), '3f', ['in_normal']) vao.index_buffer(numpy.array(stl_mesh.faces, dtype='u4')) scene_mesh.vao = vao scene_mesh.add_attribute('POSITION', 'in_position', 3) scene_mesh.add_attribute('NORMAL', 'in_normal', 3) scene.meshes.append(scene_mesh) scene.root_nodes.append(Node(mesh=scene_mesh)) scene.prepare() return scene
def load(self, materials): name_map = { 'POSITION': self.meta.attr_names.POSITION, 'NORMAL': self.meta.attr_names.NORMAL, 'TEXCOORD_0': self.meta.attr_names.TEXCOORD_0, 'TANGENT': self.meta.attr_names.TANGENT, 'JOINTS_0': self.meta.attr_names.JOINTS_0, 'WEIGHTS_0': self.meta.attr_names.WEIGHTS_0, 'COLOR_0': self.meta.attr_names.COLOR_0, } 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( moderngl_window.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
class Terrain(mglw.WindowConfig): """""" title = "Terrain" resource_dir = (Path(__file__) / '../resources').absolute() aspect_ratio = None window_size = 1280, 720 resizable = True samples = 16 clear_color = (51 / 255, 51 / 255, 51 / 255) def __init__(self, **kwargs): super().__init__(**kwargs) self.camera = KeyboardCamera(self.wnd.keys, aspect_ratio=self.wnd.aspect_ratio) self.terrain_program = self.load_program('my_shader.glsl') self.ctx.front_face = 'cw' # self.ctx.wireframe = True self.translate = Matrix44.from_translation((0, 1., .25)) self.rotate = Matrix44.look_at((0., 1., .25), (0., 0., -1.), (0., 1., 0.)) self.projection = (self.camera.projection.matrix * (self.translate * self.rotate)).astype('f4') self.terrain_program['projection'].write(self.projection.tobytes()) terrain_resolution = 5 points = generate(terrain_resolution).astype('f4') self.buffer = self.ctx.buffer(points) indices = generate_index_buffer(terrain_resolution).astype('i4') print(indices) self.index_buffer = self.ctx.buffer(indices) self.vao_1 = VAO(name='vao_1') self.vao_1.buffer(self.buffer, '3f', ['in_position']) self.vao_1.index_buffer(self.index_buffer) def render(self, time, frame_time): self.ctx.clear(*self.clear_color) # self.ctx.clear(0.) self.ctx.enable_only(moderngl.DEPTH_TEST | moderngl.CULL_FACE) self.vao_1.render(self.terrain_program, mode=moderngl.POINTS) def resize(self, width: int, height: int): self.camera.projection.update(aspect_ratio=self.wnd.aspect_ratio)
def sphere( radius=0.5, sectors=32, rings=16, normals=True, uvs=True, name: str = None, attr_names=AttributeNames, ) -> VAO: """Creates a sphere. Keyword Args: radius (float): Radius or the sphere rings (int): number or horizontal rings sectors (int): number of vertical segments normals (bool): Include normals in the VAO uvs (bool): Include texture coordinates in the VAO name (str): An optional name for the VAO attr_names (AttributeNames): Attribute names Returns: A :py:class:`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 vao = VAO(name or "sphere", mode=mlg.TRIANGLES) vbo_vertices = numpy.array(vertices, dtype=numpy.float32) vao.buffer(vbo_vertices, "3f", [attr_names.POSITION]) if normals: vbo_normals = numpy.array(normals, dtype=numpy.float32) vao.buffer(vbo_normals, "3f", [attr_names.NORMAL]) if uvs: vbo_uvs = numpy.array(uvs, dtype=numpy.float32) vao.buffer(vbo_uvs, "2f", [attr_names.TEXCOORD_0]) vbo_elements = numpy.array(indices, dtype=numpy.uint32) vao.index_buffer(vbo_elements, index_element_size=4) return vao
class VolumetricTetrahedralMesh(CameraWindow): """Volumetric Tetrahedral Mesh. The dataset was provided by: Mara Catalina Aguilera Canon at the Bournemouth University (UK). Area of research: Graph Neuro Networks, Finite Element Method An example rendering a volumetric mesh of the format: ``[[p1, p2, p3, p4], [p1, p2, p3, p4], ..]`` were ```px``` represent a 3d point in a tetraherdon. A geometry shader calculates and emits the tetraherdons as triangles and calculate normals on the fly while rendering data. This helps us avoid doing this expensive operation in python and greatly reduces the memory requirement. Controls: - Camera: Mouse for rotation. AWSD + QE for translation - Press b to toggle blend mode on/off - Mouse wheel to increase or decrease the threshold for a tetra to be alive """ gl_version = (4, 1) title = "Volumetric Tetrahedra lMesh" aspect_ratio = None resource_dir = (Path(__file__) / '../../resources').resolve() samples = 4 def __init__(self, **kwargs): super().__init__(**kwargs) # Finetune camera self.wnd.mouse_exclusivity = True self.camera.projection.update(near=.01, far=100) self.camera.mouse_sensitivity = .5 self.camera.velocity = 2.5 self.camera.projection.update(fov=60) # Scene states self.with_blending = False self.line_color = (0.0, 0.0, 0.0) self.mesh_color = (0.0, 0.8, 0.0) self.threshold = 0.5 # For rendering background self.quad_fs = geometry.quad_fs() # (172575,) | 57,525 vertices vertices = np.load(self.resource_dir / 'data/tetrahedral_mesh/mesh_nodes.npy') vertices = np.concatenate(vertices) # (259490, 4) (1037960,) indices indices = np.load(self.resource_dir / 'data/tetrahedral_mesh/element_nodes.npy') indices = np.concatenate(indices) - 1 # Probability of a tetrahedron is still alive w, h = 8192, int(np.ceil(indices.shape[0] / 8192)) self.alive_data = np.random.random_sample(w * h) self.alive_texture = self.ctx.texture((w, h), 1, dtype='f2') self.alive_texture.write(self.alive_data.astype('f2')) # Original geometry with indices self.geometry = VAO(name='geometry_indices') self.geometry.buffer(vertices, '3f', 'in_position') self.geometry.index_buffer(indices, index_element_size=4) self.prog_background = self.load_program( 'programs/tetrahedral_mesh/bg.glsl') self.prog_gen_tetra = self.load_program( vertex_shader='programs/tetrahedral_mesh/gen_tetra_vert.glsl', geometry_shader='programs/tetrahedral_mesh/gen_tetra_geo.glsl', fragment_shader='programs/tetrahedral_mesh/gen_tetra_frag.glsl', ) self.prog_gen_tetra_lines = self.load_program( vertex_shader='programs/tetrahedral_mesh/gen_tetra_vert.glsl', geometry_shader='programs/tetrahedral_mesh/gen_tetra_geo.glsl', fragment_shader='programs/tetrahedral_mesh/lines_frag.glsl', ) # Query object for measuring the rendering call in OpenGL # It delivers the GPU time it took to process commands self.query = self.ctx.query(samples=True, any_samples=True, time=True, primitives=True) self.total_elapsed = 0 def render(self, time, frametime): # Render background self.ctx.wireframe = False if not self.with_blending: self.ctx.enable_only(moderngl.NOTHING) self.quad_fs.render(self.prog_background) # Handle blend mode toggle if self.with_blending: self.ctx.enable_only(moderngl.BLEND) self.ctx.blend_func = moderngl.ONE, moderngl.ONE else: self.ctx.enable_only(moderngl.DEPTH_TEST | moderngl.CULL_FACE) # Render tetrahedral mesh translate = Matrix44.from_translation((0.0, 2.5, -15.0), dtype='f4') rotate = Matrix44.from_eulers((np.radians(180), 0, 0), dtype='f4') scale = Matrix44.from_scale((400, 400, 400), dtype='f4') mat = self.camera.matrix * translate * rotate * scale # All render calls inside this context are timed with self.query: self.alive_texture.use(location=0) self.prog_gen_tetra['alive_texture'].value = 0 self.prog_gen_tetra['threshold'].value = self.threshold self.prog_gen_tetra['color'].value = self.mesh_color self.prog_gen_tetra['m_cam'].write(mat) self.prog_gen_tetra['m_proj'].write(self.camera.projection.matrix) self.geometry.render(self.prog_gen_tetra, mode=moderngl.LINES_ADJACENCY) # Render lines self.ctx.wireframe = True self.alive_texture.use(location=0) self.prog_gen_tetra_lines['alive_texture'].value = 0 self.prog_gen_tetra_lines['threshold'].value = self.threshold self.prog_gen_tetra_lines['color'].value = self.line_color self.prog_gen_tetra_lines['m_cam'].write(mat) self.prog_gen_tetra_lines['m_proj'].write( self.camera.projection.matrix) self.geometry.render(self.prog_gen_tetra_lines, mode=moderngl.LINES_ADJACENCY) self.total_elapsed = self.query.elapsed def key_event(self, key, action, modifiers): super().key_event(key, action, modifiers) keys = self.wnd.keys if action == keys.ACTION_PRESS: if key == keys.B: self.with_blending = not self.with_blending print("With blending:", self.with_blending) if self.with_blending: self.mesh_color = 0.01, 0.01, 0.01 self.line_color = 0.01, 0.01, 0.01 else: self.mesh_color = 0.0, 0.8, 0.0 self.line_color = 0.0, 0.0, 0.0 def mouse_scroll_event(self, x_offset, y_offset): if y_offset > 0: self.threshold += 0.01 else: self.threshold -= 0.01 self.threshold = max(min(self.threshold, 1.0), 0.0) def close(self): # 1 s = 1000000000 ns # 1 s = 1000000 μs avg = self.total_elapsed / self.wnd.frames print("Average rendering time per frame: {} ns | {} μs".format( round(avg, 4), # ns round(avg / 1000, 4), # μs ))