v2 / vlib / gg / draw.c.v
1140 lines · 1018 sloc · 33.63 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3module gg
4
5import math
6import sokol.sgl
7
8@[params]
9pub struct DrawPixelConfig {
10pub mut:
11 size f32 = 1.0
12}
13
14// draw_pixel draws one pixel on the screen.
15//
16// NOTE calling this function frequently is very *inefficient*,
17// for drawing shapes it's recommended to draw whole primitives with
18// functions like `draw_rect_empty` or `draw_triangle_empty` etc.
19pub fn (ctx &Context) draw_pixel(x f32, y f32, c Color, params DrawPixelConfig) {
20 if c.a != 255 {
21 sgl.load_pipeline(ctx.pipeline.alpha)
22 }
23 sgl.begin_points()
24 sgl.c4b(c.r, c.g, c.b, c.a)
25 sgl.point_size(params.size)
26 sgl.v2f(x * ctx.scale, y * ctx.scale)
27 sgl.end()
28}
29
30// draw_pixels draws pixels from an array of points [x, y, x2, y2, etc...]
31//
32// NOTE calling this function frequently is very *inefficient*,
33// for drawing shapes it's recommended to draw whole primitives with
34// functions like `draw_rect_empty` or `draw_triangle_empty` etc.
35@[direct_array_access]
36pub fn (ctx &Context) draw_pixels(points []f32, c Color, params DrawPixelConfig) {
37 if points.len % 2 != 0 {
38 return
39 }
40 len := points.len / 2
41
42 if c.a != 255 {
43 sgl.load_pipeline(ctx.pipeline.alpha)
44 }
45 sgl.begin_points()
46 sgl.c4b(c.r, c.g, c.b, c.a)
47 sgl.point_size(params.size)
48 for i in 0 .. len {
49 x, y := points[i * 2], points[i * 2 + 1]
50 sgl.v2f(x * ctx.scale, y * ctx.scale)
51 }
52 sgl.end()
53}
54
55// draw_line draws a line between the points `x,y` and `x2,y2` in color `c`.
56pub fn (ctx &Context) draw_line(x f32, y f32, x2 f32, y2 f32, c Color) {
57 $if macos {
58 if ctx.native_rendering {
59 // Make the line more clear on hi dpi screens: draw a rectangle
60 // TODO this is broken if the line's x1 != x2
61 mut width := math.abs(x2 - x)
62 mut height := math.abs(y2 - y)
63 if width == 0 {
64 width = 1
65 } else if height == 0 {
66 height = 1
67 }
68 ctx.draw_rect_filled(x, y, f32(width), f32(height), c)
69 return
70 }
71 }
72
73 if c.a != 255 {
74 sgl.load_pipeline(ctx.pipeline.alpha)
75 }
76 sgl.c4b(c.r, c.g, c.b, c.a)
77
78 sgl.begin_line_strip()
79 sgl.v2f(x * ctx.scale, y * ctx.scale)
80 sgl.v2f(x2 * ctx.scale, y2 * ctx.scale)
81 sgl.end()
82}
83
84// draw_line_with_config draws a line between the points `x,y` and `x2,y2` using `PenConfig`.
85pub fn (ctx &Context) draw_line_with_config(x f32, y f32, x2 f32, y2 f32, config PenConfig) {
86 if config.color.a != 255 {
87 sgl.load_pipeline(ctx.pipeline.alpha)
88 }
89
90 if config.thickness <= 0 {
91 return
92 }
93
94 nx := x * ctx.scale
95 ny := y * ctx.scale
96 nx2 := x2 * ctx.scale
97 ny2 := y2 * ctx.scale
98
99 dx := nx2 - nx
100 dy := ny2 - ny
101 length := math.sqrtf(math.powf(x2 - x, 2) + math.powf(y2 - y, 2))
102 theta := f32(math.atan2(dy, dx))
103
104 sgl.push_matrix()
105
106 sgl.translate(nx, ny, 0)
107 sgl.rotate(theta, 0, 0, 1)
108 sgl.translate(-nx, -ny, 0)
109
110 if config.line_type == .solid {
111 ctx.draw_rect_filled(x, y, length, config.thickness, config.color)
112 } else {
113 size := if config.line_type == .dotted { config.thickness } else { config.thickness * 3 }
114 space := if size == 1 { 2 } else { size }
115
116 mut available := length
117 mut start_x := x
118
119 for i := 0; available > 0; i++ {
120 if i % 2 == 0 {
121 ctx.draw_rect_filled(start_x, y, size, config.thickness, config.color)
122 available -= size
123 start_x += size
124 continue
125 }
126
127 available -= space
128 start_x += space
129 }
130 }
131
132 sgl.pop_matrix()
133}
134
135// draw_poly_empty draws the outline of a polygon, given an array of points, and a color.
136// NOTE that the points must be given in clockwise winding order.
137pub fn (ctx &Context) draw_poly_empty(points []f32, c Color) {
138 len := points.len / 2
139 if points.len % 2 != 0 || len < 3 {
140 return
141 }
142
143 if c.a != 255 {
144 sgl.load_pipeline(ctx.pipeline.alpha)
145 }
146 sgl.c4b(c.r, c.g, c.b, c.a)
147
148 sgl.begin_line_strip()
149 for i in 0 .. len {
150 sgl.v2f(points[2 * i] * ctx.scale, points[2 * i + 1] * ctx.scale)
151 }
152 sgl.v2f(points[0] * ctx.scale, points[1] * ctx.scale)
153 sgl.end()
154}
155
156// draw_convex_poly draws a convex polygon, given an array of points, and a color.
157// NOTE that the points must be given in clockwise winding order.
158// The contents of the `points` array should be `x` and `y` coordinate pairs.
159pub fn (ctx &Context) draw_convex_poly(points []f32, c Color) {
160 len := points.len / 2
161 if points.len % 2 != 0 || len < 3 {
162 return
163 }
164
165 if c.a != 255 {
166 sgl.load_pipeline(ctx.pipeline.alpha)
167 }
168 sgl.c4b(c.r, c.g, c.b, c.a)
169
170 sgl.begin_triangle_strip()
171 x0 := points[0] * ctx.scale
172 y0 := points[1] * ctx.scale
173 for i in 1 .. len {
174 x := points[i * 2] * ctx.scale
175 y := points[i * 2 + 1] * ctx.scale
176 sgl.v2f(x, y)
177 if i & 0 == 0 {
178 sgl.v2f(x0, y0)
179 }
180 }
181 sgl.end()
182}
183
184@[inline]
185fn rect_empty_screen_bounds(scale f32, x f32, y f32, w f32, h f32) (f32, f32, f32, f32) {
186 // Keep the outline inside pixels so the top-left corner stays aligned and the
187 // border renders consistently across different OpenGL implementations.
188 toffset := f32(0.1)
189 boffset := f32(-0.1)
190 return toffset + x * scale, toffset + y * scale, boffset + (x + w) * scale, boffset +
191 (y + h) * scale
192}
193
194// draw_rect_empty draws the outline of a rectangle.
195// `x`,`y` is the top-left corner of the rectangle.
196// `w` is the width, `h` is the height and `c` is the color of the outline.
197// Note: it is much more efficient to draw lots of empty rectangles one after the other,
198// without filled rectangles between them, than to draw a mix.
199pub fn (ctx &Context) draw_rect_empty(x f32, y f32, w f32, h f32, c Color) {
200 if c.a != 255 {
201 sgl.load_pipeline(ctx.pipeline.alpha)
202 }
203 sgl.c4b(c.r, c.g, c.b, c.a)
204 tleft_x, tleft_y, bright_x, bright_y := rect_empty_screen_bounds(ctx.scale, x, y, w, h)
205 sgl.begin_lines() // more predictable, compared to sgl.begin_line_strip, at the price of more vertexes send
206 // top:
207 sgl.v2f(tleft_x, tleft_y)
208 sgl.v2f(bright_x, tleft_y)
209 // left:
210 sgl.v2f(tleft_x, tleft_y)
211 sgl.v2f(tleft_x, bright_y)
212 // right:
213 sgl.v2f(bright_x, tleft_y)
214 sgl.v2f(bright_x, bright_y)
215 // bottom:
216 sgl.v2f(tleft_x, bright_y)
217 sgl.v2f(bright_x, bright_y)
218 sgl.end()
219}
220
221// draw_rect_empty_no_context draws the outline of a rectangle, but without saving/restoring the context.
222// It is intended to be used in loops, where you do manually: `sgl.begin_lines()` *before* the loop,
223// then draw many rectangles, then call manually `sgl.end()` *after* the loop.
224// `x`,`y` is the top-left corner of the rectangle.
225// `w` is the width, `h` is the height and `c` is the color of the outline.
226// Note: it is much more efficient to draw lots of empty rectangles one after the other,
227// without filled rectangles between them, than to draw a mix.
228pub fn (ctx &Context) draw_rect_empty_no_context(x f32, y f32, w f32, h f32, c Color) {
229 tleft_x, tleft_y, bright_x, bright_y := rect_empty_screen_bounds(ctx.scale, x, y, w, h)
230 // Note: the following line is deliberately commented, compare to draw_rect_empty/5;
231 // sgl.begin_lines() // more predictable, compared to sgl.begin_line_strip, at the price of more vertexes send
232 sgl.c4b(c.r, c.g, c.b, c.a)
233 // top:
234 sgl.v2f(tleft_x, tleft_y)
235 sgl.v2f(bright_x, tleft_y)
236 // left:
237 sgl.v2f(tleft_x, tleft_y)
238 sgl.v2f(tleft_x, bright_y)
239 // right:
240 sgl.v2f(bright_x, tleft_y)
241 sgl.v2f(bright_x, bright_y)
242 // bottom:
243 sgl.v2f(tleft_x, bright_y)
244 sgl.v2f(bright_x, bright_y)
245 // Note: the following line is deliberately commented, compare to draw_rect_empty/5;
246 // sgl.end()
247}
248
249// draw_rect_filled draws a filled rectangle.
250// `x`,`y` is the top-left corner of the rectangle.
251// `w` is the width, `h` is the height and `c` is the color of the fill.
252// Note: it is much more efficient to draw lots of filled rectangles one after the other,
253// without empty rectangles between them, than to draw a mix.
254pub fn (ctx &Context) draw_rect_filled(x f32, y f32, w f32, h f32, c Color) {
255 $if macos {
256 if ctx.native_rendering {
257 C.darwin_draw_rect(x, ctx.height - (y + h), w, h, c)
258 return
259 }
260 }
261
262 if c.a != 255 {
263 sgl.load_pipeline(ctx.pipeline.alpha)
264 }
265 sgl.c4b(c.r, c.g, c.b, c.a)
266
267 sgl.begin_quads()
268 sgl.v2f(x * ctx.scale, y * ctx.scale)
269 sgl.v2f((x + w) * ctx.scale, y * ctx.scale)
270 sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale)
271 sgl.v2f(x * ctx.scale, (y + h) * ctx.scale)
272 sgl.end()
273}
274
275// draw_rect_filled_no_context draws a filled rectangle, but without saving/restoring the context.
276// It is intended to be used in loops, where you do manually: `sgl.begin_quads()` *before* the loop,
277// then draw many rectangles, then call manually `sgl.end()` *after* the loop.
278// `x`,`y` is the top-left corner of the rectangle.
279// `w` is the width, `h` is the height and `c` is the color of the fill.
280// Note: it is much more efficient to draw lots of filled rectangles one after the other,
281// without empty rectangles between them, than to draw a mix.
282pub fn (ctx &Context) draw_rect_filled_no_context(x f32, y f32, w f32, h f32, c Color) {
283 // Note: the following line is deliberately commented, compare to draw_rect_empty/5;
284 // sgl.begin_quads()
285 sgl.c4b(c.r, c.g, c.b, c.a)
286 sgl.v2f(x * ctx.scale, y * ctx.scale)
287 sgl.v2f((x + w) * ctx.scale, y * ctx.scale)
288 sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale)
289 sgl.v2f(x * ctx.scale, (y + h) * ctx.scale)
290 // Note: the following line is deliberately commented, compare to draw_rect_filled/5;
291 // sgl.end()
292}
293
294pub enum PaintStyle {
295 fill
296 stroke
297}
298
299@[params]
300pub struct DrawRectParams {
301pub:
302 x f32
303 y f32
304 w f32
305 h f32
306 color Color = black
307 style PaintStyle = .fill
308 is_rounded bool
309 radius f32
310}
311
312pub fn (ctx &Context) draw_rect(p DrawRectParams) {
313 if p.is_rounded {
314 if p.style == .fill {
315 ctx.draw_rounded_rect_filled(p.x, p.y, p.w, p.h, p.radius, p.color)
316 } else {
317 ctx.draw_rounded_rect_empty(p.x, p.y, p.w, p.h, p.radius, p.color)
318 }
319 } else {
320 if p.style == .fill {
321 ctx.draw_rect_filled(p.x, p.y, p.w, p.h, p.color)
322 } else {
323 ctx.draw_rect_empty(p.x, p.y, p.w, p.h, p.color)
324 }
325 }
326}
327
328// draw_rounded_rect_empty draws the outline of a rounded rectangle with a thickness of 1 px.
329// `x`,`y` is the top-left corner of the rectangle.
330// `w` is the width, `h` is the height.
331// `radius` is the radius of the corner-rounding in pixels.
332// `c` is the color of the outline.
333pub fn (ctx &Context) draw_rounded_rect_empty(x f32, y f32, w f32, h f32, radius f32, c Color) {
334 if w <= 0 || h <= 0 || radius < 0 {
335 return
336 }
337
338 if c.a != 255 {
339 sgl.load_pipeline(ctx.pipeline.alpha)
340 }
341 sgl.c4b(c.r, c.g, c.b, c.a)
342
343 mut new_radius := radius
344 if radius < 1 {
345 new_radius = 0
346 }
347 if w >= h && radius > h / 2 {
348 new_radius = h / 2
349 } else if radius > w / 2 {
350 new_radius = w / 2
351 }
352
353 r := new_radius * ctx.scale
354 sx := x * ctx.scale // start point x
355 sy := y * ctx.scale
356 width := w * ctx.scale
357 height := h * ctx.scale
358
359 align_pixel := fn (v f32) f32 {
360 return math.floorf(v) + 0.5
361 }
362 // circle center coordinates
363 ltx := align_pixel(sx + r)
364 lty := align_pixel(sy + r)
365 rtx := align_pixel(sx + width - r)
366 rty := align_pixel(sy + r)
367 rbx := align_pixel(sx + width - r)
368 rby := align_pixel(sy + height - r)
369 lbx := align_pixel(sx + r)
370 lby := align_pixel(sy + height - r)
371
372 mut rad := f32(0)
373 mut dx := f32(0)
374 mut dy := f32(0)
375
376 if r == 0 {
377 sgl.begin_line_strip()
378 // top
379 sgl.v2f(ltx, lty)
380 sgl.v2f(rtx, rty)
381 // right
382 sgl.v2f(rbx, rby)
383 // bottom
384 sgl.v2f(lbx, lby)
385 // left
386 sgl.v2f(ltx, lty)
387 sgl.end()
388 return
389 }
390
391 sgl.begin_line_strip()
392 // left top quarter
393 for i in 0 .. 31 {
394 rad = f32(math.radians(i * 3))
395 dx = r * math.cosf(rad)
396 dy = r * math.sinf(rad)
397 sgl.v2f(ltx - dx, lty - dy)
398 }
399 // right top quarter
400 for i in 0 .. 31 {
401 rad = f32(math.radians(i * 3))
402 dx = r * math.sinf(rad)
403 dy = r * math.cosf(rad)
404 sgl.v2f(rtx + dx, rty - dy)
405 }
406 // right bottom quarter
407 for i in 0 .. 31 {
408 rad = f32(math.radians(i * 3))
409 dx = r * math.cosf(rad)
410 dy = r * math.sinf(rad)
411 sgl.v2f(rbx + dx, rby + dy)
412 }
413 // left bottom quarter
414 for i in 0 .. 31 {
415 rad = f32(math.radians(i * 3))
416 dx = r * math.sinf(rad)
417 dy = r * math.cosf(rad)
418 sgl.v2f(lbx - dx, lby + dy)
419 }
420 // close
421 sgl.v2f(ltx - r, lty)
422 sgl.end()
423}
424
425// draw_rounded_rect_filled draws a filled rounded rectangle.
426// `x`,`y` is the top-left corner of the rectangle.
427// `w` is the width, `h` is the height .
428// `radius` is the radius of the corner-rounding in pixels.
429// `c` is the color of the filled.
430// it divides the rounded rectangle into 2 shapes, the top rounded part and the bottom rounded part which are connected at both extremes.
431pub fn (ctx &Context) draw_rounded_rect_filled(x f32, y f32, w f32, h f32, radius f32, c Color) {
432 if w <= 0 || h <= 0 || radius < 0 {
433 return
434 }
435
436 if c.a != 255 {
437 sgl.load_pipeline(ctx.pipeline.alpha)
438 }
439 sgl.c4b(c.r, c.g, c.b, c.a)
440
441 mut new_radius := radius
442 if w >= h && radius > h / 2 {
443 new_radius = h / 2
444 } else if radius > w / 2 {
445 new_radius = w / 2
446 }
447 r := new_radius * ctx.scale
448 sx := x * ctx.scale // start point x
449 sy := y * ctx.scale
450 width := w * ctx.scale
451 height := h * ctx.scale
452
453 // left x coordinate
454 lx := sx + r
455 // right x coordinate
456 rx := sx + width - r
457 // top y coordinate
458 ty := sy + r
459 // bottom y coordinate
460 by := sy + height - r
461
462 if r == 0 {
463 // No radius means juste a rectangle
464 sgl.begin_quads()
465 sgl.v2f(sx, ty)
466 sgl.v2f(rx + r, ty)
467 sgl.v2f(rx + r, by)
468 sgl.v2f(sx, by)
469 sgl.end()
470 } else {
471 // draw the top then the bottom and link them with 2 triangle
472 mut rad := f32(0)
473 mut dx := f32(0)
474 mut dy := f32(0)
475
476 // top part
477 // starting at -30 then multiplying it by -1 makes you ends with the angle closer to the side which is needed to link both parts
478 sgl.begin_triangle_strip()
479 for i in -30 .. 1 {
480 rad = f32(math.radians(-i * 3))
481 dx = r * math.cosf(rad)
482 dy = r * math.sinf(rad)
483 sgl.v2f(rx + dx, ty - dy)
484 sgl.v2f(lx - dx, ty - dy)
485 }
486
487 // bottom part
488 for i in 0 .. 31 {
489 rad = f32(math.radians(i * 3))
490 dx = r * math.cosf(rad)
491 dy = r * math.sinf(rad)
492 sgl.v2f(rx + dx, by + dy)
493 sgl.v2f(lx - dx, by + dy)
494 }
495 sgl.end()
496 }
497}
498
499// draw_triangle_empty draws the outline of a triangle.
500// `x`,`y` defines the first point
501// `x2`,`y2` defines the second point
502// `x3`,`y3` defines the third point
503// `c` is the color of the outline.
504pub fn (ctx &Context) draw_triangle_empty(x f32, y f32, x2 f32, y2 f32, x3 f32, y3 f32, c Color) {
505 if c.a != 255 {
506 sgl.load_pipeline(ctx.pipeline.alpha)
507 }
508 sgl.c4b(c.r, c.g, c.b, c.a)
509
510 sgl.begin_line_strip()
511 sgl.v2f(x * ctx.scale, y * ctx.scale)
512 sgl.v2f(x2 * ctx.scale, y2 * ctx.scale)
513 sgl.v2f(x3 * ctx.scale, y3 * ctx.scale)
514 sgl.v2f(x * ctx.scale, y * ctx.scale)
515 sgl.end()
516}
517
518// draw_triangle_filled draws a filled triangle.
519// `x`,`y` defines the first point
520// `x2`,`y2` defines the second point
521// `x3`,`y3` defines the third point
522// `c` is the color of the outline.
523pub fn (ctx &Context) draw_triangle_filled(x f32, y f32, x2 f32, y2 f32, x3 f32, y3 f32, c Color) {
524 if c.a != 255 {
525 sgl.load_pipeline(ctx.pipeline.alpha)
526 }
527 sgl.c4b(c.r, c.g, c.b, c.a)
528
529 sgl.begin_triangles()
530 sgl.v2f(x * ctx.scale, y * ctx.scale)
531 sgl.v2f(x2 * ctx.scale, y2 * ctx.scale)
532 sgl.v2f(x3 * ctx.scale, y3 * ctx.scale)
533 sgl.end()
534}
535
536// draw_square_empty draws the outline of a square.
537// `x`,`y` is the top-left corner of the square.
538// `s` is the length of each side of the square.
539// `c` is the color of the outline.
540@[inline]
541pub fn (ctx &Context) draw_square_empty(x f32, y f32, s f32, c Color) {
542 ctx.draw_rect_empty(x, y, s, s, c)
543}
544
545// draw_square_filled draws a filled square.
546// `x`,`y` is the top-left corner of the square.
547// `s` is the length of each side of the square.
548// `c` is the fill color.
549@[inline]
550pub fn (ctx &Context) draw_square_filled(x f32, y f32, s f32, c Color) {
551 ctx.draw_rect_filled(x, y, s, s, c)
552}
553
554// The table here is derived by looking at the result of vlib/gg/testdata/tweak_circles.vv
555// and then choosing the most circle-ish drawing with the minimum number of segments.
556const small_circle_segments = [0, 2, 4, 6, 6, 8, 8, 13, 10, 18, 12, 12, 10, 13, 16, 15, 16]!
557
558@[direct_array_access]
559fn radius_to_segments(r f32) int {
560 if r < 30 {
561 ir := int(math.ceil(r))
562 if ir > 0 && ir < small_circle_segments.len {
563 return small_circle_segments[ir]
564 }
565 return ir
566 }
567 return int(math.ceil(math.tau * r / 8))
568}
569
570// draw_circle_empty draws the outline of a circle.
571// `x`,`y` defines the center of the circle.
572// `radius` defines the radius of the circle.
573// `c` is the color of the outline.
574pub fn (ctx &Context) draw_circle_empty(x f32, y f32, radius f32, c Color) {
575 $if macos {
576 if ctx.native_rendering {
577 C.darwin_draw_circle_empty(x - radius + 1, ctx.height - (y + radius + 3), radius, c)
578 return
579 }
580 }
581 if c.a != 255 {
582 sgl.load_pipeline(ctx.pipeline.alpha)
583 }
584 sgl.c4b(c.r, c.g, c.b, c.a)
585
586 nx := x * ctx.scale
587 ny := y * ctx.scale
588 nr := radius * ctx.scale
589 mut theta := f32(0)
590 mut xx := f32(0)
591 mut yy := f32(0)
592 segments := radius_to_segments(radius * ctx.scale)
593
594 sgl.begin_line_strip()
595 for i := 0; i < segments + 1; i++ {
596 theta = f32(math.tau) * f32(i) / f32(segments)
597 xx = nr * math.cosf(theta)
598 yy = nr * math.sinf(theta)
599 sgl.v2f(xx + nx, yy + ny)
600 }
601 sgl.end()
602}
603
604// draw_circle_filled draws a filled circle.
605// `x`,`y` defines the center of the circle.
606// `radius` defines the radius of the circle.
607// `c` is the fill color.
608pub fn (ctx &Context) draw_circle_filled(x f32, y f32, radius f32, c Color) {
609 $if macos {
610 if ctx.native_rendering {
611 C.darwin_draw_circle(x - radius + 1, ctx.height - (y + radius + 3), radius, c)
612 return
613 }
614 }
615 ctx.draw_polygon_filled(x, y, radius, radius_to_segments(radius * ctx.scale), 0, c)
616}
617
618// draw_polygon_filled draws a filled polygon.
619// `x`,`y` defines the center of the polygon.
620// `size` defines the size of the polygon.
621// `edges` defines number of edges in the polygon.
622// `rotation` defines rotation of the polygon.
623// `c` is the fill color.
624pub fn (ctx &Context) draw_polygon_filled(x f32, y f32, size f32, edges int, rotation f32, c Color) {
625 if edges <= 0 {
626 return
627 }
628
629 if c.a != 255 {
630 sgl.load_pipeline(ctx.pipeline.alpha)
631 }
632 sgl.c4b(c.r, c.g, c.b, c.a)
633
634 nx := x * ctx.scale
635 ny := y * ctx.scale
636 nr := size * ctx.scale
637 mut theta := f32(0)
638 mut xx := f32(0)
639 mut yy := f32(0)
640
641 sgl.begin_triangle_strip()
642 for i := 0; i < edges + 1; i++ {
643 theta = f32(math.tau) * f32(i) / f32(edges)
644 xx = nr * math.cosf(theta + f32(math.radians(rotation)))
645 yy = nr * math.sinf(theta + f32(math.radians(rotation)))
646 sgl.v2f(xx + nx, yy + ny)
647 sgl.v2f(nx, ny)
648 }
649 sgl.end()
650}
651
652// draw_circle_with_segments draws a filled circle with a specific number of segments.
653// `x`,`y` defines the center of the circle.
654// `radius` defines the radius of the circle.
655// `segments` affects how smooth/round the circle is.
656// `c` is the fill color.
657pub fn (ctx &Context) draw_circle_with_segments(x f32, y f32, radius f32, segments int, c Color) {
658 ctx.draw_polygon_filled(x, y, radius, segments, 0, c)
659}
660
661// draw_circle_line draws the outline of a circle with a specific number of segments.
662// `x`,`y` defines the center of the circle.
663// `radius` defines the radius of the circle.
664// `segments` affects how smooth/round the circle is.
665// `c` is the color of the outline.
666pub fn (ctx &Context) draw_circle_line(x f32, y f32, radius int, segments int, c Color) {
667 if segments <= 0 {
668 return
669 }
670
671 $if macos {
672 if ctx.native_rendering {
673 C.darwin_draw_circle(x - radius + 1, ctx.height - (y + radius + 3), radius, c)
674 return
675 }
676 }
677
678 if c.a != 255 {
679 sgl.load_pipeline(ctx.pipeline.alpha)
680 }
681 sgl.c4b(c.r, c.g, c.b, c.a)
682
683 nx := x * ctx.scale
684 ny := y * ctx.scale
685 nr := radius * ctx.scale
686 mut theta := f32(0)
687 mut xx := f32(0)
688 mut yy := f32(0)
689
690 sgl.begin_line_strip()
691 for i := 0; i < segments + 1; i++ {
692 theta = f32(math.tau) * f32(i) / f32(segments)
693 xx = nr * math.cosf(theta)
694 yy = nr * math.sinf(theta)
695 sgl.v2f(xx + nx, yy + ny)
696 }
697 sgl.end()
698}
699
700// draw_slice_empty draws the outline of a circle slice/pie
701pub fn (ctx &Context) draw_slice_empty(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int,
702 c Color) {
703 if segments <= 0 || radius <= 0 {
704 return
705 }
706 if c.a != 255 {
707 sgl.load_pipeline(ctx.pipeline.alpha)
708 }
709 sgl.c4b(c.r, c.g, c.b, c.a)
710
711 nx := x * ctx.scale
712 ny := y * ctx.scale
713 theta := f32(end_angle - start_angle) / f32(segments)
714 tan_factor := math.tanf(theta)
715 rad_factor := math.cosf(theta)
716 mut xx := radius * ctx.scale * math.sinf(start_angle)
717 mut yy := radius * ctx.scale * math.cosf(start_angle)
718
719 sgl.begin_line_strip()
720 sgl.v2f(nx, ny)
721 for i := 0; i < segments + 1; i++ {
722 sgl.v2f(xx + nx, yy + ny)
723 xx, yy = xx + yy * tan_factor, yy - xx * tan_factor
724 xx *= rad_factor
725 yy *= rad_factor
726 }
727 sgl.v2f(nx, ny)
728 sgl.end()
729}
730
731// draw_slice_filled draws a filled circle slice/pie
732// `x`,`y` defines the end point of the slice (center of the circle that the slice is part of).
733// `radius` defines the radius ("length") of the slice.
734// `start_angle` is the angle in radians at which the slice starts.
735// `end_angle` is the angle in radians at which the slice ends.
736// `segments` affects how smooth/round the slice is.
737// `c` is the fill color.
738pub fn (ctx &Context) draw_slice_filled(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int,
739 c Color) {
740 if segments <= 0 || radius < 0 {
741 return
742 }
743 if start_angle == end_angle {
744 ctx.draw_slice_empty(x, y, radius, start_angle, end_angle, 1, c)
745 return
746 }
747
748 if c.a != 255 {
749 sgl.load_pipeline(ctx.pipeline.alpha)
750 }
751 sgl.c4b(c.r, c.g, c.b, c.a)
752
753 nx := x * ctx.scale
754 ny := y * ctx.scale
755 theta := f32(end_angle - start_angle) / f32(segments)
756 tan_factor := math.tanf(theta)
757 rad_factor := math.cosf(theta)
758 mut xx := radius * ctx.scale * math.sinf(start_angle)
759 mut yy := radius * ctx.scale * math.cosf(start_angle)
760
761 sgl.begin_triangle_strip()
762 sgl.v2f(xx + nx, yy + ny)
763 for i := 0; i < segments; i++ {
764 xx, yy = xx + yy * tan_factor, yy - xx * tan_factor
765 xx *= rad_factor
766 yy *= rad_factor
767 sgl.v2f(xx + nx, yy + ny)
768 sgl.v2f(nx, ny)
769 }
770 sgl.end()
771}
772
773// draw_arc_line draws a line arc.
774// `x`,`y` defines the end point of the arc (center of the circle that the arc is part of).
775// `radius` defines the radius of the arc (length from the center point where the arc is drawn).
776// `start_angle` is the angle in radians at which the arc starts.
777// `end_angle` is the angle in radians at which the arc ends.
778// `segments` affects how smooth/round the arc is.
779// `c` is the color of the arc/outline.
780pub fn (ctx Context) draw_arc_line(x f32, y f32, radius f32, start_angle f32, end_angle f32, segments int,
781 c Color) {
782 if segments <= 0 || radius < 0 {
783 return
784 }
785 if radius == 0 {
786 ctx.draw_pixel(x, y, c)
787 return
788 }
789 if start_angle == end_angle {
790 xx := x + radius * math.sinf(start_angle)
791 yy := y + radius * math.cosf(start_angle)
792 ctx.draw_pixel(xx, yy, c)
793 return
794 }
795
796 if c.a != 255 {
797 sgl.load_pipeline(ctx.pipeline.alpha)
798 }
799 sgl.c4b(c.r, c.g, c.b, c.a)
800
801 nx := x * ctx.scale
802 ny := y * ctx.scale
803 theta := f32(end_angle - start_angle) / f32(segments)
804 tan_factor := math.tanf(theta)
805 rad_factor := math.cosf(theta)
806 mut xx := radius * ctx.scale * math.sinf(start_angle)
807 mut yy := radius * ctx.scale * math.cosf(start_angle)
808
809 sgl.begin_line_strip()
810 sgl.v2f(nx + xx, ny + yy)
811 for i := 0; i < segments; i++ {
812 xx, yy = xx + yy * tan_factor, yy - xx * tan_factor
813 xx *= rad_factor
814 yy *= rad_factor
815 sgl.v2f(nx + xx, ny + yy)
816 }
817 sgl.end()
818}
819
820// draw_arc_empty draws the outline of an arc.
821// `x`,`y` defines the end point of the arc (center of the circle that the arc is part of).
822// `inner_radius` defines the radius of the arc (length from the center point where the arc is drawn).
823// `thickness` defines how wide the arc is drawn.
824// `start_angle` is the angle in radians at which the arc starts.
825// `end_angle` is the angle in radians at which the arc ends.
826// `segments` affects how smooth/round the arc is.
827// `c` is the color of the arc outline.
828pub fn (ctx &Context) draw_arc_empty(x f32, y f32, inner_radius f32, thickness f32, start_angle f32, end_angle f32,
829 segments int, c Color) {
830 outer_radius := inner_radius + thickness
831 if segments <= 0 || outer_radius < 0 {
832 return
833 }
834
835 if inner_radius <= 0 {
836 ctx.draw_slice_empty(x, y, outer_radius, start_angle, end_angle, segments, c)
837 return
838 }
839 if inner_radius == outer_radius {
840 ctx.draw_arc_line(x, y, outer_radius, start_angle, end_angle, segments, c)
841 return
842 }
843
844 if c.a != 255 {
845 sgl.load_pipeline(ctx.pipeline.alpha)
846 }
847 sgl.c4b(c.r, c.g, c.b, c.a)
848
849 nx := x * ctx.scale
850 ny := y * ctx.scale
851 theta := f32(end_angle - start_angle) / f32(segments)
852 tan_factor := math.tanf(theta)
853 rad_factor := math.cosf(theta)
854
855 sgl.begin_line_strip()
856
857 // outer
858 mut xx := outer_radius * ctx.scale * math.sinf(start_angle)
859 mut yy := outer_radius * ctx.scale * math.cosf(start_angle)
860 sxx, syy := xx, yy
861 sgl.v2f(nx + xx, ny + yy)
862 for i := 0; i < segments; i++ {
863 xx, yy = xx + yy * tan_factor, yy - xx * tan_factor
864 xx *= rad_factor
865 yy *= rad_factor
866 sgl.v2f(nx + xx, ny + yy)
867 }
868
869 // inner
870 xx = inner_radius * ctx.scale * math.sinf(end_angle)
871 yy = inner_radius * ctx.scale * math.cosf(end_angle)
872 sgl.v2f(nx + xx, ny + yy)
873 for i := 0; i < segments; i++ {
874 xx, yy = xx - yy * tan_factor, yy + xx * tan_factor
875 xx *= rad_factor
876 yy *= rad_factor
877 sgl.v2f(nx + xx, ny + yy)
878 }
879
880 sgl.v2f(nx + sxx, ny + syy)
881 sgl.end()
882}
883
884// draw_arc_filled draws a filled arc.
885// `x`,`y` defines the central point of the arc (center of the circle that the arc is part of).
886// `inner_radius` defines the radius of the arc (length from the center point where the arc is drawn).
887// `thickness` defines how wide the arc is drawn.
888// `start_angle` is the angle in radians at which the arc starts.
889// `end_angle` is the angle in radians at which the arc ends.
890// `segments` affects how smooth/round the arc is.
891// `c` is the fill color of the arc.
892pub fn (ctx &Context) draw_arc_filled(x f32, y f32, inner_radius f32, thickness f32, start_angle f32, end_angle f32,
893 segments int, c Color) {
894 outer_radius := inner_radius + thickness
895 if segments <= 0 || outer_radius < 0 {
896 return
897 }
898
899 if inner_radius <= 0 {
900 ctx.draw_slice_filled(x, y, outer_radius, start_angle, end_angle, segments, c)
901 return
902 }
903 if inner_radius == outer_radius {
904 ctx.draw_arc_line(x, y, outer_radius, start_angle, end_angle, segments, c)
905 return
906 }
907 if start_angle == end_angle {
908 ctx.draw_arc_empty(x, y, inner_radius, thickness, start_angle, end_angle, 1, c)
909 return
910 }
911
912 if c.a != 255 {
913 sgl.load_pipeline(ctx.pipeline.alpha)
914 }
915 sgl.c4b(c.r, c.g, c.b, c.a)
916
917 nx := x * ctx.scale
918 ny := y * ctx.scale
919 theta := f32(end_angle - start_angle) / f32(segments)
920 tan_factor := math.tanf(theta)
921 rad_factor := math.cosf(theta)
922 mut ix := ctx.scale * math.sinf(start_angle)
923 mut iy := ctx.scale * math.cosf(start_angle)
924 mut ox := outer_radius * ix
925 mut oy := outer_radius * iy
926 ix *= inner_radius
927 iy *= inner_radius
928
929 sgl.begin_triangle_strip()
930 sgl.v2f(nx + ix, ny + iy)
931 sgl.v2f(nx + ox, ny + oy)
932 for i := 0; i < segments; i++ {
933 ix, iy = ix + iy * tan_factor, iy - ix * tan_factor
934 ix *= rad_factor
935 iy *= rad_factor
936 sgl.v2f(nx + ix, ny + iy)
937 ox, oy = ox + oy * tan_factor, oy - ox * tan_factor
938 ox *= rad_factor
939 oy *= rad_factor
940 sgl.v2f(nx + ox, ny + oy)
941 }
942 sgl.end()
943}
944
945// draw_ellipse_empty draws the outline of an ellipse.
946// `x`,`y` defines the center of the ellipse.
947// `rw` defines the *width* radius of the ellipse.
948// `rh` defines the *height* radius of the ellipse.
949// `c` is the color of the outline.
950pub fn (ctx &Context) draw_ellipse_empty(x f32, y f32, rw f32, rh f32, c Color) {
951 if c.a != 255 {
952 sgl.load_pipeline(ctx.pipeline.alpha)
953 }
954 sgl.c4b(c.r, c.g, c.b, c.a)
955
956 sgl.begin_line_strip()
957 for i := 0; i < 360; i += 10 {
958 sgl.v2f(x + math.sinf(f32(math.radians(i))) * rw, y + math.cosf(f32(math.radians(i))) * rh)
959 }
960 sgl.v2f(x, y + rh)
961 sgl.end()
962}
963
964// draw_ellipse_empty draws the outline of an ellipse.
965// `x`,`y` defines the center of the ellipse.
966// `rw` defines the *width* radius of the ellipse.
967// `rh` defines the *height* radius of the ellipse.
968// `th` defines the *thickness* of the ellipse.
969// `c` is the color of the outline.
970pub fn (ctx &Context) draw_ellipse_thick(x f32, y f32, rw f32, rh f32, th f32, c Color) {
971 if c.a != 255 {
972 sgl.load_pipeline(ctx.pipeline.alpha)
973 }
974 sgl.c4b(c.r, c.g, c.b, c.a)
975
976 sgl.begin_triangle_strip()
977 for i := 0; i < 360; i += 10 {
978 xfactor := math.sinf(f32(math.radians(i)))
979 yfactor := math.cosf(f32(math.radians(i)))
980
981 sgl.v2f(x + xfactor * (rw - th / 2), y + yfactor * (rh - th / 2))
982 sgl.v2f(x + xfactor * (rw + th / 2), y + yfactor * (rh + th / 2))
983 }
984 sgl.v2f(x, y + (rh - th / 2))
985 sgl.v2f(x, y + (rh + th / 2))
986 sgl.end()
987}
988
989// draw_ellipse_filled draws an opaque ellipse.
990// `x`,`y` defines the center of the ellipse.
991// `rw` defines the *width* radius of the ellipse.
992// `rh` defines the *height* radius of the ellipse.
993// `c` is the fill color.
994pub fn (ctx &Context) draw_ellipse_filled(x f32, y f32, rw f32, rh f32, c Color) {
995 if c.a != 255 {
996 sgl.load_pipeline(ctx.pipeline.alpha)
997 }
998 sgl.c4b(c.r, c.g, c.b, c.a)
999
1000 sgl.begin_triangle_strip()
1001 for i := 0; i < 360; i += 10 {
1002 sgl.v2f(x, y)
1003 sgl.v2f(x + math.sinf(f32(math.radians(i))) * rw, y + math.cosf(f32(math.radians(i))) * rh)
1004 }
1005 sgl.v2f(x, y + rh)
1006 sgl.end()
1007}
1008
1009// draw_ellipse_empty_rotate draws the outline of an ellipse.
1010// `x`,`y` defines the center of the ellipse.
1011// `rw` defines the *width* radius of the ellipse.
1012// `rh` defines the *height* radius of the ellipse.
1013// `rota` defines the *rotation* angle of the ellipse, in radians.
1014// `c` is the color of the outline.
1015pub fn (ctx &Context) draw_ellipse_empty_rotate(x f32, y f32, rw f32, rh f32, rota f32, c Color) {
1016 if c.a != 255 {
1017 sgl.load_pipeline(ctx.pipeline.alpha)
1018 }
1019 sgl.c4b(c.r, c.g, c.b, c.a)
1020
1021 cos_rot := math.cosf(rota)
1022 sin_rot := math.sinf(rota)
1023 sgl.begin_line_strip()
1024 for i := 0; i < 360; i += 10 {
1025 x_current := math.sinf(f32(math.radians(i))) * rw
1026 y_current := math.cosf(f32(math.radians(i))) * rh
1027
1028 sgl.v2f(x + x_current * cos_rot - y_current * sin_rot, y + x_current * sin_rot +
1029 y_current * cos_rot)
1030 }
1031 sgl.v2f(x - rh * sin_rot, y + rh * cos_rot)
1032 sgl.end()
1033}
1034
1035// draw_ellipse_empty draws the outline of an ellipse.
1036// `x`,`y` defines the center of the ellipse.
1037// `rw` defines the *width* radius of the ellipse.
1038// `rh` defines the *height* radius of the ellipse.
1039// `th` defines the *thickness* of the ellipse.
1040// `rota` defines the *rotation* angle of the ellipse, in radians.
1041// `c` is the color of the outline.
1042pub fn (ctx &Context) draw_ellipse_thick_rotate(x f32, y f32, rw f32, rh f32, th f32, rota f32, c Color) {
1043 if c.a != 255 {
1044 sgl.load_pipeline(ctx.pipeline.alpha)
1045 }
1046 sgl.c4b(c.r, c.g, c.b, c.a)
1047
1048 cos_rot := math.cosf(rota)
1049 sin_rot := math.sinf(rota)
1050 sgl.begin_triangle_strip()
1051 for i := 0; i < 360; i += 10 {
1052 xfactor := math.sinf(f32(math.radians(i)))
1053 yfactor := math.cosf(f32(math.radians(i)))
1054
1055 sgl.v2f(x + xfactor * (rw - th / 2) * cos_rot - yfactor * (rh - th / 2) * sin_rot, y +
1056 yfactor * (rh - th / 2) * cos_rot + xfactor * (rw - th / 2) * sin_rot)
1057 sgl.v2f(x + xfactor * (rw + th / 2) * cos_rot - yfactor * (rh + th / 2) * sin_rot, y +
1058 yfactor * (rh + th / 2) * cos_rot + xfactor * (rw + th / 2) * sin_rot)
1059 }
1060 sgl.v2f(x - (rh - th / 2) * sin_rot, y + (rh - th / 2) * cos_rot)
1061 sgl.v2f(x - (rh + th / 2) * sin_rot, y + (rh + th / 2) * cos_rot)
1062 sgl.end()
1063}
1064
1065// draw_ellipse_filled draws an opaque ellipse.
1066// `x`,`y` defines the center of the ellipse.
1067// `rw` defines the *width* radius of the ellipse.
1068// `rh` defines the *height* radius of the ellipse.
1069// `rota` defines the *rotation* angle of the ellipse, in radians.
1070// `c` is the fill color.
1071pub fn (ctx &Context) draw_ellipse_filled_rotate(x f32, y f32, rw f32, rh f32, rota f32, c Color) {
1072 if c.a != 255 {
1073 sgl.load_pipeline(ctx.pipeline.alpha)
1074 }
1075 sgl.c4b(c.r, c.g, c.b, c.a)
1076
1077 cos_rot := math.cosf(rota)
1078 sin_rot := math.sinf(rota)
1079 sgl.begin_triangle_strip()
1080 for i := 0; i < 360; i += 10 {
1081 sgl.v2f(x, y)
1082 x_current := math.sinf(f32(math.radians(i))) * rw
1083 y_current := math.cosf(f32(math.radians(i))) * rh
1084 sgl.v2f(x + x_current * cos_rot - y_current * sin_rot, y + x_current * sin_rot +
1085 y_current * cos_rot)
1086 }
1087 sgl.v2f(x - rh * sin_rot, y + rh * cos_rot)
1088 sgl.end()
1089}
1090
1091// draw_cubic_bezier draws a cubic Bézier curve, also known as a spline, from four points.
1092// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates).
1093// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`.
1094// Please see `draw_cubic_bezier_in_steps` to control the amount of steps (segments) used to draw the curve.
1095pub fn (ctx &Context) draw_cubic_bezier(points []f32, c Color) {
1096 ctx.draw_cubic_bezier_in_steps(points, u32(30 * ctx.scale), c)
1097}
1098
1099// draw_cubic_bezier_in_steps draws a cubic Bézier curve, also known as a spline, from four points.
1100// The smoothness of the curve can be controlled with the `steps` parameter. `steps` determines how many iterations is
1101// taken to draw the curve.
1102// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates).
1103// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`.
1104pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, steps u32, c Color) {
1105 if steps <= 0 || steps >= 20000 || points.len != 8 {
1106 return
1107 }
1108 if c.a != 255 {
1109 sgl.load_pipeline(ctx.pipeline.alpha)
1110 }
1111 sgl.c4b(c.r, c.g, c.b, c.a)
1112
1113 sgl.begin_line_strip()
1114
1115 p1_x, p1_y := points[0], points[1]
1116 p2_x, p2_y := points[6], points[7]
1117
1118 ctrl_p1_x, ctrl_p1_y := points[2], points[3]
1119 ctrl_p2_x, ctrl_p2_y := points[4], points[5]
1120
1121 // The constant 3 is actually points.len() - 1;
1122
1123 step := f32(1) / steps
1124 sgl.v2f(p1_x * ctx.scale, p1_y * ctx.scale)
1125 for u := f32(0); u <= f32(1); u += step {
1126 pow_2_u := u * u
1127 pow_3_u := pow_2_u * u
1128
1129 x := pow_3_u * (p2_x + 3 * (ctrl_p1_x - ctrl_p2_x) - p1_x) +
1130 3 * pow_2_u * (p1_x - 2 * ctrl_p1_x + ctrl_p2_x) + 3 * u * (ctrl_p1_x - p1_x) + p1_x
1131
1132 y := pow_3_u * (p2_y + 3 * (ctrl_p1_y - ctrl_p2_y) - p1_y) +
1133 3 * pow_2_u * (p1_y - 2 * ctrl_p1_y + ctrl_p2_y) + 3 * u * (ctrl_p1_y - p1_y) + p1_y
1134
1135 sgl.v2f(x * ctx.scale, y * ctx.scale)
1136 }
1137 sgl.v2f(p2_x * ctx.scale, p2_y * ctx.scale)
1138
1139 sgl.end()
1140}
1141