HansVisual

  • Getting started
  • Basic concepts
  • Coordinate systems
  • Objects
    • Points
    • Lines
    • Triangles
    • Cubes
  • Colors
    • Color systems
    • Array of colors
    • Palettes
  • GUI
  • Bonus

HansVisual_example_1.png

Getting started

HansVisual is a C++ library for 3D data visualization. It allows the programmer to easily show different objects (points, lines, surfaces, cubes) with different properties. If what you want is to call functions, from your code, for showing graphics, this library is for you. Libraries used:

  • OpenGL 3.3 (also 4.4) (get it by updating your drivers)
  • GLEW 2.1.0
  • GLFW 3.3
  • GLM 0.9.9.5
  • ImGui 1.72b

First steps:

  • Download the source code from the repository here.
  • Build the library project using CMake (v. 3.14.4 or superior).
  • Compile the library project as a static library. This library compiles in Ubuntu (make) and Windows (MVS) .
  • Link this library to your code to be able to use it. Check the project inside the provided folder “example” which has the visualizer already linked using CMake.

“example” folder: It contains a project that serves as a helpful example of how to use this library in your code and how to write a CMakeLists.txt file to link this library to your application. Remember to indicate in its CMakeLists whether you are working on Linux or Windows.

Compilation modes (debug/release) (x86/x64): You can specify in your app’s CMakeLists which files to use for Debug or Release modes (you have to had compiled the visualizer in Debug or Release). Then, use MVS or your Ubuntu compiler to compile in Debug or Release. Also, use your compiler to compile for a certain architecture (x86, x64).

Setting up OpenGL and related libraries: See here.

Basic concepts

From your code you can call functions from this library that allows you to open a window and draw different objects in a 3 dimensional space. You can create different layers and draw different things on each one. Each layer has its own palette of colors, and you can modify it whenever you want in your code.

  • Create a visualizerClass object with zero layers and open a visualizing window.
visualizerClass visual;
visual.open_window();
  • Create a window with 3 layers of each type (points, lines, triangles, cubes). You have to specify the number of layers, the name of each layer (array of strings) and the maximum capacity of each layer (array of unsigned int).
visualizerClass visual(
     3, point_layer_names, max_points_per_layer,
     3, line_layer_names, max_lines_per_layer,
     3, triangle_layer_names, max_triangles_per_layer,
     3, cube_layer_names, max_cubes_per_layer
);
visualizerClass visual(
     3, point_layer_names, max_points_per_layer,
     3, line_layer_names, max_lines_per_layer
);
  • Add a new layer when you want (points, lines, triangles, cubes)
visual.add_layer(points, "New layer", 12);
  • Draw data in the data window.

You can send data to the data window if you want to show some written information in the visualizer window. Just call the method fill_data_window(), pass a pointer to an array of X strings to it and the number of strings from that array that you want to show. Then, the method will publish one string per line. The empty strings (“”) won’t be published.

visual.fill_data_window(array_of_strings, 7);

Coordinate systems

  • OpenGL system:

X goes to the right, Y goes up, Z goes to you.

  • Automotive system:

X goes to the front, Y goes to the left, Z goes up. If you have the coordinates in this system, you have to transform them to OpenGL coordinates (use the transform_coordinates() method) before sending them to the visualizer.

visual.transform_coordinates(&box[0][0], 9);

Objects

You can draw points, lines, triangles and cubes. You have to provide the number of elements and a pointer to the first position in an array containing the necessary elements. Optionally, you can also provide an array of labels or an array of colors.

 > Points

To draw points, you have to send and array of points (X, Y, Z) to the visualizer, such as this:

float box[9][3] = {
     -3,  3,  3,
     -3, -3,  3,
      3, -3,  3,
      3,  3,  3,
      3,  3, -3,
      3, -3, -3,
     -3, -3, -3,
     -3,  3, -3,
     -3,  3,  3 };
  • Draw 12 points in the layer “Points 1”
