Volume¶
This example volume-renders a generated 3D gyroid scalar field.
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 visuals/volume (build and run), or rerun ./build/examples/c/visuals/volume |
| Python | Available | python3 -m examples.python.gallery.visuals.volume |
| Browser | Planned | Use the native route for this 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¶
The FIELD_SIZE cubed data array is attached as a 3D sampled field, with color and alpha scales controlling which values remain visible. Compare the rotating volume with the boundary box to understand the data bounds, transfer-function opacity, and maximum-intensity projection style used for volumetric microscopy, simulation, or tomography data.
Source¶
#!/usr/bin/env python3
"""Generated gyroid scalar field rendered as a 3D volume."""
from __future__ import annotations
import ctypes
import numpy as np
import datoviz as dvz
from examples.python.gallery import common as ex
FIELD_SIZE = 128
BOX_SEGMENTS = 12
ROTATION_SPEED_RAD_PER_SEC = 0.16
TAU = 2.0 * np.pi
VOLUME_BOUNDS_MIN = (-0.88, -0.74, -1.04)
VOLUME_BOUNDS_MAX = (+0.88, +0.74, +1.04)
class VolumeState:
def __init__(self) -> None:
self.volume_rotation = None
self.box_rotation = None
self.volume_animation = None
self.box_animation = None
def destroy_tracks(self) -> None:
for track in (self.box_rotation, self.volume_rotation):
if track:
dvz.dvz_track_destroy(track)
self.box_rotation = None
self.volume_rotation = None
def _smoothstep(edge0: float, edge1: float, value):
if edge0 == edge1:
return np.where(value < edge0, 0.0, 1.0)
t = np.clip((value - edge0) / (edge1 - edge0), 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
def _gyroid_value(x, y, z):
scale = 1.16 * TAU
gx = scale * x
gy = scale * y
gz = scale * z
return np.sin(gx) * np.cos(gy) + np.sin(gy) * np.cos(gz) + np.sin(gz) * np.cos(gx)
def _sample_volume(x, y, z):
g0 = _gyroid_value(x, y, z)
g1 = _gyroid_value(x + 0.035, y - 0.020, z + 0.045)
sheet = np.exp(-(g0 * g0) / (2.0 * 0.115 * 0.115))
shoulder = np.exp(-((np.abs(g1) - 0.34) ** 2) / (2.0 * 0.135 * 0.135))
rx = x / 0.92
ry = y / 0.86
rz = z / 0.96
r = np.sqrt(rx * rx + ry * ry + rz * rz)
envelope = 1.0 - _smoothstep(0.82, 1.00, r)
rim = _smoothstep(0.72, 0.94, r) * (1.0 - _smoothstep(0.96, 1.03, r))
directional_light = 0.78 + 0.22 * np.clip(0.5 + 0.5 * (0.45 * x - y + z), 0.0, 1.0)
lattice = envelope * directional_light * (0.88 * sheet + 0.16 * shoulder)
return np.clip(0.86 * lattice + 0.12 * rim * sheet, 0.0, 1.0)
def _volume_data():
coords = np.linspace(-1.0, 1.0, FIELD_SIZE, dtype=np.float32)
z, y, x = np.meshgrid(coords, coords, coords, indexing="ij")
values = _sample_volume(x, y, z)
return np.clip(255.0 * values + 0.5, 0.0, 255.0).astype(np.uint8)
def _set_default_3d_camera(panel) -> None:
camera = dvz.dvz_camera_desc()
camera.view.eye[:] = (2.35, -2.95, 2.15)
camera.view.target[:] = (0.0, 0.0, 0.0)
camera.view.up[:] = (0.0, 0.0, 1.0)
camera.projection.fov_y = 0.66
camera.projection.near_clip = 0.05
camera.projection.far_clip = 100.0
if dvz.dvz_panel_set_camera_desc(panel, ctypes.byref(camera)) != 0:
raise RuntimeError("dvz_panel_set_camera_desc() failed")
def _attach_transfer(scene, visual) -> None:
desc = dvz.dvz_scale_desc()
desc.kind = dvz.DVZ_SCALE_CONTINUOUS
scale = dvz.dvz_scale(scene, ctypes.byref(desc))
if not scale:
raise RuntimeError("dvz_scale() failed")
if dvz.dvz_scale_set_domain(scale, 0.0, 1.0) != 0:
raise RuntimeError("dvz_scale_set_domain() failed")
colormap = dvz.dvz_colormap(scene, None)
if not colormap:
raise RuntimeError("dvz_colormap() failed")
stop_values = (
(0.00, (14, 17, 23, 255)),
(0.10, (14, 17, 23, 255)),
(0.24, (18, 58, 96, 255)),
(0.40, (44, 166, 209, 255)),
(0.56, (76, 201, 240, 255)),
(0.74, (128, 255, 219, 255)),
(0.92, (238, 252, 232, 255)),
(1.00, (255, 183, 3, 255)),
)
stops = (dvz.DvzColormapStop * len(stop_values))()
for stop, (position, rgba) in zip(stops, stop_values, strict=True):
stop.position = position
stop.rgba[:] = rgba
if dvz.dvz_colormap_set_stops(colormap, stops, len(stops)) != 0:
raise RuntimeError("dvz_colormap_set_stops() failed")
if dvz.dvz_scale_set_colormap(scale, colormap) != 0:
raise RuntimeError("dvz_scale_set_colormap() failed")
alpha_values = (
(0.00, 0.00),
(0.09, 0.00),
(0.18, 0.08),
(0.32, 0.32),
(0.52, 0.62),
(0.74, 0.86),
(1.00, 0.98),
)
alpha = (dvz.DvzVolumeAlphaStop * len(alpha_values))()
for stop, (position, opacity) in zip(alpha, alpha_values, strict=True):
stop.position = position
stop.alpha = opacity
if dvz.dvz_volume_set_alpha_stops(visual, alpha, len(alpha)) != 0:
raise RuntimeError("dvz_volume_set_alpha_stops() failed")
if dvz.dvz_visual_set_scale(visual, b"color", scale) != 0:
raise RuntimeError("dvz_visual_set_scale(volume) failed")
def _configure_volume(visual) -> None:
if dvz.dvz_visual_set_alpha_mode(visual, dvz.DVZ_ALPHA_BLENDED) != 0:
raise RuntimeError("dvz_visual_set_alpha_mode(volume) failed")
bmin = (ctypes.c_double * 3)(*VOLUME_BOUNDS_MIN)
bmax = (ctypes.c_double * 3)(*VOLUME_BOUNDS_MAX)
if dvz.dvz_volume_set_bounds(visual, bmin, bmax) != 0:
raise RuntimeError("dvz_volume_set_bounds() failed")
if dvz.dvz_volume_set_value_range(visual, 0.0, 0.88) != 0:
raise RuntimeError("dvz_volume_set_value_range() failed")
if dvz.dvz_volume_set_opacity(visual, 1.0) != 0:
raise RuntimeError("dvz_volume_set_opacity() failed")
if dvz.dvz_volume_set_step_count(visual, 128) != 0:
raise RuntimeError("dvz_volume_set_step_count() failed")
if dvz.dvz_volume_set_render_mode(visual, dvz.DVZ_VOLUME_RENDER_MIP) != 0:
raise RuntimeError("dvz_volume_set_render_mode() failed")
def _add_volume(scene, panel):
field = dvz.dvz_sampled_field_from_array(
scene,
_volume_data(),
format=dvz.DVZ_FIELD_FORMAT_R8_UNORM,
semantic=dvz.DVZ_FIELD_SEMANTIC_SCALAR,
dim=dvz.DVZ_FIELD_DIM_3D,
)
volume = dvz.dvz_volume(scene, 0)
if not volume:
raise RuntimeError("dvz_volume() failed")
if dvz.dvz_visual_set_field(volume, b"field", field) != 0:
raise RuntimeError("dvz_visual_set_field(volume) failed")
_configure_volume(volume)
_attach_transfer(scene, volume)
ex.add_visual(panel, volume)
return volume
def _add_boundary_box(scene, panel):
xmin, ymin, zmin = VOLUME_BOUNDS_MIN
xmax, ymax, zmax = VOLUME_BOUNDS_MAX
corners = np.array(
[
[xmin, ymin, zmin],
[xmax, ymin, zmin],
[xmax, ymax, zmin],
[xmin, ymax, zmin],
[xmin, ymin, zmax],
[xmax, ymin, zmax],
[xmax, ymax, zmax],
[xmin, ymax, zmax],
],
dtype=np.float32,
)
edges = np.array(
[
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
],
dtype=np.uint32,
)
starts = corners[edges[:, 0]]
ends = corners[edges[:, 1]]
colors = np.tile(np.array([[ex.CYAN.r, ex.CYAN.g, ex.CYAN.b, 46]], dtype=np.uint8), (12, 1))
widths = np.full(BOX_SEGMENTS, 1.0, dtype=np.float32)
box = dvz.dvz_segment(scene, 0)
if not box:
raise RuntimeError("dvz_segment() failed")
if dvz.dvz_visual_set_data_many(
box,
{
"position_start": starts,
"position_end": ends,
"color": colors,
"stroke_width_px": widths,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(box) failed")
if dvz.dvz_segment_set_caps(box, dvz.DVZ_SEGMENT_CAP_ROUND, dvz.DVZ_SEGMENT_CAP_ROUND) != 0:
raise RuntimeError("dvz_segment_set_caps(box) failed")
if dvz.dvz_visual_set_alpha_mode(box, dvz.DVZ_ALPHA_BLENDED) != 0:
raise RuntimeError("dvz_visual_set_alpha_mode(box) failed")
ex.add_visual(panel, box)
return box
def _add_rotation_animation(scene, visual, state: VolumeState, attr: str, controller=None) -> None:
rotation_desc = dvz.dvz_track_rotation_desc()
rotation_desc.axis[:] = (0.0, 1.0, 0.0)
rotation_desc.speed_rad_per_sec = 1.0
rotation = dvz.dvz_track_rotation(ctypes.byref(rotation_desc))
if not rotation:
raise RuntimeError("dvz_track_rotation() failed")
setattr(state, attr, rotation)
transform_desc = dvz.dvz_transform_motion_desc()
transform_desc.rotation = rotation
animation = dvz.dvz_anim_visual_transform(scene, visual, ctypes.byref(transform_desc))
if not animation:
raise RuntimeError("dvz_anim_visual_transform() failed")
if controller is not None and dvz.dvz_anim_set_interaction_policy(
animation,
controller,
dvz.DVZ_ANIM_INTERACTION_PAUSE,
0.0,
) != 0:
raise RuntimeError("dvz_anim_set_interaction_policy() failed")
if dvz.dvz_anim_set_speed(animation, ROTATION_SPEED_RAD_PER_SEC) != 0:
raise RuntimeError("dvz_anim_set_speed() failed")
if dvz.dvz_anim_start(animation, 0.0) != 0:
raise RuntimeError("dvz_anim_start() failed")
if attr == "volume_rotation":
state.volume_animation = animation
else:
state.box_animation = animation
def _build_scene():
scene, figure, panel = ex.scene_panel()
_set_default_3d_camera(panel)
volume = _add_volume(scene, panel)
box = _add_boundary_box(scene, panel)
state = VolumeState()
return scene, figure, panel, volume, box, state
def _configure_view(view, scene, panel, volume, box, state: VolumeState) -> None:
controller = dvz.dvz_arcball(scene, None)
if not controller:
raise RuntimeError("dvz_arcball() failed")
if dvz.dvz_view_bind_controller(view, panel, controller, dvz.DVZ_DIM_MASK_XYZ) != 0:
raise RuntimeError("dvz_view_bind_controller(arcball) failed")
_add_rotation_animation(scene, volume, state, "volume_rotation", controller)
_add_rotation_animation(scene, box, state, "box_rotation", controller)
def _run(scene, figure, panel, volume, box, state: VolumeState) -> None:
app = dvz.dvz_app(scene)
if not app:
dvz.dvz_scene_destroy(scene)
state.destroy_tracks()
raise RuntimeError("dvz_app() failed")
try:
view = dvz.dvz_view_window(app, figure, ex.WIDTH, ex.HEIGHT, b"Volume")
if not view:
raise RuntimeError("dvz_view_window() failed")
_configure_view(view, scene, panel, volume, box, state)
ex.run_app(app, view)
finally:
dvz.dvz_app_destroy(app)
dvz.dvz_scene_destroy(scene)
state.destroy_tracks()
def main() -> None:
scene, figure, panel, volume, box, state = _build_scene()
_run(scene, figure, panel, volume, box, state)
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
*/
/* volume - This example volume-renders a generated 3D gyroid scalar field.
*
* What to look for: the FIELD_SIZE cubed data array is attached as a 3D sampled field, with color
* and alpha scales controlling which values remain visible. Compare the rotating volume with the
* boundary box to understand the data bounds, transfer-function opacity, and maximum-intensity
* projection style used for volumetric microscopy, simulation, or tomography data.
*
* Scenario: visuals_volume
* Style: visuals, graphite_cyan, 1280x720 window target
*
* Build: just example-c visuals/volume
* Run: ./build/examples/c/visuals/volume --live
* Smoke: ./build/examples/c/visuals/volume --png
* Video: ./build/examples/c/visuals/volume --offscreen-record 360
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "_alloc.h"
#include "_assertions.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
#include "runner/scenario_runner.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define FIELD_SIZE 128u
#define BOX_SEGMENTS 12u
#define ROTATION_SPEED_RAD_PER_SEC 0.16f
static const float TAU = 6.28318530718f;
static const double VOLUME_BOUNDS_MIN[3] = {-0.88, -0.74, -1.04};
static const double VOLUME_BOUNDS_MAX[3] = {+0.88, +0.74, +1.04};
/*************************************************************************************************/
/* Structs */
/*************************************************************************************************/
typedef struct VolumeState
{
DvzTrack* volume_rotation;
DvzTrack* box_rotation;
DvzAnimation* volume_animation;
DvzAnimation* box_animation;
} VolumeState;
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
/**
* Clamp a scalar to [0, 1].
*
* @param value input value
* @return clamped value
*/
static float _clamp01(float value)
{
if (value < 0.0f)
return 0.0f;
if (value > 1.0f)
return 1.0f;
return value;
}
/**
* Convert a normalized scalar to an 8-bit value.
*
* @param value normalized value
* @return clamped 8-bit value
*/
static uint8_t _u8(float value) { return (uint8_t)(255.0f * _clamp01(value) + 0.5f); }
/**
* Smooth Hermite interpolation between two edges.
*
* @param edge0 lower edge
* @param edge1 upper edge
* @param value input value
* @return smooth transition value
*/
static float _smoothstep(float edge0, float edge1, float value)
{
if (edge0 == edge1)
return value < edge0 ? 0.0f : 1.0f;
const float t = _clamp01((value - edge0) / (edge1 - edge0));
return t * t * (3.0f - 2.0f * t);
}
/**
* Return one triply periodic gyroid implicit value.
*
* @param x normalized centered x coordinate
* @param y normalized centered y coordinate
* @param z normalized centered z coordinate
* @return gyroid field value
*/
static float _gyroid_value(float x, float y, float z)
{
const float scale = 1.16f * TAU;
const float gx = scale * x;
const float gy = scale * y;
const float gz = scale * z;
return sinf(gx) * cosf(gy) + sinf(gy) * cosf(gz) + sinf(gz) * cosf(gx);
}
/**
* Return a bounded gyroid sheet density.
*
* @param x normalized centered x coordinate
* @param y normalized centered y coordinate
* @param z normalized centered z coordinate
* @return normalized scalar sample
*/
static float _sample_volume(float x, float y, float z)
{
const float g0 = _gyroid_value(x, y, z);
const float g1 = _gyroid_value(x + 0.035f, y - 0.020f, z + 0.045f);
const float sheet = expf(-(g0 * g0) / (2.0f * 0.115f * 0.115f));
const float shoulder =
expf(-((fabsf(g1) - 0.34f) * (fabsf(g1) - 0.34f)) / (2.0f * 0.135f * 0.135f));
const float rx = x / 0.92f;
const float ry = y / 0.86f;
const float rz = z / 0.96f;
const float r = sqrtf(rx * rx + ry * ry + rz * rz);
const float envelope = 1.0f - _smoothstep(0.82f, 1.00f, r);
const float rim = _smoothstep(0.72f, 0.94f, r) * (1.0f - _smoothstep(0.96f, 1.03f, r));
const float directional_light = 0.78f + 0.22f * _clamp01(0.5f + 0.5f * (0.45f * x - y + z));
const float lattice = envelope * directional_light * (0.88f * sheet + 0.16f * shoulder);
return _clamp01(0.86f * lattice + 0.12f * rim * sheet);
}
/**
* Fill one deterministic bounded gyroid scalar field.
*
* @param data output R8 volume data
* @param size cubic field edge length
*/
static void _fill_volume(uint8_t* data, uint32_t size)
{
ANN(data);
ASSERT(size > 1u);
for (uint32_t z = 0; z < size; z++)
{
const float nz = 2.0f * (float)z / (float)(size - 1u) - 1.0f;
for (uint32_t y = 0; y < size; y++)
{
const float ny = 2.0f * (float)y / (float)(size - 1u) - 1.0f;
for (uint32_t x = 0; x < size; x++)
{
const float nx = 2.0f * (float)x / (float)(size - 1u) - 1.0f;
const float value = _sample_volume(nx, ny, nz);
data[(z * size + y) * size + x] = _u8(value);
}
}
}
}
/**
* Attach a curated graphite/cyan volume transfer function.
*
* @param scene scene owning scale resources
* @param visual volume visual
* @return true when the transfer resources were attached
*/
static bool _attach_transfer(DvzScene* scene, DvzVisual* visual)
{
ANN(scene);
ANN(visual);
DvzScale* scale = dvz_scale(
scene,
&(DvzScaleDesc){DVZ_STRUCT_INIT_FIELDS(DvzScaleDesc), .kind = DVZ_SCALE_CONTINUOUS});
if (scale == NULL)
return false;
dvz_scale_set_domain(scale, 0.0, 1.0);
DvzColormap* colormap = dvz_colormap(scene, NULL);
if (colormap == NULL)
return false;
DvzColormapStop stops[8] = {
{.position = 0.00, .rgba = {14, 17, 23, 255}},
{.position = 0.10, .rgba = {14, 17, 23, 255}},
{.position = 0.24, .rgba = {18, 58, 96, 255}},
{.position = 0.40, .rgba = {44, 166, 209, 255}},
{.position = 0.56, .rgba = {76, 201, 240, 255}},
{.position = 0.74, .rgba = {128, 255, 219, 255}},
{.position = 0.92, .rgba = {238, 252, 232, 255}},
{.position = 1.00, .rgba = {255, 183, 3, 255}},
};
dvz_colormap_set_stops(colormap, stops, 8);
dvz_scale_set_colormap(scale, colormap);
DvzVolumeAlphaStop alpha[7] = {
{.position = 0.00, .alpha = 0.00f}, {.position = 0.09, .alpha = 0.00f},
{.position = 0.18, .alpha = 0.08f}, {.position = 0.32, .alpha = 0.32f},
{.position = 0.52, .alpha = 0.62f}, {.position = 0.74, .alpha = 0.86f},
{.position = 1.00, .alpha = 0.98f},
};
if (dvz_volume_set_alpha_stops(visual, alpha, 7) != 0)
return false;
return dvz_visual_set_scale(visual, "color", scale) == 0;
}
/**
* Configure visual-family volume rendering defaults.
*
* @param visual volume visual
* @return true when all retained controls were applied
*/
static bool _configure_volume(DvzVisual* visual)
{
ANN(visual);
if (dvz_visual_set_alpha_mode(visual, DVZ_ALPHA_BLENDED) != 0)
return false;
if (dvz_volume_set_bounds(visual, VOLUME_BOUNDS_MIN, VOLUME_BOUNDS_MAX) != 0)
return false;
if (dvz_volume_set_value_range(visual, 0.0, 0.88) != 0)
return false;
if (dvz_volume_set_opacity(visual, 1.0f) != 0)
return false;
if (dvz_volume_set_step_count(visual, 128u) != 0)
return false;
if (dvz_volume_set_render_mode(visual, DVZ_VOLUME_RENDER_MIP) != 0)
return false;
return true;
}
/**
* Add a subtle bounding box around the rendered volume.
*
* @param scene scene owning the segment visual
* @param panel panel receiving the visual
* @param out optional created visual output
* @return true when the boundary was added
*/
static bool _add_boundary_box(DvzScene* scene, DvzPanel* panel, DvzVisual** out)
{
ANN(scene);
ANN(panel);
const float xmin = (float)VOLUME_BOUNDS_MIN[0], xmax = (float)VOLUME_BOUNDS_MAX[0];
const float ymin = (float)VOLUME_BOUNDS_MIN[1], ymax = (float)VOLUME_BOUNDS_MAX[1];
const float zmin = (float)VOLUME_BOUNDS_MIN[2], zmax = (float)VOLUME_BOUNDS_MAX[2];
const vec3 corners[8] = {
{xmin, ymin, zmin}, {xmax, ymin, zmin}, {xmax, ymax, zmin}, {xmin, ymax, zmin},
{xmin, ymin, zmax}, {xmax, ymin, zmax}, {xmax, ymax, zmax}, {xmin, ymax, zmax},
};
const uint32_t edges[BOX_SEGMENTS][2] = {
{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6},
{6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7},
};
vec3 start[BOX_SEGMENTS] = {{0}};
vec3 end[BOX_SEGMENTS] = {{0}};
DvzColor colors[BOX_SEGMENTS] = {{0}};
float widths[BOX_SEGMENTS] = {0};
DvzColor accent = example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY);
for (uint32_t i = 0; i < BOX_SEGMENTS; i++)
{
memcpy(start[i], corners[edges[i][0]], sizeof(vec3));
memcpy(end[i], corners[edges[i][1]], sizeof(vec3));
colors[i] = dvz_color_rgba(accent.r, accent.g, accent.b, 46);
widths[i] = 1.0f;
}
DvzVisual* box = dvz_segment(scene, 0);
if (box == NULL)
return false;
DvzVisualDataUpdate updates[] = {
{.attr_name = "position_start", .data = start, .item_count = BOX_SEGMENTS},
{.attr_name = "position_end", .data = end, .item_count = BOX_SEGMENTS},
{.attr_name = "color", .data = colors, .item_count = BOX_SEGMENTS},
{.attr_name = "stroke_width_px", .data = widths, .item_count = BOX_SEGMENTS},
};
if (dvz_visual_set_data_many(box, updates, 4) != 0)
return false;
if (dvz_segment_set_caps(box, DVZ_SEGMENT_CAP_ROUND, DVZ_SEGMENT_CAP_ROUND) != 0)
return false;
if (dvz_visual_set_alpha_mode(box, DVZ_ALPHA_BLENDED) != 0)
return false;
if (dvz_panel_add_visual(panel, box, NULL) != 0)
return false;
if (out != NULL)
*out = box;
return true;
}
/**
* Destroy the retained volume visual scenario state.
*
* @param state scenario state
*/
static void _volume_state_destroy(VolumeState* state)
{
if (state == NULL)
return;
dvz_track_destroy(state->box_rotation);
dvz_track_destroy(state->volume_rotation);
dvz_free(state);
}
/*************************************************************************************************/
/* Scenario callbacks */
/*************************************************************************************************/
/**
* Initialize the retained volume visual scenario.
*
* @param ctx scenario context
* @param out_user scenario state output
* @return whether initialization succeeded
*/
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
uint8_t* data = NULL;
if (ctx == NULL)
return false;
if (out_user != NULL)
*out_user = NULL;
bool ok = false;
VolumeState* state = (VolumeState*)dvz_calloc(1, sizeof(*state));
if (state == NULL)
return false;
if (out_user != NULL)
*out_user = state;
ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
EXAMPLE_CHECK(ctx->figure != NULL, "dvz_figure() failed");
DvzPanel* panel = dvz_panel_full(ctx->figure);
EXAMPLE_CHECK(panel != NULL, "dvz_panel_full() failed");
example_graphite_cyan_set_panel_background(panel);
EXAMPLE_CHECK(example_set_default_3d_camera(panel, 1.15f), "dvz_panel_set_camera_desc() failed");
const uint64_t bytes = (uint64_t)FIELD_SIZE * FIELD_SIZE * FIELD_SIZE;
data = (uint8_t*)dvz_malloc(bytes);
EXAMPLE_CHECK(data != NULL, "volume allocation failed");
_fill_volume(data, FIELD_SIZE);
DvzSampledField* field = dvz_sampled_field(
ctx->scene, &(DvzSampledFieldDesc){
DVZ_STRUCT_INIT_FIELDS(DvzSampledFieldDesc), .dim = DVZ_FIELD_DIM_3D,
.format = DVZ_FIELD_FORMAT_R8_UNORM, .semantic = DVZ_FIELD_SEMANTIC_SCALAR,
.width = FIELD_SIZE, .height = FIELD_SIZE, .depth = FIELD_SIZE});
EXAMPLE_CHECK(field != NULL, "dvz_sampled_field() failed");
ok = dvz_sampled_field_set_data(
field, &(DvzFieldDataView){
DVZ_STRUCT_INIT_FIELDS(DvzFieldDataView), .data = data,
.bytes_per_row = FIELD_SIZE, .rows_per_image = FIELD_SIZE}) == DVZ_OK;
EXAMPLE_CHECK(ok, "dvz_sampled_field_set_data() failed");
dvz_free(data);
data = NULL;
DvzVisual* volume = dvz_volume(ctx->scene, 0);
EXAMPLE_CHECK(volume != NULL, "dvz_volume() failed");
ok = dvz_visual_set_field(volume, "field", field) == DVZ_OK;
EXAMPLE_CHECK(ok, "dvz_visual_set_field() failed");
ok = _configure_volume(volume);
EXAMPLE_CHECK(ok, "volume configuration failed");
ok = _attach_transfer(ctx->scene, volume);
EXAMPLE_CHECK(ok, "volume transfer setup failed");
int rc = dvz_panel_add_visual(panel, volume, NULL);
EXAMPLE_CHECK(rc == 0, "dvz_panel_add_visual() failed");
DvzVisual* box = NULL;
ok = _add_boundary_box(ctx->scene, panel, &box);
EXAMPLE_CHECK(ok, "boundary box setup failed");
DvzController* arcball_controller = dvz_arcball(ctx->scene, NULL);
EXAMPLE_CHECK(arcball_controller != NULL, "dvz_arcball() failed");
EXAMPLE_CHECK(
dvz_scenario_bind_controller(ctx, panel, arcball_controller, DVZ_DIM_MASK_XYZ) == 0,
"dvz_scenario_bind_controller() failed");
DvzTrackRotationDesc rotation_desc = dvz_track_rotation_desc();
rotation_desc.axis[1] = 1.0f;
rotation_desc.speed_rad_per_sec = 1.0f;
state->volume_rotation = dvz_track_rotation(&rotation_desc);
EXAMPLE_CHECK(state->volume_rotation != NULL, "dvz_track_rotation(volume) failed");
DvzTransformMotionDesc transform_desc = dvz_transform_motion_desc();
transform_desc.rotation = state->volume_rotation;
state->volume_animation = dvz_anim_visual_transform(ctx->scene, volume, &transform_desc);
EXAMPLE_CHECK(state->volume_animation != NULL, "dvz_anim_visual_transform(volume) failed");
dvz_anim_set_interaction_policy(
state->volume_animation, arcball_controller, DVZ_ANIM_INTERACTION_PAUSE, 0.0);
dvz_anim_set_speed(state->volume_animation, ROTATION_SPEED_RAD_PER_SEC);
dvz_anim_start(state->volume_animation, 0.0);
state->box_rotation = dvz_track_rotation(&rotation_desc);
EXAMPLE_CHECK(state->box_rotation != NULL, "dvz_track_rotation(box) failed");
transform_desc = dvz_transform_motion_desc();
transform_desc.rotation = state->box_rotation;
state->box_animation = dvz_anim_visual_transform(ctx->scene, box, &transform_desc);
EXAMPLE_CHECK(state->box_animation != NULL, "dvz_anim_visual_transform(box) failed");
dvz_anim_set_interaction_policy(
state->box_animation, arcball_controller, DVZ_ANIM_INTERACTION_PAUSE, 0.0);
dvz_anim_set_speed(state->box_animation, ROTATION_SPEED_RAD_PER_SEC);
dvz_anim_start(state->box_animation, 0.0);
ok = true;
cleanup:
dvz_free(data);
return ok;
}
/**
* Destroy the retained volume visual scenario state.
*
* @param ctx scenario context
* @param user scenario state
*/
static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
(void)ctx;
_volume_state_destroy((VolumeState*)user);
}
/**
* Return the retained volume visual scenario.
*
* @return scenario specification
*/
static DvzScenarioSpec _volume_scenario(void)
{
return (DvzScenarioSpec){
.id = "visuals_volume",
.title = "Volume",
.width = WIDTH,
.height = HEIGHT,
.fps = 60.0,
.init = _scenario_init,
.destroy = _scenario_destroy,
};
}
/*************************************************************************************************/
/* Functions */
/*************************************************************************************************/
/**
* Run the retained volume visual through the native scenario runner.
*
* @param argc command-line argument count
* @param argv command-line argument vector
* @return process exit code
*/
int main(int argc, char** argv)
{
DvzScenarioSpec spec = _volume_scenario();
return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
Example details
- ID:
visuals_volume - Category:
visual - Lane:
visuals - Status:
supported - Source:
examples/c/visuals/volume.c - Approved adaptation starter:
yes - Reference: Volume visual family
- Python source:
examples/python/gallery/visuals/volume.py - Python adaptation: Available
- Browser support: Planned
- Browser capability tags:
volume,sampled-field,texture-3d,arcball - Validation:
smoke+screenshot
Tags
sampled-field, volume, transfer-function, boundary-box, arcball, capture
Data
| Field | Value |
|---|---|
kind |
synthetic |