class Actor(QObject): class RenderType: NoType = 0 ## No special state changes are done. Solid = 1 ## Depth testing and depth writing are enabled. Transparent = 2 ## Depth testing is enabled, depth writing is disabled. Overlay = 3 ## Depth testing is disabled. Types = [NoType, Solid, Transparent, Overlay] ## The mode to render objects in. These correspond to OpenGL render modes. class RenderMode: Points = GL.GL_POINTS Lines = GL.GL_LINES LineLoop = GL.GL_LINE_LOOP LineStrip = GL.GL_LINE_STRIP Triangles = GL.GL_TRIANGLES TriangleStrip = GL.GL_TRIANGLE_STRIP TriangleFan = GL.GL_TRIANGLE_FAN Modes = [ Points, Lines, LineLoop, LineStrip, Triangles, TriangleStrip, TriangleFan ] ## initialization def __init__(self, scene, **kwargs): """Initialize actor.""" super(Actor, self).__init__() self._state = 0 #1=scale, 2=rotation, 3=scale self._lines = None self._axis = 0 # 1=x, 2=y, 3=z self._scene = scene self._transform = kwargs.get("transform", QMatrix4x4()) self._render_mode = kwargs.get("mode", Actor.RenderMode.Triangles) self._render_type = kwargs.get("type", Actor.RenderType.Solid) self._material = kwargs.get("material", Material()) self._wireframe = kwargs.get( "wireframe", Material(diffuse=QVector3D(0.25, 0.25, 0.25))) self._viewport = kwargs.get("viewport", (0.0, 0.0, 1.0, 1.0)) self._name = kwargs.get("name", "Actor" + str(id(self))) self._shader_collection = Shaders() #self._texture_collection = Textures() self._solid_shader = self._shader_collection.uniformMaterialPhongShader( ) self._solid_flat_shader = self._shader_collection.uniformMaterialPhongFlatShader( ) self._nolight_solid_shader = self._shader_collection.uniformMaterialShader( ) self._wireframe_shader = self._shader_collection.uniformMaterialShader( ) self._nolight_wireframe_shader = self._shader_collection.uniformMaterialShader( ) self._normal_visualizing_shader = self._shader_collection.normalVisShader( ) self._active_shader = self._solid_shader self._active_material = self._material self._vao = QOpenGLVertexArrayObject() self._vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) self._ibo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) self._num_vertices = 0 self._num_indices = 0 self._hasNormals = False self._hasColors = False self._hasTextureCoords = False self._hasIndices = False self._hasFaces = False self._texture = None #self._bbox = None self._visible = True self._enabled = False self._pickable = True self._selectable = False self._selected = False self._highlighted = False self._errorMaterial = Material.ruby() self._errorHighlight = False self._warningMaterial = Material.gold() self._warningHighlight = False self._pickFactor = 1.0 def update(self, **kwargs): """Update this node""" self._transform = kwargs.get("transform", QMatrix4x4()) self._render_mode = kwargs.get("mode", Actor.RenderMode.Triangles) self._render_type = kwargs.get("type", Actor.RenderType.Solid) self._material = kwargs.get("material", Material()) self._wireframe = kwargs.get( "wireframe", Material(diffuse=QVector3D(0.25, 0.25, 0.25))) def scene(self): return self._scene @property def name(self): """Returns the name of this actor""" return self._name def setName(self, name): """Sets this actor's name""" self._name = name @property def material(self): """Returns the material of this node""" return self._material def setTransform(self, xform): self._transform = xform def scale(self, amt): if (self._axis == 1): self._transform.scale(amt, 1.0, 1.0) elif (self._axis == 2): self._transform.scale(1.0, amt, 1.0) elif (self._axis == 3): self._transform.scale(1.0, 1.0, amt) def translate(self, pos): self._transform[0, 3] += pos.x() self._transform[1, 3] += pos.y() self._transform[2, 3] += pos.z() def transform(self): return self._transform def position(self): xform = self.transform() return QVector3D(xform[0, 3], xform[1, 3], xform[2, 3]) def setPosition(self, pos): #print("pos==",pos) self._transform = QMatrix4x4() self._transform.translate(pos.x(), pos.y(), pos.z()) def texture(self): """Returns the texture image""" return self._texture def setTexture(self, texture): """Sets the current texture""" self._texture = texture def isPickable(self): """Sets whether or not this actor is pickable""" return self._pickable def setPickable(self, value): """Sets whether this actor is pickable""" self._pickable = value def isVisible(self): """Sets the visibility of this actor""" return self._visible def setVisible(self, value): """Sets the visibility of this actor""" self._visible = value def isEnabled(self): """Returns whether this actor is enabled or not""" return self._enabled def setEnabled(self, value): """Sets whether this actor is enabled or not""" self._enabled = value def setSelectable(self, value): """Sets whther or not this actor is selectable""" self._selectable = value def isSelectable(self): """Returns true if actor is selectable""" return self._selectable def setSelected(self, value): """Sets selection to value""" self._selected = value def isSelected(self): """Returns true if it is selected""" return self._selected def setHighlighted(self, value): """Sets the highlight value""" self._highlighted = value def isHighlighted(self): """Returns true if it is highlighted""" return self._highlighted def setErrorMaterial(self, material): """Sets the error material""" self._errorMaterial = material def setErrorHighlight(self, value): """Sets the error highlight""" self._errorHighlight = value def setWarningMaterial(self, material): """Sets the error material""" self._warningMaterial = material def setWarningHighlight(self, value): """Sets the warning highlight""" self._warningHighlight = value @property def shaderCollection(self): """Returns the shader collection""" return self._shader_collection @property def renderType(self): """Returns the rendering type of this actor""" return self._render_type @property def renderMode(self): """Returns the rendering mode of this actor""" return self._render_mode @property def solidShader(self): """Returns the default solid shader of this actor""" return self._solid_shader def setSolidShader(self, shader): """Sets the solid shader of this actor""" self._solid_shader = shader @property def solidFlatShader(self): """Returns the default solid flat shader of this actor""" return self._solid_flat_shader def setSolidFlatShader(self, shader): """Sets the solid flat shader of this actor""" self._solid_flat_shader = shader @property def noLightSolidShader(self): """Returns the default no light solid shader of this actor""" return self._nolight_solid_shader def setNoLightSolidShader(self, shader): """Sets the solid shader of this actor""" self._nolight_solid_shader = shader @property def wireframeShader(self): """Returns the default wireframe shader of this actor""" return self._wireframe_shader def setWireframeShader(self, shader): """Sets the default wireframe shader of this actor""" self._wireframe_shader = shader @property def noLightWireframeShader(self): """Returns the default no light wireframe shader of this actor""" return self._nolight_wireframe_shader def setNoLightWireframeShader(self, shader): """Sets the no light wireframe shader of this actor""" self._nolight_wireframe_shader = shader @property def numberOfVertices(self): """Returns the number of vertices of this actor""" return self._num_vertices @property def numberOfIndices(self): """Returns the number of indices of this actor""" return self._num_indices def mapBuffer(self, offset, count, access): """Map the given buffer into a numpy array""" vbo_ptr = self._vbo.mapRange(offset, count, access) vp_array = ctypes.cast( ctypes.c_void_p(int(vbo_ptr)), ctypes.POINTER(ctypes.c_byte * self._vbo.size())).contents # Note: we could have returned the raw ctypes.c_byte array instead... see pyglet github for map/unmap classes array = np.frombuffer(vp_array, 'B') return array def unmapBuffer(self): """Update the GPU with new buffer contents""" self._vbo.unmap() def updateBuffer(self, vertices=None, normals=None, colors=None, texcoords=None): """Update buffer with new data""" self._vbo.bind() if vertices is not None: vertices = vertices.tostring() #vertices = np.fromstring(vertices, dtype="uint8") self._vbo.write(0, vertices, len(vertices)) #buffer = self.mapBuffer(0, len(vertices), QOpenGLBuffer.RangeWrite | QOpenGLBuffer.RangeInvalidate) #buffer[:len(vertices)] = vertices #self.unmapBuffer() if normals is not None: normals = normals.tostring() self._vbo.write(self._offsetNormals, normals, len(normals)) if colors is not None: colors = colors.tostring() self._vbo.write(self._offsetColors, colors, len(colors)) if texcoords is not None: texcoords = texcoords.tostring() self._vbo.write(self._offsetTexCoords, texcoords, len(texcoords)) self._vbo.release() def create(self, vertices, normals=None, colors=None, texcoords=None, indices=None, faces=None, usage=QOpenGLBuffer.StaticDraw): """Create object vertex arrays and buffers""" ## list of shaders shaders = [ self._solid_shader, self._wireframe_shader, self._nolight_solid_shader, self._nolight_wireframe_shader, self._normal_visualizing_shader ] ## bind vao self._vao.create() self._vao.bind() ## define total sizes vertices = vertices.tostring() total_vertices = len(vertices) total_normals = 0 total_colors = 0 total_texcoords = 0 total_faces = 0 self._num_vertices = total_vertices // (np.dtype(np.float32).itemsize * 3) #print('total vertices=', self._num_vertices) if normals is not None: self._hasNormals = True normals = normals.tostring() total_normals = len(normals) if colors is not None: self._hasColors = True colors = colors.tostring() total_colors = len(colors) if texcoords is not None: self._hasTextureCoords = True texcoords = texcoords.tostring() total_texcoords = len(texcoords) if indices is not None: self._hasIndices = True indices = indices.tostring() total_indices = len(indices) self._num_indices = total_indices // np.dtype(np.uint32).itemsize #print('total indices=', self._num_indices) if faces is not None: self._hasFaces = True faces = faces.tostring() total_faces = len(faces) ## create vertex buffer object self._vbo.setUsagePattern(usage) self._vbo.create() self._vbo.bind() ## populate vertex buffer object with data offset = 0 self._vbo.allocate(total_vertices + total_normals + total_colors + total_texcoords + total_faces) self._vbo.write(offset, vertices, total_vertices) for each in shaders: each.setAttributeBuffer('position', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_vertices self._offsetNormals = offset if self._hasNormals: self._vbo.write(offset, normals, total_normals) for each in shaders: each.setAttributeBuffer('normal', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_normals if self._hasColors: self._offsetColors = offset self._vbo.write(offset, colors, total_colors) for each in shaders: each.setAttributeBuffer('color', GL.GL_FLOAT, offset, 3, 3 * np.dtype(np.float32).itemsize) offset += total_colors if self._hasTextureCoords: self._offsetTexCoords = offset self._vbo.write(offset, texcoords, total_texcoords) for each in shaders: each.setAttributeBuffer('texcoord', GL.GL_FLOAT, offset, 2, 2 * np.dtype(np.float32).itemsize) offset += total_texcoords if self._hasFaces: self._offsetFaces = offset self._vbo.write(offset, faces, total_faces) for each in shaders: each.setAttributeBuffer('faces', GL.GL_INT, offset, 9, 9 * np.dtype(np.int).itemsize) offset += total_faces ## release buffer self._vbo.release(QOpenGLBuffer.VertexBuffer) ## enable arrays as part of the vao state for each in shaders: each.enableAttributeArray('position') if self._hasNormals: for each in shaders: each.enableAttributeArray('normal') if self._hasColors: for each in shaders: each.enableAttributeArray('color') if self._hasTextureCoords: for each in shaders: each.enableAttributeArray('texcoord') if self._hasFaces: for each in shaders: each.enableAttributeArray('faces') ## create index buffer object if required by the actor if self._hasIndices: self._ibo.setUsagePattern(usage) self._ibo.create() self._ibo.bind() self._ibo.allocate(total_indices) self._ibo.write(0, indices, total_indices) ## release vao self._vao.release() ## release ibo if self._hasIndices: self._ibo.release(QOpenGLBuffer.IndexBuffer) def setUniformBindings(self, wireframe=False): """Sets up uniform shader bindings""" normalMatrix = self._transform.normalMatrix() self._active_shader.setUniformValue("modelMatrix", self._transform) self._active_shader.setUniformValue("viewMatrix", self._scene.camera.viewMatrix) self._active_shader.setUniformValue( "projectionMatrix", self._scene.camera.projectionMatrix) self._active_shader.setUniformValue("normalMatrix", normalMatrix) if self.texture() is not None: self._active_shader.setUniformValue("texObject", 1.0) ## bind active material if self.isSelectable() and self.isSelected(): self._active_shader.setUniformValue("selected", 0.5) else: self._active_shader.setUniformValue("selected", 0.0) ## set highlight color if self.isHighlighted(): self._active_shader.setUniformValue("material.emission", QVector3D(0.25, 0.25, 0.25)) else: self._active_shader.setUniformValue( "material.emission", self._active_material.emissionColor) self._active_shader.setUniformValue("material.ambient", self._active_material.ambientColor) ## set the enabled color if self.isEnabled(): self._active_shader.setUniformValue("material.emission", QVector3D(0.25, 0.25, 0.25)) self._active_shader.setUniformValue( "material.diffuse", self._active_material.diffuseColor) else: self._active_shader.setUniformValue( "material.diffuse", self._active_material.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._active_material.specularColor) self._active_shader.setUniformValue("material.shininess", self._active_material.shininess) ## set the error and warning colors if self._errorHighlight: self._active_shader.setUniformValue( "material.ambient", self._errorMaterial.ambientColor) self._active_shader.setUniformValue( "material.diffuse", self._errorMaterial.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._errorMaterial.specularColor) self._active_shader.setUniformValue("material.shininess", self._errorMaterial.shininess) if self._warningHighlight: self._active_shader.setUniformValue( "material.ambient", self._warningMaterial.ambientColor) self._active_shader.setUniformValue( "material.diffuse", self._warningMaterial.diffuseColor) self._active_shader.setUniformValue( "material.specular", self._warningMaterial.specularColor) self._active_shader.setUniformValue( "material.shininess", self._warningMaterial.shininess) ## bind lights camera_position = QVector4D(self._scene.camera.position[0], self._scene.camera.position[1], self._scene.camera.position[2], 1.0) if self._scene.light.headlight: if self._scene.light.directional: self._active_shader.setUniformValue( "lightPosition", QVector4D(0.0, 0.0, 1.0, 0.0)) else: self._active_shader.setUniformValue( "lightPosition", QVector4D(0.0, 0.0, 0.0, 1.0)) else: self._active_shader.setUniformValue( "lightPosition", self._scene.camera.viewMatrix * self._scene.light.position) self._active_shader.setUniformValue("light.ambient", self._scene.light.ambientColor) self._active_shader.setUniformValue("light.diffuse", self._scene.light.diffuseColor) self._active_shader.setUniformValue("light.specular", self._scene.light.specularColor) self._active_shader.setUniformValue("lightAttenuation", self._scene.light.attenuation) ## This should set up any required state before any actual rendering happens. def beginRendering(self, draw_style, lighting, shading, passNumber): ## determine right shader to bind if lighting: if draw_style == GL.GL_LINE: self._active_shader = self._wireframe_shader self._active_material = self._material if passNumber == 0 else self._wireframe else: if shading == GL.GL_SMOOTH: self._active_shader = self._solid_shader else: self._active_shader = self._solid_flat_shader self._active_material = self._material else: if draw_style == GL.GL_LINE: self._active_shader = self._nolight_wireframe_shader self._active_material = self._material if passNumber == 0 else self._wireframe else: self._active_shader = self._nolight_solid_shader self._active_material = self._material GL.glPolygonMode(GL.GL_FRONT_AND_BACK, draw_style) ## determine rendering type to use if self._render_type == self.RenderType.Solid: GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthMask(GL.GL_TRUE) elif self._render_type == self.RenderType.Transparent: GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthMask(GL.GL_FALSE) elif self._render_type == self.RenderType.Overlay: GL.glDisable(GL.GL_DEPTH_TEST) ## bind shader self._active_shader.bind() ## set up uniform variables self.setUniformBindings() if self._texture is not None: #self.glEnable(GL.GL_BLEND) self._texture.bind() ## bind shader self._vao.bind() def render(self): """Render this actor""" raise NotImplementedError( "render() must be implemented in child class") def endRendering(self): """Finished rendering, clean yourself up""" ## unbind vao self._vao.release() ## unbind texture if self._texture is not None: self._texture.release() ## unbind shader self._active_shader.release() def pickFactor(self): """Returns the pick factor for intersection calculations""" return self._pickFactor def setPickFactor(self, value): """Sets the pick factor for intersection calculations""" self._pickFactor = value def destroy(self): self._vao.destroy() self._vbo.destroy() self._ibo.destroy() def intersect(self, ray): """Returns intersection if any""" tMin = -math.inf tMax = math.inf obb_xform = self.transform() obb_center = QVector3D(obb_xform[0, 3], obb_xform[1, 3], obb_xform[2, 3]) point = obb_center - ray.origin() for i in range(3): axis = QVector3D(obb_xform[0, i], obb_xform[1, i], obb_xform[2, i]).normalized() half_length = QVector3D(obb_xform[i, 0], obb_xform[i, 1], obb_xform[i, 2]).length() / 2.0 e = QVector3D.dotProduct(axis, point) f = QVector3D.dotProduct(axis, ray.direction()) if abs(f) > 10E-6: t1 = (e + half_length * self._pickFactor) / f t2 = (e - half_length * self._pickFactor) / f if t1 > t2: w = t1 t1 = t2 t2 = w if t1 > tMin: tMin = t1 if t2 < tMax: tMax = t2 if tMin > tMax: return (False, math.inf) if tMax < 0: return (False, math.inf) elif -e - half_length > 0.0 or -e + half_length < 0.0: return (False, math.inf) if tMin > 0: return (True, tMin) return (True, tMax)
class DDSWidget(QOpenGLWidget): def __init__(self, ddsFile, debugContext = False, parent = None, f = Qt.WindowFlags()): super(DDSWidget, self).__init__(parent, f) self.ddsFile = ddsFile self.clean = True self.logger = None self.program = None self.transparecyProgram = None self.texture = None self.vbo = None self.vao = None self.backgroundColour = None if debugContext: format = QSurfaceFormat() format.setOption(QSurfaceFormat.DebugContext) self.setFormat(format) self.logger = QOpenGLDebugLogger(self) qDebug("__init__()") def __del__(self): qDebug("__del__()") self.cleanup() def __dtor__(self): qDebug("__dtor__()") self.cleanup() def initializeGL(self): qDebug("initializeGL()") if self.logger: self.logger.initialize() self.logger.messageLogged.connect(lambda message: qDebug(self.__tr("OpenGL debug message: {0}").fomat(message.message()))) self.logger.startLogging() gl = QOpenGLContext.currentContext().versionFunctions(glVersionProfile) QOpenGLContext.currentContext().aboutToBeDestroyed.connect(self.cleanup) self.clean = False fragmentShader = None vertexShader = vertexShader2D if self.ddsFile.isCubemap: fragmentShader = fragmentShaderCube vertexShader = vertexShaderCube if QOpenGLContext.currentContext().hasExtension(b"GL_ARB_seamless_cube_map"): GL_TEXTURE_CUBE_MAP_SEAMLESS = 0x884F gl.glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS) elif self.ddsFile.glFormat.samplerType == "F": fragmentShader = fragmentShaderFloat elif self.ddsFile.glFormat.samplerType == "UI": fragmentShader = fragmentShaderUInt else: fragmentShader = fragmentShaderSInt self.program = QOpenGLShaderProgram(self) self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader) self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader) self.program.bindAttributeLocation("position", 0) self.program.bindAttributeLocation("texCoordIn", 1) self.program.link() self.transparecyProgram = QOpenGLShaderProgram(self) self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.Vertex, transparencyVS) self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.Fragment, transparencyFS) self.transparecyProgram.bindAttributeLocation("position", 0) self.transparecyProgram.link() self.vao = QOpenGLVertexArrayObject(self) vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) self.vbo.create() self.vbo.bind() theBytes = struct.pack("%sf" % len(vertices), *vertices) self.vbo.allocate(theBytes, len(theBytes)) gl.glEnableVertexAttribArray(0) gl.glEnableVertexAttribArray(1) gl.glVertexAttribPointer(0, 4, gl.GL_FLOAT, False, 6 * 4, 0) gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, False, 6 * 4, 4 * 4) self.texture = self.ddsFile.asQOpenGLTexture(gl, QOpenGLContext.currentContext()) def resizeGL(self, w, h): qDebug("resizeGL(" + str(w) + ", " + str(h) + ")") aspectRatioTex = self.texture.width() / self.texture.height() if self.texture else 1.0 aspectRatioWidget = w / h ratioRatio = aspectRatioTex / aspectRatioWidget self.program.bind() self.program.setUniformValue("aspectRatioRatio", ratioRatio) self.program.release() def paintGL(self): qDebug("paintGL()") gl = QOpenGLContext.currentContext().versionFunctions(glVersionProfile) vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) # Draw checkerboard so transparency is obvious self.transparecyProgram.bind() if self.backgroundColour and self.backgroundColour.isValid(): self.transparecyProgram.setUniformValue("backgroundColour", self.backgroundColour) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) self.transparecyProgram.release() self.program.bind() if self.texture: self.texture.bind() gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) if self.texture: self.texture.release() self.program.release() def cleanup(self): qDebug("cleanup()") if not self.clean: self.makeCurrent() self.program = None self.transparecyProgram = None if self.texture: self.texture.destroy() self.texture = None self.vbo.destroy() self.vbo = None self.vao.destroy() self.vao = None self.doneCurrent() self.clean = True def setBackgroundColour(self, colour): self.backgroundColour = colour def getBackgroundColour(self): return self.backgroundColour def __tr(self, str): return QCoreApplication.translate("DDSWidget", str)