visual.send_points("Points 1", 12, &pnts[0][0]);
  • The same as before, but this time include an array with labels (ideally, starting from 0) for each point (point_categories) and tell the visualizer the type of this array (categories) (it is categories by default). The layer will use its own palette to paint each set of points with the same label with the same color.
visual.send_points("Points 1", 12, &pnts[0][0], points_categories);
visual.send_points("Points 1", 12, &pnts[0][0], points_categories, nullptr, categories);
  • Points selection: The variable that contains nullptr is used for passing a pointer to an array of strings, the same size as the number of points you are sending. When you select points (right mouse button + drag and drop), the strings corresponding to the selected points are printed in the console (useful for showing data about certain points). If you don’t want to send an array, just pass a nullptr, but no data will be displayed if you select points of that layer.
string points_names[12] = {"P1","P2","P3","P4","P5","P6","P7","P8","P9","P10","P11","P12"};
visual.send_points("Points 1", 12, &pnts[0][0], points_categories, points_names);
  • This time, send an array with RGB 0-1 colors. Tell the visualizer the array is of type colors.
visual.send_points("Points 2", 12, &pnts[0][0], &points_colors_RGB[0][0], nullptr, colors);
  • Send an array of labels and ask the visualizer to color the points in a gradient way. Tell the visualizer the array is of type gradient. You must provide an array of labels (any range). Also, provide the minimum and maximum values (inclusive) you want to use for the gradient (If not specified, the minimum is 0 and the maximum is 1). If a point is labeled with a value smaller than the minimum, it is colored with the same color as a point labeled with the minimum would have been; the opposite happens with a point labeled with a bigger value than the maximum
visual.send_points("Points 3", 12, &pnts[0][0], points_gradients, nullptr, gradient, 1, 12);
visual.send_points("Points 3", 12, &pnts[0][0], points_gradients, nullptr, gradient);

Gradient example: I want to color a set of points so, the higher the point, the more white it is, and the lower the point, the more black it is. So, I change the palette of the layer by one with a grey scale, starting from black and ending in white (send_palette…). Then, I send the points (send_points) with an array of labels indicating the height. I also send the maximum and minimum height, so the visualizer knows how to color the points according to the palette.

 > Lines

To draw lines, you have to send and array of points (X, Y, Z) to the visualizer. The visualizer will draw the lines that join consecutive points in the array. If you send a point which XYZ coordinates are { 1.2f, 3.4f, 5.6f }, there will happen a line jump/gap (no line will join to this point).

  • Draw lines using 111 points in the layer “Lines 1”. The number of points must include the gap-points.
visual.send_lines("Lines 1", 111, &myLines[0][0]);
  • The same as before, but this time include an array with labels (ideally, starting from 0) for each segment (a line joining two points) and tell the visualizer the type of this array (categories) (it is categories by default). The layer will use its own palette to paint each segment with the same label with the same color.
visual.send_lines("Lines 1", 111, &myLines[0][0], &lines_categories[0], categories);
visual.send_lines("Lines 1", 111, &myLines[0][0], &lines_categories[0]);
  • This time, send an array with RGB 0-1 colors. Tell the visualizer the array is of type colors. The colors array will contain a set of colors, one for each segment.
visual.send_lines("Lines 2", 111, &myLines[0][0], &lines_colors[0][0], colors);
  • Send an array of labels for each segment and ask the visualizer to color the segments in a gradient way. Tell the visualizer the array is of type gradient. You must provide an array of labels (any range). Also, provide the minimum and maximum values (inclusive) you want to use for the gradient (If not specified, the minimum is 0 and the maximum is 1). If a segment is labeled with a value smaller than the minimum, it is colored with the same color as a segment labeled with the minimum would have been; the opposite happens with a segment labeled with a bigger value than the maximum.
visual.send_lines("Lines 3", 111, &myLines[0][0], lines_gradients, gradient, 10, 109);
visual.send_lines("Lines 3", 111, &myLines[0][0], lines_gradients, gradient);

 > Triangles

