From d7ffb4c301758bd959601a226db71b16743b4295 Mon Sep 17 00:00:00 2001 From: BRATEAU Quentin Date: Thu, 24 Sep 2026 10:13:59 +0200 Subject: [PATCH] feat(graphics): add Rerun and OBJ 2D/3D visualization backends - Implement Rerun visualizer for Figure2D and Figure3D with live viewer spawning and .rrd file export - Implement OBJ 3D mesh exporter for Figure3D - Add layer support and batched Boxes3D logging for pavings in Rerun - Add Python bindings for Figure3D and Figure2D graphic backends - Add documentation in doc/manual/manual/visualization/rerun.rst - Add examples 06_graphics_3D and 15_rerun --- .gitignore | 2 + CMakeLists.txt | 20 +- doc/CMakeLists.txt | 8 +- .../manual/visualization/3d_visualization.rst | 76 ++- doc/manual/manual/visualization/figures.rst | 20 +- doc/manual/manual/visualization/index.rst | 1 + doc/manual/manual/visualization/rerun.rst | 108 ++++ examples/00_graphics/graphic_examples.cpp | 9 +- examples/06_graphics_3D/main.cpp | 22 +- examples/06_graphics_3D/main.py | 45 ++ examples/15_rerun/CMakeLists.txt | 19 + examples/15_rerun/main.cpp | 118 ++++ .../graphics/figures/codac2_py_Figure2D.cpp | 6 +- .../graphics/figures/codac2_py_Figure3D.cpp | 42 +- src/CMakeLists.txt | 15 + src/graphics/3rd/obj/codac2_Figure3D_OBJ.cpp | 412 ++++++++++++ src/graphics/3rd/obj/codac2_Figure3D_OBJ.h | 73 +++ .../3rd/rerun/codac2_Figure2D_Rerun.cpp | 436 +++++++++++++ .../3rd/rerun/codac2_Figure2D_Rerun.h | 100 +++ .../3rd/rerun/codac2_Figure3D_Rerun.cpp | 611 ++++++++++++++++++ .../3rd/rerun/codac2_Figure3D_Rerun.h | 103 +++ src/graphics/3rd/rerun/codac2_Rerun_common.h | 82 +++ src/graphics/CMakeLists.txt | 19 + src/graphics/figures/codac2_Figure2D.cpp | 5 +- src/graphics/figures/codac2_Figure2D.h | 14 +- src/graphics/figures/codac2_Figure3D.cpp | 410 ++++-------- src/graphics/figures/codac2_Figure3D.h | 289 ++++++--- .../figures/codac2_Figure3DInterface.h | 242 +++++++ src/graphics/figures/codac2_GraphicOutput.h | 31 + .../figures/codac2_OutputFigure3D.cpp | 123 ++++ src/graphics/figures/codac2_OutputFigure3D.h | 76 +++ 31 files changed, 3109 insertions(+), 428 deletions(-) create mode 100644 doc/manual/manual/visualization/rerun.rst create mode 100644 examples/15_rerun/CMakeLists.txt create mode 100644 examples/15_rerun/main.cpp create mode 100644 src/graphics/3rd/obj/codac2_Figure3D_OBJ.cpp create mode 100644 src/graphics/3rd/obj/codac2_Figure3D_OBJ.h create mode 100644 src/graphics/3rd/rerun/codac2_Figure2D_Rerun.cpp create mode 100644 src/graphics/3rd/rerun/codac2_Figure2D_Rerun.h create mode 100644 src/graphics/3rd/rerun/codac2_Figure3D_Rerun.cpp create mode 100644 src/graphics/3rd/rerun/codac2_Figure3D_Rerun.h create mode 100644 src/graphics/3rd/rerun/codac2_Rerun_common.h create mode 100644 src/graphics/figures/codac2_Figure3DInterface.h create mode 100644 src/graphics/figures/codac2_GraphicOutput.h create mode 100644 src/graphics/figures/codac2_OutputFigure3D.cpp create mode 100644 src/graphics/figures/codac2_OutputFigure3D.h diff --git a/.gitignore b/.gitignore index 64523ced0..633370cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,8 @@ examples/**/*.png examples/**/*.jpg examples/**/*.svg *.ipe +*.rrd +*.obj # Matlab files *.asv diff --git a/CMakeLists.txt b/CMakeLists.txt index f65251466..8c3b0cd26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,24 @@ find_package(CAPD REQUIRED) endif() +################################################################################ +# Looking for Rerun (if needed) +################################################################################ + + option(WITH_RERUN "Build with Rerun graphics support" ON) + + if(WITH_RERUN) + find_package(rerun_sdk QUIET) + if(NOT rerun_sdk_FOUND) + FetchContent_Declare( + rerun_sdk + URL https://github.com/rerun-io/rerun/releases/download/0.38.1/rerun_cpp_sdk.zip + ) + FetchContent_MakeAvailable(rerun_sdk) + endif() + add_compile_definitions(CODAC_WITH_RERUN) + endif() + ################################################################################ # Compile sources ################################################################################ @@ -189,7 +207,7 @@ if(BUILD_TESTS) include(CTest) add_custom_target(check - COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure $(ARGS) + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure DEPENDS ${PROJECT_NAME} COMMENT "Running the tests") add_subdirectory(tests) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1747bfb62..07ff97cd5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -70,13 +70,7 @@ file(COPY ${static_file} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/manual/tmp/) endforeach() - # todo: the SPHINX_EXECUTABLE is already set by FindSphinx.cmake: - # check that it works without the following overload for Win and Linux: - if(WIN32) - set(SPHINX_EXECUTABLE "sphinx-build") - else() - set(SPHINX_EXECUTABLE "python3" "-msphinx") - endif() + add_custom_target(manual COMMAND diff --git a/doc/manual/manual/visualization/3d_visualization.rst b/doc/manual/manual/visualization/3d_visualization.rst index 8c2ee2766..8fd1abd83 100644 --- a/doc/manual/manual/visualization/3d_visualization.rst +++ b/doc/manual/manual/visualization/3d_visualization.rst @@ -3,7 +3,7 @@ The 3D Figure class =================== - Main author: `Maël Godard `_, `Damien Massé `_ + Main author: `Maël Godard `_, `Damien Massé `_, `Quentin Brateau `_ This page describes the class used in Codac for 3D visualization. @@ -11,33 +11,44 @@ Figure3D -------- The basic class for 3D visualization is Figure3D. It is used to generate a ``.obj`` file that can be imported in any 3D visualization software. +The basic class for 3D visualization is Figure3D. It can be used to generate a ``.obj`` file that can be imported in any 3D visualization software, or streamed live to the :ref:`Rerun visualizer ` (``.rrd``). For online visualization, the free website `3dviewer `_ can be used. +For online OBJ visualization, the free website `3dviewer `_ can be used. The constructor takes one arguments: the name of the object file. +The constructor takes the name of the figure and optionnaly the graphic output backend (defaulting to ``GraphicOutput::OBJ | GraphicOutput::RERUN``). .. tabs:: .. code-tab:: py fig = Figure3D("my_object") # for the object file my_object.obj + fig = Figure3D("my_object", GraphicOutput.OBJ | GraphicOutput.RERUN) .. code-tab:: c++ Figure3D fig ("my_object"); // for the object file my_object.obj + Figure3D fig("my_object", GraphicOutput::OBJ | GraphicOutput::RERUN); .. code-tab:: matlab fig = Figure3D("my_object") % for the object file my_object.obj + fig = Figure3D("my_object", GraphicOutput().OBJ.union(GraphicOutput().RERUN)); Drawing functions ----------------- Below are the detailled available drawing functions. The shapes that can be drawn are: +Below are the detailed available drawing functions. The shapes that can be drawn are: Geometric shapes + - Point + - Line + - Polyline - Box - Sphere + - Ellipsoid - Arrow - Zonotope - Parallelepiped @@ -49,19 +60,26 @@ Geometric shapes Vehicles - Car - Plane + - AUV Paving - PavingOut (Paving with contractors) - PavingInOut (Paving with separators) - Subpaving +Trajectories & Tubes + - Sampled and Analytic Trajectories + - Sliced Tubes +Note that stroke and fill colors can be specified via :ref:`StyleProperties `. Note that only the stroke color is used in all of the supported drawing functions. +In addition, a function ``draw_axes`` is available to draw the three axes of the 3D space. It can take two arguments: In addition, a function ``draw_axes`` is available to draw the three axes of the 3D space. It can take two arguments : - float : the size of the axes +- double : the size of the axes - Vector : the origin of the axes @@ -69,23 +87,47 @@ Geometric shapes ---------------- All the drawable geometric objects can take a last optionnal argument to set up their stroke color. +All the drawable geometric objects can take a last optional argument to set up their style properties (color, line width, layer). For further details, refer to :ref:`subsec-graphics-colors-style-properties`. The geometric shapes that can be drawn and their arguments are listed below : +The geometric shapes that can be drawn and their arguments are listed below: + +- draw_point + + - Vector : coordinates of the 3D point + +- draw_line + + - Vector : start of the line + - Vector : end of the line + +- draw_polyline + + - vector : sequence of 3D points forming the polyline - draw_box - IntervalVector : the box to draw + - IntervalVector : the 3D box to draw - draw_sphere + - Vector : the center of the sphere - Matrix : the scaling matrix + - Matrix : the scaling and rotation matrix + +- draw_ellipsoid + + - Ellipsoid : the ellipsoid object to draw - draw_arrow - Vector : start of the arrow - Matrix : orientation of the arrow (first column) + - Vector : start origin of the arrow + - Matrix : orientation matrix (first column is the direction) - draw_zonotope @@ -118,6 +160,7 @@ The geometric shapes that can be drawn and their arguments are listed below : - Vector : third point The ``draw_polygon`` can be used to draw a `star-shaped polygon `_ when the vectors are coplanar, and more +The ``draw_polygon`` function can be used to draw a `star-shaped polygon `_ when the vectors are coplanar, and more generally a sequence of adjacent triangles sharing a same vertex. - draw_polygon @@ -135,35 +178,51 @@ generally a sequence of adjacent triangles sharing a same vertex. - Interval : bounds of p2 - double : incrementation for p2 - function -\> Vector : the function of the surface, linking each (p1,p2) to a 3D point + - function : the parametric surface function linking (p1, p2) to a 3D point Vehicles -------- All the drawable vehicles can take a last optionnal argument to set up their stroke color. +All the drawable vehicles can take a last optional argument to set up their style properties. For further details, refer to :ref:`subsec-graphics-colors-style-properties`. The vehicles that can be drawn and their arguments are listed below : +The vehicles that can be drawn and their arguments are listed below: - draw_car - Vector : center of the car - Matrix : orientation of the car + - Vector : center position of the car + - Matrix : orientation matrix of the car - draw_plane - Vector : center of the plane - Matrix : orientation of the plane - bool : (optionnal) defines if the yaw axis is up, default to true + - Vector : center position of the plane + - Matrix : orientation matrix of the plane + - bool : (optional) defines if the yaw axis is pointing upwards, default to true + +- draw_AUV + + - Vector : center position of the AUV + - Matrix : orientation matrix of the AUV Paving ------ When a paving is drawn, only the inside and boundary boxes are drawn. This is done to avoid outside boxes masking them. +When a paving is drawn, only the inside and boundary boxes are drawn to avoid outside boxes masking them. If only one type of paving is drawn (for example a paving with contractors), only one :ref:`subsec-graphics-colors-style-properties` can be defined to choose its edge color. +If only one type of paving is drawn (for example a paving with contractors), only one :ref:`subsec-graphics-colors-style-properties` can be defined to choose its color. If two types are drawn (boundary and inside), two :ref:`subsec-graphics-colors-style-properties` can be passed to select both colors. The paving that can be drawn and their arguments are listed below : +The paving that can be drawn and their arguments are listed below: - draw_paving @@ -171,4 +230,17 @@ The paving that can be drawn and their arguments are listed below : - draw_subpaving - - Subpaving : the subpaving to draw \ No newline at end of file + - Subpaving : the subpaving to draw + +Trajectories & Tubes +-------------------- + +3D trajectories and tubes can also be drawn on 3D figures: + +- draw_trajectory + + - SampledTraj | AnalyticTraj : the 3D trajectory to draw + +- draw_tube + + - SlicedTube : the 3D tube of interval vectors to draw \ No newline at end of file diff --git a/doc/manual/manual/visualization/figures.rst b/doc/manual/manual/visualization/figures.rst index 95a525a95..7827c5edf 100644 --- a/doc/manual/manual/visualization/figures.rst +++ b/doc/manual/manual/visualization/figures.rst @@ -3,7 +3,7 @@ The 2D Figure classes ===================== - Main author: `Maël Godard `_ + Main author: `Maël Godard `_, `Quentin Brateau `_ This page describes the classes used in Codac for 2D visualization. @@ -14,6 +14,8 @@ Graphical outputs Two graphical outputs are currently supported in Codac: :ref:`VIBes ` and :ref:`IPE `. VIBes is used for real-time visualization while IPE creates a file that can be edited by the IPE editor. These outputs are referenced by the enumeration GraphicOutput: +Three graphical outputs are currently supported in Codac: :ref:`VIBes `, :ref:`IPE `, and :ref:`Rerun `. VIBes is used for real-time +visualization, IPE creates a file that can be edited by the IPE editor, and Rerun generates an interactive recording (`.rrd`). These outputs are referenced by the enumeration GraphicOutput: .. tabs:: @@ -22,18 +24,24 @@ visualization while IPE creates a file that can be edited by the IPE editor. The GraphicOutput.VIBES # for VIBes GraphicOutput.IPE # for IPE GraphicOutput.VIBES | GraphicOutput.IPE # for both + GraphicOutput.RERUN # for Rerun + GraphicOutput.VIBES | GraphicOutput.IPE | GraphicOutput.RERUN # for all .. code-tab:: c++ GraphicOutput::VIBES // for VIBes GraphicOutput::IPE // for IPE GraphicOutput::VIBES | GraphicOutput::IPE // for both + GraphicOutput::RERUN // for Rerun + GraphicOutput::VIBES | GraphicOutput::IPE | GraphicOutput::RERUN // for all .. code-tab:: matlab GraphicOutput().VIBES % for VIBes GraphicOutput().IPE % for IPE GraphicOutput().VIBES.union(GraphicOutput().IPE) % for both + GraphicOutput().RERUN % for Rerun + GraphicOutput().VIBES.union(GraphicOutput().IPE).union(GraphicOutput().RERUN) % for all Note that for the VIBes output to work, the VIBes viewer must be launched before the program is run. @@ -43,6 +51,7 @@ Figure2D -------- The basic class for 2D visualization is Figure2D. It is used to create a figure that can be displayed in VIBes or saved in an xml file for IPE. +The basic class for 2D visualization is Figure2D. It is used to create a figure that can be displayed in VIBes, saved in an xml file for IPE, or exported to Rerun (.rrd). The constructor takes two arguments: the name of the figure and the graphical output. A boolean can be added to specify if the figure is to be used DefaultFigure (see :ref:`subsec-graphics-2d-figures-defaultfigure`). @@ -51,14 +60,17 @@ DefaultFigure (see :ref:`subsec-graphics-2d-figures-defaultfigure`). .. code-tab:: py fig = Figure2D("My figure", GraphicOutput.VIBES | GraphicOutput.IPE) + fig = Figure2D("My figure", GraphicOutput.VIBES | GraphicOutput.IPE | GraphicOutput.RERUN) .. code-tab:: c++ Figure2D fig ("My Figure",GraphicOutput::VIBES|GraphicOutput::IPE); + Figure2D fig ("My Figure", GraphicOutput::VIBES | GraphicOutput::IPE | GraphicOutput::RERUN); .. code-tab:: matlab fig = Figure2D("My figure 1", GraphicOutput().VIBES.union(GraphicOutput().IPE)); + fig = Figure2D("My figure 1", GraphicOutput().VIBES.union(GraphicOutput().IPE).union(GraphicOutput().RERUN)); .. _subsec-graphics-2d-figures-defaultfigure: @@ -72,21 +84,21 @@ Any Figure2D object can be used as DefaultFigure with the set method: .. code-tab:: py - fig = Figure2D("My figure", GraphicOutput.VIBES | GraphicOutput.IPE) + fig = Figure2D("My figure", GraphicOutput.VIBES | GraphicOutput.IPE | GraphicOutput.RERUN) fig.is_default() # is False DefaultFigure.set(fig) fig.is_default() # is True .. code-tab:: c++ - std::shared_ptr fig = std::make_shared("My Figure",GraphicOutput::VIBES|GraphicOutput::IPE); + std::shared_ptr fig = std::make_shared("My Figure",GraphicOutput::VIBES|GraphicOutput::IPE|GraphicOutput::RERUN); fig->is_default(); // is false DefaultFigure::set(fig); fig->is_default(); // is true .. code-tab:: matlab - fig = Figure2D("My figure", GraphicOutput().VIBES.union(GraphicOutput().IPE)); + fig = Figure2D("My figure", GraphicOutput().VIBES.union(GraphicOutput().IPE.union(GraphicOutput().RERUN))); fig.is_default() % is False DefaultFigure().set(fig); fig.is_default() % is True diff --git a/doc/manual/manual/visualization/index.rst b/doc/manual/manual/visualization/index.rst index 042b77f2c..341317af5 100644 --- a/doc/manual/manual/visualization/index.rst +++ b/doc/manual/manual/visualization/index.rst @@ -11,6 +11,7 @@ Visualization colors.rst vibes.rst ipe.rst + rerun.rst 2d_example.rst 3d_visualization.rst 3d_example.rst \ No newline at end of file diff --git a/doc/manual/manual/visualization/rerun.rst b/doc/manual/manual/visualization/rerun.rst new file mode 100644 index 000000000..398cfff41 --- /dev/null +++ b/doc/manual/manual/visualization/rerun.rst @@ -0,0 +1,108 @@ +.. _sec-graphics-rerun: + +The Rerun visualizer +==================== + + Main author: `Quentin Brateau `_ + +One of the graphical outputs supported in Codac is `the Rerun visualizer `_: an interactive, multimodal 2D and 3D visualization engine designed for robotics and spatial computing. + +Installation +------------ + +To install the standalone Rerun viewer application, you can install the Rerun CLI using ``pip`` or ``cargo``: + +.. code-block:: bash + + # via pip + pip install rerun-sdk + + # or via cargo (Rust) + cargo install rerun-cli + +Use +--- + +Rerun can be used with both ``Figure2D`` and ``Figure3D`` via the ``GraphicOutput::RERUN`` flag (in C++) or ``GraphicOutput.RERUN`` (in Python). + +.. tabs:: + + .. code-tab:: py + + from codac import * + + # 2D figure with Rerun + fig2d = Figure2D("My 2D Figure", GraphicOutput.RERUN) + fig2d.draw_box(IntervalVector([[-2, 2], [-2, 2]]), StyleProperties(Color.blue(0.5), "boxes")) + + # 3D figure with Rerun and OBJ + fig3d = Figure3D("My 3D Figure", GraphicOutput.OBJ | GraphicOutput.RERUN) + fig3d.draw_sphere([0, 0, 0], Matrix.eye(3, 3), StyleProperties(Color.yellow(0.6), "sphere")) + + .. code-tab:: c++ + + #include + + using namespace codac2; + + // 2D figure with Rerun + Figure2D fig2d("My 2D Figure", GraphicOutput::RERUN); + fig2d.draw_box({{-2, 2}, {-2, 2}}, {Color::blue(0.5), "boxes"}); + + // 3D figure with Rerun and OBJ + Figure3D fig3d("My 3D Figure", GraphicOutput::OBJ | GraphicOutput::RERUN); + fig3d.draw_sphere({0, 0, 0}, Matrix::eye(3, 3), {Color::yellow(0.6), "sphere"}); + +Layers and Organization +----------------------- + +All shapes and pavings drawn on a Rerun figure can be assigned to a named **layer** via ``StyleProperties``: + +.. tabs:: + + .. code-tab:: py + + fig.draw_box(box, StyleProperties(Color.green(0.5), "my_layer")) + + .. code-tab:: c++ + + fig.draw_box(box, {Color::green(0.5), "my_layer"}); + +In the Rerun viewer, entities are organized under their layer hierarchy (``//...``). Each layer can be independently toggled, recolored, or adjusted in opacity. + +For 3D pavings, boxes are batched per layer into efficient 3D box primitives, and 3D meshes (triangles, polygons, surfaces, parallelepipeds) include computed vertex normals and albedo lighting for realistic shading and depth perception. + +Streaming and Saving +-------------------- + +When a figure is created with Rerun output enabled, all drawing operations stream directly into a ``.rrd`` (Rerun Recording Data) file (by default ``.rrd``). + +You can also explicitly save the recording to a specific file: + +.. tabs:: + + .. code-tab:: py + + fig.save("my_visualization.rrd") + + .. code-tab:: c++ + + fig.save("my_visualization.rrd"); + +Visualizing the Results +----------------------- + +To visualize the generated ``.rrd`` file in the Rerun viewer: + +.. code-block:: bash + + rerun my_visualization.rrd + +Live Spawning +------------- + +You can also automatically spawn the Rerun viewer application in real time during the execution of your program by setting the environment variable ``CODAC_RERUN_SPAWN=1``: + +.. code-block:: bash + + CODAC_RERUN_SPAWN=1 ./my_program diff --git a/examples/00_graphics/graphic_examples.cpp b/examples/00_graphics/graphic_examples.cpp index f10e08181..21ac57dd6 100644 --- a/examples/00_graphics/graphic_examples.cpp +++ b/examples/00_graphics/graphic_examples.cpp @@ -23,11 +23,12 @@ int main(){ // Predefined Color objects can be configured with a float parameter for opacity (1=opaque, 0=transparent) // Custom figures can also be created: - std::shared_ptr fig1 = std::make_shared("My Figure 1",GraphicOutput::VIBES|GraphicOutput::IPE); + std::shared_ptr fig1 = std::make_shared("My Figure 1",GraphicOutput::VIBES|GraphicOutput::IPE|GraphicOutput::RERUN); - // Here, graphics will be rendered by two tools: both VIBES and IPE + // Here, graphics will be rendered by three tools: VIBES, IPE and RERUN // For VIBES, it requires the VIBes viewer to be launched prior to the execution // For IPE, it generates a file named "My figure 1.xml" that can be edited with IPE, and converted to PDF + // For RERUN, it generates a file named "My Figure 1.rrd" that can be visualized with Rerun (e.g., `rerun "My Figure 1.rrd"`) fig1->set_window_properties({50,50},{500,500}); // position, window size fig1->set_axes(IntervalVector::constant(2,{-10,10})); // bounding box @@ -35,7 +36,7 @@ int main(){ fig1->draw_circle({1,1},0.5,Color({255,155,5})); // drawing a circle at (1,1) of radius 0.5 with a custom RGB color fig1->draw_ring({1,1},{4,6},Color::red()); // drawing a ring at (1,1) of radius {4,6} with a predefined red color - std::shared_ptr fig2 = std::make_shared("My Figure 2",GraphicOutput::VIBES|GraphicOutput::IPE); + std::shared_ptr fig2 = std::make_shared("My Figure 2",GraphicOutput::VIBES|GraphicOutput::IPE|GraphicOutput::RERUN); fig2->set_axes(axis(0,{-1,5}), axis(1,{-1,5})); // (axis_id,{range_of_values_on_this_axis}) fig2->set_window_properties({250,250},{500,500}); @@ -83,7 +84,7 @@ int main(){ // HSV color without and with opacity fig2->draw_box({{2.6,3.1},{2.6,3.1}},{Color({108,90,78},Model::HSV),Color({108,90,78,20},Model::HSV)}); - Figure2D fig3 ("ColorMap figure",GraphicOutput::VIBES|GraphicOutput::IPE); + Figure2D fig3 ("ColorMap figure",GraphicOutput::VIBES|GraphicOutput::IPE|GraphicOutput::RERUN); fig3.set_window_properties({800,250},{500,500}); ColorMap cmap_haxby = ColorMap::haxby(); diff --git a/examples/06_graphics_3D/main.cpp b/examples/06_graphics_3D/main.cpp index d0fcf103f..e8b46879f 100644 --- a/examples/06_graphics_3D/main.cpp +++ b/examples/06_graphics_3D/main.cpp @@ -1,4 +1,5 @@ // The generated .obj files can be visualized on https://3dviewer.net +// The generated .rrd files can be visualized with: rerun #include @@ -19,9 +20,8 @@ int main() CtcInverse ctc(f, IntervalVector::zero(2)); auto p_ctc = pave({{0,2},{2,4},{0,10}}, ctc, 0.02); - Figure3D fig_ctc("Paving contractor"); - fig_ctc.draw_paving(p_ctc); - + Figure3D fig_ctc("Paving contractor", GraphicOutput::OBJ|GraphicOutput::RERUN); + fig_ctc.draw_paving(p_ctc, { Color::yellow(0.5), "paving" }); SepInverse sep_ellipsoid1( AnalyticFunction({x}, 0.5*sqr(x[0])+x[0]*x[1]+x[0]*x[2]+2*sqr(x[1])+2*sqr(x[2])), @@ -32,22 +32,21 @@ int main() auto p_sep = pave({{-1.1,1.1},{-1.1,1.1},{-1.1,1.1}}, sep_ellipsoid1&sep_ellipsoid2, 0.1); - Figure3D fig_sep("Paving separator"); + Figure3D fig_sep("Paving separator", GraphicOutput::OBJ|GraphicOutput::RERUN); fig_sep.draw_axes(0.4); - fig_sep.draw_paving(p_sep); - + fig_sep.draw_paving(p_sep, { Color::yellow(0.3), "boundary" }, { Color::green(0.5), "inside" }); - Figure3D fig_examples("3D examples"); + Figure3D fig_examples("3D examples", GraphicOutput::OBJ|GraphicOutput::RERUN); fig_examples.draw_axes(); fig_examples.draw_axes(0.5); fig_examples.draw_axes(2.0,{0.5,0.5,0.5}); fig_examples.draw_triangle({1,0,0},{0,1,0},{0,0,1},{ Color::dark_green(0.5), "triangle1" }); fig_examples.draw_triangle({2,0,0},{{-1,0,0},{0,1,1},{0,0,-1}}, - {1,0,0},{0,1,0},{0,0,1},Color::purple(0.5)); + {1,0,0},{0,1,0},{0,0,1},{ Color::purple(0.5), "triangle2" }); fig_examples.draw_sphere({0,0,2},{{-1,0,0},{0,1,1},{0,0,-1}}, { Color::yellow(0.6), "sphere" }); fig_examples.draw_arrow({0,2,0},{{-1,0,0},{0,1,1},{0,0,-1}}, - Color::red(1.0)); + { Color::red(1.0), "arrow" }); fig_examples.draw_car({-1,0,0},0.3*Matrix::eye(3,3), { Color::green(0.8), "car" }); fig_examples.draw_plane({3,0,0},0.5*Matrix::eye(3,3),true, @@ -76,5 +75,10 @@ int main() (1-cos(2*phi))*cos(phi)*cos(psi), (1-cos(phi))*cos(phi)*sin(psi) }; }, { Color::red(0.6), "example_surface" }); + + // Save Rerun figures to .rrd files (viewable with: rerun ) + fig_ctc.save("Paving_contractor.rrd"); + fig_sep.save("Paving_separator.rrd"); + fig_examples.save("3D_examples.rrd"); } diff --git a/examples/06_graphics_3D/main.py b/examples/06_graphics_3D/main.py index a92d701bd..89e9f4b4f 100644 --- a/examples/06_graphics_3D/main.py +++ b/examples/06_graphics_3D/main.py @@ -13,29 +13,52 @@ p_ctc = pave([[0,2],[2,4],[0,10]], ctc, 0.02) fig_ctc=Figure3D("Paving contractor") fig_ctc.draw_paving(p_ctc) +fig_ctc = Figure3D("Paving contractor", GraphicOutput.OBJ | GraphicOutput.RERUN) +fig_ctc.draw_paving(p_ctc, StyleProperties(Color.yellow(0.5), "paving")) sep_ellipsoid1=SepInverse (AnalyticFunction([x], 0.5*sqr(x[0])+x[0]*x[1]+x[0]*x[2]+2*sqr(x[1])+2*sqr(x[2])),Interval(0.4,1)) sep_ellipsoid2=SepInverse (AnalyticFunction([x], 3*sqr(x[0])+x[0]*x[1]+x[0]*x[2]+sqr(x[1])+sqr(x[2])),Interval(0.,1)) p_sep = pave([[-1.5,1.5],[-1.5,1.5],[-1.5,1.5]], sep_ellipsoid1&sep_ellipsoid2, 0.1) fig_sep = Figure3D("Paving separator") +sep_ellipsoid1 = SepInverse(AnalyticFunction([x], 0.5*sqr(x[0])+x[0]*x[1]+x[0]*x[2]+2*sqr(x[1])+2*sqr(x[2])), Interval(0.4, 1)) +sep_ellipsoid2 = SepInverse(AnalyticFunction([x], 3*sqr(x[0])+x[0]*x[1]+x[0]*x[2]+sqr(x[1])+sqr(x[2])), Interval(0.0, 1)) +p_sep = pave([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]], sep_ellipsoid1 & sep_ellipsoid2, 0.1) +fig_sep = Figure3D("Paving separator", GraphicOutput.OBJ | GraphicOutput.RERUN) fig_sep.draw_axes(0.4) fig_sep.draw_paving(p_sep) +fig_sep.draw_paving(p_sep, StyleProperties(Color.yellow(0.3), "boundary"), StyleProperties(Color.green(0.5), "inside")) fig_examples = Figure3D("3D examples") +fig_examples = Figure3D("3D examples", GraphicOutput.OBJ | GraphicOutput.RERUN) fig_examples.draw_axes() fig_examples.draw_axes(0.5) fig_examples.draw_axes(2.0,[0.5,0.5,0.5]) fig_examples.draw_triangle([1,0,0],[0,1,0],[0,0,1],StyleProperties(Color.dark_green(0.5),"triangle")) +fig_examples.draw_triangle([1,0,0],[0,1,0],[0,0,1],StyleProperties(Color.dark_green(0.5),"triangle1")) fig_examples.draw_triangle([2,0,0], Matrix([[-1,0,0],[0,1,1],[0,0,-1]]), [1,0,0],[0,1,0],[0,0,1],Color.purple(0.5)) + [1,0,0],[0,1,0],[0,0,1],StyleProperties(Color.purple(0.5),"triangle2")) fig_examples.draw_sphere([0,0,2],Matrix([[-1,0,0],[0,1,1],[0,0,-1]]), StyleProperties(Color.yellow(0.6),"sphere")) fig_examples.draw_arrow([0,2,0],Matrix([[-1,0,0],[0,1,1],[0,0,-1]]),Color.red(1.0)) +fig_examples.draw_arrow([0,2,0],Matrix([[-1,0,0],[0,1,1],[0,0,-1]]),StyleProperties(Color.red(1.0),"arrow")) fig_examples.draw_car([-1,0,0],0.3*Matrix.eye(3,3), StyleProperties(Color.green(0.8),"car")) fig_examples.draw_plane([3,0,0],0.5*Matrix.eye(3,3),True, StyleProperties(Color.dark_gray(0.8),"plane")) +fig_examples.draw_axes(2.0, [0.5, 0.5, 0.5]) +fig_examples.draw_triangle([1, 0, 0], [0, 1, 0], [0, 0, 1], StyleProperties(Color.dark_green(0.5), "triangle1")) +fig_examples.draw_triangle([2, 0, 0], Matrix([[-1, 0, 0], [0, 1, 1], [0, 0, -1]]), + [1, 0, 0], [0, 1, 0], [0, 0, 1], StyleProperties(Color.purple(0.5), "triangle2")) +fig_examples.draw_sphere([0, 0, 2], Matrix([[-1, 0, 0], [0, 1, 1], [0, 0, -1]]), + StyleProperties(Color.yellow(0.6), "sphere")) +fig_examples.draw_arrow([0, 2, 0], Matrix([[-1, 0, 0], [0, 1, 1], [0, 0, -1]]), + StyleProperties(Color.red(1.0), "arrow")) +fig_examples.draw_car([-1, 0, 0], 0.3 * Matrix.eye(3, 3), + StyleProperties(Color.green(0.8), "car")) +fig_examples.draw_plane([3, 0, 0], 0.5 * Matrix.eye(3, 3), True, + StyleProperties(Color.dark_gray(0.8), "plane")) fig_examples.draw_zonotope(Zonotope([1.5,1.5,1.5], Matrix([[0.3,-0.2,-0.2,0.3,-0.1,0.0],[0.2,0.1,-0.1,0.0,0.05,0.2],[0.4,0.3,0.0,-0.1,0.2,0.1]])),StyleProperties(Color.dark_green(1.0),"zonotope")) @@ -44,20 +67,42 @@ [0.2,0.1,-0.1,0.0,0.0,0.05,0.2,0.0], [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1]])), StyleProperties(Color.dark_green(1.0),"zonotope2")) +fig_examples.draw_zonotope(Zonotope([1.5, 1.5, 1.5], + Matrix([[0.3, -0.2, -0.2, 0.3, -0.1, 0.0], + [0.2, 0.1, -0.1, 0.0, 0.05, 0.2], + [0.4, 0.3, 0.0, -0.1, 0.2, 0.1]])), StyleProperties(Color.dark_green(1.0), "zonotope")) +fig_examples.draw_zonotope(Zonotope([-1.5, -1.5, -1.5], + Matrix([[0.3, -0.2, -0.2, 0.3, -0.2, -0.1, 0.0, 0.0], + [0.2, 0.1, -0.1, 0.0, 0.0, 0.05, 0.2, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1]])), + StyleProperties(Color.dark_green(1.0), "zonotope2")) fig_examples.draw_parallelepiped(Parallelepiped([1,-1.5,1.5], Matrix([[1,0,0],[0,0.5,0],[0,0.2,0.1]])),StyleProperties(Color.blue(0.5),"parallelepiped")) +fig_examples.draw_parallelepiped(Parallelepiped([1, -1.5, 1.5], + Matrix([[1, 0, 0], [0, 0.5, 0], [0, 0.2, 0.1]])), StyleProperties(Color.blue(0.5), "parallelepiped")) def f(phi,psi): return Vector([(1-math.cos(phi))*math.sin(phi), (1-math.cos(2*phi))*math.cos(phi)*math.cos(psi), (1-math.cos(phi))*math.cos(phi)*math.sin(psi)]) +def f(phi, psi): + return Vector([(1 - math.cos(phi)) * math.sin(phi), + (1 - math.cos(2 * phi)) * math.cos(phi) * math.cos(psi), + (1 - math.cos(phi)) * math.cos(phi) * math.sin(psi)]) fig_examples.draw_surface([0,-2,0], 0.5*Matrix.eye(3,3), [0,2*PI],0.05*PI,[0,2*PI],0.05*PI, f,StyleProperties(Color.red(0.6),"example_surface")) +fig_examples.draw_surface([0, -2, 0], 0.5 * Matrix.eye(3, 3), + [0, 2 * PI], 0.05 * PI, [0, 2 * PI], 0.05 * PI, + f, StyleProperties(Color.red(0.6), "example_surface")) # to flush the files del fig_ctc del fig_sep del fig_examples +# Save Rerun figures to .rrd files +fig_ctc.save("Paving_contractor.rrd") +fig_sep.save("Paving_separator.rrd") +fig_examples.save("3D_examples.rrd") diff --git a/examples/15_rerun/CMakeLists.txt b/examples/15_rerun/CMakeLists.txt new file mode 100644 index 000000000..ce7606b99 --- /dev/null +++ b/examples/15_rerun/CMakeLists.txt @@ -0,0 +1,19 @@ +# ================================================================== +# codac / rerun example - cmake configuration file +# ================================================================== + +cmake_minimum_required(VERSION 3.5) +project(codac_rerun_example LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(CODAC REQUIRED) +message(STATUS "Found Codac version ${CODAC_VERSION}") + +ibex_init_common() + +add_executable(${PROJECT_NAME} main.cpp) +target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) +target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) diff --git a/examples/15_rerun/main.cpp b/examples/15_rerun/main.cpp new file mode 100644 index 000000000..592a0f4ee --- /dev/null +++ b/examples/15_rerun/main.cpp @@ -0,0 +1,118 @@ +#include +#include + +using namespace std; +using namespace codac2; + +int main() +{ + cout << "=== Codac Rerun Visualizer Example ===" << endl; + + // ========================================================================= + // 1. 2D Figure with Rerun + // ========================================================================= + cout << "Creating 2D figure with Rerun..." << endl; + Figure2D fig2d("Codac_2D_Rerun", GraphicOutput::RERUN); + fig2d.set_axes(axis(0, {-10, 10}, "x"), axis(1, {-10, 10}, "y")); + + // Boxes + fig2d.draw_box({{-2, 2}, {-2, 2}}, {Color::blue(), Color::cyan(0.4)}); + fig2d.draw_box({{4, 6}, {4, 7}}, {Color::red(), Color::yellow(0.5)}); + + // Robots + fig2d.draw_tank({2.0, 1.0, 3.14 / 4.0}, 1.5, {{Color::black(), Color::green()}, "vehicles"}); + fig2d.draw_AUV({-4.0, 3.0, -3.14 / 6.0}, 2.0, {{Color::black(), Color::yellow()}, "vehicles"}); + fig2d.draw_motor_boat({-3.0, -4.0, 0.0}, 1.5, {{Color::black(), Color::blue()}, "vehicles"}); + + // Circle and Ellipse + fig2d.draw_circle({0.0, 0.0}, 3.0, Color::purple()); + fig2d.draw_ellipse({-3.0, 2.0}, {1.0, 2.5}, 0.5, {Color::dark_green(), Color::green(0.3)}); + + // 2D Trajectory + SampledTraj traj2d; + for(double t = 0.0; t <= 10.0; t += 0.1) + { + traj2d.set(Vector({t - 5.0, 3.0 * std::sin(t)}), t); + } + fig2d.draw_trajectory(traj2d, {Color::red(), "trajectory", "w:2.0"}); + + // 2D Tube + SlicedTube tube2d(create_tdomain({0.0, 5.0}, 0.2), IntervalVector(2)); + for(auto it = tube2d.tdomain()->begin(); it != tube2d.tdomain()->end(); ++it) + { + double t = it->mid(); + IntervalVector slice_box({ {t - 2.5 - 0.3, t - 2.5 + 0.3}, {std::cos(t) - 0.4, std::cos(t) + 0.4} }); + tube2d.slice(it)->set(slice_box, false); + } + fig2d.draw_tube(tube2d, {Color::blue(0.8), "tube"}); + + // Lines, arrows, polylines + fig2d.draw_line({-8.0, -8.0}, {-2.0, -5.0}, Color::dark_gray()); + fig2d.draw_arrow({0.0, -6.0}, {3.0, -6.0}, 0.5, Color::red()); + fig2d.draw_text("Codac 2D Rerun Export", {-8.0, 8.0}, 1.0, Color::black()); + + // Save 2D figure to .rrd file + fig2d.save("figure_2d.rrd"); + cout << "Saved 2D figure to figure_2d.rrd" << endl; + + + // ========================================================================= + // 2. 3D Figure with Rerun + // ========================================================================= + cout << "Creating 3D figure with Rerun..." << endl; + Figure3D fig3d("Codac_3D_Rerun", GraphicOutput::RERUN); + + // Coordinate axes + fig3d.draw_axes(2.0, Vector::zero(3)); + + // 3D Boxes + fig3d.draw_box({{-1, 1}, {-1, 1}, {-1, 1}}, {Color::blue(0.6), "box3d"}); + fig3d.draw_box({{3, 5}, {3, 5}, {1, 3}}, {Color::red(0.6), "box3d"}); + + // 3D Vehicles / Robots + fig3d.draw_car({-3, 0, 0}, 0.5 * Matrix::eye(3, 3), {Color::yellow(0.8), "robots"}); + fig3d.draw_plane({3, 0, 2}, 0.6 * Matrix::eye(3, 3), true, {Color::dark_gray(0.8), "robots"}); + fig3d.draw_AUV({0, -4, 1}, 0.7 * Matrix::eye(3, 3), {Color::cyan(0.8), "robots"}); + + // 3D Trajectory + SampledTraj traj3d; + for(double t = 0.0; t <= 10.0; t += 0.1) + { + double r = 3.0; + traj3d.set(Vector({r * std::cos(t), r * std::sin(t), 0.5 * t}), t); + } + fig3d.draw_trajectory(traj3d, {Color::red(), "trajectory", "w:2.0"}); + + // 3D Sphere and Ellipsoid + fig3d.draw_sphere({0, 0, 4}, Matrix::eye(3, 3), {Color::green(0.6), "sphere"}); + fig3d.draw_ellipsoid(Ellipsoid({-3, -3, 2}, Matrix({{1.5, 0, 0}, {0, 0.8, 0}, {0, 0, 0.5}})), + {Color::purple(0.6), "ellipsoid"}); + + // 3D Arrow and Zonotope + fig3d.draw_arrow({0, 0, 0}, 2.0 * Matrix::eye(3, 3), Color::red()); + fig3d.draw_zonotope({{2, -2, 2}, + {{0.3, -0.2, 0.1}, + {0.1, 0.3, 0.0}, + {0.0, 0.1, 0.4}}}, + {Color::dark_green(0.8), "zonotope"}); + + // 3D Paving with contractor + VectorVar x(3); + AnalyticFunction f { {x}, + { + sqr(x[0]) + sqr(x[1]) + sqr(x[2]) - 4.0 + } + }; + CtcInverse ctc(f, IntervalVector({Interval(-0.2, 0.2)})); + auto p_ctc = pave({{-3, 3}, {-3, 3}, {-3, 3}}, ctc, 0.3); + fig3d.draw_paving(p_ctc, {Color::yellow(0.5), "paving"}); + + // Save 3D figure to .rrd file + fig3d.save("figure_3d.rrd"); + cout << "Saved 3D figure to figure_3d.rrd" << endl; + + cout << "Visualization files successfully generated!" << endl; + cout << "To view in Rerun: rerun figure_2d.rrd (or rerun figure_3d.rrd)" << endl; + + return 0; +} diff --git a/python/src/graphics/figures/codac2_py_Figure2D.cpp b/python/src/graphics/figures/codac2_py_Figure2D.cpp index e78d90819..d92d5762c 100644 --- a/python/src/graphics/figures/codac2_py_Figure2D.cpp +++ b/python/src/graphics/figures/codac2_py_Figure2D.cpp @@ -2,7 +2,7 @@ * Codac binding (graphics) * ---------------------------------------------------------------------------- * \date 2024 - * \author Simon Rohou, Maël Godard + * \author Simon Rohou, Maël Godard, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -30,6 +30,8 @@ void export_Figure2D(py::module& m) .def(py::init<>()) .def_static("VIBES", [](){ return GraphicOutput::VIBES; }) .def_static("IPE", [](){ return GraphicOutput::IPE; }) + .def_static("RERUN", [](){ return GraphicOutput::RERUN; }) + .def_static("OBJ", [](){ return GraphicOutput::OBJ; }) .def("union", [](GraphicOutput a, GraphicOutput b){ return a | b; }, GRAPHICOUTPUT_OPERATORUNION_GRAPHICOUTPUT_GRAPHICOUTPUT, "x"_a) @@ -41,6 +43,8 @@ void export_Figure2D(py::module& m) py::enum_(m, "GraphicOutput") .value("VIBES", GraphicOutput::VIBES) .value("IPE", GraphicOutput::IPE) + .value("RERUN", GraphicOutput::RERUN) + .value("OBJ", GraphicOutput::OBJ) .export_values() .def(py::self | py::self, GRAPHICOUTPUT_OPERATORUNION_GRAPHICOUTPUT_GRAPHICOUTPUT) ; diff --git a/python/src/graphics/figures/codac2_py_Figure3D.cpp b/python/src/graphics/figures/codac2_py_Figure3D.cpp index 620e9ecdc..aab953f7b 100644 --- a/python/src/graphics/figures/codac2_py_Figure3D.cpp +++ b/python/src/graphics/figures/codac2_py_Figure3D.cpp @@ -2,7 +2,7 @@ * Codac binding (graphics) * ---------------------------------------------------------------------------- * \date 2024 - * \author Maël Godard + * \author Maël Godard, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -28,18 +28,37 @@ void export_Figure3D(py::module& m) exported(m, "Figure3D", FIGURE3D_MAIN); exported + .def(py::init(), + "name"_a, "o"_a = GraphicOutput::OBJ) + "name"_a, "o"_a = GraphicOutput::OBJ | GraphicOutput::RERUN) + .def(py::init(), + [](const std::string& name) { + return std::make_unique(name, GraphicOutput::OBJ | GraphicOutput::RERUN); + }, FIGURE3D_FIGURE3D_CONST_STRING_REF, "name"_a) .def("name", &Figure3D::name, CONST_STRING_REF_FIGURE3D_NAME_CONST) + .def("clear", &Figure3D::clear) + .def("save", &Figure3D::save, "filename"_a) + .def("draw_axes", &Figure3D::draw_axes, VOID_FIGURE3D_DRAW_AXES_DOUBLE_CONST_VECTOR_REF, "size"_a=1.0, "origin"_a=Vector::zero(3)) // Geometric shapes + .def("draw_point", &Figure3D::draw_point, + "c"_a, "style"_a=StyleProperties()) + + .def("draw_line", &Figure3D::draw_line, + "p1"_a, "p2"_a, "style"_a=StyleProperties()) + + .def("draw_polyline", &Figure3D::draw_polyline, + "x"_a, "style"_a=StyleProperties()) + .def("draw_triangle", (void(Figure3D::*)(const Vector &c, const Matrix &A, const Vector &p1, const Vector &p2, const Vector &p3, const StyleProperties &s))&Figure3D::draw_triangle, VOID_FIGURE3D_DRAW_TRIANGLE_CONST_VECTOR_REF_CONST_MATRIX_REF_CONST_VECTOR_REF_CONST_VECTOR_REF_CONST_VECTOR_REF_CONST_STYLEPROPERTIES_REF, "c"_a, "A"_a, "p1"_a, "p2"_a, "p3"_a, "style"_a=StyleProperties()) @@ -91,17 +110,36 @@ void export_Figure3D(py::module& m) .def("draw_plane", &Figure3D::draw_plane, VOID_FIGURE3D_DRAW_PLANE_CONST_VECTOR_REF_CONST_MATRIX_REF_BOOL_CONST_STYLEPROPERTIES_REF, "c"_a, "A"_a, "yaw_is_up"_a=true, "style"_a=StyleProperties(Color::dark_gray(0.8))) - + + .def("draw_AUV", &Figure3D::draw_AUV, + "c"_a, "A"_a, "style"_a=StyleProperties(Color::yellow(0.5))) // Pavings .def("draw_paving", (void(Figure3D::*)(const PavingOut&, const StyleProperties&))&Figure3D::draw_paving, VOID_FIGURE3D_DRAW_PAVING_CONST_PAVINGOUT_REF_CONST_STYLEPROPERTIES_REF, "p"_a, "boundary_style"_a=StyleProperties(Color::yellow(0.5))) + "p"_a, "boundary_style"_a=StyleProperties(Color::yellow(0.5), "paving_bound")) + + .def("draw_paving", (void(Figure3D::*)(const PavingOut&, const PavingStyle&))&Figure3D::draw_paving, + "p"_a, "style"_a=PavingStyle::default_style()) .def("draw_paving", (void(Figure3D::*)(const PavingInOut&,const StyleProperties&, const StyleProperties&))&Figure3D::draw_paving, VOID_FIGURE3D_DRAW_PAVING_CONST_PAVINGINOUT_REF_CONST_STYLEPROPERTIES_REF_CONST_STYLEPROPERTIES_REF, "p"_a, "boundary_style"_a=StyleProperties(Color::yellow(0.15), "boundary"), "inside_style"_a=StyleProperties(Color::green(0.5), "inside")) + .def("draw_paving", (void(Figure3D::*)(const PavingInOut&, const PavingStyle&))&Figure3D::draw_paving, + "p"_a, "style"_a=PavingStyle::default_style()) + + // Trajectories & Tubes + .def("draw_trajectory", (void(Figure3D::*)(const SampledTraj&, const StyleProperties&))&Figure3D::draw_trajectory, + "x"_a, "style"_a=StyleProperties()) + + .def("draw_trajectory", (void(Figure3D::*)(const AnalyticTraj&, const StyleProperties&))&Figure3D::draw_trajectory, + "x"_a, "style"_a=StyleProperties()) + + .def("draw_tube", &Figure3D::draw_tube, + "x"_a, "style"_a=StyleProperties()) + ; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28fd1c4fc..e0b50a643 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -110,6 +110,21 @@ endif() + if(WITH_RERUN) + + file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " + + # Optional 3rd party: Rerun + find_library(RERUN_SDK_LIBRARY NAMES rerun_sdk PATH_SUFFIXES lib) + find_library(RERUN_C_LIBRARY NAMES rerun_c librerun_c PATH_SUFFIXES lib) + find_library(ARROW_STATIC_LIBRARY NAMES arrow libarrow PATH_SUFFIXES lib) + find_library(ARROW_BUNDLED_LIBRARY NAMES arrow_bundled_dependencies libarrow_bundled_dependencies PATH_SUFFIXES lib) + + set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${RERUN_SDK_LIBRARY} \${RERUN_C_LIBRARY} \${ARROW_STATIC_LIBRARY} \${ARROW_BUNDLED_LIBRARY} -lws2_32 -luserenv -lntdll -lbcrypt -lcrypt32 -liphlpapi -lncrypt -lnetapi32 -lpdh -lpowrprof -lpsapi -lsecur32) + ") + + endif() + file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) ") diff --git a/src/graphics/3rd/obj/codac2_Figure3D_OBJ.cpp b/src/graphics/3rd/obj/codac2_Figure3D_OBJ.cpp new file mode 100644 index 000000000..5a6b2f2c8 --- /dev/null +++ b/src/graphics/3rd/obj/codac2_Figure3D_OBJ.cpp @@ -0,0 +1,412 @@ +/** + * \file codac2_Figure3D_OBJ.cpp + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Maël Godard, Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_Figure3D.h" +#include "codac2_Figure3D_OBJ.h" +#include "codac2_math.h" + +using namespace std; +using namespace codac2; + +Figure3D_OBJ::Figure3D_OBJ(const Figure3D& fig) + : OutputFigure3D(fig) +{ + _file.open(fig.name() + ".obj"); + _is_open = _file.is_open(); +} + +Figure3D_OBJ::~Figure3D_OBJ() +{ + if(_file.is_open()) + _file.close(); +} + +void Figure3D_OBJ::update_axes() +{ +} + +void Figure3D_OBJ::clear() +{ + if(_file.is_open()) + _file.close(); + vertex_count = 0; + _file.open(_fig.name() + ".obj", std::ofstream::trunc); + _is_open = _file.is_open(); +} + +void Figure3D_OBJ::save(const std::string& filename) +{ + // Do not handle files explicitly meant for other backends (such as .rrd or .xml) + if(filename.size() >= 4 && (filename.compare(filename.size() - 4, 4, ".rrd") == 0 || + filename.compare(filename.size() - 4, 4, ".xml") == 0)) + return; + + std::string target = filename; + if(target.find(".obj") == std::string::npos) + if(target.size() < 4 || target.compare(target.size() - 4, 4, ".obj") != 0) + target += ".obj"; + + if(_file.is_open()) + _file.close(); + + // If different filename than default, rename or copy + // If different filename than default, copy + if(target != _fig.name() + ".obj") + { + std::ifstream src(_fig.name() + ".obj", std::ios::binary); + std::ofstream dst(target, std::ios::binary); + dst << src.rdbuf(); + } +} + +void Figure3D_OBJ::set_color_internal(const Color &c) +{ + _file << "newmtl " << c.hex_str().substr(1,6) << "\n"; + _file << "Kd " << c.rgb()[0]/255. << " " << c.rgb()[1]/255. << " " << c.rgb()[2]/255. << "\n"; + _file << "d "<< c.rgb()[3]/255.<<"\n"; + _file << "usemtl " << c.hex_str().substr(1,6) << "\n"; +} + +void Figure3D_OBJ::set_style_internal(const StyleProperties& style) +{ + if (lock_style) return; + if (style.layer=="" || style.layer=="alpha") { + _file<< "o "<< _fig.name()<<"\n"; + } else { + _file<< "o "<< style.layer<<"\n"; + } + this->set_color_internal(style.stroke_color.rgb()); +} + +size_t Figure3D_OBJ::move_write_v(const Vector &c, const Matrix &A, const Vector &p) +{ + Vector pos = c + A * p; + _file << "v " << pos[0] << " " << pos[1] << " " << pos[2] << "\n"; + return (++this->vertex_count); +} + +void Figure3D_OBJ::draw_point(const Vector& c, const StyleProperties& style) +{ + // Point in OBJ represented by small box + double s = style.line_width > 0 ? style.line_width * 0.01 : 0.02; + IntervalVector b = { {c[0]-s, c[0]+s}, {c[1]-s, c[1]+s}, {c[2]-s, c[2]+s} }; + draw_box(b, style); +} + +void Figure3D_OBJ::draw_polyline(const std::vector& x, const StyleProperties& style) +{ + if (x.size() < 2) return; + this->set_style_internal(style); + std::vector idx(x.size()); + for(size_t k = 0; k < x.size(); ++k) + idx[k] = this->move_write_v(Vector::Zero(3), Matrix::Identity(3,3), x[k]); + + _file << "l"; + for(size_t k = 0; k < x.size(); ++k) + _file << " " << idx[k]; + _file << "\n"; +} + +void Figure3D_OBJ::draw_triangle(const Vector &c, const Matrix &A, + const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style) +{ + this->set_style_internal(style); + size_t ip1 = this->move_write_v(c,A,p1); + size_t ip2 = this->move_write_v(c,A,p2); + size_t ip3 = this->move_write_v(c,A,p3); + _file << "f " << ip1 << " " << ip2 << " " << ip3 << "\n"; +} + +void Figure3D_OBJ::draw_triangle(const Vector &p1, const Vector &p2, + const Vector &p3, + const StyleProperties& style) +{ + this->draw_triangle(Vector::zero(3), Matrix::Identity(3,3), p1, p2, p3, style); +} + +void Figure3D_OBJ::draw_polygon(const Vector &c, const Matrix &A, + const std::vector &l, + const StyleProperties& style) +{ + if (l.size() <= 2) return; + this->set_style_internal(style); + size_t ip1 = this->move_write_v(c,A,l[0]); + size_t ip2 = this->move_write_v(c,A,l[1]); + for (size_t i = 2; i < l.size(); i++) { + size_t ip3 = this->move_write_v(c,A,l[i]); + _file << "f " << ip1 << " " << ip2 << " " << ip3 << "\n"; + ip2 = ip3; + } +} + +void Figure3D_OBJ::draw_parallelogram(const Vector &c, const Matrix &A, + const Vector &p, const Vector &v1, const Vector &v2, + const StyleProperties& style) +{ + this->set_style_internal(style); + size_t ip1 = this->move_write_v(c,A,p-v1-v2); + size_t ip2 = this->move_write_v(c,A,p-v1+v2); + size_t ip3 = this->move_write_v(c,A,p+v1+v2); + size_t ip4 = this->move_write_v(c,A,p+v1-v2); + _file << "f " << ip1 << " " << ip2 << " " << ip3 << " " << ip4 << "\n"; +} + +void Figure3D_OBJ::draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style) +{ + assert_release(p.c.size() == 3); + assert_release(p.A.rows() == 3 && p.A.cols() == 3); + + this->set_style_internal(style); + + size_t ip0 = this->move_write_v(p.c,p.A,Vector({-1,-1,-1})); + size_t ip1 = this->move_write_v(p.c,p.A,Vector({-1,-1,1})); + size_t ip2 = this->move_write_v(p.c,p.A,Vector({-1,1,-1})); + size_t ip3 = this->move_write_v(p.c,p.A,Vector({-1,1,1})); + size_t ip4 = this->move_write_v(p.c,p.A,Vector({1,-1,-1})); + size_t ip5 = this->move_write_v(p.c,p.A,Vector({1,-1,1})); + size_t ip6 = this->move_write_v(p.c,p.A,Vector({1,1,-1})); + size_t ip7 = this->move_write_v(p.c,p.A,Vector({1,1,1})); + + _file << "f " << ip0 << " " << ip1 << " " << ip3 << " " << ip2 << "\n"; + _file << "f " << ip4 << " " << ip5 << " " << ip7 << " " << ip6 << "\n"; + _file << "f " << ip0 << " " << ip4 << " " << ip6 << " " << ip2 << "\n"; + _file << "f " << ip1 << " " << ip5 << " " << ip7 << " " << ip3 << "\n"; + _file << "f " << ip0 << " " << ip4 << " " << ip5 << " " << ip1 << "\n"; + _file << "f " << ip2 << " " << ip6 << " " << ip7 << " " << ip3 << "\n"; +} + +void Figure3D_OBJ::draw_box(const IntervalVector& x, const StyleProperties& style) +{ + assert_release(x.size()==3); + Vector inter = (x-x.mid()).ub(); + Matrix A ({{inter[0],0,0},{0,inter[1],0},{0,0,inter[2]}}); + draw_parallelepiped({x.mid(), A}, style); +} + +void Figure3D_OBJ::draw_zonotope(const Zonotope& z, const StyleProperties& style) +{ + assert_release(z.c.size() == 3); + Matrix id = Matrix::Identity(3,3); + this->set_style_internal(style); + lock_style = true; + size_t nb = z.A.cols(); + for (size_t j = 1; j < nb; j++) { + const Eigen::Vector3d &Aj = z.A.col(j); + for (size_t i = 0; i < j; i++) { + const Eigen::Vector3d &Ai = z.A.col(i); + Eigen::Vector3d Norm = Ai.cross(Aj); + if (Norm.lpNorm() < 1e-8) continue; + Eigen::Vector3d R1 = Eigen::Vector3d::Zero(); + Eigen::Vector3d R2 = Eigen::Vector3d::Zero(); + for (size_t k = 0; k < nb; k++) { + if (k == i || k == j) continue; + const Eigen::Vector3d &Ak = z.A.col(k); + double prod = Norm.dot(Ak); + if (std::fabs(prod) < 1e-8) { + if (k > j) { + R2 -= Ak; + continue; + } + Eigen::Vector3d N2 = Ai.cross(Ak); + if (N2.lpNorm() < 1e-8) { + if (Ai.dot(Ak) < 0 || i < k) R2 -= Ak; else R2 += Ak; + continue; + } + if (N2.dot(Norm) >= 0) R2 += Ak; else R2 -= Ak; + } + else if (prod > 0.0) { + R1 += Ak; + } + else { + R1 -= Ak; + } + } + this->draw_parallelogram(z.c, id, R1+R2, Ai, Aj, style); + this->draw_parallelogram(z.c, id, -R1+R2, Ai, Aj, style); + } + } + lock_style = false; +} + +void Figure3D_OBJ::draw_arrow(const Vector& c, const Matrix &A, + const StyleProperties& style) +{ + Vector v = c + A * Vector({0.5, 0.0, 0.0}); + Matrix tr {{0.5, 0, 0}, {0, 0.01, 0}, {0, 0, 0.01}}; + double a = 0.05; + this->set_style_internal(style); + lock_style = true; + draw_parallelepiped({v, A*tr}, style); + std::vector l_points { + { 1+4*a, 0, 0 }, + { 1, a, -a }, + { 1, -a, -a }, + { 1, -a, a }, + { 1, a, a }, + { 1, a, -a } + }; + this->draw_polygon(c, A, l_points, style); + lock_style = false; +} + +void Figure3D_OBJ::draw_axes(double size, const Vector& origin) +{ + assert_release(origin.size() == 3); + const std::string name = "axes"; + Vector z = origin; + // X axis + Matrix AX = Matrix::Identity(3,3); + draw_arrow(z, size*AX, StyleProperties(Color::red(), name)); + // Y axis + Matrix AY {{0,0,1},{1,0,0},{0,1,0}}; + draw_arrow(z, size*AY, StyleProperties(Color::green(), name)); + // Z axis + Matrix AZ {{0,1,0},{0,0,1},{1,0,0}}; + draw_arrow(z, size*AZ, StyleProperties(Color::blue(), name)); + _file << "o " << _fig.name() << "\n"; +} + +void Figure3D_OBJ::draw_surface(const Vector &c, const Matrix &A, + const Interval &Ip1, double dp1, + const Interval &Ip2, double dp2, + std::function f, + const StyleProperties& style) +{ + this->set_style_internal(style); + lock_style = true; + for (double p1 = Ip1.lb(); p1 < Ip1.ub(); p1 += dp1) + for (double p2 = Ip2.lb(); p2 < Ip2.ub(); p2 += dp2) { + Vector a0 = f(p1, p2); + Vector a1 = f(p1+dp1, p2); + Vector a2 = f(p1+dp1, p2+dp2); + Vector a3 = f(p1, p2+dp2); + draw_triangle(c, A, a0, a1, a2, style); + draw_triangle(c, A, a0, a2, a3, style); + } + lock_style = false; +} + +void Figure3D_OBJ::draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + this->draw_surface(c, A, Interval(-0.5*PI, 0.5*PI), 0.05*PI, + Interval(0, 2.0*PI), 0.05*PI, + [](double p1, double p2) -> Vector { + return { std::cos(p1)*std::cos(p2), std::cos(p1)*std::sin(p2), std::sin(p1) }; + }, + style); +} + +void Figure3D_OBJ::draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style) +{ + this->draw_sphere(e.mu, e.G, style); +} + +void Figure3D_OBJ::draw_car(const Vector &c, const Matrix &A, + const StyleProperties& style) +{ + this->set_style_internal(style); + lock_style = true; + /* left side (y=-0.8) */ + std::vector side { + {1.5, -0.8, 1}, + {2.8, -0.8, 1}, + {3, -0.8, 0}, + {-1, -0.8, 0}, + {0, -0.8, 2}, + {1, -0.8, 2} + }; + draw_polygon(c, A, side, style); + for (auto &e : side) { e[1] = 0.8; } + draw_polygon(c, A, side, style); + /* rear and top */ + std::vector reartop { + {0, -0.8, 2}, + {-1, -0.8, 0}, + {-1, 0.8, 0}, + {0, 0.8, 2}, + {1, 0.8, 2}, + {1, -0.8, 2} + }; + draw_polygon(c, A, reartop, style); + /* front and motor */ + std::vector front { + {2.8, -0.8, 1}, + {1.5, -0.8, 1}, + {1.5, 0.8, 1}, + {2.8, 0.8, 1}, + {3, 0.8, 0}, + {3, -0.8, 0} + }; + draw_polygon(c, A, front, style); + lock_style = false; + /* windscreen */ + draw_parallelogram(c, A, Vector({1.25, 0.0, 1.5}), + Vector({0, 0.8, 0}), Vector({-0.25, 0, 0.5}), + StyleProperties(Color::blue(0.5), style.layer)); +} + +void Figure3D_OBJ::draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up, + const StyleProperties& style) +{ + this->set_style_internal(style); + lock_style = true; + /* wings */ + std::vector wings { + {-1.5, 0, 0}, + {-1.5, -1, 0}, + {-1, -1, 0}, + {0, 0, 0}, + {-1, 1, 0}, + {-1.5, 1, 0} + }; + draw_polygon(c, A, wings, style); + double vert = yaw_is_up ? 1.0 : -1.0; + /* body */ + draw_triangle(c, A, {-1.5, 0, 0}, {1.5, 0, 0}, {0.5, -0.1, 0.5*vert}, style); + draw_triangle(c, A, {-1.5, 0, 0}, {1.5, 0, 0}, {0.5, 0.1, 0.5*vert}, style); + /* vertical stabilizer */ + draw_triangle(c, A, {-1.5, 0, 0}, {-1.5, 0, vert}, {-0.5, 0, 0}, style); + lock_style = false; +} + +void Figure3D_OBJ::draw_AUV(const Vector &c, const Matrix &A, + const StyleProperties& style) +{ + this->set_style_internal(style); + lock_style = true; + // AUV body: main cylinder/capsule along X axis + // Main hull triangles + const int N = 8; + double r = 0.3; + double len = 2.0; + for(int i = 0; i < N; ++i) + { + double th1 = 2 * PI * i / N; + double th2 = 2 * PI * (i + 1) / N; + Vector p1_nose { len * 0.5 + 0.5, 0, 0 }; + Vector p1_front { len * 0.5, r * std::cos(th1), r * std::sin(th1) }; + Vector p2_front { len * 0.5, r * std::cos(th2), r * std::sin(th2) }; + draw_triangle(c, A, p1_nose, p1_front, p2_front, style); + + Vector p1_back { -len * 0.5, r * std::cos(th1), r * std::sin(th1) }; + Vector p2_back { -len * 0.5, r * std::cos(th2), r * std::sin(th2) }; + draw_triangle(c, A, p1_front, p1_back, p2_back, style); + draw_triangle(c, A, p1_front, p2_back, p2_front, style); + + Vector p_tail { -len * 0.5 - 0.4, 0, 0 }; + draw_triangle(c, A, p_tail, p2_back, p1_back, style); + } + // Fins + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0, 0.6}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0, -0.6}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0.6, 0}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, -0.6, 0}, {-1.0, 0, 0}, style); + lock_style = false; +} diff --git a/src/graphics/3rd/obj/codac2_Figure3D_OBJ.h b/src/graphics/3rd/obj/codac2_Figure3D_OBJ.h new file mode 100644 index 000000000..5e76c31b4 --- /dev/null +++ b/src/graphics/3rd/obj/codac2_Figure3D_OBJ.h @@ -0,0 +1,73 @@ +/** + * \file codac2_Figure3D_OBJ.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Maël Godard, Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include +#include "codac2_OutputFigure3D.h" + +namespace codac2 +{ + /** + * \class Figure3D_OBJ + * \brief OBJ export implementation for 3D figures + */ + class Figure3D_OBJ : public OutputFigure3D + { + public: + + Figure3D_OBJ(const Figure3D& fig); + virtual ~Figure3D_OBJ(); + + void update_axes() override; + void clear() override; + void save(const std::string& filename) override; + + void draw_point(const Vector& c, const StyleProperties& style = StyleProperties()) override; + void draw_box(const IntervalVector& x, const StyleProperties& style = StyleProperties()) override; + void draw_polyline(const std::vector& x, const StyleProperties& style = StyleProperties()) override; + void draw_triangle(const Vector &c, const Matrix &A, + const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style = StyleProperties()) override; + void draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style = StyleProperties()) override; + void draw_polygon(const Vector &c, const Matrix &A, const std::vector &l, + const StyleProperties& style = StyleProperties()) override; + void draw_parallelogram(const Vector &c, const Matrix &A, + const Vector &p, const Vector &v1, const Vector &v2, + const StyleProperties& style = StyleProperties()) override; + void draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style = StyleProperties()) override; + void draw_zonotope(const Zonotope& z, const StyleProperties& style = StyleProperties()) override; + void draw_arrow(const Vector &c, const Matrix& A, const StyleProperties& style = StyleProperties()) override; + void draw_axes(double size = 1.0, const Vector& origin = Vector::Zero(3)) override; + void draw_surface(const Vector &c, const Matrix &A, + const Interval &Ip1, double dp1, + const Interval &Ip2, double dp2, + std::function f, + const StyleProperties& style = StyleProperties()) override; + void draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + void draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style = StyleProperties()) override; + void draw_car(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + void draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up = true, const StyleProperties& style = StyleProperties()) override; + void draw_AUV(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + + private: + + size_t move_write_v(const Vector &c, const Matrix &A, const Vector &p); + void set_color_internal(const Color &c); + void set_style_internal(const StyleProperties& style); + + std::ofstream _file; + size_t vertex_count = 0; + bool lock_style = false; + bool _is_open = false; + }; +} diff --git a/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.cpp b/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.cpp new file mode 100644 index 000000000..3372b63ca --- /dev/null +++ b/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.cpp @@ -0,0 +1,436 @@ +/** + * codac2_Figure2D_Rerun.cpp + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_Figure2D.h" +#include "codac2_Figure2D_Rerun.h" +#include "codac2_math.h" +#include "codac2_trunc.h" +#include +#include +#include + +using namespace std; +using namespace codac2; + +Figure2D_Rerun::Figure2D_Rerun(const Figure2D& fig) +#ifdef CODAC_WITH_RERUN + : OutputFigure2D(fig), _rec(fig.name()) +{ + const char* spawn_env = std::getenv("CODAC_RERUN_SPAWN"); + if(spawn_env && std::string(spawn_env) != "0") + { + _rec.spawn().exit_on_failure(); + } +} +#else + : OutputFigure2D(fig) +{ +} +#endif + +Figure2D_Rerun::~Figure2D_Rerun() +{ +#ifdef CODAC_WITH_RERUN + if(!_is_saved) + { + save(_fig.name() + ".rrd"); + } + else + { + _rec.flush_blocking(); + } +#endif +} + +void Figure2D_Rerun::update_axes() +{ +#ifdef CODAC_WITH_RERUN + if(_fig.axes().size() >= 2) + { + const auto& ax = _fig.axes()[0]; + const auto& ay = _fig.axes()[1]; + auto entity = sanitize_rerun_path(_fig.name()) + "/axes"; + std::vector x_axis = { + rerun::Position2D(static_cast(ax.limits.lb()), 0.0f), + rerun::Position2D(static_cast(ax.limits.ub()), 0.0f) + }; + std::vector y_axis = { + rerun::Position2D(0.0f, static_cast(ay.limits.lb())), + rerun::Position2D(0.0f, static_cast(ay.limits.ub())) + }; + _rec.log(entity, rerun::LineStrips2D(std::vector{ + rerun::components::LineStrip2D(x_axis), + rerun::components::LineStrip2D(y_axis) + }).with_colors({rerun::Color(180, 180, 180, 200)})); + } +#endif +} + +void Figure2D_Rerun::update_window_properties() +{ +} + +void Figure2D_Rerun::center_viewbox([[maybe_unused]] const Vector& c, [[maybe_unused]] const Vector& r) +{ +} + +void Figure2D_Rerun::clear() +{ +#ifdef CODAC_WITH_RERUN + _rec.log(sanitize_rerun_path(_fig.name()), rerun::Clear::RECURSIVE); + _item_count = 0; +#endif +} + +void Figure2D_Rerun::save([[maybe_unused]] const std::string& filename) +{ +#ifdef CODAC_WITH_RERUN + // Do not handle files explicitly meant for other backends (such as .xml or .obj) + if(filename.size() >= 4 && (filename.compare(filename.size() - 4, 4, ".xml") == 0 || + filename.compare(filename.size() - 4, 4, ".obj") == 0)) + return; + + std::string target = filename; + if(target.find(".rrd") == std::string::npos) + if(target.size() < 4 || target.compare(target.size() - 4, 4, ".rrd") != 0) + target += ".rrd"; + + (void)_rec.save(target); + _rec.flush_blocking(); + _is_saved = true; + _last_saved_filename = target; +#endif +} + +void Figure2D_Rerun::draw_point([[maybe_unused]] const Vector& c, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= c.size()); + auto entity = make_entity_path(_fig.name(), style.layer, "point", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::Points2D({{ static_cast(c[i()]), static_cast(c[j()]) }}) + .with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_box([[maybe_unused]] const IntervalVector& x, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= x.size()); + float min_x = static_cast(graphic_trunc(x[i()].lb())); + float max_x = static_cast(graphic_trunc(x[i()].ub())); + float min_y = static_cast(graphic_trunc(x[j()].lb())); + float max_y = static_cast(graphic_trunc(x[j()].ub())); + float cx = (min_x + max_x) * 0.5f; + float cy = (min_y + max_y) * 0.5f; + float w = max_x - min_x; + float h = max_y - min_y; + + auto entity = make_entity_path(_fig.name(), style.layer, "box", ++_item_count); + Color c = (style.fill_color != codac2::Color::none()) ? style.fill_color : ((style.stroke_color != codac2::Color::none()) ? style.stroke_color : Color::black()); + _rec.log(entity, rerun::Boxes2D::from_centers_and_sizes({{cx, cy}}, {{w, h}}) + .with_colors({to_rerun_color(c)})); +#endif +} + +void Figure2D_Rerun::draw_circle([[maybe_unused]] const Vector& c, [[maybe_unused]] double r, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= c.size()); + const int N = 48; + std::vector pts(N + 1); + for(int k = 0; k <= N; ++k) + { + double th = 2.0 * PI * k / N; + pts[k] = rerun::Position2D(static_cast(c[i()] + r * std::cos(th)), + static_cast(c[j()] + r * std::sin(th))); + } + auto entity = make_entity_path(_fig.name(), style.layer, "circle", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_ring([[maybe_unused]] const Vector& c, [[maybe_unused]] const Interval& r, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= c.size()); + const int N = 48; + std::vector inner_pts(N + 1); + std::vector outer_pts(N + 1); + for(int k = 0; k <= N; ++k) + { + double th = 2.0 * PI * k / N; + inner_pts[k] = rerun::Position2D(static_cast(c[i()] + r.lb() * std::cos(th)), + static_cast(c[j()] + r.lb() * std::sin(th))); + outer_pts[k] = rerun::Position2D(static_cast(c[i()] + r.ub() * std::cos(th)), + static_cast(c[j()] + r.ub() * std::sin(th))); + } + auto entity = make_entity_path(_fig.name(), style.layer, "ring", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{ + rerun::components::LineStrip2D(inner_pts), + rerun::components::LineStrip2D(outer_pts) + }).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_polyline([[maybe_unused]] const std::vector& x, [[maybe_unused]] float tip_length, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + if(x.empty()) return; + std::vector pts(x.size()); + for(size_t k = 0; k < x.size(); ++k) + { + pts[k] = rerun::Position2D(static_cast(graphic_trunc(x[k][i()])), + static_cast(graphic_trunc(x[k][j()]))); + } + auto entity = make_entity_path(_fig.name(), style.layer, "polyline", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); + + if(tip_length > 0.0f && pts.size() >= 2) + { + auto p_end = pts.back(); + auto p_prev = pts[pts.size() - 2]; + float dx = p_end.x() - p_prev.x(); + float dy = p_end.y() - p_prev.y(); + float norm = std::hypot(dx, dy); + if(norm > 1e-6f) + { + float arrow_dx = (dx / norm) * tip_length; + float arrow_dy = (dy / norm) * tip_length; + auto arrow_entity = entity + "/tip"; + _rec.log(arrow_entity, rerun::Arrows2D::from_vectors({{arrow_dx, arrow_dy}}) + .with_origins({{p_end.x() - arrow_dx, p_end.y() - arrow_dy}}) + .with_colors({to_rerun_color(col)})); + } + } +#endif +} + +void Figure2D_Rerun::draw_polygon([[maybe_unused]] const std::vector& x, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + if(x.empty()) return; + if(x.size() == 1) + { + draw_point(x[0], style); + return; + } + std::vector pts(x.size() + 1); + for(size_t k = 0; k < x.size(); ++k) + { + pts[k] = rerun::Position2D(static_cast(graphic_trunc(x[k][i()])), + static_cast(graphic_trunc(x[k][j()]))); + } + pts.back() = pts.front(); // close polygon + + auto entity = make_entity_path(_fig.name(), style.layer, "polygon", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_pie([[maybe_unused]] const Vector& c, [[maybe_unused]] const Interval& r, [[maybe_unused]] const Interval& theta, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= c.size()); + const int N = 32; + double dtheta = theta.diam() / N; + std::vector pts; + pts.reserve(2 * N + 3); + + // Outer arc + for(int k = 0; k <= N; ++k) + { + double th = theta.lb() + k * dtheta; + pts.push_back(rerun::Position2D(static_cast(c[i()] + r.ub() * std::cos(th)), + static_cast(c[j()] + r.ub() * std::sin(th)))); + } + // Inner arc reverse + for(int k = N; k >= 0; --k) + { + double th = theta.lb() + k * dtheta; + pts.push_back(rerun::Position2D(static_cast(c[i()] + r.lb() * std::cos(th)), + static_cast(c[j()] + r.lb() * std::sin(th)))); + } + pts.push_back(pts.front()); + + auto entity = make_entity_path(_fig.name(), style.layer, "pie", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_ellipse([[maybe_unused]] const Vector& c, [[maybe_unused]] const Vector& ab, [[maybe_unused]] double theta, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(c.size() == 2 && ab.size() == 2); + const int N = 48; + std::vector pts(N + 1); + double cos_th = std::cos(theta); + double sin_th = std::sin(theta); + for(int k = 0; k <= N; ++k) + { + double t = 2.0 * PI * k / N; + double ex = ab[0] * std::cos(t); + double ey = ab[1] * std::sin(t); + double rx = c[0] + ex * cos_th - ey * sin_th; + double ry = c[1] + ex * sin_th + ey * cos_th; + pts[k] = rerun::Position2D(static_cast(rx), static_cast(ry)); + } + auto entity = make_entity_path(_fig.name(), style.layer, "ellipse", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_tank([[maybe_unused]] const Vector& x, [[maybe_unused]] float size, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= x.size() + 1); + assert(j() + 1 < x.size()); + assert(size >= 0.0f); + + double heading = x[j() + 1]; + double cos_th = std::cos(heading); + double sin_th = std::sin(heading); + double scale = size / 4.0; + double cx = x[i()]; + double cy = x[j()]; + + // Tank contour vertices from Codac/VIBes/IPE definition + const std::vector> raw_shape = { + {1, -1.5}, {-1, -1.5}, {0, -1.5}, {0, -1}, {-1, -1}, {-1, 1}, + {0, 1}, {0, 1.5}, {-1, 1.5}, {1, 1.5}, {0, 1.5}, {0, 1}, + {3, 0.5}, {3, -0.5}, {0, -1}, {0, -1.5} + }; + + std::vector pts(raw_shape.size() + 1); + for(size_t k = 0; k < raw_shape.size(); ++k) + pts[k] = transform_2d(raw_shape[k].first, raw_shape[k].second, cx, cy, cos_th, sin_th, scale); + pts.back() = pts.front(); + + auto entity = make_entity_path(_fig.name(), style.layer, "tank", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{rerun::components::LineStrip2D(pts)}).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_AUV([[maybe_unused]] const Vector& x, [[maybe_unused]] float size, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= x.size() + 1); + assert(j() + 1 < x.size()); + assert(size >= 0.0f); + + double heading = x[j() + 1]; + double cos_th = std::cos(heading); + double sin_th = std::sin(heading); + double scale = size / 7.0; + double cx = x[i()]; + double cy = x[j()]; + + const std::vector> body = { + {-4, 0}, {-2, 1}, {2, 1}, {2.17365, 0.984808}, {2.34202, 0.939693}, + {2.5, 0.866025}, {2.64279, 0.766044}, {2.76604, 0.642788}, {2.86603, 0.5}, + {2.93969, 0.34202}, {2.98481, 0.173648}, {3, 0}, {2.98481, -0.173648}, + {2.93969, -0.34202}, {2.86603, -0.5}, {2.76604, -0.642788}, {2.64279, -0.766044}, + {2.5, -0.866025}, {2.34202, -0.939693}, {2.17365, -0.984808}, {2, -1}, {-2, -1}, {-4, 0} + }; + + const std::vector> prop = { + {-4, 1}, {-3.25, 1}, {-3.25, -1}, {-4, -1}, {-4, 1} + }; + + std::vector body_pts(body.size()); + for(size_t k = 0; k < body.size(); ++k) + body_pts[k] = transform_2d(body[k].first, body[k].second, cx, cy, cos_th, sin_th, scale); + + std::vector prop_pts(prop.size()); + for(size_t k = 0; k < prop.size(); ++k) + prop_pts[k] = transform_2d(prop[k].first, prop[k].second, cx, cy, cos_th, sin_th, scale); + + auto entity = make_entity_path(_fig.name(), style.layer, "AUV", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{ + rerun::components::LineStrip2D(body_pts), + rerun::components::LineStrip2D(prop_pts) + }).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_motor_boat([[maybe_unused]] const Vector& x, [[maybe_unused]] float size, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= x.size() + 1); + assert(j() + 1 < x.size()); + assert(size >= 0.0f); + + double heading = x[j() + 1]; + double cos_th = std::cos(heading); + double sin_th = std::sin(heading); + double scale = size / 408.0; + double cx = x[i()]; + double cy = x[j()]; + + const std::vector> hull = { + {-72, -80}, {-72, 80}, {120, 80}, {184, 80}, {264, 64}, {312, 32}, + {328, 0}, {312, -32}, {264, -64}, {184, -80}, {120, -80}, {-72, -80} + }; + + const std::vector> engine = { + {-24, 32}, {-24, -32}, {40, -32}, {40, 32}, {-24, 32} + }; + + const std::vector> left_prop = { + {-72, 48}, {-72, 16}, {-80, 16}, {-80, 48}, {-72, 48} + }; + + const std::vector> right_prop = { + {-72, -16}, {-72, -48}, {-80, -48}, {-80, -16}, {-72, -16} + }; + + auto transform_list = [&](const std::vector>& l) { + std::vector res(l.size()); + for(size_t k = 0; k < l.size(); ++k) + res[k] = transform_2d(l[k].first, l[k].second, cx, cy, cos_th, sin_th, scale); + return res; + }; + + auto entity = make_entity_path(_fig.name(), style.layer, "motor_boat", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::LineStrips2D(std::vector{ + rerun::components::LineStrip2D(transform_list(hull)), + rerun::components::LineStrip2D(transform_list(engine)), + rerun::components::LineStrip2D(transform_list(left_prop)), + rerun::components::LineStrip2D(transform_list(right_prop)) + }).with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_text([[maybe_unused]] const std::string& text, [[maybe_unused]] const Vector& ul, [[maybe_unused]] double scale, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(_fig.size() <= ul.size()); + auto entity = make_entity_path(_fig.name(), style.layer, "text", ++_item_count); + Color col = (style.stroke_color != Color::none()) ? style.stroke_color : ((style.fill_color != Color::none()) ? style.fill_color : Color::black()); + _rec.log(entity, rerun::Points2D({{ static_cast(ul[i()]), static_cast(ul[j()]) }}) + .with_labels({text}) + .with_colors({to_rerun_color(col)})); +#endif +} + +void Figure2D_Rerun::draw_raster([[maybe_unused]] const std::string& filename, [[maybe_unused]] const IntervalVector& bbox, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + draw_box(bbox, style); +#endif +} diff --git a/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.h b/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.h new file mode 100644 index 000000000..fbd6b10f0 --- /dev/null +++ b/src/graphics/3rd/rerun/codac2_Figure2D_Rerun.h @@ -0,0 +1,100 @@ +/** + * \file codac2_Figure2D_Rerun.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include +#include "codac2_OutputFigure2D.h" +#include "codac2_Rerun_common.h" + +namespace codac2 +{ + class Figure2D; + + /** + * \class Figure2D_Rerun + * \brief 2D Rerun graphics backend + */ + class Figure2D_Rerun : public OutputFigure2D + { + public: + + /** + * \brief Creates a new Figure2D_Rerun object linked to a given figure + * + * \param fig Figure2D to use + */ + Figure2D_Rerun(const Figure2D& fig); + + /** + * \brief Destructor for the Figure2D_Rerun object + */ + virtual ~Figure2D_Rerun(); + + /** + * \brief Updates the axes of the figure + */ + void update_axes() override; + + /** + * \brief Updates the window properties of the figure + */ + void update_window_properties() override; + + /** + * \brief Centers the viewbox + */ + void center_viewbox(const Vector& c, const Vector& r) override; + + /** + * \brief Clears the figure + */ + void clear() override; + + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to (.rrd) + */ + void save(const std::string& filename) override; + + // Geometric shapes + + void draw_point(const Vector& c, const StyleProperties& style = StyleProperties()) override; + void draw_box(const IntervalVector& x, const StyleProperties& style = StyleProperties()) override; + void draw_circle(const Vector& c, double r, const StyleProperties& style = StyleProperties()) override; + void draw_ring(const Vector& c, const Interval& r, const StyleProperties& style = StyleProperties()) override; + void draw_polyline(const std::vector& x, float tip_length, const StyleProperties& style = StyleProperties()) override; + void draw_polygon(const std::vector& x, const StyleProperties& style = StyleProperties()) override; + void draw_pie(const Vector& c, const Interval& r, const Interval& theta, const StyleProperties& style = StyleProperties()) override; + void draw_ellipse(const Vector& c, const Vector& ab, double theta, const StyleProperties& style = StyleProperties()) override; + + // Robots + + void draw_tank(const Vector& x, float size, const StyleProperties& style = StyleProperties()) override; + void draw_AUV(const Vector& x, float size, const StyleProperties& style = StyleProperties()) override; + void draw_motor_boat(const Vector& x, float size, const StyleProperties& style = StyleProperties()) override; + + // Miscellaneous + + void draw_text(const std::string& text, const Vector& ul, double scale, const StyleProperties& style = StyleProperties()) override; + void draw_raster(const std::string& filename, const IntervalVector& bbox, const StyleProperties& style = StyleProperties()) override; + + private: + +#ifdef CODAC_WITH_RERUN + rerun::RecordingStream _rec; +#endif + size_t _item_count = 0; + bool _is_saved = false; + std::string _last_saved_filename; + }; +} diff --git a/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.cpp b/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.cpp new file mode 100644 index 000000000..749831f7c --- /dev/null +++ b/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.cpp @@ -0,0 +1,611 @@ +/** + * codac2_Figure3D_Rerun.cpp + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_Figure3D.h" +#include "codac2_Figure3D_Rerun.h" +#include "codac2_Paving.h" +#include "codac2_math.h" +#include "codac2_trunc.h" +#include + +using namespace std; +using namespace codac2; + +Figure3D_Rerun::Figure3D_Rerun(const Figure3D& fig) +#ifdef CODAC_WITH_RERUN + : OutputFigure3D(fig), _rec(fig.name()) +{ + const char* spawn_env = std::getenv("CODAC_RERUN_SPAWN"); + if(spawn_env && std::string(spawn_env) != "0") + { + _rec.spawn().exit_on_failure(); + } +} +#else + : OutputFigure3D(fig) +{ +} +#endif + +Figure3D_Rerun::~Figure3D_Rerun() +{ +#ifdef CODAC_WITH_RERUN + if(!_is_saved) + { + save(_fig.name() + ".rrd"); + } + else + { + _rec.flush_blocking(); + } +#endif +} + +void Figure3D_Rerun::update_axes() +{ +} + +void Figure3D_Rerun::clear() +{ +#ifdef CODAC_WITH_RERUN + _rec.log(sanitize_rerun_path(_fig.name()), rerun::Clear::RECURSIVE); + _item_count = 0; +#endif +} + +void Figure3D_Rerun::save([[maybe_unused]] const std::string& filename) +{ +#ifdef CODAC_WITH_RERUN + // Do not handle files explicitly meant for other backends (such as .obj or .xml) + if(filename.size() >= 4 && (filename.compare(filename.size() - 4, 4, ".obj") == 0 || + filename.compare(filename.size() - 4, 4, ".xml") == 0)) + return; + + std::string target = filename; + if(target.find(".rrd") == std::string::npos) + if(target.size() < 4 || target.compare(target.size() - 4, 4, ".rrd") != 0) + target += ".rrd"; + + (void)_rec.save(target); + _rec.flush_blocking(); + _is_saved = true; + _last_saved_filename = target; +#endif +} + +void Figure3D_Rerun::draw_point([[maybe_unused]] const Vector& c, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(c.size() >= 3); + auto entity = make_entity_path(_fig.name(), style.layer, "point3d", ++_item_count); + float radius = style.line_width > 0.0 ? static_cast(style.line_width * 0.02) : 0.05f; + _rec.log(entity, rerun::Points3D({{ static_cast(c[0]), static_cast(c[1]), static_cast(c[2]) }}) + .with_colors({to_rerun_color(style.stroke_color)}) + .with_radii({radius})); +#endif +} + +void Figure3D_Rerun::draw_box([[maybe_unused]] const IntervalVector& x, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(x.size() == 3); + float cx = 0.5f * static_cast(x[0].lb() + x[0].ub()); + float cy = 0.5f * static_cast(x[1].lb() + x[1].ub()); + float cz = 0.5f * static_cast(x[2].lb() + x[2].ub()); + float hx = 0.5f * static_cast(x[0].diam()); + float hy = 0.5f * static_cast(x[1].diam()); + float hz = 0.5f * static_cast(x[2].diam()); + + auto entity = make_entity_path(_fig.name(), style.layer, "box3d", ++_item_count); + Color c = style.fill_color == codac2::Color::none() ? style.stroke_color : style.fill_color; + _rec.log(entity, rerun::Boxes3D::from_centers_and_half_sizes({{cx, cy, cz}}, {{hx, hy, hz}}) + .with_colors({to_rerun_color(c)})); +#endif +} + +void Figure3D_Rerun::draw_polyline([[maybe_unused]] const std::vector& x, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + if(x.empty()) return; + std::vector pts(x.size()); + for(size_t k = 0; k < x.size(); ++k) + { + assert(x[k].size() >= 3); + pts[k] = rerun::components::Position3D(static_cast(x[k][0]), + static_cast(x[k][1]), + static_cast(x[k][2])); + } + auto entity = make_entity_path(_fig.name(), style.layer, "polyline3d", ++_item_count); + _rec.log(entity, rerun::LineStrips3D(std::vector{rerun::components::LineStrip3D(pts)}).with_colors({to_rerun_color(style.stroke_color)})); +#endif +} + +#ifdef CODAC_WITH_RERUN +static rerun::components::Vector3D compute_normal_3d(const Vector& p1, const Vector& p2, const Vector& p3) +{ + float dx1 = static_cast(p2[0] - p1[0]); + float dy1 = static_cast(p2[1] - p1[1]); + float dz1 = static_cast(p2[2] - p1[2]); + float dx2 = static_cast(p3[0] - p1[0]); + float dy2 = static_cast(p3[1] - p1[1]); + float dz2 = static_cast(p3[2] - p1[2]); + float nx = dy1 * dz2 - dz1 * dy2; + float ny = dz1 * dx2 - dx1 * dz2; + float nz = dx1 * dy2 - dy1 * dx2; + float len = std::sqrt(nx * nx + ny * ny + nz * nz); + if(len > 1e-6f) + { + nx /= len; + ny /= len; + nz /= len; + } + return rerun::components::Vector3D(nx, ny, nz); +} +#endif + +void Figure3D_Rerun::draw_triangle([[maybe_unused]] const Vector &c, [[maybe_unused]] const Matrix &A, + [[maybe_unused]] const Vector &p1, [[maybe_unused]] const Vector &p2, [[maybe_unused]] const Vector &p3, + [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + Vector v1 = c + A * p1; + Vector v2 = c + A * p2; + Vector v3 = c + A * p3; + + std::vector vertices = { + rerun::components::Position3D(static_cast(v1[0]), static_cast(v1[1]), static_cast(v1[2])), + rerun::components::Position3D(static_cast(v2[0]), static_cast(v2[1]), static_cast(v2[2])), + rerun::components::Position3D(static_cast(v3[0]), static_cast(v3[1]), static_cast(v3[2])) + }; + std::vector indices = { rerun::TriangleIndices(0, 1, 2) }; + + auto norm = compute_normal_3d(v1, v2, v3); + std::vector normals = { norm, norm, norm }; + + auto entity = make_entity_path(_fig.name(), style.layer, "triangle", ++_item_count); + Color col = style.fill_color == codac2::Color::none() ? style.stroke_color : style.fill_color; + _rec.log(entity, rerun::Mesh3D(vertices) + .with_triangle_indices(indices) + .with_vertex_normals(normals) + .with_albedo_factor(to_rerun_albedo(col))); +#endif +} + +void Figure3D_Rerun::draw_polygon([[maybe_unused]] const Vector &c, [[maybe_unused]] const Matrix &A, + [[maybe_unused]] const std::vector &l, + [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + if(l.size() < 3) return; + + std::vector vertices(l.size()); + std::vector world_pts(l.size()); + for(size_t i = 0; i < l.size(); ++i) + { + world_pts[i] = c + A * l[i]; + vertices[i] = rerun::components::Position3D(static_cast(world_pts[i][0]), static_cast(world_pts[i][1]), static_cast(world_pts[i][2])); + } + + std::vector indices; + indices.reserve(l.size() - 2); + for(size_t i = 1; i + 1 < l.size(); ++i) + { + indices.push_back(rerun::TriangleIndices(0, static_cast(i), static_cast(i + 1))); + } + + auto norm = compute_normal_3d(world_pts[0], world_pts[1], world_pts[2]); + std::vector normals(l.size(), norm); + + auto entity = make_entity_path(_fig.name(), style.layer, "polygon3d", ++_item_count); + Color col = style.fill_color == codac2::Color::none() ? style.stroke_color : style.fill_color; + _rec.log(entity, rerun::Mesh3D(vertices) + .with_triangle_indices(indices) + .with_vertex_normals(normals) + .with_albedo_factor(to_rerun_albedo(col))); +#endif +} + +void Figure3D_Rerun::draw_parallelogram([[maybe_unused]] const Vector &c, [[maybe_unused]] const Matrix &A, + [[maybe_unused]] const Vector &p, [[maybe_unused]] const Vector &v1, [[maybe_unused]] const Vector &v2, + [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + std::vector poly = { + p - v1 - v2, + p - v1 + v2, + p + v1 + v2, + p + v1 - v2 + }; + draw_polygon(c, A, poly, style); +#endif +} + +void Figure3D_Rerun::draw_parallelepiped([[maybe_unused]] const Parallelepiped& p, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert_release(p.c.size() == 3); + assert_release(p.A.rows() == 3 && p.A.cols() == 3); + + const std::vector> face_corners = { + { Vector({-1, -1, -1}), Vector({-1, -1, 1}), Vector({-1, 1, 1}), Vector({-1, 1, -1}) }, // -X + { Vector({ 1, -1, -1}), Vector({ 1, 1, -1}), Vector({ 1, 1, 1}), Vector({ 1, -1, 1}) }, // +X + { Vector({-1, -1, -1}), Vector({ 1, -1, -1}), Vector({ 1, -1, 1}), Vector({-1, -1, 1}) }, // -Y + { Vector({-1, 1, -1}), Vector({-1, 1, 1}), Vector({ 1, 1, 1}), Vector({ 1, 1, -1}) }, // +Y + { Vector({-1, -1, -1}), Vector({-1, 1, -1}), Vector({ 1, 1, -1}), Vector({ 1, -1, -1}) }, // -Z + { Vector({-1, -1, 1}), Vector({ 1, -1, 1}), Vector({ 1, 1, 1}), Vector({-1, 1, 1}) } // +Z + }; + + std::vector vertices; + std::vector normals; + std::vector indices; + vertices.reserve(24); + normals.reserve(24); + indices.reserve(12); + + for(size_t f = 0; f < 6; ++f) + { + uint32_t base = static_cast(vertices.size()); + Vector w0 = p.c + p.A * face_corners[f][0]; + Vector w1 = p.c + p.A * face_corners[f][1]; + Vector w2 = p.c + p.A * face_corners[f][2]; + Vector w3 = p.c + p.A * face_corners[f][3]; + + auto n = compute_normal_3d(w0, w1, w2); + + vertices.push_back(rerun::components::Position3D(static_cast(w0[0]), static_cast(w0[1]), static_cast(w0[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w1[0]), static_cast(w1[1]), static_cast(w1[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w2[0]), static_cast(w2[1]), static_cast(w2[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w3[0]), static_cast(w3[1]), static_cast(w3[2]))); + + normals.push_back(n); + normals.push_back(n); + normals.push_back(n); + normals.push_back(n); + + indices.push_back(rerun::TriangleIndices(base, base + 1, base + 2)); + indices.push_back(rerun::TriangleIndices(base, base + 2, base + 3)); + } + + auto entity = make_entity_path(_fig.name(), style.layer, "parallelepiped", ++_item_count); + Color col = style.fill_color == codac2::Color::none() ? style.stroke_color : style.fill_color; + _rec.log(entity, rerun::Mesh3D(vertices) + .with_triangle_indices(indices) + .with_vertex_normals(normals) + .with_albedo_factor(to_rerun_albedo(col))); +#endif +} + +void Figure3D_Rerun::draw_zonotope([[maybe_unused]] const Zonotope& z, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert_release(z.c.size() == 3); + Matrix id = Matrix::Identity(3,3); + size_t nb = z.A.cols(); + for (size_t j = 1; j < nb; j++) { + const Eigen::Vector3d &Aj = z.A.col(j); + for (size_t i = 0; i < j; i++) { + const Eigen::Vector3d &Ai = z.A.col(i); + Eigen::Vector3d Norm = Ai.cross(Aj); + if (Norm.lpNorm() < 1e-8) continue; + Eigen::Vector3d R1 = Eigen::Vector3d::Zero(); + Eigen::Vector3d R2 = Eigen::Vector3d::Zero(); + for (size_t k = 0; k < nb; k++) { + if (k == i || k == j) continue; + const Eigen::Vector3d &Ak = z.A.col(k); + double prod = Norm.dot(Ak); + if (std::fabs(prod) < 1e-8) { + if (k > j) { + R2 -= Ak; + continue; + } + Eigen::Vector3d N2 = Ai.cross(Ak); + if (N2.lpNorm() < 1e-8) { + if (Ai.dot(Ak) < 0 || i < k) R2 -= Ak; else R2 += Ak; + continue; + } + if (N2.dot(Norm) >= 0) R2 += Ak; else R2 -= Ak; + } + else if (prod > 0.0) { + R1 += Ak; + } + else { + R1 -= Ak; + } + } + draw_parallelogram(z.c, id, R1+R2, Ai, Aj, style); + draw_parallelogram(z.c, id, -R1+R2, Ai, Aj, style); + } + } +#endif +} + +void Figure3D_Rerun::draw_arrow([[maybe_unused]] const Vector &c, [[maybe_unused]] const Matrix& A, [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + assert(c.size() >= 3 && A.cols() >= 1 && A.rows() >= 3); + Vector dir = A.col(0); + auto entity = make_entity_path(_fig.name(), style.layer, "arrow3d", ++_item_count); + _rec.log(entity, rerun::Arrows3D::from_vectors({{ static_cast(dir[0]), static_cast(dir[1]), static_cast(dir[2]) }}) + .with_origins({{ static_cast(c[0]), static_cast(c[1]), static_cast(c[2]) }}) + .with_colors({to_rerun_color(style.stroke_color)})); +#endif +} + +void Figure3D_Rerun::draw_axes([[maybe_unused]] double size, [[maybe_unused]] const Vector& origin) +{ +#ifdef CODAC_WITH_RERUN + assert(origin.size() == 3); + auto entity = sanitize_rerun_path(_fig.name()) + "/axes"; + float ox = static_cast(origin[0]); + float oy = static_cast(origin[1]); + float oz = static_cast(origin[2]); + float s = static_cast(size); + + _rec.log(entity, rerun::Arrows3D::from_vectors({ + {s, 0.0f, 0.0f}, // X + {0.0f, s, 0.0f}, // Y + {0.0f, 0.0f, s} // Z + }).with_origins({ + {ox, oy, oz}, + {ox, oy, oz}, + {ox, oy, oz} + }).with_colors({ + rerun::Color(255, 0, 0, 255), + rerun::Color(0, 255, 0, 255), + rerun::Color(0, 0, 255, 255) + })); +#endif +} + +void Figure3D_Rerun::draw_surface([[maybe_unused]] const Vector &c, [[maybe_unused]] const Matrix &A, + [[maybe_unused]] const Interval &Ip1, [[maybe_unused]] double dp1, + [[maybe_unused]] const Interval &Ip2, [[maybe_unused]] double dp2, + [[maybe_unused]] std::function f, + [[maybe_unused]] const StyleProperties& style) +{ +#ifdef CODAC_WITH_RERUN + std::vector vertices; + std::vector normals; + std::vector indices; + + for(double p1 = Ip1.lb(); p1 < Ip1.ub(); p1 += dp1) + { + for(double p2 = Ip2.lb(); p2 < Ip2.ub(); p2 += dp2) + { + double next_p1 = std::min(p1 + dp1, Ip1.ub()); + double next_p2 = std::min(p2 + dp2, Ip2.ub()); + + Vector w0 = c + A * f(p1, p2); + Vector w1 = c + A * f(next_p1, p2); + Vector w2 = c + A * f(next_p1, next_p2); + Vector w3 = c + A * f(p1, next_p2); + + auto rn = compute_normal_3d(w0, w1, w3); + + uint32_t base = static_cast(vertices.size()); + vertices.push_back(rerun::components::Position3D(static_cast(w0[0]), static_cast(w0[1]), static_cast(w0[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w1[0]), static_cast(w1[1]), static_cast(w1[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w2[0]), static_cast(w2[1]), static_cast(w2[2]))); + vertices.push_back(rerun::components::Position3D(static_cast(w3[0]), static_cast(w3[1]), static_cast(w3[2]))); + + normals.push_back(rn); + normals.push_back(rn); + normals.push_back(rn); + normals.push_back(rn); + + indices.push_back(rerun::TriangleIndices(base, base + 1, base + 2)); + indices.push_back(rerun::TriangleIndices(base, base + 2, base + 3)); + } + } + + auto entity = make_entity_path(_fig.name(), style.layer, "surface", ++_item_count); + Color col = style.fill_color == codac2::Color::none() ? style.stroke_color : style.fill_color; + _rec.log(entity, rerun::Mesh3D(vertices) + .with_triangle_indices(indices) + .with_vertex_normals(normals) + .with_albedo_factor(to_rerun_albedo(col))); +#endif +} + +void Figure3D_Rerun::draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + draw_surface(c, A, Interval(-0.5*PI, 0.5*PI), 0.1*PI, + Interval(0, 2.0*PI), 0.1*PI, + [](double p1, double p2) -> Vector { + return { std::cos(p1)*std::cos(p2), std::cos(p1)*std::sin(p2), std::sin(p1) }; + }, + style); +} + +void Figure3D_Rerun::draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style) +{ + draw_sphere(e.mu, e.G, style); +} + +void Figure3D_Rerun::draw_car(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + // Left side + std::vector side = { + {1.5, -0.8, 1}, {2.8, -0.8, 1}, {3, -0.8, 0}, {-1, -0.8, 0}, {0, -0.8, 2}, {1, -0.8, 2} + }; + draw_polygon(c, A, side, style); + + // Right side + for(auto &e : side) { e[1] = 0.8; } + draw_polygon(c, A, side, style); + + // Rear and top + std::vector reartop = { + {0, -0.8, 2}, {-1, -0.8, 0}, {-1, 0.8, 0}, {0, 0.8, 2}, {1, 0.8, 2}, {1, -0.8, 2} + }; + draw_polygon(c, A, reartop, style); + + // Front and motor + std::vector front = { + {2.8, -0.8, 1}, {1.5, -0.8, 1}, {1.5, 0.8, 1}, {2.8, 0.8, 1}, {3, 0.8, 0}, {3, -0.8, 0} + }; + draw_polygon(c, A, front, style); + + // Windscreen + StyleProperties ws_style = style; + ws_style.stroke_color = Color::blue(0.6); + ws_style.fill_color = Color::blue(0.3); + draw_parallelogram(c, A, Vector({1.25, 0.0, 1.5}), + Vector({0, 0.8, 0}), Vector({-0.25, 0, 0.5}), ws_style); +} + +void Figure3D_Rerun::draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up, const StyleProperties& style) +{ + // Wings + std::vector wings = { + {-1.5, 0, 0}, {-1.5, -1, 0}, {-1, -1, 0}, {0, 0, 0}, {-1, 1, 0}, {-1.5, 1, 0} + }; + draw_polygon(c, A, wings, style); + + double vert = yaw_is_up ? 1.0 : -1.0; + // Fuselage body + draw_triangle(c, A, {-1.5, 0, 0}, {1.5, 0, 0}, {0.5, -0.1, 0.5*vert}, style); + draw_triangle(c, A, {-1.5, 0, 0}, {1.5, 0, 0}, {0.5, 0.1, 0.5*vert}, style); + // Vertical stabilizer + draw_triangle(c, A, {-1.5, 0, 0}, {-1.5, 0, vert}, {-0.5, 0, 0}, style); +} + +void Figure3D_Rerun::draw_AUV(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + const int N = 8; + double r = 0.3; + double len = 2.0; + + for(int i = 0; i < N; ++i) + { + double th1 = 2 * PI * i / N; + double th2 = 2 * PI * (i + 1) / N; + Vector p1_nose { len * 0.5 + 0.5, 0, 0 }; + Vector p1_front { len * 0.5, r * std::cos(th1), r * std::sin(th1) }; + Vector p2_front { len * 0.5, r * std::cos(th2), r * std::sin(th2) }; + draw_triangle(c, A, p1_nose, p1_front, p2_front, style); + + Vector p1_back { -len * 0.5, r * std::cos(th1), r * std::sin(th1) }; + Vector p2_back { -len * 0.5, r * std::cos(th2), r * std::sin(th2) }; + draw_triangle(c, A, p1_front, p1_back, p2_back, style); + draw_triangle(c, A, p1_front, p2_back, p2_front, style); + + Vector p_tail { -len * 0.5 - 0.4, 0, 0 }; + draw_triangle(c, A, p_tail, p2_back, p1_back, style); + } + // Stabilizer fins + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0, 0.6}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0, -0.6}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, 0.6, 0}, {-1.0, 0, 0}, style); + draw_triangle(c, A, {-0.5, 0, 0}, {-1.2, -0.6, 0}, {-1.0, 0, 0}, style); +} + +void Figure3D_Rerun::draw_paving([[maybe_unused]] const PavingOut& p, [[maybe_unused]] const StyleProperties& boundary_style) +{ +#ifdef CODAC_WITH_RERUN + if(!p.tree() || !p.tree()->left()) + return; + + std::vector centers; + std::vector half_sizes; + + p.tree()->left()->visit([&](std::shared_ptr n) { + const IntervalVector& outer = get<0>(n->boxes()); + if(n->is_leaf() && !outer.is_empty() && outer.size() == 3) + { + float cx = 0.5f * static_cast(outer[0].lb() + outer[0].ub()); + float cy = 0.5f * static_cast(outer[1].lb() + outer[1].ub()); + float cz = 0.5f * static_cast(outer[2].lb() + outer[2].ub()); + float hx = 0.5f * static_cast(outer[0].diam()); + float hy = 0.5f * static_cast(outer[1].diam()); + float hz = 0.5f * static_cast(outer[2].diam()); + centers.push_back(rerun::components::Position3D(cx, cy, cz)); + half_sizes.push_back(rerun::components::HalfSize3D(hx, hy, hz)); + } + return true; + }); + + if(centers.empty()) + return; + + std::string layer = (boundary_style.layer.empty() || boundary_style.layer == "alpha") ? "boundary" : boundary_style.layer; + auto entity = make_entity_path(_fig.name(), layer, "paving", ++_item_count); + Color c = boundary_style.fill_color == codac2::Color::none() ? boundary_style.stroke_color : boundary_style.fill_color; + _rec.log(entity, rerun::Boxes3D::from_centers_and_half_sizes(centers, half_sizes) + .with_colors({to_rerun_color(c)})); +#endif +} + +void Figure3D_Rerun::draw_paving([[maybe_unused]] const PavingInOut& p, [[maybe_unused]] const StyleProperties& boundary_style, + [[maybe_unused]] const StyleProperties& inside_style) +{ +#ifdef CODAC_WITH_RERUN + if(!p.tree()) + return; + + std::vector bound_centers, in_centers; + std::vector bound_half_sizes, in_half_sizes; + + p.tree()->visit([&](std::shared_ptr n) { + const IntervalVector& outer = get<0>(n->boxes()); + const IntervalVector& inner = get<1>(n->boxes()); + + IntervalVector hull = inner | outer; + for(const auto& bi : hull.diff(inner)) + { + if(!bi.is_empty() && bi.size() == 3) + { + float cx = 0.5f * static_cast(bi[0].lb() + bi[0].ub()); + float cy = 0.5f * static_cast(bi[1].lb() + bi[1].ub()); + float cz = 0.5f * static_cast(bi[2].lb() + bi[2].ub()); + float hx = 0.5f * static_cast(bi[0].diam()); + float hy = 0.5f * static_cast(bi[1].diam()); + float hz = 0.5f * static_cast(bi[2].diam()); + in_centers.push_back(rerun::components::Position3D(cx, cy, cz)); + in_half_sizes.push_back(rerun::components::HalfSize3D(hx, hy, hz)); + } + } + + if(n->is_leaf()) + { + auto boundary = inner & outer; + if(!boundary.is_empty() && boundary.size() == 3) + { + float cx = 0.5f * static_cast(boundary[0].lb() + boundary[0].ub()); + float cy = 0.5f * static_cast(boundary[1].lb() + boundary[1].ub()); + float cz = 0.5f * static_cast(boundary[2].lb() + boundary[2].ub()); + float hx = 0.5f * static_cast(boundary[0].diam()); + float hy = 0.5f * static_cast(boundary[1].diam()); + float hz = 0.5f * static_cast(boundary[2].diam()); + bound_centers.push_back(rerun::components::Position3D(cx, cy, cz)); + bound_half_sizes.push_back(rerun::components::HalfSize3D(hx, hy, hz)); + } + } + return true; + }); + + if(!in_centers.empty()) + { + std::string in_layer = (inside_style.layer.empty() || inside_style.layer == "alpha") ? "inside" : inside_style.layer; + auto in_entity = make_entity_path(_fig.name(), in_layer, "paving_in", ++_item_count); + Color c_in = inside_style.fill_color == codac2::Color::none() ? inside_style.stroke_color : inside_style.fill_color; + _rec.log(in_entity, rerun::Boxes3D::from_centers_and_half_sizes(in_centers, in_half_sizes) + .with_colors({to_rerun_color(c_in)})); + } + + if(!bound_centers.empty()) + { + std::string bound_layer = (boundary_style.layer.empty() || boundary_style.layer == "alpha") ? "boundary" : boundary_style.layer; + auto bound_entity = make_entity_path(_fig.name(), bound_layer, "paving_bound", ++_item_count); + Color c_bound = boundary_style.fill_color == codac2::Color::none() ? boundary_style.stroke_color : boundary_style.fill_color; + _rec.log(bound_entity, rerun::Boxes3D::from_centers_and_half_sizes(bound_centers, bound_half_sizes) + .with_colors({to_rerun_color(c_bound)})); + } +#endif +} diff --git a/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.h b/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.h new file mode 100644 index 000000000..ff6b86825 --- /dev/null +++ b/src/graphics/3rd/rerun/codac2_Figure3D_Rerun.h @@ -0,0 +1,103 @@ +/** + * \file codac2_Figure3D_Rerun.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include +#include "codac2_OutputFigure3D.h" +#include "codac2_Rerun_common.h" + +namespace codac2 +{ + class Figure3D; + + /** + * \class Figure3D_Rerun + * \brief 3D Rerun graphics backend + */ + class Figure3D_Rerun : public OutputFigure3D + { + public: + + /** + * \brief Creates a new Figure3D_Rerun object linked to a given figure + * + * \param fig Figure3D to use + */ + Figure3D_Rerun(const Figure3D& fig); + + /** + * \brief Destructor for the Figure3D_Rerun object + */ + virtual ~Figure3D_Rerun(); + + /** + * \brief Updates the axes of the figure + */ + void update_axes() override; + + /** + * \brief Clears the figure + */ + void clear() override; + + /** + * \brief Saves the figure to a file (.rrd) + */ + void save(const std::string& filename) override; + + // Geometric shapes + + void draw_point(const Vector& c, const StyleProperties& style = StyleProperties()) override; + void draw_box(const IntervalVector& x, const StyleProperties& style = StyleProperties()) override; + void draw_polyline(const std::vector& x, const StyleProperties& style = StyleProperties()) override; + void draw_triangle(const Vector &c, const Matrix &A, + const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style = StyleProperties()) override; + void draw_polygon(const Vector &c, const Matrix &A, const std::vector &l, + const StyleProperties& style = StyleProperties()) override; + void draw_parallelogram(const Vector &c, const Matrix &A, + const Vector &p, const Vector &v1, const Vector &v2, + const StyleProperties& style = StyleProperties()) override; + void draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style = StyleProperties()) override; + void draw_zonotope(const Zonotope& z, const StyleProperties& style = StyleProperties()) override; + void draw_arrow(const Vector &c, const Matrix& A, const StyleProperties& style = StyleProperties()) override; + void draw_axes(double size = 1.0, const Vector& origin = Vector::Zero(3)) override; + void draw_surface(const Vector &c, const Matrix &A, + const Interval &Ip1, double dp1, + const Interval &Ip2, double dp2, + std::function f, + const StyleProperties& style = StyleProperties()) override; + void draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + void draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style = StyleProperties()) override; + + // Vehicles / Robots + + void draw_car(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + void draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up = true, const StyleProperties& style = StyleProperties()) override; + void draw_AUV(const Vector &c, const Matrix &A, const StyleProperties& style = StyleProperties()) override; + + // Pavings + + void draw_paving(const PavingOut& p, const StyleProperties& boundary_style = StyleProperties()) override; + void draw_paving(const PavingInOut& p, const StyleProperties& boundary_style = StyleProperties(), + const StyleProperties& inside_style = StyleProperties()) override; + + private: + +#ifdef CODAC_WITH_RERUN + rerun::RecordingStream _rec; +#endif + size_t _item_count = 0; + bool _is_saved = false; + std::string _last_saved_filename; + }; +} diff --git a/src/graphics/3rd/rerun/codac2_Rerun_common.h b/src/graphics/3rd/rerun/codac2_Rerun_common.h new file mode 100644 index 000000000..310fa4df3 --- /dev/null +++ b/src/graphics/3rd/rerun/codac2_Rerun_common.h @@ -0,0 +1,82 @@ +/** + * \file codac2_Rerun_common.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "codac2_Color.h" +#include "codac2_StyleProperties.h" +#include "codac2_Vector.h" + +#ifdef CODAC_WITH_RERUN +#include + +namespace codac2 +{ + inline rerun::Color to_rerun_color(const Color& c) + { + const auto& rgb = c.rgb(); + uint8_t r = static_cast(std::clamp(rgb[0], 0.0f, 255.0f)); + uint8_t g = static_cast(std::clamp(rgb[1], 0.0f, 255.0f)); + uint8_t b = static_cast(std::clamp(rgb[2], 0.0f, 255.0f)); + uint8_t a = static_cast(std::clamp(rgb[3], 0.0f, 255.0f)); + return rerun::Color(r, g, b, a); + } + + inline rerun::components::AlbedoFactor to_rerun_albedo(const Color& c) + { + const auto& rgb = c.rgb(); + uint8_t r = static_cast(std::clamp(rgb[0], 0.0f, 255.0f)); + uint8_t g = static_cast(std::clamp(rgb[1], 0.0f, 255.0f)); + uint8_t b = static_cast(std::clamp(rgb[2], 0.0f, 255.0f)); + uint8_t a = static_cast(std::clamp(rgb[3], 0.0f, 255.0f)); + return rerun::components::AlbedoFactor(rerun::encodings::Rgba32(r, g, b, a)); + } + + inline std::string sanitize_rerun_path(const std::string& name) + { + std::string res; + res.reserve(name.size()); + for(char ch : name) + { + if(std::isspace(static_cast(ch)) || ch == '/' || ch == '\\' || ch == ':' || ch == '*' || ch == '?' || ch == '"' || ch == '<' || ch == '>' || ch == '|' || ch == '`' || ch == '$' || ch == '@') + res.push_back('_'); + else + res.push_back(ch); + } + if(res.empty()) + res = "item"; + return res; + } + + inline std::string make_entity_path(const std::string& fig_name, const std::string& layer, + const std::string& type_name, size_t id) + { + std::string l = (layer.empty() || layer == "alpha") ? "default" : layer; + return sanitize_rerun_path(fig_name) + "/" + sanitize_rerun_path(l) + "/" + sanitize_rerun_path(type_name) + "_" + std::to_string(id); + } + + // 2D Rotation and translation helper + inline rerun::Position2D transform_2d(double x_raw, double y_raw, + double cx, double cy, + double cos_th, double sin_th, double scale) + { + double x_scaled = x_raw * scale; + double y_scaled = y_raw * scale; + double rx = cx + x_scaled * cos_th - y_scaled * sin_th; + double ry = cy + x_scaled * sin_th + y_scaled * cos_th; + return rerun::Position2D(static_cast(rx), static_cast(ry)); + } +} +#endif diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 453a547d1..4a5881c81 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -10,13 +10,24 @@ ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes/codac2_Figure2D_VIBes.h ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes/vibes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes/vibes.h + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun/codac2_Rerun_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun/codac2_Figure2D_Rerun.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun/codac2_Figure2D_Rerun.h + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun/codac2_Figure3D_Rerun.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun/codac2_Figure3D_Rerun.h + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/obj/codac2_Figure3D_OBJ.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/obj/codac2_Figure3D_OBJ.h + ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_GraphicOutput.h ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure2D.cpp ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure2D.h ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure2D_pave.h ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure2DInterface.h ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_OutputFigure2D.cpp ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_OutputFigure2D.h + ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure3DInterface.h + ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_OutputFigure3D.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_OutputFigure3D.h ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure3D.cpp ${CMAKE_CURRENT_SOURCE_DIR}/figures/codac2_Figure3D.h @@ -49,11 +60,19 @@ target_include_directories(${PROJECT_NAME}-graphics PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/3rd/ipe ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/rerun + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/obj ${CMAKE_CURRENT_SOURCE_DIR}/figures ${CMAKE_CURRENT_SOURCE_DIR}/paver # deprecated, to be removed ${CMAKE_CURRENT_SOURCE_DIR}/styles ) target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${PROJECT_NAME}-core) + if(WITH_RERUN) + target_compile_definitions(${PROJECT_NAME}-graphics PUBLIC CODAC_WITH_RERUN) + if(TARGET rerun_sdk) + target_link_libraries(${PROJECT_NAME}-graphics PUBLIC rerun_sdk) + endif() + endif() ################################################################################ diff --git a/src/graphics/figures/codac2_Figure2D.cpp b/src/graphics/figures/codac2_Figure2D.cpp index 2658e715b..6bb85950f 100644 --- a/src/graphics/figures/codac2_Figure2D.cpp +++ b/src/graphics/figures/codac2_Figure2D.cpp @@ -2,7 +2,7 @@ * codac2_Figure2D.cpp * ---------------------------------------------------------------------------- * \date 2024 - * \author Simon Rohou, Maël Godard, Morgan Louédec + * \author Simon Rohou, Maël Godard, Morgan Louédec, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -11,6 +11,7 @@ #include "codac2_Figure2D.h" #include "codac2_Figure2D_VIBes.h" #include "codac2_Figure2D_IPE.h" +#include "codac2_Figure2D_Rerun.h" #include "codac2_math.h" #include "codac2_pave.h" #include "codac2_matrices.h" @@ -29,6 +30,8 @@ Figure2D::Figure2D(const string& name, GraphicOutput o) _output_figures.push_back(make_shared(*this)); if(o & GraphicOutput::IPE) _output_figures.push_back(make_shared(*this)); + if(o & GraphicOutput::RERUN) + _output_figures.push_back(make_shared(*this)); } vector> Figure2D::output_figures() diff --git a/src/graphics/figures/codac2_Figure2D.h b/src/graphics/figures/codac2_Figure2D.h index ec637baa8..5770c7b29 100644 --- a/src/graphics/figures/codac2_Figure2D.h +++ b/src/graphics/figures/codac2_Figure2D.h @@ -2,7 +2,7 @@ * \file codac2_Figure2D.h * ---------------------------------------------------------------------------- * \date 2024 - * \author Simon Rohou, Maël Godard + * \author Simon Rohou, Maël Godard, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -25,21 +25,11 @@ #include "codac2_Ctc.h" #include "codac2_Sep.h" +#include "codac2_GraphicOutput.h" #define DEFAULT_FIG_NAME "Codac - default figure" namespace codac2 { - enum class GraphicOutput - { - VIBES = 0x01, - IPE = 0x02 - }; - - constexpr int operator&(GraphicOutput a, GraphicOutput b) - { return static_cast(static_cast(a) & static_cast(b)); } - - constexpr GraphicOutput operator|(GraphicOutput a, GraphicOutput b) - { return static_cast(static_cast(a) | static_cast(b)); } struct FigureAxis { diff --git a/src/graphics/figures/codac2_Figure3D.cpp b/src/graphics/figures/codac2_Figure3D.cpp index ca0aee3ef..ecbe33103 100644 --- a/src/graphics/figures/codac2_Figure3D.cpp +++ b/src/graphics/figures/codac2_Figure3D.cpp @@ -2,7 +2,7 @@ * codac2_Figure3D.cpp * ---------------------------------------------------------------------------- * \date 2024 - * \author Maël Godard + * \author Maël Godard, Damien Massé, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -10,22 +10,25 @@ #include "codac2_Index.h" #include "codac2_IntervalMatrix.h" #include "codac2_Figure3D.h" +#include "codac2_OutputFigure3D.h" +#include "codac2_Figure3D_OBJ.h" +#include "codac2_Figure3D_Rerun.h" #include "codac2_math.h" - using namespace std; using namespace codac2; -Figure3D::Figure3D(const std::string& name) +Figure3D::Figure3D(const std::string& name, GraphicOutput o) : _name(name) { - _file.open(name + ".obj"); -// _file<< "o "<(*this)); + if(o & GraphicOutput::RERUN) + _output_figures.push_back(make_shared(*this)); } Figure3D::~Figure3D() { - _file.close(); } const std::string& Figure3D::name() const @@ -33,334 +36,183 @@ const std::string& Figure3D::name() const return _name; } -void Figure3D::set_color_internal(const Color &c) { - _file << "newmtl " << c.hex_str().substr(1,6) << "\n"; - _file << "Kd " << c.rgb()[0]/255. << " " << c.rgb()[1]/255. << " " << c.rgb()[2]/255. << "\n"; - _file << "d "<< c.rgb()[3]/255.<<"\n"; - _file << "usemtl " << c.hex_str().substr(1,6) << "\n"; +std::vector> Figure3D::output_figures() +{ + return _output_figures; } -void Figure3D::set_style_internal(const StyleProperties& style) { - if (lock_style) return; - if (style.layer=="" || style.layer=="alpha") { - _file<< "o "<< _name<<"\n"; - } else { - _file<< "o "<< style.layer<<"\n"; - } - this->set_color_internal(style.stroke_color.rgb()); +void Figure3D::clear() +{ + for(const auto& output_fig : _output_figures) + output_fig->clear(); } -size_t Figure3D::move_write_v(const Vector &c, const Matrix &A, const Vector &p) { - Vector pos = c+A*p; - - _file << "v " << pos[0] << " " << pos[1] << " " << pos[2] << "\n"; - return (++this->vertex_count); +void Figure3D::save(const std::string& filename) +{ + for(const auto& output_fig : _output_figures) + output_fig->save(filename); +} + +void Figure3D::draw_point(const Vector& c, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_point(c, style); +} + +void Figure3D::draw_line(const Vector& p1, const Vector& p2, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_line(p1, p2, style); +} + +void Figure3D::draw_polyline(const std::vector& x, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_polyline(x, style); } void Figure3D::draw_triangle(const Vector &c, const Matrix &A, const Vector &p1, const Vector &p2, const Vector &p3, - const StyleProperties& style) { - - this->set_style_internal(style); - size_t ip1 = this->move_write_v(c,A,p1); - size_t ip2 = this->move_write_v(c,A,p2); - size_t ip3 = this->move_write_v(c,A,p3); - _file << "f " << ip1 << " " << ip2 << " " << ip3 << "\n"; + const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_triangle(c, A, p1, p2, p3, style); } void Figure3D::draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, - const StyleProperties& style) { - - this->draw_triangle(Vector::zero(3),Matrix::Identity(3,3),p1,p2,p3,style); + const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_triangle(p1, p2, p3, style); } void Figure3D::draw_polygon(const Vector &c, const Matrix &A, - const std::vector &l, - const StyleProperties& style) { - - if (l.size()<=2) return; - this->set_style_internal(style); - size_t ip1 = this->move_write_v(c,A,l[0]); - size_t ip2 = this->move_write_v(c,A,l[1]); - for (size_t i=2;imove_write_v(c,A,l[i]); - _file << "f " << ip1 << " " << ip2 << " " << ip3 << "\n"; - ip2=ip3; - } + const std::vector &l, + const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_polygon(c, A, l, style); } - void Figure3D::draw_parallelogram(const Vector &c, const Matrix &A, const Vector &p, const Vector &v1, const Vector &v2, - const StyleProperties& style) { - - this->set_style_internal(style); - size_t ip1 = this->move_write_v(c,A,p-v1-v2); - size_t ip2 = this->move_write_v(c,A,p-v1+v2); - size_t ip3 = this->move_write_v(c,A,p+v1+v2); - size_t ip4 = this->move_write_v(c,A,p+v1-v2); - _file << "f " << ip1 << " " << ip2 << " " << ip3 << " " << ip4 << "\n"; + const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_parallelogram(c, A, p, v1, v2, style); } void Figure3D::draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style) { - assert_release(p.c.size() == 3); - assert_release(p.A.rows() == 3 && p.A.cols() == 3); - - this->set_style_internal(style); - - size_t ip0 = this->move_write_v(p.c,p.A,Vector({-1,-1,-1})); - size_t ip1 = this->move_write_v(p.c,p.A,Vector({-1,-1,1})); - size_t ip2 = this->move_write_v(p.c,p.A,Vector({-1,1,-1})); - size_t ip3 = this->move_write_v(p.c,p.A,Vector({-1,1,1})); - size_t ip4 = this->move_write_v(p.c,p.A,Vector({1,-1,-1})); - size_t ip5 = this->move_write_v(p.c,p.A,Vector({1,-1,1})); - size_t ip6 = this->move_write_v(p.c,p.A,Vector({1,1,-1})); - size_t ip7 = this->move_write_v(p.c,p.A,Vector({1,1,1})); - - _file << "f " << ip0 << " " << ip1 << " " << ip3 << " " << ip2 << "\n"; - _file << "f " << ip4 << " " << ip5 << " " << ip7 << " " << ip6 << "\n"; - _file << "f " << ip0 << " " << ip4 << " " << ip6 << " " << ip2 << "\n"; - _file << "f " << ip1 << " " << ip5 << " " << ip7 << " " << ip3 << "\n"; - _file << "f " << ip0 << " " << ip4 << " " << ip5 << " " << ip1 << "\n"; - _file << "f " << ip2 << " " << ip6 << " " << ip7 << " " << ip3 << "\n"; + for(const auto& output_fig : _output_figures) + output_fig->draw_parallelepiped(p, style); } +void Figure3D::draw_zonotope(const Zonotope& z, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_zonotope(z, style); +} void Figure3D::draw_box(const IntervalVector& x, const StyleProperties& style) { - assert_release(x.size()==3); - Vector inter = (x-x.mid()).ub(); - Matrix A ({{inter[0],0,0},{0,inter[1],0},{0,0,inter[2]}}); - draw_parallelepiped({x.mid(), A}, style); + for(const auto& output_fig : _output_figures) + output_fig->draw_box(x, style); } -void Figure3D::draw_zonotope(const Zonotope& z, const StyleProperties& style) { - assert_release(z.c.size() == 3); - Matrix id = Matrix::Identity(3,3); - this->set_style_internal(style); - lock_style=true; - /* method : for each couple of vectors Ai Aj with ij then add -AK - ii) if k0 and k>i add Ak - otherwise add -Ak - (seems to work, but must be validated... possibly there is a reference - somewhere) - 3) result is center of one face, the other is symetric for R1 (not R2) - */ - size_t nb = z.A.cols(); - for (size_t j=1;j()<1e-8) continue; - Eigen::Vector3d R1 = Eigen::Vector3d::Zero(); - Eigen::Vector3d R2 = Eigen::Vector3d::Zero(); - for (size_t k=0;kj) { - R2 -= Ak; - continue; - } - Eigen::Vector3d N2 = Ai.cross(Ak); - if (N2.lpNorm()<1e-8) { - if (Ai.dot(Ak)<0 || i=0) R2 += Ak; else R2 -= Ak; - } - else if (prod>0.0) { - R1 += Ak; - } - else { - R1 -= Ak; - } - } - this->draw_parallelogram(z.c,id,R1+R2,Ai,Aj,style); - this->draw_parallelogram(z.c,id,-R1+R2,Ai,Aj,style); - } - } - lock_style=false; +void Figure3D::draw_arrow(const Vector &c, const Matrix& A, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_arrow(c, A, style); } -void Figure3D::draw_arrow(const Vector& c, const Matrix &A, +void Figure3D::draw_axes(double size, const Vector& origin) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_axes(size, origin); +} + +void Figure3D::draw_surface(const Vector &c, const Matrix &A, + const Interval &Ip1, double dp1, + const Interval &Ip2, double dp2, + std::function f, const StyleProperties& style) { - Vector v = c+A*Vector({0.5,0.0,0.0}); - Matrix tr {{0.5,0,0},{0,0.01,0},{0,0,0.01}}; - double a=0.05; - this->set_style_internal(style); - lock_style=true; - draw_parallelepiped({v, A*tr}, style); - std::vector l_points { - { 1+4*a,0,0 }, - { 1,a,-a }, - { 1,-a,-a }, - { 1,-a,a }, - { 1,a,a }, - { 1,a,-a } }; - this->draw_polygon(c,A,l_points,style); - lock_style=false; + for(const auto& output_fig : _output_figures) + output_fig->draw_surface(c, A, Ip1, dp1, Ip2, dp2, f, style); } +void Figure3D::draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_sphere(c, A, style); +} -void Figure3D::draw_axes(double size, const Vector& origin) +void Figure3D::draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style) { - assert_release(origin.size()==3); - const std::string name="axes"; - Vector z = origin; - // X axis - Matrix AX = Matrix::Identity(3,3); - draw_arrow(z,size*AX,StyleProperties(Color::red(),name)); - // Y axis - Matrix AY {{0,0,1},{1,0,0},{0,1,0}}; - draw_arrow(z,size*AY,StyleProperties(Color::green(),name)); - // Z axis - Matrix AZ {{0,1,0},{0,0,1},{1,0,0}}; - draw_arrow(z,size*AZ,StyleProperties(Color::blue(),name)); - _file<< "o "<<_name<<"\n"; + for(const auto& output_fig : _output_figures) + output_fig->draw_ellipsoid(e, style); } -void Figure3D::draw_surface(const Vector &c, const Matrix &A, - const Interval &Ip1, double dp1, - const Interval &Ip2, double dp2, - std::function f, - const StyleProperties& style) { - - this->set_style_internal(style); - lock_style=true; - for (double p1 = Ip1.lb(); p1 < Ip1.ub(); p1+=dp1) - for (double p2 = Ip2.lb(); p2 < Ip2.ub(); p2+=dp2) { - Vector a0 = f(p1,p2); - Vector a1 = f(p1+dp1,p2); - Vector a2 = f(p1+dp1,p2+dp2); - Vector a3 = f(p1,p2+dp2); - draw_triangle(c,A,a0,a1,a2,style); - draw_triangle(c,A,a0,a2,a3,style); - } - lock_style=false; +void Figure3D::draw_car(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_car(c, A, style); } -void Figure3D::draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style) { - this->draw_surface(c,A,Interval(-0.5*PI,0.5*PI),0.05*PI, - Interval(0,2.0*PI),0.05*PI, - [](double p1, double p2) -> Vector { - Vector a({ std::cos(p1)*std::cos(p2),std::cos(p1)*std::sin(p2),std::sin(p1) }); - return a; - }, - style - ); +void Figure3D::draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up, + const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_plane(c, A, yaw_is_up, style); } -void Figure3D::draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style) { - this->draw_sphere(e.mu, e.G,style); +void Figure3D::draw_AUV(const Vector &c, const Matrix &A, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_AUV(c, A, style); } -void Figure3D::draw_car(const Vector &c, const Matrix &A, - const StyleProperties& style) { - this->set_style_internal(style); - lock_style=true; - /* left side (y=-0.8) */ - std::vector side { {1.5,-0.8,1}, - {2.8,-0.8,1}, - {3,-0.8,0}, - {-1,-0.8,0}, - {0,-0.8,2}, - {1,-0.8,2} }; - draw_polygon(c,A,side,style); - for (auto &e : side) { e[1] = 0.8; } - draw_polygon(c,A,side,style); - /* rear and top */ - std::vector reartop { {0,-0.8,2}, - {-1,-0.8,0}, - {-1,0.8,0}, - {0,0.8,2}, - {1,0.8,2}, - {1,-0.8,2} }; - draw_polygon(c,A,reartop,style); - /* front and motor */ - std::vector front { {2.8,-0.8,1}, - {1.5,-0.8,1}, - {1.5,0.8,1}, - {2.8,0.8,1}, - {3,0.8,0}, - {3,-0.8,0} }; - draw_polygon(c,A,front,style); - lock_style=false; - /* windscreen */ - draw_parallelogram(c,A,Vector({1.25,0.0,1.5}), - Vector({0,0.8,0}), Vector({-0.25,0,0.5}), - StyleProperties(Color::blue(0.5),style.layer)); +void Figure3D::draw_paving(const PavingOut& p, const StyleProperties& bound_style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_paving(p, bound_style); } -void Figure3D::draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up, - const StyleProperties& style) { - this->set_style_internal(style); - lock_style=true; - /* wings */ - std::vector wings { {-1.5,0,0}, - {-1.5,-1,0}, - {-1,-1,0}, - {0,0,0}, - {-1,1,0}, - {-1.5,1,0} }; - draw_polygon(c,A,wings,style); - double vert = yaw_is_up ? 1.0 : -1.0; - /* body */ - draw_triangle(c,A,{-1.5,0,0},{1.5,0,0},{0.5,-0.1,0.5*vert},style); - draw_triangle(c,A,{-1.5,0,0},{1.5,0,0},{0.5,0.1,0.5*vert},style); - /* vertical stabilizer */ - draw_triangle(c,A,{-1.5,0,0},{-1.5,0,vert},{-0.5,0,0},style); - lock_style=false; +void Figure3D::draw_paving(const PavingOut& p, const PavingStyle& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_paving(p, style.boundary); } +void Figure3D::draw_paving(const PavingInOut& p, const StyleProperties& bound_style, + const StyleProperties& in_style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_paving(p, bound_style, in_style); +} -void Figure3D::draw_paving(const PavingOut& p, - const StyleProperties& boundary_style) +void Figure3D::draw_paving(const PavingInOut& p, const PavingStyle& style) { - p.tree()->left()->visit([&] - (std::shared_ptr n) - { - const IntervalVector& outer = get<0>(n->boxes()); + for(const auto& output_fig : _output_figures) + output_fig->draw_paving(p, style.boundary, style.inside); +} - if(n->is_leaf() && !outer.is_empty()) - draw_box(outer, boundary_style); +void Figure3D::draw_trajectory(const SampledTraj& x, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_trajectory(x, style); +} - return true; - }); +void Figure3D::draw_trajectory(const AnalyticTraj& x, const StyleProperties& style) +{ + for(const auto& output_fig : _output_figures) + output_fig->draw_trajectory(x, style); } -void Figure3D::draw_paving(const PavingInOut& p, const StyleProperties& boundary_style, - const StyleProperties& inside_style) +void Figure3D::draw_tube(const SlicedTube& x, const StyleProperties& style) { - p.tree()->visit([&] - (std::shared_ptr n) - { - const IntervalVector& outer = get<0>(n->boxes()); - const IntervalVector& inner = get<1>(n->boxes()); - - IntervalVector hull = inner | outer; - - for(const auto& bi : hull.diff(inner)) - if (!bi.is_empty()) - draw_box(bi, inside_style); - - if(n->is_leaf()) - { - auto boundary = inner & outer; - if (!boundary.is_empty()) - draw_box(boundary, boundary_style); - } - return true; - }); + for(const auto& output_fig : _output_figures) + output_fig->draw_tube(x, style); } diff --git a/src/graphics/figures/codac2_Figure3D.h b/src/graphics/figures/codac2_Figure3D.h index 442293832..9f0e524c0 100644 --- a/src/graphics/figures/codac2_Figure3D.h +++ b/src/graphics/figures/codac2_Figure3D.h @@ -2,7 +2,7 @@ * \file codac2_Figure3D.h * ---------------------------------------------------------------------------- * \date 2024 - * \author Maël Godard, Damien Massé + * \author Maël Godard, Damien Massé, Quentin Brateau * \copyright Copyright 2024 Codac Team * \license GNU Lesser General Public License (LGPL) */ @@ -10,9 +10,12 @@ #pragma once #include +#include #include -#include +#include +#include "codac2_GraphicOutput.h" #include "codac2_StyleProperties.h" +#include "codac2_PavingStyle.h" #include "codac2_IntervalVector.h" #include "codac2_Paving.h" #include "codac2_Vector.h" @@ -20,26 +23,33 @@ #include "codac2_IntervalMatrix.h" #include "codac2_Ellipsoid.h" #include "codac2_Parallelepiped.h" +#include "codac2_SampledTraj.h" +#include "codac2_AnalyticTraj.h" +#include "codac2_SlicedTube.h" namespace codac2 { + class OutputFigure3D; + template + class Subpaving; /** * \class Figure3D * \brief Figure3D class, used for 3D figures * - * This class is used to display 3D figures. It generates an OBJ file that can be opened with a 3D viewer. + * This class is used to display 3D figures. It can interact with OBJ and Rerun. */ - class Figure3D + class Figure3D : public std::enable_shared_from_this { public: /** - * \brief Creates a new Figure3D object, with a given name + * \brief Creates a new Figure3D object, with a given name and output backend * * \param name Name of the figure + * \param o Graphic output backend (OBJ, RERUN, or combination) */ - Figure3D(const std::string& name); + Figure3D(const std::string& name, GraphicOutput o = GraphicOutput::OBJ | GraphicOutput::RERUN); /** * \brief Destructor for the Figure3D class @@ -53,114 +63,154 @@ namespace codac2 */ const std::string& name() const; + /** + * \brief Returns OutputFigure3D objects rendering the current figure + */ + std::vector> output_figures(); + + /** + * \brief Clears the figure + */ + void clear(); + + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + void save(const std::string& filename); + // Geometric shapes /** - * \brief Draws a triangle + * \brief Draws a 3D point on the figure + * + * \param c Coordinates of the 3D point (Vector of dimension >= 3) + * \param style Style properties (color, line width, layer) + */ + void draw_point(const Vector& c, const StyleProperties& style = { Color::dark_gray(0.5) }); + + /** + * \brief Draws a line between two 3D points + * + * \param p1 Starting 3D point + * \param p2 Ending 3D point + * \param style Style properties (stroke color, line width, layer) + */ + void draw_line(const Vector& p1, const Vector& p2, const StyleProperties& style = { Color::dark_gray(0.5) }); + + /** + * \brief Draws a 3D polyline connecting a sequence of points + * + * \param x Vector of 3D points forming the polyline + * \param style Style properties (stroke color, line width, layer) + */ + void draw_polyline(const std::vector& x, const StyleProperties& style = { Color::dark_gray(0.5) }); + + /** + * \brief Draws a triangle with coordinate transformation * - * \param c translation - * \param A scaling - * \param p1 first point - * \param p2 second point - * \param p3 third point - * \param style style + * \param c Translation vector (center offset) + * \param A Linear transformation matrix (3x3) + * \param p1 First local vertex + * \param p2 Second local vertex + * \param p3 Third local vertex + * \param style Style properties (fill/stroke color, layer) */ void draw_triangle(const Vector &c, const Matrix &A, const Vector &p1, const Vector &p2, const Vector &p3, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a triangle, shorter version + * \brief Draws a triangle with direct global vertices * - * \param p1 first point - * \param p2 second point - * \param p3 third point - * \param style style + * \param p1 First vertex + * \param p2 Second vertex + * \param p3 Third vertex + * \param style Style properties (fill/stroke color, layer) */ void draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a star-shaped polygon as a sequence of adjacent + * \brief Draws a star-shaped polygon as a sequence of adjacent * triangles (l[0],l[k],l[k+1]) with k>=1. * - * \param c translation - * \param A scaling - * \param l points - * \param style style + * \param c Translation vector (center offset) + * \param A Linear transformation matrix (3x3) + * \param l Vector of vertices in local coordinates + * \param style Style properties (fill/stroke color, layer) */ void draw_polygon(const Vector &c, const Matrix &A, const std::vector &l, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a parallelogram c + A (p + [-1,1]*v1 + [-1,1]*v2) + * \brief Draws a parallelogram c + A * (p + [-1,1]*v1 + [-1,1]*v2) * - * \param c translation - * \param A scaling - * \param p base point - * \param v1 vector - * \param v2 vector - * \param style style + * \param c Translation vector (center offset) + * \param A Linear transformation matrix (3x3) + * \param p Base offset point + * \param v1 First direction vector + * \param v2 Second direction vector + * \param style Style properties (fill/stroke color, layer) */ void draw_parallelogram(const Vector &c, const Matrix &A, const Vector &p, const Vector &v1, const Vector &v2, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a parallelepiped z+A*[-1,1]^3 on the figure + * \brief Draws a parallelepiped c + A * [-1,1]^3 on the figure * - * \param p Parallelepiped to draw (center and shape matrix) - * \param style Style of the parallelepiped (edge color) + * \param p Parallelepiped to draw (center c and generator matrix A) + * \param style Style properties (fill/stroke color, layer) */ void draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a zonotope c+sum_i [-1,1] A_i on the figure + * \brief Draws a zonotope c + sum_i [-1,1] * A_i on the figure * - * \param z Zonotope to draw (center and shape matrix) - * \param style Style of the zonotope (edge color) + * \param z Zonotope to draw (center and generator matrix columns) + * \param style Style properties (fill/stroke color, layer) */ void draw_zonotope(const Zonotope& z, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a box on the figure + * \brief Draws an axis-aligned box (IntervalVector) on the figure * - * \param x Box to draw - * \param style Style of the box (edge color) + * \param x 3D interval box to draw + * \param style Style properties (fill/stroke color, layer) */ void draw_box(const IntervalVector& x, const StyleProperties& style = { Color::dark_gray(0.5) }); - /** - * \brief Draws an arrow (box c + A * ([0,1],[-0.01,0.01],[-0.01,0.01]) and a - * tip at the end) + * \brief Draws a 3D oriented arrow * - * \param c start - * \param A orientation (first column) - * \param style Style (color) + * \param c Starting origin position of the arrow + * \param A Orientation/direction matrix (first column is the direction vector) + * \param style Style properties (stroke color, line width, layer) */ void draw_arrow(const Vector &c, const Matrix& A, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws the (x,y,z) axes on the figure in red, green and blue + * \brief Draws the (x,y,z) coordinate axes on the figure in red, green and blue * - * \param size Size of the axes - * \param origin Origin of the axes + * \param size Length of the coordinate axis arrows + * \param origin Origin position of the axes (default: (0,0,0)) */ void draw_axes(double size = 1.0, const Vector& origin = Vector::Zero(3)); /** - * \brief Draws a parametric surface + * \brief Draws a parametric 3D surface * - * \param c translation - * \param A scaling - * \param Ip1 bounds of p1 - * \param dp1 incrementation for p1 - * \param Ip2 bounds of p2 - * \param dp2 incrementation for p2 - * \param f function computing the values - * \param style Style (color) + * \param c Translation vector (center offset) + * \param A Linear transformation matrix (3x3) + * \param Ip1 Parameter 1 interval domain [min, max] + * \param dp1 Discretization step size for parameter 1 + * \param Ip2 Parameter 2 interval domain [min, max] + * \param dp2 Discretization step size for parameter 2 + * \param f Parametric mapping function (p1, p2) -> Vector (3D coordinates) + * \param style Style properties (fill/stroke color, layer) */ void draw_surface(const Vector &c, const Matrix &A, const Interval &Ip1, double dp1, @@ -169,73 +219,100 @@ namespace codac2 const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a sphere (ellipsoid) + * \brief Draws a transformed sphere / ellipsoid mesh * - * \param c translation - * \param A scaling - * \param style Style (color) + * \param c Center position of the sphere + * \param A Scaling and orientation transformation matrix (3x3) + * \param style Style properties (fill/stroke color, layer) */ void draw_sphere(const Vector &c, const Matrix &A, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws an ellipsoid (from the Ellipsoid class) + * \brief Draws an ellipsoid from an Ellipsoid domain object * - * \param e Ellipsoid to draw - * \param style Style (color) + * \param e Ellipsoid object (mean center vector and transformation matrix) + * \param style Style properties (fill/stroke color, layer) */ void draw_ellipsoid(const Ellipsoid &e, const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Draws a car, with blue windscreen + * \brief Draws a 3D car model with windscreen * - * \param c center of the car - * \param A orientation - * \param style Style (color) + * \param c Center position of the car + * \param A Scaling and orientation transformation matrix (3x3) + * \param style Style properties (body color, layer) */ void draw_car(const Vector &c, const Matrix &A, const StyleProperties& style = { Color::yellow(0.5) }); /** - * \brief Draws a (paper) plane + * \brief Draws a 3D (paper) airplane model * - * \param c center of the car - * \param A orientation - * \param yaw_is_up yaw axis is up (default true) - * \param style Style (color) + * \param c Center position of the plane + * \param A Scaling and orientation transformation matrix (3x3) + * \param yaw_is_up Whether the vertical yaw axis is pointing upwards (default: true) + * \param style Style properties (body color, layer) */ void draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up=true, const StyleProperties& style = { Color::dark_gray(0.8) }); - + + /** + * \brief Draws a 3D Autonomous Underwater Vehicle (AUV / submarine) model + * + * \param c Center position of the AUV + * \param A Scaling and orientation transformation matrix (3x3) + * \param style Style properties (body color, layer) + */ + void draw_AUV(const Vector &c, const Matrix &A, + const StyleProperties& style = { Color::yellow(0.5) }); // Pavings /** - * \brief Draws a paving on the figure (Only the boundary is drawn). + * \brief Draws a paving on the figure (boundary boxes from contractor paving) + * + * \param p PavingOut object to draw (result of a paving with contractors) + * \param bound_style Style properties of the boundary boxes (color, layer) + */ + void draw_paving(const PavingOut& p, + const StyleProperties& bound_style = { Color::yellow(0.5), "paving_bound" }); + + /** + * \brief Draws a paving on the figure with a PavingStyle object (boundary only) * - * \param p PavingOut to draw (result of a paving with contractors). - * \param bound_style Style of the boundary of the paving + * \param p PavingOut object to draw + * \param style PavingStyle properties controlling boundary style and layer */ void draw_paving(const PavingOut& p, - const StyleProperties& bound_style = { Color::yellow(0.5) }); + const PavingStyle& style); /** - * \brief Draws a paving on the figure (Only the boundary and the inside is drawn). + * \brief Draws a paving on the figure (boundary and inside boxes from separator paving) * - * \param p PavingInOut to draw (result of a paving with separators). - * \param bound_style Style of the boundary of the paving - * \param in_style Style of the inside of the paving + * \param p PavingInOut object to draw (result of a paving with separators) + * \param bound_style Style properties of the boundary boxes (color, layer) + * \param in_style Style properties of the inside/interior boxes (color, layer) */ void draw_paving(const PavingInOut& p, const StyleProperties& bound_style = { Color::yellow(0.3), "paving_bound" }, const StyleProperties& in_style = { Color::green(0.5) , "paving_in" }); + + /** + * \brief Draws a paving on the figure with a PavingStyle object (boundary and inside) + * + * \param p PavingInOut object to draw + * \param style PavingStyle properties controlling boundary and inside styles and layers + */ + void draw_paving(const PavingInOut& p, + const PavingStyle& style); /** * \brief Draws a subpaving on the figure * - * \param p Subpaving to draw - * \param style Style of the subpaving + * \param p Subpaving object containing the collection of boxes + * \param style Style properties for the boxes (color, layer) */ template inline void draw_subpaving(const Subpaving

