diff --git a/examples/gg/arcs_and_slices.v b/examples/gg/arcs_and_slices.v new file mode 100644 index 0000000000..69eff43b97 --- /dev/null +++ b/examples/gg/arcs_and_slices.v @@ -0,0 +1,167 @@ +module main + +import gg +import gx +import math + +const ( + win_width = 700 + win_height = 800 + bg_color = gx.white + colour = gx.black +) + +enum Selection { + segs = 0 + len +} + +struct App { +mut: + gg &gg.Context = unsafe { nil } + mouse struct { + mut: + x f32 + y f32 + } + + sel Selection + segs int = 8 +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'Arcs and Slices' + user_data: app + bg_color: bg_color + frame_fn: on_frame + event_fn: on_event + ) + app.gg.run() +} + +fn on_frame(mut app App) { + app.gg.begin() + + start := math.tau * app.mouse.y / (win_width * app.gg.scale) + end := math.tau * app.mouse.x / (win_width * app.gg.scale) + + segs := if app.sel == .segs { '[$app.segs]' } else { '$app.segs' } + app.gg.draw_text_def(10, 10, 'Segments: $segs') + app.gg.draw_text_def(250, 10, 'Drawing Angles (radians)') + app.gg.draw_text_def(200, 26, 'Start: $start°') + app.gg.draw_text_def(350, 26, 'End: $end°') + mut x, mut y := 0, -80 + + y += 150 + x = 20 + app.gg.draw_text_def(10, y + 40, 'slice') + x += 150 + app.gg.draw_slice_empty(x, y + 60, 50, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=50 empty') + x += 150 + app.gg.draw_slice_empty(x, y + 60, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=0 empty') + x += 150 + app.gg.draw_slice_filled(x, y + 60, 50, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=50 filled') + x += 150 + app.gg.draw_slice_filled(x, y + 60, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=0 filled') + + y += 150 + x = 20 + app.gg.draw_text_def(10, y + 40, 'arc_empty') + x += 150 + app.gg.draw_arc_empty(x, y + 60, 30, 20, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[30,50]') + x += 150 + app.gg.draw_arc_empty(x, y + 60, -10, 60, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[-10,50]') + x += 150 + app.gg.draw_arc_empty(x, y + 60, 50, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[50,50]') + x += 150 + app.gg.draw_arc_empty(x, y + 60, 0, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[0,0]') + + y += 150 + x = 20 + app.gg.draw_text_def(10, y + 40, 'arc_filled') + x += 150 + app.gg.draw_arc_filled(x, y + 60, 30, 20, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[30,50]') + x += 150 + app.gg.draw_arc_filled(x, y + 60, -10, 60, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[-10,50]') + x += 150 + app.gg.draw_arc_filled(x, y + 60, 50, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[50,50]') + x += 150 + app.gg.draw_arc_filled(x, y + 60, 0, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=[0,0]') + + y += 150 + x = 20 + app.gg.draw_text_def(10, y + 40, 'arc_line') + x += 150 + app.gg.draw_arc_line(x, y + 60, 50, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=50') + x += 150 + app.gg.draw_arc_line(x, y + 60, 0, start, end, app.segs, colour) + app.gg.draw_text_def(x - 50, y + 120, 'r=0') + + y += 150 + app.gg.draw_text_def(10, y + 20, 'Use arrow keys to increase/decrease number of segments.') + app.gg.draw_text_def(10, y + 36, 'Use the mouse to adjust the start/end angles, in radians. Mouse position (0,0) is at the top-left of the window.') + app.gg.draw_text_def(10, y + 52, 'Note: because y=0 is at the top of the screen and not the bottom, angle=0 is at the bottom of an arc, not the top!') + app.gg.draw_text_def(10, y + 68, 'Compared to a graph, where y=0 is at the bottom, arcs therefore appear y-flipped.') + + app.gg.end() +} + +fn on_event(e &gg.Event, mut app App) { + match e.typ { + .key_down { + match e.key_code { + .escape { + app.gg.quit() + } + .up { + app.sel = Selection(math.max(0, int(app.sel) - 1)) + } + .down { + app.sel = Selection(math.min(int(Selection.len) - 1, int(app.sel) + 1)) + } + .left { + match app.sel { + .segs { + app.segs = math.max(1, app.segs / 2) + } + else {} + } + } + .right { + match app.sel { + .segs { + app.segs = math.min(64, app.segs * 2) + } + else {} + } + } + else {} + } + } + .mouse_move { + app.mouse.x = e.mouse_x + app.mouse.y = e.mouse_y + } + else {} + } +} diff --git a/vlib/gg/draw.c.v b/vlib/gg/draw.c.v index df7fb1f620..666a760880 100644 --- a/vlib/gg/draw.c.v +++ b/vlib/gg/draw.c.v @@ -17,8 +17,8 @@ pub fn (ctx &Context) draw_pixel(x f32, y f32, c gx.Color) { if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_points() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.end() @@ -31,14 +31,16 @@ pub fn (ctx &Context) draw_pixel(x f32, y f32, c gx.Color) { // functions like `draw_rect_empty` or `draw_triangle_empty` etc. [direct_array_access; inline] pub fn (ctx &Context) draw_pixels(points []f32, c gx.Color) { - assert points.len % 2 == 0 + if points.len % 2 != 0 { + return + } len := points.len / 2 if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_points() for i in 0 .. len { x, y := points[i * 2], points[i * 2 + 1] @@ -63,10 +65,12 @@ pub fn (ctx &Context) draw_line(x f32, y f32, x2 f32, y2 f32, c gx.Color) { return } } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) @@ -127,9 +131,10 @@ pub fn (ctx &Context) draw_line_with_config(x f32, y f32, x2 f32, y2 f32, config // draw_poly_empty draws the outline of a polygon, given an array of points, and a color. // NOTE that the points must be given in clockwise winding order. pub fn (ctx &Context) draw_poly_empty(points []f32, c gx.Color) { - assert points.len % 2 == 0 len := points.len / 2 - assert len >= 3 + if points.len % 2 != 0 || len < 3 { + return + } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) @@ -148,9 +153,10 @@ pub fn (ctx &Context) draw_poly_empty(points []f32, c gx.Color) { // NOTE that the points must be given in clockwise winding order. // The contents of the `points` array should be `x` and `y` coordinate pairs. pub fn (ctx &Context) draw_convex_poly(points []f32, c gx.Color) { - assert points.len % 2 == 0 len := points.len / 2 - assert len >= 3 + if points.len % 2 != 0 || len < 3 { + return + } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) @@ -160,14 +166,13 @@ pub fn (ctx &Context) draw_convex_poly(points []f32, c gx.Color) { sgl.begin_triangle_strip() x0 := points[0] * ctx.scale y0 := points[1] * ctx.scale - for i in 1 .. (len / 2 + 1) { - sgl.v2f(x0, y0) - sgl.v2f(points[i * 4 - 2] * ctx.scale, points[i * 4 - 1] * ctx.scale) - sgl.v2f(points[i * 4] * ctx.scale, points[i * 4 + 1] * ctx.scale) - } - - if len % 2 == 0 { - sgl.v2f(points[2 * len - 2] * ctx.scale, points[2 * len - 1] * ctx.scale) + for i in 1 .. len { + x := points[i * 2] * ctx.scale + y := points[i * 2 + 1] * ctx.scale + sgl.v2f(x, y) + if i & 0 == 0 { + sgl.v2f(x0, y0) + } } sgl.end() } @@ -180,6 +185,7 @@ pub fn (ctx &Context) draw_rect_empty(x f32, y f32, w f32, h f32, c gx.Color) { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.v2f((x + w) * ctx.scale, y * ctx.scale) @@ -199,10 +205,12 @@ pub fn (ctx &Context) draw_rect_filled(x f32, y f32, w f32, h f32, c gx.Color) { return } } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.v2f((x + w) * ctx.scale, y * ctx.scale) @@ -220,6 +228,7 @@ pub fn (ctx &Context) draw_rounded_rect_empty(x f32, y f32, w f32, h f32, radius if w <= 0 || h <= 0 || radius < 0 { return } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } @@ -318,6 +327,7 @@ pub fn (ctx &Context) draw_rounded_rect_filled(x f32, y f32, w f32, h f32, radiu if w <= 0 || h <= 0 || radius < 0 { return } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } @@ -427,8 +437,8 @@ pub fn (ctx &Context) draw_triangle_empty(x f32, y f32, x2 f32, y2 f32, x3 f32, if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) @@ -447,6 +457,7 @@ pub fn (ctx &Context) draw_triangle_filled(x f32, y f32, x2 f32, y2 f32, x3 f32, sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_triangles() sgl.v2f(x * ctx.scale, y * ctx.scale) sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) @@ -478,14 +489,14 @@ const small_circle_segments = [0, 2, 4, 6, 6, 8, 8, 13, 10, 18, 12, 12, 10, 13, [direct_array_access] fn radius_to_segments(r f32) int { - if r < 30.0 { + if r < 30 { ir := int(math.ceil(r)) if ir > 0 && ir < gg.small_circle_segments.len { return gg.small_circle_segments[ir] } return ir } - return int(math.ceil(2 * math.pi * r / 8)) + return int(math.ceil(math.tau * r / 8)) } // draw_circle_empty draws the outline of a circle. @@ -496,19 +507,19 @@ pub fn (ctx &Context) draw_circle_empty(x f32, y f32, radius f32, c gx.Color) { if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale ny := y * ctx.scale nr := radius * ctx.scale mut theta := f32(0) mut xx := f32(0) mut yy := f32(0) - segments := radius_to_segments(radius) - sgl.c4b(c.r, c.g, c.b, c.a) sgl.begin_line_strip() for i := 0; i < segments + 1; i++ { - theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + theta = f32(math.tau) * f32(i) / f32(segments) xx = nr * math.cosf(theta) yy = nr * math.sinf(theta) sgl.v2f(xx + nx, yy + ny) @@ -534,23 +545,29 @@ pub fn (ctx &Context) draw_circle_filled(x f32, y f32, radius f32, c gx.Color) { // draw_polygon_filled draws a filled polygon. // `x`,`y` defines the center of the polygon. // `size` defines the size of the polygon. -// `edge` defines edge number of the polygon. +// `edges` defines number of edges in the polygon. // `rotation` defines rotation of the polygon. // `c` is the fill color. -pub fn (ctx &Context) draw_polygon_filled(x f32, y f32, size f32, edge int, rotation f32, c gx.Color) { +pub fn (ctx &Context) draw_polygon_filled(x f32, y f32, size f32, edges int, rotation f32, c gx.Color) { + if edges <= 0 { + return + } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale ny := y * ctx.scale nr := size * ctx.scale mut theta := f32(0) mut xx := f32(0) mut yy := f32(0) + sgl.begin_triangle_strip() - for i := 0; i < edge + 1; i++ { - theta = 2.0 * f32(math.pi) * f32(i) / f32(edge) + for i := 0; i < edges + 1; i++ { + theta = f32(math.tau) * f32(i) / f32(edges) xx = nr * math.cosf(theta + f32(math.radians(rotation))) yy = nr * math.sinf(theta + f32(math.radians(rotation))) sgl.v2f(xx + nx, yy + ny) @@ -574,6 +591,10 @@ pub fn (ctx &Context) draw_circle_with_segments(x f32, y f32, radius f32, segmen // `segments` affects how smooth/round the circle is. // `c` is the color of the outline. pub fn (ctx &Context) draw_circle_line(x f32, y f32, radius int, segments int, c gx.Color) { + if segments <= 0 { + return + } + $if macos { if ctx.native_rendering { C.darwin_draw_circle(x - radius + 1, ctx.height - (y + radius + 3), radius, @@ -581,19 +602,22 @@ pub fn (ctx &Context) draw_circle_line(x f32, y f32, radius int, segments int, c return } } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale ny := y * ctx.scale nr := radius * ctx.scale mut theta := f32(0) mut xx := f32(0) mut yy := f32(0) + sgl.begin_line_strip() for i := 0; i < segments + 1; i++ { - theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + theta = f32(math.tau) * f32(i) / f32(segments) xx = nr * math.cosf(theta) yy = nr * math.sinf(theta) sgl.v2f(xx + nx, yy + ny) @@ -602,26 +626,28 @@ pub fn (ctx &Context) draw_circle_line(x f32, y f32, radius int, segments int, c } // draw_slice_empty draws the outline of a circle slice/pie -pub fn (ctx &Context) draw_slice_empty(x f32, y f32, outer_radius f32, start_angle f32, end_angle f32, segments int, c gx.Color) { +pub fn (ctx &Context) draw_slice_empty(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int, c gx.Color) { + if segments <= 0 || radius <= 0 { + return + } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) - theta := f32(end_angle / f32(segments)) - tan_factor := math.tanf(theta) - rad_factor := math.cosf(theta) + nx := x * ctx.scale ny := y * ctx.scale - mut xx := outer_radius * math.cosf(start_angle) - mut yy := outer_radius * math.sinf(start_angle) + theta := f32(end_angle - start_angle) / f32(segments) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + mut xx := radius * ctx.scale * math.sinf(start_angle) + mut yy := radius * ctx.scale * math.cosf(start_angle) + sgl.begin_line_strip() sgl.v2f(nx, ny) for i := 0; i < segments + 1; i++ { sgl.v2f(xx + nx, yy + ny) - tx := -yy - ty := xx - xx += tx * tan_factor - yy += ty * tan_factor + xx, yy = xx + yy * tan_factor, yy - xx * tan_factor xx *= rad_factor yy *= rad_factor } @@ -632,32 +658,88 @@ pub fn (ctx &Context) draw_slice_empty(x f32, y f32, outer_radius f32, start_ang // draw_slice_filled draws a filled circle slice/pie // `x`,`y` defines the end point of the slice (center of the circle that the slice is part of). // `radius` defines the radius ("length") of the slice. -// `start_angle` is the radians at which the slice starts. -// `end_angle` is the radians at which the slice ends. +// `start_angle` is the angle in radians at which the slice starts. +// `end_angle` is the angle in radians at which the slice ends. // `segments` affects how smooth/round the slice is. // `c` is the fill color. pub fn (ctx &Context) draw_slice_filled(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int, c gx.Color) { + if segments <= 0 || radius < 0 { + return + } + if start_angle == end_angle { + ctx.draw_slice_empty(x, y, radius, start_angle, end_angle, 1, c) + return + } + if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale ny := y * ctx.scale - theta := f32(end_angle / f32(segments)) + theta := f32(end_angle - start_angle) / f32(segments) tan_factor := math.tanf(theta) rad_factor := math.cosf(theta) - mut xx := radius * math.cosf(start_angle) - mut yy := radius * math.sinf(start_angle) + mut xx := radius * ctx.scale * math.sinf(start_angle) + mut yy := radius * ctx.scale * math.cosf(start_angle) + sgl.begin_triangle_strip() - for i := 0; i < segments + 1; i++ { - sgl.v2f(xx + nx, yy + ny) - sgl.v2f(nx, ny) - tx := -yy - ty := xx - xx += tx * tan_factor - yy += ty * tan_factor + sgl.v2f(xx + nx, yy + ny) + for i := 0; i < segments; i++ { + xx, yy = xx + yy * tan_factor, yy - xx * tan_factor xx *= rad_factor yy *= rad_factor + sgl.v2f(xx + nx, yy + ny) + if i & 1 == 0 { + sgl.v2f(nx, ny) + } + } + sgl.end() +} + +// draw_arc_line draws a line arc. +// `x`,`y` defines the end point of the arc (center of the circle that the arc is part of). +// `radius` defines the radius of the arc (length from the center point where the arc is drawn). +// `start_angle` is the angle in radians at which the arc starts. +// `end_angle` is the angle in radians at which the arc ends. +// `segments` affects how smooth/round the arc is. +// `c` is the color of the arc/outline. +pub fn (ctx Context) draw_arc_line(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int, c gx.Color) { + if segments <= 0 || radius < 0 { + return + } + if radius == 0 { + ctx.draw_pixel(x, y, c) + return + } + if start_angle == end_angle { + xx := x + radius * math.sinf(start_angle) + yy := y + radius * math.cosf(start_angle) + ctx.draw_pixel(xx, yy, c) + return + } + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + nx := x * ctx.scale + ny := y * ctx.scale + theta := f32(end_angle - start_angle) / f32(segments) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + mut xx := radius * ctx.scale * math.sinf(start_angle) + mut yy := radius * ctx.scale * math.cosf(start_angle) + + sgl.begin_line_strip() + sgl.v2f(nx + xx, ny + yy) + for i := 0; i < segments; i++ { + xx, yy = xx + yy * tan_factor, yy - xx * tan_factor + xx *= rad_factor + yy *= rad_factor + sgl.v2f(nx + xx, ny + yy) } sgl.end() } @@ -666,66 +748,62 @@ pub fn (ctx &Context) draw_slice_filled(x f32, y f32, radius f32, start_angle f3 // `x`,`y` defines the end point of the arc (center of the circle that the arc is part of). // `inner_radius` defines the radius of the arc (length from the center point where the arc is drawn). // `thickness` defines how wide the arc is drawn. -// `start_angle` is the radians at which the arc starts. -// `end_angle` is the radians at which the arc ends. +// `start_angle` is the angle in radians at which the arc starts. +// `end_angle` is the angle in radians at which the arc ends. // `segments` affects how smooth/round the arc is. -// `c` is the color of the arc/outline. +// `c` is the color of the arc outline. pub fn (ctx &Context) draw_arc_empty(x f32, y f32, inner_radius f32, thickness f32, start_angle f32, end_angle f32, segments int, c gx.Color) { - if start_angle == end_angle || inner_radius <= 0.0 { + outer_radius := inner_radius + thickness + if segments <= 0 || outer_radius < 0 { + return + } + + if inner_radius <= 0 { + ctx.draw_slice_empty(x, y, outer_radius, start_angle, end_angle, segments, c) + return + } + if inner_radius == outer_radius { + ctx.draw_arc_line(x, y, outer_radius, start_angle, end_angle, segments, c) return } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - - mut a1 := start_angle - mut a2 := end_angle - - if a2 < a1 { - a1, a2 = a2, a1 - } - - if inner_radius <= 0.0 { - ctx.draw_slice_empty(x, y, int(thickness), a1, a2, segments, c) - return - } - - outer_radius := inner_radius + thickness - mut step_length := (a2 - a1) / f32(segments) - mut angle := a1 - - sgl.begin_line_strip() sgl.c4b(c.r, c.g, c.b, c.a) - // Outer circle - for _ in 0 .. segments { - msa := f32(math.sin(angle)) - mca := f32(math.cos(angle)) - ms := f32(math.sin(angle + step_length)) - mc := f32(math.cos(angle + step_length)) - sgl.v2f(x + msa * outer_radius, y + mca * outer_radius) - sgl.v2f(x + ms * outer_radius, y + mc * outer_radius) - angle += step_length + nx := x * ctx.scale + ny := y * ctx.scale + theta := f32(end_angle - start_angle) / f32(segments) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + + sgl.begin_line_strip() + + // outer + mut xx := outer_radius * ctx.scale * math.sinf(start_angle) + mut yy := outer_radius * ctx.scale * math.cosf(start_angle) + sxx, syy := xx, yy + sgl.v2f(nx + xx, ny + yy) + for i := 0; i < segments; i++ { + xx, yy = xx + yy * tan_factor, yy - xx * tan_factor + xx *= rad_factor + yy *= rad_factor + sgl.v2f(nx + xx, ny + yy) } - // Inner circle - for _ in 0 .. segments { - msa := f32(math.sin(angle)) - mca := f32(math.cos(angle)) - msb := f32(math.sin(angle - step_length)) - mcb := f32(math.cos(angle - step_length)) - sgl.v2f(x + msa * inner_radius, y + mca * inner_radius) - sgl.v2f(x + msb * inner_radius, y + mcb * inner_radius) - - angle -= step_length + // inner + xx = inner_radius * ctx.scale * math.sinf(end_angle) + yy = inner_radius * ctx.scale * math.cosf(end_angle) + sgl.v2f(nx + xx, ny + yy) + for i := 0; i < segments; i++ { + xx, yy = xx - yy * tan_factor, yy + xx * tan_factor + xx *= rad_factor + yy *= rad_factor + sgl.v2f(nx + xx, ny + yy) } - // Closing end - msa := f32(math.sin(angle)) - mca := f32(math.cos(angle)) - sgl.v2f(x + msa * inner_radius, y + mca * inner_radius) - sgl.v2f(x + msa * outer_radius, y + mca * outer_radius) + sgl.v2f(nx + sxx, ny + syy) sgl.end() } @@ -733,48 +811,58 @@ pub fn (ctx &Context) draw_arc_empty(x f32, y f32, inner_radius f32, thickness f // `x`,`y` defines the central point of the arc (center of the circle that the arc is part of). // `inner_radius` defines the radius of the arc (length from the center point where the arc is drawn). // `thickness` defines how wide the arc is drawn. -// `start_angle` is the radians at which the arc starts. -// `end_angle` is the radians at which the arc ends. +// `start_angle` is the angle in radians at which the arc starts. +// `end_angle` is the angle in radians at which the arc ends. // `segments` affects how smooth/round the arc is. // `c` is the fill color of the arc. pub fn (ctx &Context) draw_arc_filled(x f32, y f32, inner_radius f32, thickness f32, start_angle f32, end_angle f32, segments int, c gx.Color) { - if start_angle == end_angle || inner_radius <= 0.0 { + outer_radius := inner_radius + thickness + if segments <= 0 || outer_radius < 0 { + return + } + + if inner_radius <= 0 { + ctx.draw_slice_filled(x, y, outer_radius, start_angle, end_angle, segments, c) + return + } + if inner_radius == outer_radius { + ctx.draw_arc_line(x, y, outer_radius, start_angle, end_angle, segments, c) + return + } + if start_angle == end_angle { + ctx.draw_arc_empty(x, y, inner_radius, thickness, start_angle, end_angle, 1, c) return } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - - mut a1 := start_angle - mut a2 := end_angle - - if a2 < a1 { - a1, a2 = a2, a1 - } - - if inner_radius <= 0.0 { - ctx.draw_slice_filled(x, y, int(thickness), a1, a2, segments, c) - } - - outer_radius := inner_radius + thickness - mut step_length := (a2 - a1) / f32(segments) - mut angle := a1 - - sgl.begin_quads() sgl.c4b(c.r, c.g, c.b, c.a) - for _ in 0 .. segments { - msa := f32(math.sin(angle)) - mca := f32(math.cos(angle)) - sgl.v2f(x + msa * inner_radius, y + mca * inner_radius) - sgl.v2f(x + msa * outer_radius, y + mca * outer_radius) - ms := f32(math.sin(angle + step_length)) - mc := f32(math.cos(angle + step_length)) - sgl.v2f(x + ms * outer_radius, y + mc * outer_radius) - sgl.v2f(x + ms * inner_radius, y + mc * inner_radius) + nx := x * ctx.scale + ny := y * ctx.scale + theta := f32(end_angle - start_angle) / f32(segments) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + mut ix := ctx.scale * math.sinf(start_angle) + mut iy := ctx.scale * math.cosf(start_angle) + mut ox := outer_radius * ix + mut oy := outer_radius * iy + ix *= inner_radius + iy *= inner_radius - angle += step_length + sgl.begin_triangle_strip() + sgl.v2f(nx + ix, ny + iy) + sgl.v2f(nx + ox, ny + oy) + for i := 0; i < segments; i++ { + ix, iy = ix + iy * tan_factor, iy - ix * tan_factor + ix *= rad_factor + iy *= rad_factor + sgl.v2f(nx + ix, ny + iy) + ox, oy = ox + oy * tan_factor, oy - ox * tan_factor + ox *= rad_factor + oy *= rad_factor + sgl.v2f(nx + ox, ny + oy) } sgl.end() } @@ -788,8 +876,8 @@ pub fn (ctx &Context) draw_ellipse_empty(x f32, y f32, rw f32, rh f32, c gx.Colo if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() for i := 0; i < 360; i += 10 { sgl.v2f(x + math.sinf(f32(math.radians(i))) * rw, y + math.cosf(f32(math.radians(i))) * rh) @@ -808,8 +896,8 @@ pub fn (ctx &Context) draw_ellipse_filled(x f32, y f32, rw f32, rh f32, c gx.Col if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } - sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_triangle_strip() for i := 0; i < 360; i += 10 { sgl.v2f(x, y) @@ -834,9 +922,9 @@ pub fn (ctx &Context) draw_cubic_bezier(points []f32, c gx.Color) { // The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates). // Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`. pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, steps u32, c gx.Color) { - assert steps > 0 - assert points.len == 8 - + if steps <= 0 || points.len != 8 { + return + } if c.a != 255 { sgl.load_pipeline(ctx.timage_pip) } @@ -852,9 +940,9 @@ pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, steps u32, c gx.C // The constant 3 is actually points.len() - 1; - step := f32(1.0) / steps + step := f32(1) / steps sgl.v2f(p1_x * ctx.scale, p1_y * ctx.scale) - for u := f32(0.0); u <= f32(1.0); u += step { + for u := f32(0); u <= f32(1); u += step { pow_2_u := u * u pow_3_u := pow_2_u * u