view.h
📄 View in API Reference (Doxygen) · View on basilisk.fr
Requires: view.h · utils.h · input.h · vertexbuffer.h · draw.h · parse.h · draw_get.h
Test cases (25): axi_rising_bubble, basilisk, bleck, couette, cylinders, fall, hydrostatic2, hydrostatic3, marangoni, meniscus3D, neumann, neumann-axi, neumann3D, no-coalescence, plateau, poiseuille45, porous, redistance-ellipse, sessile3D, shrinking, starting, taylor, view, viscodrop, wannier
Examples (12): atomisation, breaking, bubble, distance, gaussian-ns, isotropic, naca2414-starting, porous3D, sphere, swasi, tangaroa, yang
Basilisk View
This module defines functions which compute various graphical representations ("drawings") of Basilisk fields, including reconstructed Volume-Of-Fluid facets and colorscale representations of cross-sections of scalar fields. These representations are rendered using OpenGL and can be saved in various formats (PPM, Gnuplot, OBJ, KML, PDF, SVG etc.).
Installation
In contrast with other Basilisk modules, this module relies on additional libraries which needs to be installed and linked with the Basilisk program. See [INSTALL#visualisation]() for instructions.
Usage
A simple example would look like:
~~~literatec ... #include "view.h" ... event image (t = 1) { clear(); draw_vof ("f"); box(); save ("image.ppm"); } ~~~
The *clear()* function resets the image, *draw_vof()* and *box()* add two graphical representations and *save()* saves the resulting image in PPM format. See User functions for a detailed documentation.
The resulting program needs to be linked with the appropriate libraries. This can be done automatically using something like:
~~~bash qcc -autolink -Wall -O2 program.c -o program -lm ~~~
or manually using e.g.:
~~~bash qcc -Wall -O2 program.c -o program -L$BASILISK/gl -lglutils -lfb_tiny -lm ~~~
Implementation
We include the various helper functions defined either by the system or by the Basilisk libraries in gl/.
#include <gl/framebuffer.h>
#include <gl/trackball.h>
#include <gl/utils.h>
#pragma autolink -L$BASILISK/gl -lglutils $OPENGLIBS
#include "utils.h" [api]
#include "input.h" [api]
A cache of "compiled" expressions
The cache has a maximum size and least-used expressions are discarded first.
typedef struct {
char * expr;
scalar s;
} cexpr;
static scalar get_cexpr (cexpr * cache, const char * expr)
{
cexpr * c = cache;
while (c->expr) {
if (!strcmp (c->expr, expr)) {
// move this expression to the top of the cache.
// the "top" is the last element.
cexpr tmp = *c;
while ((c + 1)->expr)
*c = *(c + 1), c++;
*c = tmp;
return c->s;
}
c++;
}
return (scalar){-1};
}
static cexpr * add_cexpr (cexpr * cache, int maxlen,
const char * expr, scalar s)
{
cexpr * c = cache;
while (c->expr) c++;
int len = c - cache;
if (len < maxlen) {
cache = realloc (cache, sizeof(cexpr)*(len + 2));
c = &cache[len];
}
else {
// discard first expression
c = cache;
free (c->expr);
scalar s = c->s;
delete ({s});
// shift remaining expressions
while ((c + 1)->expr)
*c = *(c + 1), c++;
}
c->expr = strdup (expr);
c->s = s;
(c + 1)->expr = NULL;
return cache;
}
static void free_cexpr (cexpr * cache)
{
cexpr * c = cache;
while (c->expr) {
free (c->expr);
scalar s = c->s;
delete ({s});
c++;
}
free (cache);
}
The *bview* class
Contains the definition of the current view.
typedef void (* MapFunc) (coord *);
struct _bview {
float tx, ty, sx, sy, sz;
float quat[4];
float fov;
float tz, near, far;
bool gfsview; // rotate axis to match gfsview
bool reversed; // reverse normals
float bg[3];
float lc;
float res;
unsigned width, height, samples;
int maxlevel; // the maximum level to draw
framebuffer * fb;
Frustum frustum; // the view frustum
MapFunc map; // an optional mapping function
int ni; // number of items drawn
bool active;
cexpr * cache; // a cache of compiled expressions
int maxlen; // the maximum number of cached expressions
};
typedef struct _bview bview;
The allocator method.
bview * bview_new()
{
bview * p = qcalloc (1, bview);
p->tx = p->ty = 0;
p->sx = p->sy = p->sz = 1.;
p->quat[0] = p->quat[1] = p->quat[2] = 0; p->quat[3] = 1;
p->fov = 24.;
gl_trackball (p->quat, 0.0, 0.0, 0.0, 0.0);
#if dimension <= 2
p->bg[0] = 1; p->bg[1] = 1; p->bg[2] = 1;
#else
p->bg[0] = 0.3; p->bg[1] = 0.4; p->bg[2] = 0.6;
#endif
p->res = 1.;
p->lc = 0.004;
p->samples = 4;
p->width = 600*p->samples, p->height = 600*p->samples;
p->maxlevel = -1;
/* OpenGL somehow generates floating-point exceptions... turn them off */
disable_fpe (FE_DIVBYZERO|FE_INVALID);
p->fb = framebuffer_new (p->width, p->height);
init_gl();
p->active = false;
enable_fpe (FE_DIVBYZERO|FE_INVALID);
return p;
}
The destructor method.
void bview_destroy (bview * p)
{
framebuffer_destroy (p->fb);
if (p->cache)
free_cexpr (p->cache);
free (p);
}
For the moment there is a single (static) current view.
static bview * _view = NULL;
The current view needs to be destroyed when we exit Basilisk. This is done by adding this callback to the free_solver() lists of destructors.
static void destroy_view()
{
assert (_view);
bview_destroy (_view);
}
bview * get_view() {
if (!_view) {
_view = bview_new();
free_solver_func_add (destroy_view);
}
return _view;
}
The main drawing function.
static void redraw (bool clear = true) {
bview * view = get_view();
/* OpenGL somehow generates floating-point exceptions... turn them off */
disable_fpe (FE_DIVBYZERO|FE_INVALID);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
if (view->far <= view->near) { // "traditional" camera parameters
double max = 2.;
gl_perspective (view->fov, view->width/(float)view->height, 1., 1. + 2.*max);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glTranslatef (view->tx, view->ty, - (1. + max));
}
else { // camera parameters compatible with interactive Basilisk View
gl_perspective (view->fov, view->width/(float)view->height,
view->near, view->far);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glTranslatef (view->tx, view->ty, view->tz);
}
GLfloat m[4][4];
gl_build_rotmatrix (m, view->quat);
glMultMatrixf (&m[0][0]);
if (view->gfsview) { // rotate to match gfsview parameters
m[0][0] = 0., m[0][1] = 0., m[0][2] = -1.;
m[1][0] = 0., m[1][1] = -1., m[1][2] = 0.;
m[2][0] = 1., m[2][1] = 0., m[2][2] = 0.;
glMultMatrixf (&m[0][0]);
}
glScalef (view->sx/L0, view->sy/L0, view->sz/L0);
if (clear) {
glClearColor (view->bg[0], view->bg[1], view->bg[2], 0.);
glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}
gl_get_frustum (&view->frustum);
view->active = true;
view->ni = 0;
}
This is called by graphics primitives before drawing.
bview * draw() {
bview * view = get_view();
if (!view->active)
redraw();
else
/* OpenGL somehow generates floating-point exceptions... turn them off
See also: https://bugs.freedesktop.org/show_bug.cgi?id=108856 */
disable_fpe (FE_DIVBYZERO|FE_INVALID);
glMatrixMode (GL_PROJECTION);
glTranslatef (0, 0, - 1e-4); // next object is drawn below the current one
return view;
}
Helper function for parallel image composition
compose_image() returns an image buffer made by composition of the framebuffer images on each of the MPI processes.
typedef void * pointer; // fixme: trace is confused by pointers
#if !_MPI
trace
static pointer compose_image (bview * view) {
return framebuffer_image((view)->fb);
}
#else // _MPI
#if dimension <= 2
typedef struct {
GLubyte a[4];
} RGBA;
static void compose_image_op (void * pin, void * pout, int * len,
MPI_Datatype * dptr)
{
RGBA * rin = pin, * out = pout;
for (int i = 0; i < *len; i++,rin++,out++)
if (out->a[3] == 0)
*out = *rin;
}
trace
static pointer compose_image (bview * view)
{
unsigned char * image = framebuffer_image (view->fb);
assert (image);
if (npe() > 1) {
MPI_Op op;
MPI_Op_create (compose_image_op, true, &op);
MPI_Datatype rgba;
MPI_Type_contiguous (4, MPI_BYTE, &rgba);
MPI_Type_commit (&rgba);
int size = view->width*view->height;
if (pid() == 0)
MPI_Reduce (MPI_IN_PLACE, image, size, rgba, op, 0, MPI_COMM_WORLD);
else
MPI_Reduce (image, image, size, rgba, op, 0, MPI_COMM_WORLD);
MPI_Op_free (&op);
MPI_Type_free (&rgba);
}
return image;
}
#else /* 3D */
typedef struct {
GLubyte a[4];
float depth;
} RGBA;
static void compose_image_op (void * pin, void * pout, int * len,
MPI_Datatype * dptr)
{
RGBA * rin = pin, * out = pout;
for (int i = 0; i < *len; i++,rin++,out++)
if (out->depth > rin->depth)
*out = *rin;
}
trace
static pointer compose_image (bview * view)
{
unsigned char * image = framebuffer_image (view->fb);
assert (image);
if (npe() > 1) {
MPI_Op op;
MPI_Op_create (compose_image_op, true, &op);
MPI_Datatype rgba;
MPI_Type_create_struct (2,
(int[]){4,1},
(MPI_Aint[]){0,4},
(MPI_Datatype[]){MPI_BYTE, MPI_FLOAT},
&rgba);
MPI_Type_commit (&rgba);
float * depth = framebuffer_depth (view->fb);
int size = view->width*view->height;
RGBA * buf = malloc (size*sizeof(RGBA));
unsigned char * ptr = image;
float * dptr = depth;
for (int i = 0; i < size; i++) {
for (int j = 0; j < 4; j++)
buf[i].a[j] = *ptr++;
buf[i].depth = *dptr++;
}
if (pid() == 0) {
MPI_Reduce (MPI_IN_PLACE, buf, size, rgba, op, 0, MPI_COMM_WORLD);
unsigned char * ptr = image;
for (int i = 0; i < size; i++)
for (int j = 0; j < 4; j++)
*ptr++ = buf[i].a[j];
}
else
MPI_Reduce (buf, buf, size, rgba, op, 0, MPI_COMM_WORLD);
free (buf);
MPI_Op_free (&op);
MPI_Type_free (&rgba);
}
return image;
}
#endif /* 3D */
#endif /* _MPI */
#include "vertexbuffer.h"
User functions
Drawing user functions are defined in [draw.h]().
#include "draw.h" [api]
*load()*: read drawing commands from a file or buffer
The commands are calls of user functions. They can be read from a file defined by *fp* or *file*, or from the memory buffer *buf*.
Besides the *load()*, *save()* and drawing functions defined in [draw.h](), valid drawing commands also include:
bool load (FILE * fp = NULL, char * file = NULL, Array * buf = NULL);
static void bview_draw (bview * view)
{
if (!view->active)
return;
view->active = false;
glFinish ();
enable_fpe (FE_DIVBYZERO|FE_INVALID);
}
*save()*: saves drawing to a file in various formats
The file to write to is given either using its name *file* or the file pointer *fp*. If neither is specified, the default is *stdout*.
If *file* is used, options for 'convert' or 'ffmpeg' can be given in *opt*.
The format to use is given either explicitly using *format*, or, if a *file* name is given, using the file name extension (i.e. *.ppm*, *.bv*, etc.). If neither is specified, the default is "ppm".
For vector graphics, the base line width can be specified using *lw*. The default is one.
The recognised file formats are:
- "ppm": Portable PixMap
format. A basic uncompressed image format.
- "png", "jpg": Compressed image formats. Will only work if the
*convert* command from ImageMagick is installed on the system.
- "mp4", "gif", "ogv": Compressed animation formats. Will only work if
ffmpeg is installed on the system.
The following formats are no longer supported, but this could be fixed later:
- "bv": Basilisk View format. Saves all Basilisk function calls necessary to
reproduce the figure. Use together with load().
- "gnu": Gnuplot format. Saves a vector graphics (3D) representation
of the objects.
- "obj": Wavefront 3D object format. Can be read by a number of 3D visualisation tools.
- "kml": Keyhole Markup Language. Can be used with Google Earth and other GIS.
- "ps", "eps", "tex", "pdf", "svg", "pgf": the various
vector graphics formats supported by gl2ps.
Note that MPI-parallel output is only implemented for the "ppm" format at the moment. Other animation and image formats will be automatically converted to PPM when using MPI.
trace
bool save (char * file = NULL, char * format = "ppm", char * opt = NULL,
FILE * fp = NULL,
float lw = 0,
int sort = 0, int options = 0,
FILE * checksum = NULL,
bview * view = NULL)
{
if (file) {
char * s = strchr (file, '.'), * dot = s;
while (s) {
dot = s;
s = strchr (s + 1, '.');
}
if (dot)
format = dot + 1;
}
if (!view)
view = get_view();
if ((!strcmp (format, "png") && which ("convert")) ||
!strcmp (format, "jpg") ||
(file && is_animation (file))) {
bview_draw (view);
unsigned char * image = (unsigned char *) compose_image (view);
if (pid() == 0) {
FILE * fp = open_image (file, opt);
if (!fp) {
perror (file);
return false;
}
gl_write_image (fp, image, view->width, view->height, view->samples);
close_image (file, fp);
if (checksum) {
Adler32Hash hash;
a32_hash_init (&hash);
a32_hash_add (&hash, image, view->width*view->height*4*sizeof (unsigned char));
fputs ("# ", checksum);
if (file)
fprintf (checksum, "%s: ", file);
fprintf (checksum, "checksum: %08lx\n", (unsigned long) a32_hash (&hash));
}
}
return true;
}
if (file && (fp = fopen (file, "w")) == NULL) {
perror (file);
return false;
}
if (!fp)
fp = stdout;
if (!strcmp (format, "ppm")) {
bview_draw (view);
unsigned char * image = (unsigned char *) compose_image (view);
if (pid() == 0)
gl_write_image (fp, image, view->width, view->height, view->samples);
}
else if (!strcmp (format, "png")) {
bview_draw (view);
unsigned char * image = (unsigned char *) compose_image (view);
if (pid() == 0)
gl_write_image_png (fp, image, view->width, view->height, view->samples);
}
else if (!strcmp (format, "bv")) {
#if 1 // fixme: not implemented yet
fprintf (ferr, "save(): error: the '%s' format is no longer supported\n",
format);
#else
assert (history);
fprintf (fp,
"view (fov = %g, quat = {%g,%g,%g,%g}, "
"tx = %g, ty = %g, "
"bg = {%g,%g,%g}, "
"width = %d, height = %d, samples = %d"
");\n",
view->fov,
view->quat[0], view->quat[1], view->quat[2], view->quat[3],
view->tx, view->ty,
view->bg[0], view->bg[1], view->bg[2],
view->width/view->samples, view->height/view->samples,
view->samples);
fwrite (history->p, 1, history->len, fp);
#endif
}
else if (!strcmp (format, "gnu") ||
!strcmp (format, "obj") ||
!strcmp (format, "kml") ||
!strcmp (format, "ps") ||
!strcmp (format, "eps") ||
!strcmp (format, "tex") ||
!strcmp (format, "pdf") ||
!strcmp (format, "svg") ||
!strcmp (format, "pgf"))
fprintf (ferr, "save(): error: the '%s' format is no longer supported\n",
format);
else {
fprintf (ferr, "save(): unknown format '%s'\n", format);
if (file) {
fclose (fp);
remove (file);
}
return false;
}
fflush (fp);
if (file)
fclose (fp);
return true;
}
Implementation of the *load()* function.
The functions below parse a text file and perform the corresponding function calls.
static char * remove_blanks (char * line)
{
while (strchr (" \t", *line)) line++;
char * s = line, * cur = line;
bool instring = false;
while (*s != '\0' && *s != '#') {
if (*s == '"')
instring = !instring;
if (instring || !strchr (" \t", *s))
*cur++ = *s;
s++;
}
*cur = '\0';
return line;
}
#include "parse.h"
bool process_line (char * line)
{
if (line[0] == '\0')
return true;
char * s = mystrtok (remove_blanks (line), "(");
if (!s)
return true;
if (!strcmp (s, "restore")) {
char * file = NULL;
parse_params ((Params[]){{"file", pstring, &file}, {NULL}});
if (file) {
bview * view = get_view();
if (view->cache) {
free_cexpr (view->cache);
view->cache = calloc (1, sizeof (cexpr));
}
if (!restore (file = file, list = all))
fprintf (ferr, "could not restore from '%s'\n", file);
else {
restriction (all);
fields_stats();
clear();
}
}
}
else if (!strcmp (s, "dump")) {
char * file = NULL;
parse_params ((Params[]){{"file", pstring, &file}, {NULL}});
dump (file = file);
}
else if (!strcmp (s, "input_gfs")) {
char * file = NULL;
parse_params ((Params[]){{"file", pstring, &file}, {NULL}});
if (file) {
input_gfs (file = file, list = all);
restriction (all);
fields_stats();
clear();
}
}
else if (!strcmp (s, "save")) {
char * file = NULL;
parse_params ((Params[]){{"file", pstring, &file}, {NULL}});
if (file)
save (file = file);
}
else if (!strcmp (s, "load")) {
char * file = NULL;
parse_params ((Params[]){{"file", pstring, &file}, {NULL}});
if (file)
load (file = file);
}
The [draw_get.h]() file is generated automatically by [params.awk]() and contains parsing commands for the functions defined in [draw.h]().
#include "draw_get.h"
else if (!strcmp (s, "clear"))
clear();
else if (s[0] != '\n' && s[0] != '\0')
fprintf (ferr, "load(): syntax error: '%s'\n", s);
return true;
}
bool load (FILE * fp = NULL, char * file = NULL, Array * buf = NULL)
{
if (file) {
fp = fopen (file, "r");
if (!fp) {
perror (file);
return false;
}
}
if (fp) { // read lines from file
char line[256];
while (fgets (line, 256, fp) && process_line (line));
}
else if (buf) { // read lines from buffer
int i = 0;
char * s = (char *) buf->p;
while (i < buf->len) {
char * start = s;
while (i < buf->len && *s != '\n')
s++, i++;
if (*s == '\n' && ++s > start) {
char line[s - start + 1];
strncpy (line, start, s - start);
line[s - start] = '\0';
process_line (line);
}
}
}
return true;
}