& p, const StyleProperties& style = StyleProperties()) @@ -244,38 +321,38 @@ namespace codac2 draw_box(pi, style); } - private: - /** - * \brief describes a vertex (c+ A p). Increment the vertex_count - * - * \param c center - * \param A rotation - * \param p relative position - * \return the vertex number - **/ - size_t move_write_v(const Vector &c, const Matrix &A, const Vector &p); + // Trajectories & Tubes + + /** + * \brief Draws a sampled trajectory in 3D + * + * \param x Sampled trajectory object containing time-indexed 3D points + * \param style Style properties (stroke color, line width, layer) + */ + void draw_trajectory(const SampledTraj& x, + const StyleProperties& style = { Color::dark_gray(0.5) }); /** - * \brief Sets the current color. + * \brief Draws an analytic trajectory in 3D * - * \param c color + * \param x Analytic trajectory object evaluated over a time domain + * \param style Style properties (stroke color, line width, layer) */ - void set_color_internal(const Color &c); - + void draw_trajectory(const AnalyticTraj& x, + const StyleProperties& style = { Color::dark_gray(0.5) }); + /** - * \brief Sets the style (layer+color), if lock_style is false. - * Otherwise, do nothing. If the layer is empty of "alpha", use the - * name of the figure. + * \brief Draws a tube of IntervalVector in 3D * - * \param style layer. If empty or "alpha", use the name of the figure. + * \param x SlicedTube object containing 3D interval box slices over time + * \param style Style properties (box stroke/fill color, layer) */ - void set_style_internal(const StyleProperties& style); + void draw_tube(const SlicedTube& x, + const StyleProperties& style = { Color::dark_gray(0.5) }); protected: const std::string _name; - std::ofstream _file; - size_t vertex_count = 0; - bool lock_style=false; + std::vector> _output_figures; }; } diff --git a/src/graphics/figures/codac2_Figure3DInterface.h b/src/graphics/figures/codac2_Figure3DInterface.h new file mode 100644 index 000000000..e34033981 --- /dev/null +++ b/src/graphics/figures/codac2_Figure3DInterface.h @@ -0,0 +1,242 @@ +/** + * \file codac2_Figure3DInterface.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include +#include "codac2_Vector.h" +#include "codac2_Matrix.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" +#include "codac2_StyleProperties.h" +#include "codac2_StyleGradientProperties.h" +#include "codac2_Ellipsoid.h" +#include "codac2_Parallelepiped.h" +#include "codac2_Zonotope.h" +#include "codac2_SampledTraj.h" +#include "codac2_AnalyticTraj.h" +#include "codac2_SlicedTube.h" + +namespace codac2 +{ + class PavingOut; + class PavingInOut; + template + class Subpaving; + + /** + * \class Figure3DInterface + * \brief Interface for 3D figures + * + * This class is used to display 3D figures. It defines the basic drawing functions. + * Purely virtual interface defining low-level and high-level graphic primitives. + */ + class Figure3DInterface + { + public: + + virtual ~Figure3DInterface() = default; + + // Geometric shapes + + /** + * \brief Draws a point in 3D + * + * \param c Coordinates of the point + * \param style Style of the point + */ + virtual void draw_point(const Vector& c, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a 3D box + * + * \param x Box to draw + * \param style Style of the box + */ + virtual void draw_box(const IntervalVector& x, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a 3D line between two points + * + * \param p1 First point + * \param p2 Second point + * \param style Style of the line + */ + virtual void draw_line(const Vector& p1, const Vector& p2, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a 3D polyline connecting multiple points + * + * \param x Vector of points + * \param style Style of the polyline + */ + virtual void draw_polyline(const std::vector& x, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a triangle with translation and scaling + * + * \param c Translation + * \param A Scaling / rotation + * \param p1 First point + * \param p2 Second point + * \param p3 Third point + * \param style Style + */ + virtual void draw_triangle(const Vector &c, const Matrix &A, + const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a triangle + * + * \param p1 First point + * \param p2 Second point + * \param p3 Third point + * \param style Style + */ + virtual void draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a star-shaped polygon as a sequence of adjacent triangles + * + * \param c Translation + * \param A Scaling / rotation + * \param l Points + * \param style Style + */ + virtual void draw_polygon(const Vector &c, const Matrix &A, const std::vector &l, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a parallelogram c + A (p + [-1,1]*v1 + [-1,1]*v2) + * + * \param c Translation + * \param A Scaling / rotation + * \param p Base point + * \param v1 First direction vector + * \param v2 Second direction vector + * \param style Style + */ + virtual void draw_parallelogram(const Vector &c, const Matrix &A, + const Vector &p, const Vector &v1, const Vector &v2, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a parallelepiped + * + * \param p Parallelepiped to draw + * \param style Style + */ + virtual void draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a zonotope + * + * \param z Zonotope to draw + * \param style Style + */ + virtual void draw_zonotope(const Zonotope& z, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws an arrow + * + * \param c Start position + * \param A Orientation matrix (first column is direction) + * \param style Style + */ + virtual void draw_arrow(const Vector &c, const Matrix& A, const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws the (x,y,z) axes + * + * \param size Size of the axes + * \param origin Origin of the axes + */ + virtual void draw_axes(double size = 1.0, const Vector& origin = Vector::Zero(3)) = 0; + + /** + * \brief Draws a parametric surface + */ + virtual void draw_surface(const Vector &c, const Matrix &A, + const Interval &Ip1, double dp1, + const Interval &Ip2, double dp2, + std::function f, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a sphere + */ + virtual void draw_sphere(const Vector &c, const Matrix &A, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws an ellipsoid + */ + virtual void draw_ellipsoid(const Ellipsoid &e, + const StyleProperties& style = StyleProperties()) = 0; + + // Vehicles / Robots + + /** + * \brief Draws a car in 3D + */ + virtual void draw_car(const Vector &c, const Matrix &A, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws an airplane in 3D + */ + virtual void draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up = true, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws an AUV / submarine in 3D + */ + virtual void draw_AUV(const Vector &c, const Matrix &A, + const StyleProperties& style = StyleProperties()) = 0; + + // Pavings + + /** + * \brief Draws a paving boundary (PavingOut) + */ + virtual void draw_paving(const PavingOut& p, + const StyleProperties& bound_style = StyleProperties()) = 0; + + /** + * \brief Draws a paving with boundary and inside (PavingInOut) + */ + virtual void draw_paving(const PavingInOut& p, + const StyleProperties& bound_style = StyleProperties(), + const StyleProperties& in_style = StyleProperties()) = 0; + + // Trajectories & Tubes + + /** + * \brief Draws a sampled trajectory in 3D + */ + virtual void draw_trajectory(const SampledTraj& x, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws an analytic trajectory in 3D + */ + virtual void draw_trajectory(const AnalyticTraj& x, + const StyleProperties& style = StyleProperties()) = 0; + + /** + * \brief Draws a tube in 3D + */ + virtual void draw_tube(const SlicedTube& x, + const StyleProperties& style = StyleProperties()) = 0; + }; +} diff --git a/src/graphics/figures/codac2_GraphicOutput.h b/src/graphics/figures/codac2_GraphicOutput.h new file mode 100644 index 000000000..e96950a15 --- /dev/null +++ b/src/graphics/figures/codac2_GraphicOutput.h @@ -0,0 +1,31 @@ +/** + * \file codac2_GraphicOutput.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +namespace codac2 +{ + /** + * \enum GraphicOutput + * \brief Enumeration for graphic output backends + */ + enum class GraphicOutput + { + VIBES = 0x01, + IPE = 0x02, + RERUN = 0x04, + OBJ = 0x08 + }; + + constexpr int operator&(GraphicOutput a, GraphicOutput b) + { return static_cast(static_cast(a) & static_cast(b)); } + + constexpr GraphicOutput operator|(GraphicOutput a, GraphicOutput b) + { return static_cast(static_cast(a) | static_cast(b)); } +} diff --git a/src/graphics/figures/codac2_OutputFigure3D.cpp b/src/graphics/figures/codac2_OutputFigure3D.cpp new file mode 100644 index 000000000..f58b400c4 --- /dev/null +++ b/src/graphics/figures/codac2_OutputFigure3D.cpp @@ -0,0 +1,123 @@ +/** + * \file codac2_OutputFigure3D.cpp + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_Figure3D.h" +#include "codac2_OutputFigure3D.h" +#include "codac2_Paving.h" + +using namespace std; +using namespace codac2; + +void OutputFigure3D::draw_line(const Vector& p1, const Vector& p2, const StyleProperties& style) +{ + draw_polyline({p1, p2}, style); +} + +void OutputFigure3D::draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, const StyleProperties& style) +{ + draw_triangle(Vector::Zero(3), Matrix::eye(3, 3), p1, p2, p3, style); +} + +void OutputFigure3D::draw_trajectory(const SampledTraj& x, const StyleProperties& style) +{ + std::vector values; + + auto flush_and_draw = [&]() { + if(values.size() > 1) + draw_polyline(values, style); + else if(values.size() == 1) + draw_point(values[0], style); + values.clear(); + }; + + for(const auto& [ti, xi] : x) + { + if(!xi.is_nan()) + values.push_back(xi); + else + flush_and_draw(); + } + flush_and_draw(); +} + +void OutputFigure3D::draw_trajectory(const AnalyticTraj& x, const StyleProperties& style) +{ + draw_trajectory(x.sampled(x.tdomain().diam() / 1e4), style); +} + +void OutputFigure3D::draw_tube(const SlicedTube& x, const StyleProperties& style) +{ + if(x.is_empty()) + return; + + for(auto it = x.tdomain()->begin(); it != x.tdomain()->end(); ++it) + { + const auto& box = x.slice(it)->codomain(); + if(!box.is_empty()) + draw_box(box, style); + } +} + +void OutputFigure3D::draw_paving(const PavingOut& p, const StyleProperties& boundary_style) +{ + StyleProperties style = boundary_style; + if(style.layer.empty() || style.layer == "alpha") + style.layer = "paving_bound"; + + if(p.tree() && p.tree()->left()) + { + p.tree()->left()->visit([&](std::shared_ptr n) { + const IntervalVector& outer = get<0>(n->boxes()); + if(n->is_leaf() && !outer.is_empty()) + { + draw_box(outer, style); + } + return true; + }); + } +} + +void OutputFigure3D::draw_paving(const PavingInOut& p, const StyleProperties& boundary_style, + const StyleProperties& inside_style) +{ + StyleProperties bound = boundary_style; + if(bound.layer.empty() || bound.layer == "alpha") + bound.layer = "paving_bound"; + + StyleProperties in = inside_style; + if(in.layer.empty() || in.layer == "alpha") + in.layer = "paving_in"; + + if(p.tree()) + { + p.tree()->visit([&](std::shared_ptr n) { + const IntervalVector& outer = get<0>(n->boxes()); + const IntervalVector& inner = get<1>(n->boxes()); + + IntervalVector hull = inner | outer; + for(const auto& bi : hull.diff(inner)) + { + if(!bi.is_empty()) + { + draw_box(bi, in); + } + } + + if(n->is_leaf()) + { + auto boundary = inner & outer; + if(!boundary.is_empty()) + { + draw_box(boundary, bound); + } + } + return true; + }); + } +} diff --git a/src/graphics/figures/codac2_OutputFigure3D.h b/src/graphics/figures/codac2_OutputFigure3D.h new file mode 100644 index 000000000..2b2b35a17 --- /dev/null +++ b/src/graphics/figures/codac2_OutputFigure3D.h @@ -0,0 +1,76 @@ +/** + * \file codac2_OutputFigure3D.h + * ---------------------------------------------------------------------------- + * \date 2026 + * \author Quentin Brateau + * \copyright Copyright 2026 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include "codac2_Figure3DInterface.h" + +namespace codac2 +{ + class Figure3D; + + /** + * \class OutputFigure3D + * \brief Base class for 3D output figures + */ + class OutputFigure3D : public Figure3DInterface + { + public: + using Figure3DInterface::draw_triangle; + + /** + * \brief Creates a new OutputFigure3D object linked to a given figure + * + * \param fig Figure3D to use + */ + OutputFigure3D(const Figure3D& fig) + : _fig(fig) + { } + + virtual ~OutputFigure3D() = default; + + /** + * \brief Accessor to the associated Figure3D + */ + const Figure3D& fig() const { return _fig; } + + /** + * \brief Updates the axes of the figure + */ + virtual void update_axes() = 0; + + /** + * \brief Clears the figure + */ + virtual void clear() = 0; + + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + virtual void save(const std::string& filename) = 0; + + // Default implementations of compound drawing methods + void draw_line(const Vector& p1, const Vector& p2, const StyleProperties& style = StyleProperties()) override; + void draw_triangle(const Vector &p1, const Vector &p2, const Vector &p3, const StyleProperties& style = StyleProperties()) override; + void draw_trajectory(const SampledTraj& x, const StyleProperties& style = StyleProperties()) override; + void draw_trajectory(const AnalyticTraj& x, const StyleProperties& style = StyleProperties()) override; + void draw_tube(const SlicedTube& x, const StyleProperties& style = StyleProperties()) override; + void draw_paving(const PavingOut& p, const StyleProperties& bound_style = StyleProperties()) override; + void draw_paving(const PavingInOut& p, const StyleProperties& bound_style = StyleProperties(), const StyleProperties& in_style = StyleProperties()) override; + + protected: + + const Figure3D& _fig; + std::vector _layers; + }; +}