To draw triangles, you have to send and array of points (X, Y, Z) to the visualizer. Every 3 points represent the vertices of a triangle. So, an array with 3 points contains 1 triangle; an array with 9 points contains 3 triangles, and so on.

  • Draw lines using 4 triangles in the layer “Triangles 1”. The array triangles will contain at least 12 points.
visual.send_triangles("Triangles 1", 4, &triangles[0][0][0]);
  • The same as before, but this time include an array with labels (ideally, starting from 0) for each triangle and tell the visualizer the type of this array (categories) (it is categories by default). The layer will use its own palette to paint each triangle with the same label with the same color.
visual.send_triangles("Triangles 1", 4, &triangles[0][0][0], &triangles_categories[0], categories);
visual.send_triangles("Triangles 1", 4, &triangles[0][0][0], &triangles_categories[0]);
  • This time, send an array with RGB 0-1 colors. Tell the visualizer the array is of type colors. The colors array will contain a set of colors, one for each triangle.
visual.send_triangles("Triangles 2", 4, &triangles[0][0][0], &triangles_colors[0][0], colors);
  • Send an array of labels for each triangle and ask the visualizer to color the segments in a gradient way. Tell the visualizer the array is of type gradient. You must provide an array of labels (any range). Also, provide the minimum and maximum values (inclusive) you want to use for the gradient (If not specified, the minimum is 0 and the maximum is 1). If a triangle is labeled with a value smaller than the minimum, it is colored with the same color as a triangle labeled with the minimum would have been; the opposite happens with a triangle labeled with a bigger value than the maximum.
visual.send_triangles("Triangles 3", 4, &triangles[0][0][0], triangles_gradients, gradient, 0, 10);
visual.send_triangles("Triangles 3", 4, &triangles[0][0][0], triangles_gradients, gradient);

 > Cubes

To draw cubes, you have to send and array of cube3D objects to the visualizer. For creating one cube3D object you must specify a few parameters:

  • Center: X, Y, Z
  • Dimensions: Width, height, length.
  • Horizontal rotation: In radians

Example of an array of 3 cubes:

cube3D myCubes[3] = {
     cube3D(0,  0, 0,  1, 1,   1,   0.0*3.1415),
     cube3D(1,  1, 1,  2, 1,   0.5, 0.3*3.1415),
     cube3D(1, -1, 0,  1, 0.5, 0.1, 0.6*3.1415)
};
  • Draw 3 cubes in the layer “Cubes 1”.
visual.send_cubes("Cubes 1", 3, myCubes);
  • The same as before, but this time include an array with labels (ideally, starting from 0) for each cube and tell the visualizer the type of this array (categories) (it is categories by default). The layer will use its own palette to paint each cube with the same label with the same color.
visual.send_cubes("Cubes 1", 3, myCubes, cubes_categories, categories);
visual.send_cubes("Cubes 1", 3, myCubes, cubes_categories);
  • This time, send an array with RGB 0-1 colors. Tell the visualizer the array is of type colors. The colors array will contain a set of colors, one for each cube.
visual.send_cubes("Cubes 2", 3, myCubes, &cubes_colors[0][0], colors);
  • Send an array of labels for each cube and ask the visualizer to color the cubes in a gradient way. Tell the visualizer the array is of type gradient. You must provide an array of labels (any range). Also, provide the minimum and maximum values (inclusive) you want to use for the gradient (If not specified, the minimum is 0 and the maximum is 1). If a cube is labeled with a value smaller than the minimum, it is colored with the same color as a cube labeled with the minimum would have been; the opposite happens with a cube labeled with a bigger value than the maximum.
visual.send_cubes("Cubes 3", 3, myCubes, cubes_gradients, gradient, 1, 12);
visual.send_cubes("Cubes 3", 3, myCubes, cubes_gradients, gradient);

Colors

 > Color systems

  • RGB 0-1: A color is defined by the values for the Red, Green and Blue. These 3 values will be in the range [0, 1]
  • RGB 0-255: The same as RGB 0-1, but the 3 values of a color will be in the range [0, 255].
  • HSV: A color is defined by the values of Hue (range [0, 360]), Saturation (range [0, 1]) and Value (range [0, 1]).

 > Arrays of colors

