diff --git a/examples/gg/bezier.v b/examples/gg/bezier.v new file mode 100644 index 0000000000..3030840ee7 --- /dev/null +++ b/examples/gg/bezier.v @@ -0,0 +1,35 @@ +module main + +import gg +import gx + +const ( + p1_and_p2 = [f32(200.0), 200.0, 400.0, 300.0] + ctrl_p1_and_p2 = [f32(200.0), 100.0, 400.0, 100.0] +) + +struct App { +mut: + gg &gg.Context +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 600 + height: 400 + window_title: 'Cubic Bézier curve' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + app.gg.begin() + app.gg.draw_cubic_bezier(p1_and_p2, ctrl_p1_and_p2, gx.blue) + app.gg.end() +} diff --git a/examples/gg/bezier_anim.v b/examples/gg/bezier_anim.v new file mode 100644 index 0000000000..afa5628970 --- /dev/null +++ b/examples/gg/bezier_anim.v @@ -0,0 +1,60 @@ +module main + +import gg +import gx + +const rate = f32(1) / 60 * 10 + +struct App { +mut: + gg &gg.Context + anim &Anim +} + +struct Anim { +mut: + time f32 + reverse bool +} + +fn (mut anim Anim) advance() { + if anim.reverse { + anim.time -= 1 * rate + } else { + anim.time += 1 * rate + } + // Use some arbitrary value that fits 60 fps + if anim.time > 80 * rate || anim.time < -80 * rate { + anim.reverse = !anim.reverse + } +} + +fn main() { + mut app := &App{ + gg: 0 + anim: &Anim{} + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 600 + height: 400 + window_title: 'Animated cubic Bézier curve' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + time := app.anim.time + + ctrl_p1_x := f32(200.0) + (40 * time) + ctrl_p2_x := f32(400.0) + (-40 * time) + + p1_and_p2 := [f32(200.0), 200.0 + (10 * time), 400.0, 200.0 + (10 * time)] + + app.gg.begin() + app.gg.draw_cubic_bezier(p1_and_p2, [ctrl_p1_x, 100.0, ctrl_p2_x, 100.0], gx.blue) + app.gg.end() + app.anim.advance() +} diff --git a/vlib/gg/gg.v b/vlib/gg/gg.v index ac67b5d4e2..54691a0ed1 100644 --- a/vlib/gg/gg.v +++ b/vlib/gg/gg.v @@ -674,6 +674,58 @@ pub fn (ctx &Context) draw_empty_poly(points []f32, c gx.Color) { sgl.end() } +// draw_cubic_bezier draws a cubic Bézier curve, also known as a spline, from four points. +// The four points is provided as two arrays; `points` and `control_points`, which is both pairs of x and y coordinates. +// Thus a coordinate pair could be declared like: `points := [x1, y1, x2, y2]`. +// Please see `draw_cubic_bezier_in_steps` to control the amount of steps (segments) used to draw the curve. +pub fn (ctx &Context) draw_cubic_bezier(points []f32, control_points []f32, c gx.Color) { + ctx.draw_cubic_bezier_in_steps(points, control_points, u32(30 * ctx.scale), c) +} + +// draw_cubic_bezier_in_steps draws a cubic Bézier curve, also known as a spline, from four points. +// The smoothness of the curve can be controlled with the `steps` parameter. `steps` determines how many iterations is +// taken to draw the curve. +// The four points is provided as two arrays; `points` and `control_points`, which is both pairs of x and y coordinates. +// Thus a coordinate pair could be declared like: `points := [x1, y1, x2, y2]`. +pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, control_points []f32, steps u32, c gx.Color) { + assert steps > 0 + assert points.len == 4 + assert points.len == control_points.len + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_line_strip() + + p1_x, p1_y := points[0], points[1] + p2_x, p2_y := points[2], points[3] + + ctrl_p1_x, ctrl_p1_y := control_points[0], control_points[1] + ctrl_p2_x, ctrl_p2_y := control_points[2], control_points[3] + + // The constant 3 is actually points.len() - 1; + + step := f32(1.0) / steps + sgl.v2f(p1_x * ctx.scale, p1_y * ctx.scale) + for u := f32(0.0); u <= f32(1.0); u += step { + pow_2_u := u * u + pow_3_u := pow_2_u * u + + x := pow_3_u * (p2_x + 3 * (ctrl_p1_x - ctrl_p2_x) - p1_x) + + 3 * pow_2_u * (p1_x - 2 * ctrl_p1_x + ctrl_p2_x) + 3 * u * (ctrl_p1_x - p1_x) + p1_x + + y := pow_3_u * (p2_y + 3 * (ctrl_p1_y - ctrl_p2_y) - p1_y) + + 3 * pow_2_u * (p1_y - 2 * ctrl_p1_y + ctrl_p2_y) + 3 * u * (ctrl_p1_y - p1_y) + p1_y + + sgl.v2f(x * ctx.scale, y * ctx.scale) + } + sgl.v2f(p2_x * ctx.scale, p2_y * ctx.scale) + + sgl.end() +} + // window_size returns the `Size` of the active window pub fn window_size() Size { s := dpi_scale()