''' Copyright (C) 2020 - 2025 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 gpu from gpu.shader import create_from_info from gpu_extras.batch import batch_for_shader from mathutils import Matrix, Vector import numpy as np default_vertex_shader = ''' void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); } ''' default_fragment_shader = ''' void main() { fragColor = color; } ''' dotted_line_vertex_shader = ''' void main() { arcLengthInter = arcLength; gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); } ''' dotted_line_fragment_shader = ''' void main() { if (step(sin((arcLengthInter + offset) * scale), 0.5) == 1) { fragColor = color1; } else { fragColor = color2; } } ''' image_vertex_shader = ''' void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos, 0, 1.0); texCoordOut = texCoord; } ''' image_fragment_shader = ''' 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): default_shader_info = gpu.types.GPUShaderCreateInfo() default_shader_info.push_constant('MAT4', 'ModelViewProjectionMatrix') default_shader_info.push_constant('VEC4', 'color') default_shader_info.vertex_in(0, 'VEC2', 'pos') default_shader_info.fragment_out(0, 'VEC4', 'fragColor') default_shader_info.vertex_source(default_vertex_shader) default_shader_info.fragment_source(default_fragment_shader) self.default_shader = create_from_info(default_shader_info) self.default_shader_u_color = self.default_shader.uniform_from_name('color') dotted_line_shader_inter = gpu.types.GPUStageInterfaceInfo("dotted_line") dotted_line_shader_inter.smooth('FLOAT', "arcLengthInter") dotted_line_shader_info = gpu.types.GPUShaderCreateInfo() dotted_line_shader_info.push_constant('MAT4', 'ModelViewProjectionMatrix') dotted_line_shader_info.push_constant('FLOAT', 'scale') dotted_line_shader_info.push_constant('FLOAT', 'offset') dotted_line_shader_info.push_constant('VEC4', 'color1') dotted_line_shader_info.push_constant('VEC4', 'color2') dotted_line_shader_info.vertex_in(0, 'VEC2', 'pos') dotted_line_shader_info.vertex_in(1, 'FLOAT', 'arcLength') dotted_line_shader_info.vertex_out(dotted_line_shader_inter) dotted_line_shader_info.fragment_out(0, 'VEC4', 'fragColor') dotted_line_shader_info.vertex_source(dotted_line_vertex_shader) dotted_line_shader_info.fragment_source(dotted_line_fragment_shader) self.dotted_line_shader = create_from_info(dotted_line_shader_info) 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") image_shader_inter = gpu.types.GPUStageInterfaceInfo("image_shader") image_shader_inter.smooth('VEC2', "texCoordOut") image_shader_info = gpu.types.GPUShaderCreateInfo() image_shader_info.push_constant('MAT4', 'ModelViewProjectionMatrix') image_shader_info.sampler(0, 'FLOAT_2D', 'image') image_shader_info.vertex_in(0, 'VEC2', 'pos') image_shader_info.vertex_in(1, 'VEC2', 'texCoord') image_shader_info.vertex_out(image_shader_inter) image_shader_info.fragment_out(0, 'VEC4', 'fragColor') image_shader_info.vertex_source(image_vertex_shader) image_shader_info.fragment_source(image_fragment_shader) self.image_shader = create_from_info(image_shader_info) def render_selection_frame(self, pos, size, rot=0, scale=(1.0, 1.0)): width, height = size[0], size[1] prev_blend = gpu.state.blend_get() gpu.state.blend_set('ALPHA') gpu.state.line_width_set(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) gpu.state.blend_set(prev_blend) def render_image_sub(self, img, pos, size, rot, scale): width, height = size[0], size[1] texture = gpu.texture.from_image(img) 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_sampler('image', texture) batch.draw(self.image_shader) def render_image(self, img, pos, size, rot=0, scale=(1.0, 1.0)): prev_blend = gpu.state.blend_get() gpu.state.blend_set('ALPHA') self.render_image_sub(img, pos, size, rot, scale) gpu.state.blend_set(prev_blend) 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(): fb = gpu.state.active_framebuffer_get() fb.clear(color=(0.0, 0.0, 0.0, 0.0)) 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 = fb.read_color(0, 0, ofs_width, ofs_height, 4, 0, 'UBYTE') ofs.free() return buff, ofs_width, ofs_height def render_info_box(self, pos1, pos2): prev_blend = gpu.state.blend_get() gpu.state.blend_set('ALPHA') verts = [ pos1, (pos2[0], pos1[1]), (pos1[0], pos2[1]), pos2 ] indices = [ (0, 1, 2), (2, 1, 3) ] batch = batch_for_shader(self.default_shader, 'TRIS', {"pos": verts}, indices=indices) self.default_shader.bind() self.default_shader.uniform_vector_float(self.default_shader_u_color, np.array([0, 0, 0, 0.7], 'f'), 4) batch.draw(self.default_shader) gpu.state.blend_set(prev_blend)