The arrays of colors are used for assigning whatever colors you want to each object drawn.

The array of colors you send to the previous functions contains RGB 0-1 colors. For each object you send one color, which is made of 3 floats.

float cubes_colors[3][3] = {
     0.2f, 1.0f, 0.3f,
     0.0f, 0.0f, 1.0f,
     0.5f, 1.0f, 0.5f
};
  • If your colors array is in RGB 0-255 system, you can convert it to RGB 0-1 with:
convert_RGB255toRGB(&colors_RGB255[0][0], 21);
  • If your colors array is in HSV system, you can convert it to RGB 0-1 with:
convert_HSVtoRGB(&colors_HSV[0][0], 21);

> Palettes

Each layer has its own palette. It’s used when you paint an element using the mode “categories” (points with the same label will have the same color from the palette). This is the default mode.

You can also change the palette of a certain layer by sending a new one. The palette you send may be in RGB 0-1, RGB 0-255 or HSV. The visualizer will internally convert it to RGB 0-1 if you use the proper function to send it.

  • Send a new RGB 0-1 palette of colors to replace the current one in layer “Points 3”.
visual.send_palette_RGB_01("Points 3", points, &new_palette[0][0], 21);
  • Send a new RGB 0-255 palette of colors to replace the current one in layer “Triangles 1”.
visual.send_palette_RGB("Triangles 1", triangles, &new_palette[0][0], 21);
  • Send a new HSV palette of colors to replace the current one in layer “Cubes 2”.
visual.send_palette_HSV("Cubes 2", cubes, &new_palette[0][0], 21);

GUI

The visualizer Graphical User Interface is compounded of a main menu bar with the following options that allow you to open other menus in the visualizer window:

  • Options
    • Checkboxes
      • Show checkboxes for each layer for showing or hiding them.
    • Data
      • Show the data window with whatever data you wanted to print there by using fill_data_window().
    • Configuration
      • Control background color
      • Control the size of the points
      • Control the transparency of each layer

Bonus

Rainbow colors

The class includes a palette of rainbow colors (mainly focused on blue and red) for you to use if you want (256 colors):

visual.modified_rainbow[0][0];       // maximum value: [255][3]

Icosahedron

Pass a pointer to an array[12][3] (float) to store there the vertices of an icosahedron. You must provide the radius (float) too.

visual.icosahedron(2, &pnts[0]);

Graphic of a function

You can draw the graphic of a function of type [ y(x) = a + bx + cx^2 + dx^3 ]. It outputs the y value for the inclusive range [xmin, xmax] (including extremes). The sample variable is the number of segments (the result_array will store ‘sample’ + 1 elements, so provide one with enough capacity).

void pol_3th_degree(float *results_array, float xmin, float xmax, float sample, float a, float b, float c, float d);
visual.pol_3th_degree(&parable[0][0],  -10, 10, 100,  0, 0, 1, 0)

Highly technical data:

>>> The main loop

What does the main loop do for each frame?

    • Get the frame buffer (window) size and set the viewport to that size
    • Clear the screen to the background color. Clear the depth test
    • Select shaders for 3D drawing (glUseProgram)
    • Compute the MVP matrix from keyboard and mouse input. Also check whether a selection was made
    • Load the layer’s data on the shader (selected points, points, lines, planes, cubes) and draw them (if the layer is selected with the checkboxes).
    • Load shaders for 2D drawing (glUseProgram)
    • If RMB is pressed, load the selection square data on the shader and draw it.
    • Render ImGui windows
    • Swap buffers (window) and poll events

>>> Synchronization of function calls

After calling open_window() in your main thread, a new thread containing the main loop is created.

    • Main functions change the visualizer state
      • Constructor
      • open_window
      • (LM) add_layer
      • (LM) send_points,  send_lines,  send_triangles,  send_cubes
      • (LM) send_palette_RGB_01,  send_palette_RGB,  send_palette_HSV
      • (LM) fill_data_window
      • Copy assignment operator
      • close_window
      • Destructor
    • Main actions change the visualizer state
      • Selection via mouse
    • Secondary functions doesn’t change the visualizer state
      • transform_coordinates
      • convert_HSVtoRGB,  convert_RGB255toRGB
      • pol_3th_degree
      • icosahedron

