diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index a1eb06b3a8..877d689dba 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -32,6 +32,12 @@ from mathutils import Vector, Matrix from bpy_extras.view3d_utils import location_3d_to_region_2d from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat from gpu_extras.batch import batch_for_shader +from math import acos, pi + + +def ccw(A, B, C): + """whether a-b-c located in counter-clockwise order in 2d space""" + return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x) class BaseDecorator: @@ -66,6 +72,42 @@ class BaseDecorator: } } + bool check_counterclockwise(in vec4 A, in vec4 B, in vec4 C) { + return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x); + } + + void angle_circle_head( + in vec4 circle_start, in float circle_angle, + in bool counterclockwise, + out vec4 head[CIRCLE_SEGS+1], out float angle_segs) { + + // 1 added to CIRCLE_SEGS because we're number of vertices + // for n segments is n+1 + + float angle_d; + angle_d = PI * 2 / CIRCLE_SEGS; // 30d + // need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve + angle_segs = max(1, ceil(circle_angle / angle_d)); + angle_d = circle_angle / angle_segs; + + for(int i = 0; i < (angle_segs + 1); i++) { + float angle = angle_d * i; + if (counterclockwise) { + head[i] = vec4( + circle_start.x * cos(-angle) + circle_start.y * sin(-angle), + circle_start.x * -sin(-angle) + circle_start.y * cos(-angle), + 0, 0 + ); + } else { + head[i] = vec4( + circle_start.x * cos(angle) + circle_start.y * sin(angle), + circle_start.x * -sin(angle) + circle_start.y * cos(angle), + 0, 0 + ); + } + } + } + void cross_head(in vec4 dir, in float size, out vec4 head[3]) { vec4 nose = dir * size; float c = cos(PI/2), s = sin(PI/2); @@ -79,12 +121,15 @@ class BaseDecorator: uniform mat4 viewMatrix; in vec3 pos; in uint topo; + in vec3 next_vert; out vec4 gl_Position; out uint type; + out vec4 v_next_vert; void main() { gl_Position = viewMatrix * vec4(pos, 1.0); type = topo; + v_next_vert = viewMatrix * vec4(next_vert, 1.0); } """ @@ -267,7 +312,13 @@ class BaseDecorator: """perform actual drawing stuff""" raise NotImplementedError() - def draw_lines(self, context, obj, vertices, indices, topology=None, is_scale_dependant=True): + def draw_lines( + self, context, obj, vertices, indices, topology=None, is_scale_dependant=True, fill_next_vertices=False + ): + # use is_scale_dependant = False if shader is not using uniform viewportDrawingScale + # otherwise uniform will be discarded during the optimization process + # and you will get "ValueError: GPUShader.uniform_float: uniform viewportDrawingScale not found" + region = context.region region3d = context.region_data color = context.scene.DocProperties.decorations_colour @@ -276,12 +327,18 @@ class BaseDecorator: fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT") if topology: fmt.attr_add(id="topo", comp_type="U8", len=1, fetch_mode="INT") + if fill_next_vertices: + fmt.attr_add(id="next_vert", comp_type="F32", len=3, fetch_mode="FLOAT") vbo = GPUVertBuf(len=len(vertices), format=fmt) vbo.attr_fill(id="pos", data=vertices) if topology: vbo.attr_fill(id="topo", data=topology) + if fill_next_vertices: + shifted_vertices = vertices[1:] + [vertices[0]] + vbo.attr_fill(id="next_vert", data=shifted_vertices) + ibo = GPUIndexBuf(type="LINES", seq=indices) batch = GPUBatch(type="LINES", buf=vbo, elem=ibo) @@ -480,6 +537,132 @@ class DimensionDecorator(BaseDecorator): self.draw_label(context, text, p0 + (dir) * 0.5, dir) +class AngleDecorator(BaseDecorator): + """Decorator for angle objects + - each edge of a segment with arrow + - every non-last edge has angle circle + - every circle is labeled with angle in degrees + """ + + objecttype = "ANGLE" + + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ + #define ARROW_ANGLE PI / 12.0 + #define ARROW_SIZE 8.0 + #define CIRCLE_SIZE 6.0 + """ + ) + + GEOM_GLSL = """ + uniform vec2 winsize; + uniform float viewportDrawingScale; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + in uint type[]; + in vec4 v_next_vert[]; + + // per edge shader + void main() { + vec4 clip2win = matCLIP2WIN(); + vec4 win2clip = matWIN2CLIP(); + + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + vec4 p2 = v_next_vert[1]; + uint t0 = type[0], t1 = type[1]; + + vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); + vec4 p2w = CLIP2WIN(p2); + vec4 edge0 = p1w - p0w, dir = normalize(edge0); + + vec4 p; + // draw a segment line + p = p0w; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + + // end edge with angle circle for the non-last segment + if (t1 == 0u) { // draws only on internal vertex + edge0 = p0w - p1w; + vec4 dir0 = normalize(edge0); + vec4 edge1 = p2w - p1w; + vec4 dir1 = normalize(edge1); + + float angle_circle_size = min( length(edge0), length(edge1) ); + vec4 circle_start = dir0 * angle_circle_size; + vec4 circle_end = dir1 * angle_circle_size; + + float cos_a = dot( edge0, edge1 ) / ( length(edge0) * length(edge1) ); + float circle_angle = acos(cos_a); + + vec4 circle_head_data[CIRCLE_SEGS+1]; + float angle_segs; + bool counterclockwise = check_counterclockwise(p2w, p1w, p0w); + angle_circle_head( + circle_start, + circle_angle, + counterclockwise, + circle_head_data, + angle_segs); + + for(int i=0; i