''' Copyright (C) 2020 Akaneyu This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ''' import sys import math import time import bpy import bgl import blf import gpu from gpu_extras.batch import batch_for_shader from mathutils import Matrix, Vector import numpy as np default_vertex_shader = ''' uniform mat4 ModelViewProjectionMatrix; in vec2 pos; void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); } ''' default_fragment_shader = ''' uniform vec4 color; out vec4 fragColor; void main() { fragColor = color; } ''' dotted_line_vertex_shader = ''' uniform mat4 ModelViewProjectionMatrix; in vec2 pos; in float arcLength; out float arcLengthOut; void main() { arcLengthOut = arcLength; gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); } ''' dotted_line_fragment_shader = ''' uniform float scale; uniform float offset; uniform vec4 color1; uniform vec4 color2; in float arcLengthOut; out vec4 fragColor; void main() { if (step(sin((arcLengthOut + offset) * scale), 0.5) == 1) { fragColor = color1; } else { fragColor = color2; } } ''' image_vertex_shader = ''' uniform mat4 ModelViewProjectionMatrix; in vec2 pos; in vec2 texCoord; out vec2 texCoordOut; void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); texCoordOut = texCoord; } ''' image_fragment_shader = ''' uniform sampler2D image; in vec2 texCoordOut; out vec4 fragColor; void main() { fragColor = texture(image, texCoordOut); } ''' def make_scale_matrix(scale): return Matrix([ [scale[0], 0, 0, 0], [0, scale[1], 0, 0], [0, 0, 1.0, 0], [0, 0, 0, 1.0] ]) class UIRenderer: def __init__(self): self.default_shader = gpu.types.GPUShader(default_vertex_shader, default_fragment_shader) self.default_shader_u_color = self.default_shader.uniform_from_name("color") self.dotted_line_shader = gpu.types.GPUShader(dotted_line_vertex_shader, dotted_line_fragment_shader) self.dotted_line_shader_u_color1 = self.dotted_line_shader.uniform_from_name("color1") self.dotted_line_shader_u_color2 = self.dotted_line_shader.uniform_from_name("color2") #self.image_shader = gpu.shader.from_builtin('2D_IMAGE') self.image_shader = gpu.types.GPUShader(image_vertex_shader, image_fragment_shader) def render_selection_frame(self, pos, size, rot=0, scale=(1.0, 1.0)): width, height = size[0], size[1] bgl.glEnable(bgl.GL_BLEND) bgl.glLineWidth(2.0) with gpu.matrix.push_pop(): verts = [[0, 0], [0, height], [width, height], [width, 0], [0, 0]] # T <= R <= S <= centering mat = Matrix.Translation([pos[0] + width / 2.0, pos[1] + height / 2.0, 0]) \ @ Matrix.Rotation(rot, 4, 'Z') \ @ make_scale_matrix(scale) \ @ Matrix.Translation([-width / 2.0, -height / 2.0, 0]) for i, vert in enumerate(verts): verts[i] = (mat @ Vector(vert + [0, 1]))[:2] verts = np.array(verts, 'f') arc_lengths = [0] for a, b in zip(verts[:-1], verts[1:]): arc_lengths.append(arc_lengths[-1] + np.linalg.norm(a - b)) batch = batch_for_shader(self.dotted_line_shader, 'LINE_STRIP', {"pos": verts, "arcLength": arc_lengths}) self.dotted_line_shader.bind() self.dotted_line_shader.uniform_float("scale", 0.6) self.dotted_line_shader.uniform_float("offset", 0) self.dotted_line_shader.uniform_vector_float(self.dotted_line_shader_u_color1, np.array([1.0, 1.0, 1.0, 0.5], 'f'), 4) self.dotted_line_shader.uniform_vector_float(self.dotted_line_shader_u_color2, np.array([0.0, 0.0, 0.0, 0.5], 'f'), 4) batch.draw(self.dotted_line_shader) err = bgl.glGetError() if err != bgl.GL_NO_ERROR: print('render_selection_frame') print('OpenGL error:', err) def render_image_sub(self, img, pos, size, rot, scale): width, height = size[0], size[1] img.gl_load() bgl.glActiveTexture(bgl.GL_TEXTURE0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, img.bindcode) bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) with gpu.matrix.push_pop(): gpu.matrix.translate([pos[0] + width / 2.0, pos[1] + height / 2.0]) gpu.matrix.multiply_matrix( Matrix.Rotation(rot, 4, 'Z')) gpu.matrix.scale(scale) gpu.matrix.translate([-width / 2.0, -height / 2.0]) batch = batch_for_shader(self.image_shader, 'TRI_FAN', { "pos": [ (0, 0), (width, 0), size, (0, height) ], "texCoord": [(0, 0), (1, 0), (1, 1), (0, 1)] }) self.image_shader.bind() self.image_shader.uniform_int('image', 0) batch.draw(self.image_shader) err = bgl.glGetError() if err != bgl.GL_NO_ERROR: print('render_image') print('OpenGL error:', err) def render_image(self, img, pos, size, rot=0, scale=(1.0, 1.0)): bgl.glEnable(bgl.GL_BLEND) # FIXME: glBlendFuncSeparate does not seem to be implemented, # but we believe this blend mode was already applied #bgl.glBlendFuncSeparate(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA, # bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA); self.render_image_sub(img, pos, size, rot, scale) def render_image_offscreen(self, img, rot=0, scale=(1.0, 1.0)): width, height = img.size[0], img.size[1] box = [[0, 0], [width, 0], [0, height], [width, height]] mat = Matrix.Rotation(rot, 4, 'Z') \ @ make_scale_matrix(scale) \ @ Matrix.Translation([-width / 2.0, -height / 2.0, 0]) min_x, min_y = sys.float_info.max, sys.float_info.max max_x, max_y = -sys.float_info.max, -sys.float_info.max # calculate bounding box for pos in box: pos = mat @ Vector(pos + [0, 1]) min_x = min(min_x, pos[0]) min_y = min(min_y, pos[1]) max_x = max(max_x, pos[0]) max_y = max(max_y, pos[1]) ofs_width = math.ceil(max_x - min_x) ofs_height = math.ceil(max_y - min_y) ofs = gpu.types.GPUOffScreen(ofs_width, ofs_height) with ofs.bind(): bgl.glDisable(bgl.GL_BLEND) bgl.glClearColor(0, 0, 0, 0) bgl.glClear(bgl.GL_COLOR_BUFFER_BIT) with gpu.matrix.push_pop(): gpu.matrix.load_projection_matrix(Matrix.Identity(4)) gpu.matrix.load_identity() gpu.matrix.scale([1.0 / (ofs_width / 2.0), 1.0 / (ofs_height / 2.0)]) gpu.matrix.translate([-width / 2.0, -height / 2.0]) self.render_image_sub(img, [0, 0], [width, height], rot, scale) buff = bgl.Buffer(bgl.GL_BYTE, ofs_width * ofs_height * 4) bgl.glReadBuffer(bgl.GL_BACK) bgl.glReadPixels(0, 0, ofs_width, ofs_height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buff) ofs.free() err = bgl.glGetError() if err != bgl.GL_NO_ERROR: print('render_image_offscreen') print('OpenGL error:', err) return buff, ofs_width, ofs_height