Layer modifier functions (LM): From the main thread now you can call any LM function, which will be coordinated with the new thread using lock_guards (LM function modifies layer while main loop reads layer).

For avoiding function calls accumulation (really, calling them with almost no time between calls) in the same main loop iteration (which may happen when selecting points with the mouse, because the point’s search in the main loop may take too much time), only one LM function call of each type for each layer is allowed (this is controlled via flags).

Copy assignment operator: 

Close_window: Activates a flag that takes you out from the main loop (like when pressing esc).

Destructor:

Each layer has its own mutex (mut), and it is used for synchronizing the following:

  • constructor (mutexes are created dynamically)
  • destructor (destroy the dynamic allocated mutexes)
  • operator= (the new object gets new mutexes)
  • add_layer (destroy mutex and create them again + 1)
  • send_points,  send_lines,  send_triangles,  send_cubes
  • load_points, load_lines, load_triangles, load_cubes
  • check_selections (check ray)

The mutex mut_fill_data is used in:

  • fill_data_window
  • create_window (when showing the data window)

The extern mutex cam_mut is used in:

  • run_thread (main loop) (for getting input and making computations from it: computing MVP matrix and selections) (manage access from different visualizerClass objects to the control class)

Management of the controls class:

The control class manages the input controls. For managing mouse buttons, GLFW requires you to use callback functions (function used as an argument of another function: glfwSetMouseButtonCallback and glfwSetScrollCallback have a parameter that is a function that takes the input from the mouse and which body is defined by you). However, some problems appear depending on how you implement them:

  • As member functions: Doesn’t work. A member function pointer cannot be handled like a normal function pointer because it expects a “this” object argument (link).
  • As static member functions or global functions: Doesn’t work properly. It cannot access members of controls class.

My decision: Static member functions (because only the controls class uses them)

But, how do I grant them access to members? The visualizerClass creates a member object of class controls. When it wants to use this member object (once in each iteration of the main loop, for computations that require user input: MVP matrix, mouse and keys), it is assigned to a global controls pointer (controls *camera). The callback functions will use this global pointer to access the members of the controls object of the visualizerClass.

What if we create different instances of the visualizerClass? Every time the visualizerClass’ main loop uses its controls object, the global mutex cam_mut monitor that only one instance of visualizerClass uses the global controls pointer at a time.

>>> Selection via mouse

While selecting points, the point’s LM function waits (lock_guards) (LM function modifies layer while selection reads layer).

Selection process:

In the main loop, check_selection() checks which points are selected

  • selected_points() buffer is restarted (all its values to 0)
  • Selected points are marked in the selected_points() buffer (char booleans)
  • strings_to_show() buffer is restarted (zero size)
  • Save the point strings (if exists) of the selected points in string_to_show()
  • Set number_of_selected_points
  • Delete and resize arrays points_to_highlight and selected_point_colors
  • Fill points_to_highlight (coordinates of selected points) and selected_point_colors (selection_color)

In load_selected_points():

  • A number of items (number_of_selected_points) from points_to_highlight and selected_point_colors are sent to the GPU

Actions that affect the selected points:

 

 

 

 

Selecting points will save them in a buffer and they will remain drawn until you make a new selection (selecting an empty space will select zero points). Data structures used:

Main buffers (created by the constructor, appended by add_layer, filled by send_points):

  • selected_points (NOT NECESSARY):  Contains “booleans” that indicate which points from any layer are currently selected and which aren’t. Filled by send_points (deactivate existing selections) and mouse selection (activates selections).

 

  • points_strings: Contains the strings (or the lack of them) associated to any point from any layer. Filled by send_points().

 

 

  • points_to_highlight: Contains the coordinates of the selected points to print.
  • selected_points_colors: Contains the colors of the selected points to print.
  • strings_to_show: Contains the strings of the selected points to print.

 

 

 

 

 

  • num_selected_points: Number of selected points that will be displayed.

 

