Bezier Curve Path¶
This example shows a cubic Bezier curve tessellated into a path.
Preview¶
Run And Adapt¶
Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.
| Route | Availability | Command or action |
|---|---|---|
| C | Canonical native source | just example-c features/bezier_curve_path (build and run), or rerun ./build/examples/c/features/bezier_curve_path |
| Python | Available; direct-engine adaptation | python3 -m examples.python.gallery.features.bezier_curve_path |
| Browser | Live WebGPU route | Open live example |
This example is approved as a starting point for user code and coding agents. Keep the object lifetimes and data shapes intact while adapting the data and styling.
What To Look For¶
Four control points define the curve. The tessellated path uploads position, color, and linewidth arrays, while faint segment guides connect the control polygon and markers show the endpoints and handles. Compare the smooth rendered curve with the straight guide segments; this is useful for drawing fitted trajectories, contours, or model curves while still exposing the control data that shaped them.
Source¶
#!/usr/bin/env python3
"""Cubic Bezier control points rendered as a retained stroked path."""
from __future__ import annotations
import numpy as np
import datoviz as dvz
from examples.python.gallery import common as ex
SEGMENT_COUNT = 48
CONTROLS = np.array(
[
[-0.82, -0.45, 0.0],
[-0.42, +0.76, 0.0],
[+0.38, -0.74, 0.0],
[+0.82, +0.45, 0.0],
],
dtype=np.float32,
)
def _bezier_points(controls: np.ndarray, segment_count: int = SEGMENT_COUNT) -> np.ndarray:
t = np.linspace(0.0, 1.0, segment_count + 1, dtype=np.float32)[:, None]
one = 1.0 - t
return (
one**3 * controls[0]
+ 3.0 * one**2 * t * controls[1]
+ 3.0 * one * t**2 * controls[2]
+ t**3 * controls[3]
).astype(np.float32)
def _add_curve(scene, panel, points: np.ndarray) -> None:
count = len(points)
colors = ex.color_array(*([ex.CYAN] * count))
widths = np.full(count, 8.0, dtype=np.float32)
path = dvz.dvz_path(scene, 0)
if not path:
raise RuntimeError("dvz_path() failed")
if dvz.dvz_visual_set_data_many(
path,
{
"position": points,
"color": colors,
"stroke_width_px": widths,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(path) failed")
if dvz.dvz_path_set_caps(path, dvz.DVZ_SEGMENT_CAP_ROUND, dvz.DVZ_SEGMENT_CAP_ROUND) != 0:
raise RuntimeError("dvz_path_set_caps() failed")
if dvz.dvz_path_set_join(path, dvz.DVZ_PATH_JOIN_ROUND, 4.0) != 0:
raise RuntimeError("dvz_path_set_join() failed")
ex.add_visual(panel, path)
def _add_control_polygon(scene, panel, controls: np.ndarray) -> None:
starts = controls[:-1].copy()
ends = controls[1:].copy()
colors = ex.color_array(
dvz.DvzColor(ex.BLUE.r, ex.BLUE.g, ex.BLUE.b, 180),
dvz.DvzColor(ex.BLUE.r, ex.BLUE.g, ex.BLUE.b, 180),
dvz.DvzColor(ex.BLUE.r, ex.BLUE.g, ex.BLUE.b, 180),
)
widths = np.full(len(starts), 2.0, dtype=np.float32)
segment = dvz.dvz_segment(scene, 0)
if not segment:
raise RuntimeError("dvz_segment() failed")
if dvz.dvz_visual_set_data_many(
segment,
{
"position_start": starts,
"position_end": ends,
"color": colors,
"stroke_width_px": widths,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(segment) failed")
if dvz.dvz_segment_set_caps(segment, dvz.DVZ_SEGMENT_CAP_ROUND, dvz.DVZ_SEGMENT_CAP_ROUND) != 0:
raise RuntimeError("dvz_segment_set_caps() failed")
ex.add_visual(panel, segment)
def _add_control_points(scene, panel, controls: np.ndarray) -> None:
colors = ex.color_array(ex.YELLOW, ex.GREEN, ex.GREEN, ex.YELLOW)
diameters = np.array([34.0, 24.0, 24.0, 34.0], dtype=np.float32)
point = dvz.dvz_point(scene, 0)
if not point:
raise RuntimeError("dvz_point() failed")
if dvz.dvz_visual_set_data_many(
point,
{
"position": controls,
"color": colors,
"diameter_px": diameters,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(point) failed")
ex.add_visual(panel, point)
def _build_scene():
scene, figure, panel = ex.scene_panel()
points = _bezier_points(CONTROLS)
_add_control_polygon(scene, panel, CONTROLS)
_add_curve(scene, panel, points)
_add_control_points(scene, panel, CONTROLS)
return scene, figure, panel
def main() -> None:
scene, figure, _panel = _build_scene()
ex.run(scene, figure, "Bezier Curve Path")
if __name__ == "__main__":
main()
/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/* bezier_curve_path - This example shows a cubic Bezier curve tessellated into a retained path.
*
* Scenario: features_bezier_curve_path
* Style: features, graphite_cyan, 1280x720 window target
*
* Build: just example-c features/bezier_curve_path
* Run: ./build/examples/c/features/bezier_curve_path --live
* Smoke: ./build/examples/c/features/bezier_curve_path --png
*
* What to look for: four control points define the curve. The tessellated path uploads position,
* color, and linewidth arrays, while faint segment guides connect the control polygon and markers
* show the endpoints and handles. Compare the smooth rendered curve with the straight guide
* segments; this is useful for drawing fitted trajectories, contours, or model curves while still
* exposing the control data that shaped them.
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include "_assertions.h"
#include "datoviz/geom.h"
#include "datoviz/scene.h"
#include "example_style.h"
#include "runner/scenario_runner.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define CONTROL_COUNT 4u
#define CONTROL_EDGES 3u
/*************************************************************************************************/
/* Function prototypes */
/*************************************************************************************************/
DvzScenarioSpec dvz_example_bezier_curve_path_scenario(void);
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
/**
* Add the tessellated Bezier path.
*
* @param scene scene owning the visual
* @param panel target panel
* @param tess tessellated path
* @return true when the curve was added
*/
static bool _add_curve(DvzScene* scene, DvzPanel* panel, const DvzTessellatedPath* tess)
{
ANN(scene);
ANN(panel);
ANN(tess);
if (tess->point_count == 0u || tess->points == NULL)
return false;
vec3 positions[64] = {{0}};
DvzColor colors[64] = {{0}};
float widths[64] = {0};
if (tess->point_count > DVZ_ARRAY_COUNT(positions))
return false;
for (uint32_t i = 0; i < tess->point_count; i++)
{
positions[i][0] = (float)tess->points[i][0];
positions[i][1] = (float)tess->points[i][1];
positions[i][2] = (float)tess->points[i][2];
colors[i] = example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY);
widths[i] = 8.0f;
}
DvzVisual* path = dvz_path(scene, 0);
if (path == NULL)
return false;
DvzVisualDataUpdate updates[] = {
{.attr_name = "position", .data = positions, .item_count = tess->point_count},
{.attr_name = "color", .data = colors, .item_count = tess->point_count},
{.attr_name = "stroke_width_px", .data = widths, .item_count = tess->point_count},
};
if (dvz_visual_set_data_many(path, updates, 3) != 0)
return false;
if (dvz_path_set_caps(path, DVZ_SEGMENT_CAP_ROUND, DVZ_SEGMENT_CAP_ROUND) != 0)
return false;
if (dvz_path_set_join(path, DVZ_PATH_JOIN_ROUND, 4.0f) != 0)
return false;
return dvz_panel_add_visual(panel, path, NULL) == 0;
}
/**
* Add the Bezier control polygon as thin segments.
*
* @param scene scene owning the visual
* @param panel target panel
* @param controls cubic control points
* @return true when the control polygon was added
*/
static bool
_add_control_polygon(DvzScene* scene, DvzPanel* panel, const dvec3 controls[CONTROL_COUNT])
{
ANN(scene);
ANN(panel);
ANN(controls);
vec3 starts[CONTROL_EDGES] = {{0}};
vec3 ends[CONTROL_EDGES] = {{0}};
DvzColor colors[CONTROL_EDGES] = {{0}};
float widths[CONTROL_EDGES] = {0};
for (uint32_t i = 0; i < CONTROL_EDGES; i++)
{
starts[i][0] = (float)controls[i][0];
starts[i][1] = (float)controls[i][1];
starts[i][2] = 0.0f;
ends[i][0] = (float)controls[i + 1u][0];
ends[i][1] = (float)controls[i + 1u][1];
ends[i][2] = 0.0f;
colors[i] = example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_GRID);
colors[i].a = 180u;
widths[i] = 2.0f;
}
DvzVisual* segment = dvz_segment(scene, 0);
if (segment == NULL)
return false;
DvzVisualDataUpdate updates[] = {
{.attr_name = "position_start", .data = starts, .item_count = CONTROL_EDGES},
{.attr_name = "position_end", .data = ends, .item_count = CONTROL_EDGES},
{.attr_name = "color", .data = colors, .item_count = CONTROL_EDGES},
{.attr_name = "stroke_width_px", .data = widths, .item_count = CONTROL_EDGES},
};
if (dvz_visual_set_data_many(segment, updates, 4) != 0)
return false;
if (dvz_segment_set_caps(segment, DVZ_SEGMENT_CAP_ROUND, DVZ_SEGMENT_CAP_ROUND) != 0)
return false;
return dvz_panel_add_visual(panel, segment, NULL) == 0;
}
/**
* Add the Bezier control points.
*
* @param scene scene owning the visual
* @param panel target panel
* @param controls cubic control points
* @return true when the control points were added
*/
static bool
_add_control_points(DvzScene* scene, DvzPanel* panel, const dvec3 controls[CONTROL_COUNT])
{
ANN(scene);
ANN(panel);
ANN(controls);
vec3 positions[CONTROL_COUNT] = {{0}};
DvzColor colors[CONTROL_COUNT] = {{0}};
float diameters[CONTROL_COUNT] = {0};
for (uint32_t i = 0; i < CONTROL_COUNT; i++)
{
positions[i][0] = (float)controls[i][0];
positions[i][1] = (float)controls[i][1];
positions[i][2] = 0.0f;
colors[i] = i == 0u || i == CONTROL_COUNT - 1u
? example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_WARNING)
: example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_SECONDARY);
diameters[i] = i == 0u || i == CONTROL_COUNT - 1u ? 34.0f : 24.0f;
}
DvzVisual* point = dvz_point(scene, 0);
if (point == NULL)
return false;
DvzVisualDataUpdate updates[] = {
{.attr_name = "position", .data = positions, .item_count = CONTROL_COUNT},
{.attr_name = "color", .data = colors, .item_count = CONTROL_COUNT},
{.attr_name = "diameter_px", .data = diameters, .item_count = CONTROL_COUNT},
};
if (dvz_visual_set_data_many(point, updates, 3) != 0)
return false;
return dvz_panel_add_visual(panel, point, NULL) == 0;
}
/*************************************************************************************************/
/* Scenario callbacks */
/*************************************************************************************************/
/**
* Initialize the Bezier-curve path feature example.
*
* @param ctx scenario context
* @param out_user scenario state output
* @return true on success
*/
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
if (ctx == NULL)
return false;
if (out_user != NULL)
*out_user = NULL;
ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
if (ctx->figure == NULL)
return false;
DvzPanel* panel = dvz_panel_full(ctx->figure);
if (panel == NULL)
return false;
example_graphite_cyan_set_panel_background(panel);
const dvec3 controls[CONTROL_COUNT] = {
{-0.82, -0.45, 0.0},
{-0.42, +0.76, 0.0},
{+0.38, -0.74, 0.0},
{+0.82, +0.45, 0.0},
};
DvzBezierTessellationDesc desc = dvz_bezier_tessellation_desc();
desc.segment_count = 48u;
DvzTessellatedPath* tess =
dvz_tessellate_cubic_bezier(controls[0], controls[1], controls[2], controls[3], &desc);
if (tess == NULL)
return false;
const bool ok = _add_control_polygon(ctx->scene, panel, controls) &&
_add_curve(ctx->scene, panel, tess) &&
_add_control_points(ctx->scene, panel, controls);
dvz_tessellated_path_destroy(tess);
return ok;
}
/**
* Return the Bezier-curve path scenario specification.
*
* @return scenario specification
*/
DvzScenarioSpec dvz_example_bezier_curve_path_scenario(void)
{
return (DvzScenarioSpec){
.id = "features_bezier_curve_path",
.title = "Bezier Curve Path",
.width = WIDTH,
.height = HEIGHT,
.fps = 60.0,
.init = _scenario_init,
};
}
/*************************************************************************************************/
/* Functions */
/*************************************************************************************************/
/**
* Run the Bezier-curve path feature example through the native scenario runner.
*
* @param argc command-line argument count
* @param argv command-line argument vector
* @return process exit code
*/
#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
DvzScenarioSpec spec = dvz_example_bezier_curve_path_scenario();
return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details
- ID:
features_bezier_curve_path - Category:
feature - Lane:
features - Status:
supported - Source:
examples/c/features/bezier_curve_path.c - Approved adaptation starter:
yes - Python source:
examples/python/gallery/features/bezier_curve_path.py - Python adaptation: Available; direct-engine adaptation
- Browser support: Live in browser
- WebGPU live route:
examples/webgpu/live.html?id=features_bezier_curve_path - Browser capability tags:
path,segment,marker - Validation:
smoke+screenshot
Data
| Field | Value |
|---|---|
kind |
synthetic |