Visualizer_example_2

Links

10 thoughts on “HansVisual

Add yours

  1. Hello ,
    when I used “make” in order to compile the project (with ubuntu) , I got this errors

    /home/ctag/Downloads/HansVisual-master/extern/imgui/imgui-1.72b/examples/imgui_impl_opengl3.cpp:101:10: fatal error: GL/gl3w.h: No such file or directory
    #include // Needs to be initialized with gl3wInit() in user’s code

    Thank you

    Like

    1. In the file imgui_impl_opengl3.cpp, the if-else at line 100 is calling GL/gl3w.h header instead of the GL/glew.h (glew is the library we use). This happens because the variable IMGUI_IMPL_OPENGL_LOADER_GLEW is not defined. When that variable is not defined, the imgui_impl_opengl3.hpp defines IMGUI_IMPL_OPENGL_LOADER_GL3W, which is not what we want.
      I define IMGUI_IMPL_OPENGL_LOADER_GLEW in the HansVisual/hans_visual/CMakeLists.txt (line 24). You should define it if you didn’t.

      https://sciencesoftcode.wordpress.com/2019/04/04/building-on-ubuntu/

      Like

      1. Thank you.

        When I define it (as you did in CMakeLists ) I got this error

        CMake Error at hans_visual/CMakeLists.txt:23 (ADD_COMPILE_DEFINITIONS):
        Unknown CMake command “ADD_COMPILE_DEFINITIONS”.

        where :

        the line 23 is : ADD_COMPILE_DEFINITIONS(GLEW_STATIC=1)
        the line 24 is :ADD_COMPILE_DEFINITIONS(IMGUI_IMPL_OPENGL_LOADER_GLEW=1)

        Like

      2. The cmake version ,which I have , is newer than you used .
        I have used “add_compile_options(-D IMGUI_IMPL_OPENGL_LOADER_GLEW=1)” instead of “ADD_COMPILE_DEFINITIONS(IMGUI_IMPL_OPENGL_LOADER_GLEW=1) “and that works fine now

        Like

      3. I think your version is older. The CMake version I used is 3.14.4 or superior (the latest version as today is 3.16). “add_compile_definitions” is only available from 3.14.4. But “add_compile_options” is available, at least, from 3.0.2.

        Like

  2. Hello Anselmo,

    Im using ur visualization. After some time running, I got a crash in this point of the code (debug mode):

    load_selected_points(selectedPointsID, selectedColorsID);

    Do you know which could be the root of the failure?

    Another question, How can I set easily a single colour for a layer? For example, layer of points red.

    Thanx in advance.

    Like

    1. Hello, Jesus

      About the first question, I don’t have enough information about your problem. The function you mention (load_selected_points) loads the selected point’s data in the GPU and then orders to draw them. Maybe you are making a selection while sending points (send_points()) many times. When the visualizer looks for the selected points, a semaphore makes any send_points function to wait and, therefore, the sending orders accumulate. It may be a possible cause for the crash. I have some ideas to fix this, and  also for keeping the selected points selected even after new points are sent. I will implement them when I have time.

      About the second question, currently the 2 easiest ways I can think of to do that are:

      – By passing to send_points() an array with the colors for each point where all the colors are the same:

      visual.send_points("Points 2", 12, &pnts[0][0], &points_colors_RGB[0][0], nullptr, colors);

      – Or by passing to send_points() an array with a palette index for each point where all the indexes are the same (you can change the palette of that layer for another if you want):

      visual.send_points("Points 1", 12, &pnts[0][0], points_categories);

      Like

      1. Hi Anselmo,

        Thanx for your answer.

        Yes u r right, I call send_points function inside a infinite while loop.

        Regarding to second point I solved with the first choice, thnx.

        Like

Leave a comment

Create a website or blog at WordPress.com

Up ↑

Design a site like this with WordPress.com
Get started