diff --git a/.gitmodules b/.gitmodules index cf027f5..57222e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ [submodule "Catch2"] - path = test/Catch2 + path = test/unit-tests/Catch2 url = git@github.com:catchorg/Catch2.git branch = v2.x [submodule "imgui"] diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8cfa64f..ca062ef 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -add_subdirectory(Image2D-Example) +add_subdirectory(Inputs-Example) add_subdirectory(Primitives3D-Example) add_subdirectory(RGBD-Example) add_subdirectory(SimpleLinePlot-Example) diff --git a/examples/Image2D-Example/CMakeLists.txt b/examples/Image2D-Example/CMakeLists.txt deleted file mode 100644 index 93ee9d1..0000000 --- a/examples/Image2D-Example/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_executable( - image2d-example - main.cpp -) - -set_target_properties( - image2d-example PROPERTIES - CXX_STANDARD 17 - CXX_EXTENSIONS ON - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON -) - -# pthread -find_package(Threads REQUIRED) - -target_link_libraries( - image2d-example PRIVATE - Toucan::Toucan - Threads::Threads -) diff --git a/examples/Inputs-Example/CMakeLists.txt b/examples/Inputs-Example/CMakeLists.txt new file mode 100644 index 0000000..d07ea97 --- /dev/null +++ b/examples/Inputs-Example/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) +project(inputs-example) + +add_executable( + inputs-example + main.cpp +) + +set_target_properties( + inputs-example PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) + +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) + +target_link_libraries( + inputs-example + PRIVATE Toucan::Toucan +) diff --git a/examples/Inputs-Example/main.cpp b/examples/Inputs-Example/main.cpp new file mode 100644 index 0000000..ed34e19 --- /dev/null +++ b/examples/Inputs-Example/main.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include + +int main() { + + Toucan::Initialize(); + + bool checkbox_value = true; + + float slider_float_value = 0.0; + Toucan::Vector2f slider_float2_value = Toucan::Vector2f::Zero(); + Toucan::Vector3f slider_float3_value = Toucan::Vector3f::Zero(); + Toucan::Vector4f slider_float4_value = Toucan::Vector4f::Zero(); + + int slider_int_value = 0; + Toucan::Vector2i slider_int2_value = Toucan::Vector2i::Zero(); + Toucan::Vector3i slider_int3_value = Toucan::Vector3i::Zero(); + Toucan::Vector4i slider_int4_value = Toucan::Vector4i::Zero(); + + Toucan::Color color_value = Toucan::Color::White(); + + while (Toucan::IsWindowOpen()) { + Toucan::BeginInputWindow("Inputs"); + { + if (Toucan::ShowButton("Button")) { + std::cout << "Button clicked\n"; + } + + if (Toucan::ShowCheckbox("Checkbox", checkbox_value)) { + std::cout << "Checkbox value changed to:\n" << checkbox_value << '\n'; + } + + if (Toucan::ShowSliderFloat("Slider float", slider_float_value)) { + std::cout << "Slider float value changed to:\n" << slider_float_value << '\n'; + } + + if (Toucan::ShowSliderFloat2("Slider float 2", slider_float2_value)) { + std::cout << "Slider float 2 value changed to:\n" << slider_float2_value << '\n'; + } + + if (Toucan::ShowSliderFloat3("Slider float 3", slider_float3_value)) { + std::cout << "Slider float 3 value changed to:\n" << slider_float3_value << '\n'; + } + + if (Toucan::ShowSliderFloat4("Slider float 4", slider_float4_value)) { + std::cout << "Slider float 4 value changed to:\n" << slider_float4_value << '\n'; + } + + if (Toucan::ShowSliderInt("Slider int", slider_int_value)) { + std::cout << "Slider int value changed to:\n" << slider_int_value << '\n'; + } + + if (Toucan::ShowSliderInt2("Slider int 2", slider_int2_value)) { + std::cout << "Slider int 2 value changed to:\n" << slider_int2_value << '\n'; + } + + if (Toucan::ShowSliderInt3("Slider int 3", slider_int3_value)) { + std::cout << "Slider int 3 value changed to:\n" << slider_int3_value << '\n'; + } + + if (Toucan::ShowSliderInt4("Slider int 4", slider_int4_value)) { + std::cout << "Slider int 4 value changed to:\n" << slider_int4_value << '\n'; + } + + if (Toucan::ShowColorPicker("Color picker", color_value)) { + std::cout << "Color picker value changed to:\n (" << + color_value.r << ", " << color_value.g << ", " << color_value.b << ")\n"; + } + } + Toucan::EndInputWindow(); + + using namespace std::chrono_literals; + std::this_thread::sleep_for(250ms); + } + + Toucan::Destroy(); + + return 0; +} diff --git a/examples/Primitives3D-Example/CMakeLists.txt b/examples/Primitives3D-Example/CMakeLists.txt index fe78975..24f186c 100644 --- a/examples/Primitives3D-Example/CMakeLists.txt +++ b/examples/Primitives3D-Example/CMakeLists.txt @@ -1,3 +1,6 @@ +cmake_minimum_required(VERSION 3.10) +project(primitives3d-example) + add_executable( primitives3d-example main.cpp @@ -6,10 +9,15 @@ add_executable( set_target_properties( primitives3d-example PROPERTIES CXX_STANDARD 17 - CXX_EXTENSIONS ON CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON ) + +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) + target_link_libraries( primitives3d-example PRIVATE Toucan::Toucan diff --git a/examples/RGBD-Example/CMakeLists.txt b/examples/RGBD-Example/CMakeLists.txt index 8abe887..bbb176a 100644 --- a/examples/RGBD-Example/CMakeLists.txt +++ b/examples/RGBD-Example/CMakeLists.txt @@ -1,28 +1,28 @@ +cmake_minimum_required(VERSION 3.10) +project(rgbd-example) + add_executable( rgbd-example main.cpp - DataLoader.cpp + DataLoader.cpp DataLoader.h + stb_image.h ) set_target_properties( rgbd-example PROPERTIES CXX_STANDARD 17 - CXX_EXTENSIONS ON CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON ) -add_library(stb STATIC stb/stb_image.c) -target_include_directories(rgbd-example PRIVATE stb) - -find_package(Eigen3 REQUIRED NO_MODULE) - -find_package(Sophus REQUIRED) -target_include_directories(rgbd-example PRIVATE ${Sophus_INCLUDE_DIRS}) +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) target_link_libraries( rgbd-example - PRIVATE Toucan::Toucan stb stdc++fs Eigen3::Eigen + PRIVATE Toucan::Toucan stdc++fs ) file(COPY dataset DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/examples/RGBD-Example/DataLoader.cpp b/examples/RGBD-Example/DataLoader.cpp index 9e247f4..0f032c0 100644 --- a/examples/RGBD-Example/DataLoader.cpp +++ b/examples/RGBD-Example/DataLoader.cpp @@ -3,17 +3,18 @@ #include #include -#include +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" #include -DataLoader::DataLoader(const std::experimental::filesystem::path& dataset_path, const Sophus::SE3f& transformation) : -m_dataset_path{dataset_path}, m_transformation{transformation}, m_rgb_index{0}, m_depth_index{0}, m_groundtruth_index{0} { +DataLoader::DataLoader(const std::filesystem::path& dataset_path) : +m_dataset_path{dataset_path}, m_rgb_index{0}, m_depth_index{0}, m_groundtruth_index{0} { - const std::experimental::filesystem::path rgb_file_path = dataset_path / "rgb.txt"; + const std::filesystem::path rgb_file_path = dataset_path / "rgb.txt"; std::ifstream rgb_file(rgb_file_path.string()); - if (!rgb_file.is_open()) { throw std::invalid_argument("Unable to open rgb.txt file"); } + if (!rgb_file.is_open()) { throw std::invalid_argument("Unable to open rgb.txt file. Did you run download-dataset.sh?"); } std::string line; const std::string decimal = "."; @@ -32,10 +33,10 @@ m_dataset_path{dataset_path}, m_transformation{transformation}, m_rgb_index{0}, m_rgb_files.emplace_back(timestamp, file_name); } - const std::experimental::filesystem::path depth_file_path = dataset_path / "depth.txt"; + const std::filesystem::path depth_file_path = dataset_path / "depth.txt"; std::ifstream depth_file(depth_file_path.string()); - if (!depth_file.is_open()) { throw std::invalid_argument("Unable to open depth.txt file"); } + if (!depth_file.is_open()) { throw std::invalid_argument("Unable to open depth.txt file. Did you run download-dataset.sh?"); } while (getline(depth_file, line)) { @@ -50,10 +51,10 @@ m_dataset_path{dataset_path}, m_transformation{transformation}, m_rgb_index{0}, m_depth_files.emplace_back(timestamp, file_name); } - const std::experimental::filesystem::path groundtruth_file_path = dataset_path / "groundtruth.txt"; + const std::filesystem::path groundtruth_file_path = dataset_path / "groundtruth.txt"; std::ifstream groudtruth_file(groundtruth_file_path.string()); - if (!groudtruth_file.is_open()) { throw std::invalid_argument("Unable to open groundtruth.txt file"); } + if (!groudtruth_file.is_open()) { throw std::invalid_argument("Unable to open groundtruth.txt file. Did you run download-dataset.sh?"); } while (getline(groudtruth_file, line)) { @@ -89,11 +90,12 @@ m_dataset_path{dataset_path}, m_transformation{transformation}, m_rgb_index{0}, std::getline(ss, item, ' '); float qw = std::stof(item); - const Eigen::Quaternionf orientation(qw, qx, qy, qz); - const Eigen::Vector3f position(tx, ty, tz); - const Sophus::SE3f transform(orientation, position); + const Toucan::RigidTransform3Df pose( + Toucan::Quaternionf(qw, qx, qy, qz), + Toucan::Vector3f(tx, ty, tz) + ); - m_ground_truths.emplace_back(timestamp, transform); + m_ground_truths.emplace_back(timestamp, pose); } this->next(); @@ -127,7 +129,7 @@ int DataLoader::get_current_index() const { } Image DataLoader::get_depth() const { - const std::experimental::filesystem::path& depth_image_path = m_dataset_path / m_depth_files[m_depth_index].second; + const std::filesystem::path& depth_image_path = m_dataset_path / m_depth_files[m_depth_index].second; Image image; @@ -141,7 +143,7 @@ Image DataLoader::get_depth() const { } Image DataLoader::get_rgb() const { - const std::experimental::filesystem::path& rgb_image_path = m_dataset_path / m_rgb_files[m_rgb_index].second; + const std::filesystem::path& rgb_image_path = m_dataset_path / m_rgb_files[m_rgb_index].second; Image image; @@ -154,8 +156,8 @@ Image DataLoader::get_rgb() const { return image; } -Sophus::SE3f DataLoader::get_groundtruth() const { - return m_transformation * m_ground_truths[m_groundtruth_index].second; +Toucan::RigidTransform3Df DataLoader::get_groundtruth() const { + return m_ground_truths[m_groundtruth_index].second; } uint64_t DataLoader::get_timestamp() const diff --git a/examples/RGBD-Example/DataLoader.h b/examples/RGBD-Example/DataLoader.h index b844695..ab6288e 100644 --- a/examples/RGBD-Example/DataLoader.h +++ b/examples/RGBD-Example/DataLoader.h @@ -4,9 +4,9 @@ #include #include #include -#include +#include -#include +#include struct Image { ~Image() { @@ -23,21 +23,20 @@ struct Image { class DataLoader { public: - explicit DataLoader(const std::experimental::filesystem::path& dataset_path, const Sophus::SE3f& transformation = Sophus::SE3f()); + explicit DataLoader(const std::filesystem::path& dataset_path); void next(); [[nodiscard]] bool has_next() const; [[nodiscard]] Image get_rgb() const; [[nodiscard]] Image get_depth() const; - [[nodiscard]] Sophus::SE3f get_groundtruth() const; + [[nodiscard]] Toucan::RigidTransform3Df get_groundtruth() const; [[nodiscard]] uint64_t get_timestamp() const; [[nodiscard]] int get_size() const; [[nodiscard]] int get_current_index() const; private: - const std::experimental::filesystem::path m_dataset_path; - const Sophus::SE3f m_transformation; + const std::filesystem::path m_dataset_path; int m_rgb_index; int m_depth_index; @@ -45,5 +44,5 @@ class DataLoader { std::vector> m_rgb_files; std::vector> m_depth_files; - std::vector> m_ground_truths; + std::vector> m_ground_truths; }; diff --git a/examples/RGBD-Example/dataset/download-dataset.sh b/examples/RGBD-Example/dataset/download-dataset.sh index 79f2d85..0c0f8aa 100755 --- a/examples/RGBD-Example/dataset/download-dataset.sh +++ b/examples/RGBD-Example/dataset/download-dataset.sh @@ -1,3 +1,5 @@ #!/usr/bin/env bash +cd "$( dirname "${BASH_SOURCE[0]}" )" || exit + wget https://vision.in.tum.de/rgbd/dataset/freiburg3/rgbd_dataset_freiburg3_long_office_household.tgz -O - | tar xvzf - diff --git a/examples/RGBD-Example/main.cpp b/examples/RGBD-Example/main.cpp index 21dfca9..a88f01e 100644 --- a/examples/RGBD-Example/main.cpp +++ b/examples/RGBD-Example/main.cpp @@ -95,22 +95,16 @@ int main() { project_image(depth_points, image, image_depth); auto gt_pose = data_loader.get_groundtruth(); - auto gt_t = gt_pose.translation(); - auto gt_q = gt_pose.unit_quaternion(); - pose_path.emplace_back(Toucan::LineVertex3D{Toucan::Vector3f(gt_t.x(), gt_t.y(), gt_t.z()), Toucan::Color::Magenta()}); + pose_path.emplace_back(Toucan::LineVertex3D{gt_pose.translation, Toucan::Color::Magenta()}); Toucan::BeginFigure3D("Point Projection"); { - Toucan::PushPose3D(Toucan::RigidTransform3Df(Toucan::Quaternionf(Toucan::Vector3f::UnitX(), M_PI_2), Toucan::Vector3f::Zero())); - { // Static transform to align with our coordinate system - Toucan::ShowLines3D("Pose path", pose_path); - - Toucan::PushPose3D(Toucan::RigidTransform3Df(Toucan::Quaternionf(gt_q.w(), gt_q.x(), gt_q.y(), gt_q.z()), Toucan::Vector3f(gt_t.x(), gt_t.y(), gt_t.z()))); - { // The coordinate system of the camera - Toucan::ShowAxis3D("Axis"); - Toucan::ShowPoints3D("Depth points", depth_points); - } - Toucan::PopPose3D(); + Toucan::ShowLines3D("Pose path", pose_path); + + Toucan::PushPose3D(gt_pose); + { // The coordinate system of the camera + Toucan::ShowAxis3D("Axis"); + Toucan::ShowPoints3D("Depth points", depth_points); } Toucan::PopPose3D(); } @@ -118,9 +112,9 @@ int main() { depth_points.clear(); - pos_x_plot.emplace_back(gt_t.x()); - pos_y_plot.emplace_back(gt_t.y()); - pos_z_plot.emplace_back(gt_t.z()); + pos_x_plot.emplace_back(gt_pose.translation.x()); + pos_y_plot.emplace_back(gt_pose.translation.y()); + pos_z_plot.emplace_back(gt_pose.translation.z()); Toucan::BeginFigure2D("Position"); { diff --git a/examples/RGBD-Example/stb/stb_image.c b/examples/RGBD-Example/stb/stb_image.c deleted file mode 100644 index 0375a5a..0000000 --- a/examples/RGBD-Example/stb/stb_image.c +++ /dev/null @@ -1,3 +0,0 @@ - -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" diff --git a/examples/RGBD-Example/stb/stb_image.h b/examples/RGBD-Example/stb_image.h similarity index 100% rename from examples/RGBD-Example/stb/stb_image.h rename to examples/RGBD-Example/stb_image.h diff --git a/examples/SimpleLinePlot-Example/CMakeLists.txt b/examples/SimpleLinePlot-Example/CMakeLists.txt index 6a4eba8..4244a2d 100644 --- a/examples/SimpleLinePlot-Example/CMakeLists.txt +++ b/examples/SimpleLinePlot-Example/CMakeLists.txt @@ -1,3 +1,6 @@ +cmake_minimum_required(VERSION 3.10) +project(simple-line-plot-example) + add_executable( simple-line-plot-example main.cpp @@ -6,11 +9,15 @@ add_executable( set_target_properties( simple-line-plot-example PROPERTIES CXX_STANDARD 17 - CXX_EXTENSIONS ON CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON ) +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) + target_link_libraries( simple-line-plot-example PRIVATE Toucan::Toucan diff --git a/include/Toucan/DataTypes.h b/include/Toucan/DataTypes.h index 3fbec06..e3dc112 100644 --- a/include/Toucan/DataTypes.h +++ b/include/Toucan/DataTypes.h @@ -207,13 +207,12 @@ struct Primitive3D { struct OrbitCamera { OrbitCamera() : - pitch{-M_PI/5}, yaw{M_PI/6}, distance{3.5}, orbit_center{Vector3f::Zero()} { } + pitch{4*M_PI/3}, yaw{M_PI/6}, distance{3.5}, orbit_center{Vector3f::Zero()} { } - - void orbit(const Vector2f& delta) { yaw -= delta.x(); pitch += delta.y(); } + void orbit(const Vector2f& delta) { yaw += delta.x(); pitch += delta.y(); } void move(const Vector2f& delta) { - orbit_center.x() += (-std::cos(yaw) * delta.x() + std::sin(yaw) * delta.y()) * distance; - orbit_center.z() += (std::sin(yaw) * delta.x() + std::cos(yaw) * delta.y()) * distance; + orbit_center.x() += (-std::cos(yaw) * delta.x() - std::sin(yaw) * delta.y()) * distance; + orbit_center.y() += (-std::sin(yaw) * delta.x() + std::cos(yaw) * delta.y()) * distance; } void change_distance(float delta) { distance += static_cast(std::log1p(distance)*delta/std::log(5)); // TODO(Matias): Make distance curve (log base) user editable @@ -222,12 +221,20 @@ struct OrbitCamera { [[nodiscard]] RigidTransform3Df get_pose() const { const Quaternionf orientation = - Quaternionf(Vector3f::UnitY(), yaw) * + Quaternionf(Vector3f::UnitZ(), yaw) * Quaternionf(Vector3f::UnitX(), pitch); const Vector3f translation = orientation * Vector3f(0.0f, 0.0f, -distance) + orbit_center; return RigidTransform3Df(orientation, translation); } + [[nodiscard]] RigidTransform3Df get_orbit_pose(float distance) const { + const Quaternionf orientation = + Quaternionf(Vector3f::UnitZ(), yaw) * + Quaternionf(Vector3f::UnitX(), pitch); + const Vector3f translation = orientation * Vector3f(0.0f, 0.0f, -distance); + return RigidTransform3Df(orientation, translation); + } + float pitch; float yaw; float distance; @@ -255,7 +262,26 @@ struct Figure2DSettings { YAxisDirection y_axis_direction = YAxisDirection::UP; }; +enum class Orientation { + X_UP, X_DOWN, + Y_UP, Y_DOWN, + Z_UP, Z_DOWN +}; + +enum class Handedness { + RIGHT_HANDED, + LEFT_HANDED +}; + struct Figure3DSettings { + Orientation orientation = Orientation::Z_UP; + Handedness handedness = Handedness::RIGHT_HANDED; + float near_clip = 0.01f; + float far_clip = 200.0f; + bool show_axis_gizmo = true; +}; + +struct InputSettings { }; @@ -293,7 +319,29 @@ struct ShowLines3DSettings { struct ShowPrimitives3DSettings { ScaledTransform3Df scaled_transform; - Vector3f light_vector = Vector3f(-1.0f, -1.25f, -1.5f).normalized(); + Vector3f light_vector = Vector3f(1.0f, 1.25f, 1.5f).normalized(); +}; + +struct ShowButtonSettings { + +}; + +struct ShowCheckboxSettings { + +}; + +struct ShowSliderFloatSettings { + float min_value = -5.0f; + float max_value = 5.0f; +}; + +struct ShowSliderIntSettings { + int min_value = -5; + int max_value = 5; +}; + +struct ShowColorPickerSettings { + }; } // namespace Toucan diff --git a/include/Toucan/LinAlg.h b/include/Toucan/LinAlg.h index 3b4a285..e3fe50d 100644 --- a/include/Toucan/LinAlg.h +++ b/include/Toucan/LinAlg.h @@ -49,8 +49,9 @@ class Matrix { // Mathematical functions [[nodiscard]] constexpr scalar_t trace() const; - [[nodiscard]] constexpr scalar_t dot_product(const Matrix& rhs); - [[nodiscard]] constexpr Matrix cross_product(const Matrix& rhs); + [[nodiscard]] constexpr scalar_t dot_product(const Matrix& rhs) const; + [[nodiscard]] constexpr Matrix cross_product(const Matrix& rhs) const; + [[nodiscard]] constexpr Matrix transpose() const; // Accessors constexpr scalar_t& x(); @@ -223,7 +224,7 @@ constexpr inline scalar_t Matrix::trace() const { } template -constexpr inline scalar_t Matrix::dot_product(const Matrix& rhs) { +constexpr inline scalar_t Matrix::dot_product(const Matrix& rhs) const { scalar_t sum = scalar_t(0); for (int row_index = 0; row_index < rows; ++row_index) { for (int column_index = 0; column_index < columns; ++column_index) { @@ -233,7 +234,7 @@ constexpr inline scalar_t Matrix::dot_product(const Mat return sum; } template -constexpr inline Matrix Matrix::cross_product(const Matrix& rhs) { +constexpr inline Matrix Matrix::cross_product(const Matrix& rhs) const { static_assert((rows == 3 and columns == 1) or (rows == 1 and columns == 3), "Cross product is only defined for Vector3 or RowVector3"); Matrix product; product.x() = this->y()*rhs.z() - this->z()*rhs.y(); @@ -242,6 +243,17 @@ constexpr inline Matrix Matrix return product; } +template +constexpr inline Matrix Matrix::transpose() const { + Matrix transpose; + for (int row_index = 0; row_index < rows; ++row_index) { + for (int column_index = 0; column_index < columns; ++column_index) { + transpose(column_index, row_index) = this->operator()(row_index, column_index); + } + } + return transpose; +} + /*** Accessors implementation ***/ template constexpr inline scalar_t& Matrix::x() { diff --git a/include/Toucan/Toucan.h b/include/Toucan/Toucan.h index 53b5aed..e6cba92 100644 --- a/include/Toucan/Toucan.h +++ b/include/Toucan/Toucan.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -16,14 +17,13 @@ bool IsWindowOpen(); void SleepUntilWindowClosed(); // ***** Figure 2D ***** - void BeginFigure2D(const std::string& name, const Figure2DSettings& settings = {}); void EndFigure2D(); // ***** Elements 2D ***** - void PushPose2D(const Toucan::RigidTransform2Df& pose); void PopPose2D(); +void ClearPose2D(); void ShowLinePlot2D(const std::string& name, const Toucan::Buffer& line_buffer, int draw_layer = 0, const ShowLinePlot2DSettings& settings = {}); void ShowPoints2D(const std::string& name, const Toucan::Buffer& points_buffer, int draw_layer = 0, const ShowPoints2DSettings& settings = {}); @@ -49,12 +49,34 @@ void EndFigure3D(); // ***** Elements 3D ***** void PushPose3D(const Toucan::RigidTransform3Df& pose); void PopPose3D(); +void ClearPose3D(); void ShowAxis3D(const std::string& name, const ShowAxis3DSettings& settings = {}); void ShowPoints3D(const std::string& name, const Toucan::Buffer& points_buffer, const ShowPoints3DSettings& settings = {}); void ShowLines3D(const std::string& name, const Toucan::Buffer& lines_buffer, const ShowLines3DSettings& settings = {}); void ShowPrimitives3D(const std::string& name, const Toucan::Buffer& primitives_buffer, const ShowPrimitives3DSettings& settings = {}); +// ***** Input ***** +void BeginInputWindow(const std::string& name, const InputSettings& settings = {}); +void EndInputWindow(); + +// ***** Elements Input ***** +bool ShowButton(const std::string& name, const ShowButtonSettings& settings = {}); + +bool ShowCheckbox(const std::string& name, bool& value, const ShowCheckboxSettings& = {}); + +bool ShowSliderFloat(const std::string& name, float& value, const ShowSliderFloatSettings& settings = {}); +bool ShowSliderFloat2(const std::string& name, Vector2f& value, const ShowSliderFloatSettings& settings = {}); +bool ShowSliderFloat3(const std::string& name, Vector3f& value, const ShowSliderFloatSettings& settings = {}); +bool ShowSliderFloat4(const std::string& name, Vector4f& value, const ShowSliderFloatSettings& settings = {}); + +bool ShowSliderInt(const std::string& name, int& value, const ShowSliderIntSettings& settings = {}); +bool ShowSliderInt2(const std::string& name, Vector2i& value, const ShowSliderIntSettings& settings = {}); +bool ShowSliderInt3(const std::string& name, Vector3i& value, const ShowSliderIntSettings& settings = {}); +bool ShowSliderInt4(const std::string& name, Vector4i& value, const ShowSliderIntSettings& settings = {}); + +bool ShowColorPicker(const std::string& name, Color& value, const ShowColorPickerSettings& settings = {}); + // Helper functions template inline void ShowLinePlot2D(const std::string& name, const std::array& points, int draw_layer = 0, const ShowLinePlot2DSettings& settings = {}) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a59aba9..00aff56 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,8 @@ set( add_library(Toucan STATIC ${Toucan_source}) add_library(Toucan::Toucan ALIAS Toucan) +# Set this so examples use local Toucan instead of any version installed on system. +set(Toucan_FOUND 1 PARENT_SCOPE) set_target_properties( Toucan PROPERTIES diff --git a/src/Toucan.cpp b/src/Toucan.cpp index faafb74..66bd1fc 100644 --- a/src/Toucan.cpp +++ b/src/Toucan.cpp @@ -21,6 +21,7 @@ #include "render.h" #include "Utils.h" #include "util/tick_number.h" +#include "gl/projection.h" #include #include @@ -53,6 +54,12 @@ if (toucan_context_ptr->current_figure_3d == nullptr) { throw std::runtime_error #define validate_inactive_figure3d(function_name) \ if (toucan_context_ptr->current_figure_3d != nullptr) { throw std::runtime_error("Toucan error! 'Toucan::"#function_name"' was called while another Figure2D was active. Did you forget to call 'Toucan::EndFigure2D'?"); } +#define validate_active_input_window(function_name) \ +if (toucan_context_ptr->current_input_window == nullptr) { throw std::runtime_error("Toucan error! 'Toucan::"#function_name"' was called without an active Figure2D. Did you forget to call 'Toucan::BeginFigure2D'?"); } + +#define validate_inactive_input_window(function_name) \ +if (toucan_context_ptr->current_input_window != nullptr) { throw std::runtime_error("Toucan error! 'Toucan::"#function_name"' was called while another InputWindow was active. Did you forget to call 'Toucan::EndInputWindow'?"); } + Toucan::Element2D& get_or_create_element_2d(Toucan::Figure2D& figure, const std::string& name, int draw_layer, Toucan::ElementType2D type) { // Does the Element2D object with that name already exist? Toucan::Element2D* current_element_ptr = nullptr; @@ -110,6 +117,30 @@ Toucan::Element3D& get_or_create_element_3d(Toucan::Figure3D& figure, const std: return *current_element_ptr; } +Toucan::ElementInput& get_or_create_element_input(Toucan::InputWindow& input_window, const std::string& name, Toucan::ElementInputType type) { + // Does the Element3D object with that name already exist? + Toucan::ElementInput* current_element_ptr = nullptr; + for (auto& element : input_window.elements) { + if (element.name == name) { + current_element_ptr = &element; + break; + } + } + + // If it does not exist, we must create a new one. + if (current_element_ptr == nullptr) { + + Toucan::ElementInput new_element_3d = {}; + new_element_3d.name = name; + new_element_3d.type = type; + + auto& inserted_element = input_window.elements.emplace_back(new_element_3d); + current_element_ptr = &inserted_element; + } + + return *current_element_ptr; +} + void Toucan::Initialize(Toucan::ToucanSettings settings) { if (toucan_context_ptr != nullptr) { throw std::runtime_error("Toucan error! 'Toucan::Initialize' was called when Toucan already was initialized. Did you call 'Toucan::Initialize' multiple times?"); } toucan_context_ptr = new ToucanContext; @@ -204,6 +235,18 @@ void Toucan::PopPose2D() { current_figure.pose_stack.pop_back(); } +void Toucan::ClearPose2D() { + validate_initialized(PopPose2D) + auto& context = *toucan_context_ptr; + validate_active_figure2d(PopPose2D) + auto& current_figure = *context.current_figure_2d; + + if (current_figure.pose_stack.size() <= 1) { throw std::runtime_error("Toucan error! 'Toucan::ClearPose2D' was called without any matching call to `Toucan::PushPose2D`."); } + + current_figure.pose_stack.clear(); + current_figure.pose_stack.emplace_back(); // Add identity pose back +} + void Toucan::ShowLinePlot2D(const std::string& name, const Toucan::Buffer& line_buffer, int draw_layer, const ShowLinePlot2DSettings& settings) { validate_initialized(PopPose2D) auto& context = *toucan_context_ptr; @@ -359,6 +402,18 @@ void Toucan::PopPose3D() { current_figure.pose_stack.pop_back(); } +void Toucan::ClearPose3D() { + validate_initialized(PopPose3D) + auto& context = *toucan_context_ptr; + validate_active_figure3d(PopPose3D) + auto& current_figure = *context.current_figure_3d; + + if (current_figure.pose_stack.size() <= 1) { throw std::runtime_error("Toucan error! 'Toucan::ClearPose3D' was called without any matching call to `Toucan::PushPose3D`."); } + + current_figure.pose_stack.clear(); + current_figure.pose_stack.emplace_back(); // Add identity pose back +} + void Toucan::ShowAxis3D(const std::string& name, const ShowAxis3DSettings& settings) { validate_initialized(ShowAxis3D) auto& context = *toucan_context_ptr; @@ -449,6 +504,247 @@ void Toucan::ShowPrimitives3D(const std::string& name, const Toucan::Buffermutex.lock(); + + input_window_ptr->settings = settings; + toucan_context.current_input_window = input_window_ptr; +} + +void Toucan::EndInputWindow() { + validate_initialized(BeginInputWindow) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(BeginInputWindow) + + toucan_context.current_input_window->mutex.unlock(); + toucan_context.current_input_window = nullptr; +} + +bool Toucan::ShowButton(const std::string& name, const ShowButtonSettings& settings) { + validate_initialized(ShowButton) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowButton) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::BUTTON); + + current_element.show_button_metadata.settings = settings; + if (current_element.show_button_metadata.number_of_click_events > 0) { + current_element.show_button_metadata.number_of_click_events--; + return true; + } else { + return false; + } +} + +bool Toucan::ShowCheckbox(const std::string& name, bool& value, const ShowCheckboxSettings& settings) { + validate_initialized(ShowSliderFloat) + auto &toucan_context = *toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto ¤t_input_window = *toucan_context.current_input_window; + + auto ¤t_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::CHECKBOX); + + current_element.show_checkbox_metadata.settings = settings; + if (current_element.show_checkbox_metadata.value_changed) { + value = current_element.show_checkbox_metadata.value; + current_element.show_checkbox_metadata.value_changed = false; + return true; + } else { + current_element.show_checkbox_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderFloat(const std::string& name, float& value, const ShowSliderFloatSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_FLOAT); + + current_element.show_slider_float_metadata.settings = settings; + if (current_element.show_slider_float_metadata.value_changed) { + value = current_element.show_slider_float_metadata.value; + current_element.show_slider_float_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_float_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderFloat2(const std::string& name, Vector2f& value, const ShowSliderFloatSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_FLOAT2); + + current_element.show_slider_float2_metadata.settings = settings; + if (current_element.show_slider_float2_metadata.value_changed) { + value = current_element.show_slider_float2_metadata.value; + current_element.show_slider_float2_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_float2_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderFloat3(const std::string& name, Vector3f& value, const ShowSliderFloatSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_FLOAT3); + + current_element.show_slider_float3_metadata.settings = settings; + if (current_element.show_slider_float3_metadata.value_changed) { + value = current_element.show_slider_float3_metadata.value; + current_element.show_slider_float3_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_float3_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderFloat4(const std::string& name, Vector4f& value, const ShowSliderFloatSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_FLOAT4); + + current_element.show_slider_float4_metadata.settings = settings; + if (current_element.show_slider_float4_metadata.value_changed) { + value = current_element.show_slider_float4_metadata.value; + current_element.show_slider_float4_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_float4_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderInt(const std::string& name, int& value, const ShowSliderIntSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_INT); + + current_element.show_slider_int_metadata.settings = settings; + if (current_element.show_slider_int_metadata.value_changed) { + value = current_element.show_slider_int_metadata.value; + current_element.show_slider_int_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_int_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderInt2(const std::string& name, Vector2i& value, const ShowSliderIntSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_INT2); + + current_element.show_slider_int2_metadata.settings = settings; + if (current_element.show_slider_int2_metadata.value_changed) { + value = current_element.show_slider_int2_metadata.value; + current_element.show_slider_int2_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_int2_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderInt3(const std::string& name, Vector3i& value, const ShowSliderIntSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_INT3); + + current_element.show_slider_int3_metadata.settings = settings; + if (current_element.show_slider_int3_metadata.value_changed) { + value = current_element.show_slider_int3_metadata.value; + current_element.show_slider_int3_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_int3_metadata.value = value; + return false; + } +} + +bool Toucan::ShowSliderInt4(const std::string& name, Vector4i& value, const ShowSliderIntSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::SLIDER_INT4); + + current_element.show_slider_int4_metadata.settings = settings; + if (current_element.show_slider_int4_metadata.value_changed) { + value = current_element.show_slider_int4_metadata.value; + current_element.show_slider_int4_metadata.value_changed = false; + return true; + } else { + current_element.show_slider_int4_metadata.value = value; + return false; + } +} + +bool Toucan::ShowColorPicker(const std::string& name, Color& value, const ShowColorPickerSettings& settings) { + validate_initialized(ShowSliderFloat) + auto& toucan_context = * toucan_context_ptr; + validate_active_input_window(ShowSliderFloat) + auto& current_input_window = *toucan_context.current_input_window; + + auto& current_element = get_or_create_element_input(current_input_window, name, Toucan::ElementInputType::COLOR_PICKER); + + current_element.show_color_picker_metadata.settings = settings; + if (current_element.show_color_picker_metadata.value_changed) { + value = current_element.show_color_picker_metadata.value; + current_element.show_color_picker_metadata.value_changed = false; + return true; + } else { + current_element.show_color_picker_metadata.value = value; + return false; + } +} + Toucan::Rectangle get_lineplot_2d_data_bounds(const Toucan::Element2D& element_2d, const Toucan::RigidTransform2Df& local_transform) { assert(element_2d.type == Toucan::ElementType2D::LinePlot2D); @@ -870,15 +1166,19 @@ void render_loop(Toucan::ToucanSettings settings) { const auto x_axis_from_value = figure_2d.view.min.x(); const auto x_axis_to_value = figure_2d.view.max.x(); + constexpr float min_label_distance = 85.0f; // TODO(Matias): Compute min distance based on text size. + assert(x_axis_from_value < x_axis_to_value); - const auto [x_tick_values, x_tick_strings] = get_axis_ticks(x_axis_from_value, x_axis_to_value, 10); // TODO(Matias): Compute max number of ticks based on window size + const int number_of_x_ticks = std::max(static_cast(std::floor(axis_x_rect.GetWidth()/min_label_distance)), 2); + const auto [x_tick_values, x_tick_strings] = get_axis_ticks(x_axis_from_value, x_axis_to_value, number_of_x_ticks); const auto x_ticks_position = Toucan::data_to_pixel(x_tick_values, x_axis_from_value, x_axis_to_value, axis_x_min.x, axis_x_max.x); const auto y_axis_from_value = figure_2d.view.min.y(); const auto y_axis_to_value = figure_2d.view.max.y(); assert(y_axis_from_value < y_axis_to_value); - const auto [y_ticks_values, y_tick_strings] = get_axis_ticks(y_axis_from_value, y_axis_to_value, 10); // TODO(Matias): Compute max number of ticks based on window size + const int number_of_y_ticks = std::max(static_cast(std::floor(axis_y_rect.GetHeight()/min_label_distance)), 2); + const auto [y_ticks_values, y_tick_strings] = get_axis_ticks(y_axis_from_value, y_axis_to_value, number_of_y_ticks); std::vector y_ticks_positions; if (figure_2d.settings.y_axis_direction == Toucan::YAxisDirection::UP) { y_ticks_positions = Toucan::data_to_pixel(y_ticks_values, y_axis_from_value, y_axis_to_value, axis_y_max.y, axis_y_min.y); @@ -1016,14 +1316,24 @@ void render_loop(Toucan::ToucanSettings settings) { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // NOLINT + const Toucan::Matrix4f world_to_camera_matrix = figure_3d.camera.get_pose().inverse().transformation_matrix(); - const Toucan::Matrix4f projection_matrix = create_3d_projection_matrix(0.01, 100.0, 1024, figure_draw_size); + const Toucan::Matrix4f orientation_and_handedness_matrix = create_3d_orientation_and_handedness_matrix(figure_3d.settings.orientation, figure_3d.settings.handedness); + + const auto near_clip = figure_3d.settings.near_clip; + const auto far_clip = figure_3d.settings.far_clip; + const Toucan::Matrix4f projection_matrix = create_3d_projection_matrix(near_clip, far_clip, 1024, figure_draw_size); + for (auto& element : figure_3d.elements) { const auto model_to_world_matrix = element.pose.transformation_matrix(); - Toucan::draw_element_3d(element, model_to_world_matrix, world_to_camera_matrix, projection_matrix, toucan_context_ptr); + Toucan::draw_element_3d(element, model_to_world_matrix, orientation_and_handedness_matrix, world_to_camera_matrix, projection_matrix, toucan_context_ptr); } - glCheckError(); + if (figure_3d.settings.show_axis_gizmo) { + Toucan::draw_axis_gizmo_3d(figure_3d.camera.get_orbit_pose(100.0f).inverse(), figure_draw_size, orientation_and_handedness_matrix, toucan_context_ptr); + } + glCheckError(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); if(toucan_context_ptr->rdoc_api) toucan_context_ptr->rdoc_api->EndFrameCapture(nullptr, nullptr); } @@ -1035,6 +1345,110 @@ void render_loop(Toucan::ToucanSettings settings) { ImGui::End(); } + // Draw all InputWindows + for (auto& input_window : toucan_context_ptr->input_windows) { + if (ImGui::Begin(input_window.name.c_str())) { + std::unique_lock lock(input_window.mutex); + + for (auto& input_element : input_window.elements) { + switch (input_element.type) { + case Toucan::ElementInputType::BUTTON : { + if (ImGui::Button(input_element.name.c_str())) { + input_element.show_button_metadata.number_of_click_events++; + } + } break; + case Toucan::ElementInputType::CHECKBOX : { + bool* value_ptr = &input_element.show_checkbox_metadata.value; + + if (ImGui::Checkbox(input_element.name.c_str(), value_ptr)) { + input_element.show_checkbox_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_FLOAT : { + float* value_ptr = &input_element.show_slider_float_metadata.value; + const float min_value = input_element.show_slider_float_metadata.settings.min_value; + const float max_value = input_element.show_slider_float_metadata.settings.max_value; + + if (ImGui::SliderFloat(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_float_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_FLOAT2 : { + float* value_ptr = input_element.show_slider_float2_metadata.value.data(); + const float min_value = input_element.show_slider_float2_metadata.settings.min_value; + const float max_value = input_element.show_slider_float2_metadata.settings.max_value; + + if (ImGui::SliderFloat2(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_float2_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_FLOAT3 : { + float* value_ptr = input_element.show_slider_float3_metadata.value.data(); + const float min_value = input_element.show_slider_float3_metadata.settings.min_value; + const float max_value = input_element.show_slider_float3_metadata.settings.max_value; + + if (ImGui::SliderFloat3(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_float3_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_FLOAT4 : { + float* value_ptr = input_element.show_slider_float4_metadata.value.data(); + const float min_value = input_element.show_slider_float4_metadata.settings.min_value; + const float max_value = input_element.show_slider_float4_metadata.settings.max_value; + + if (ImGui::SliderFloat4(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_float4_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_INT : { + int* value_ptr = &input_element.show_slider_int_metadata.value; + const int min_value = input_element.show_slider_int_metadata.settings.min_value; + const int max_value = input_element.show_slider_int_metadata.settings.max_value; + + if (ImGui::SliderInt(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_int_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_INT2 : { + int* value_ptr = input_element.show_slider_int2_metadata.value.data(); + const int min_value = input_element.show_slider_int2_metadata.settings.min_value; + const int max_value = input_element.show_slider_int2_metadata.settings.max_value; + + if (ImGui::SliderInt2(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_int2_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_INT3 : { + int* value_ptr = input_element.show_slider_int3_metadata.value.data(); + const int min_value = input_element.show_slider_int3_metadata.settings.min_value; + const int max_value = input_element.show_slider_int3_metadata.settings.max_value; + + if (ImGui::SliderInt3(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_int3_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::SLIDER_INT4 : { + int* value_ptr = input_element.show_slider_int4_metadata.value.data(); + const int min_value = input_element.show_slider_int4_metadata.settings.min_value; + const int max_value = input_element.show_slider_int4_metadata.settings.max_value; + + if (ImGui::SliderInt4(input_element.name.c_str(), value_ptr, min_value, max_value)) { + input_element.show_slider_int4_metadata.value_changed = true; + } + } break; + case Toucan::ElementInputType::COLOR_PICKER : { + float* value_ptr = &input_element.show_color_picker_metadata.value.r; + + if (ImGui::ColorEdit3(input_element.name.c_str(), value_ptr)) { + input_element.show_color_picker_metadata.value_changed = true; + } + } break; + } + } + } + ImGui::End(); + } + ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/src/Utils.h b/src/Utils.h index 85ea086..ad438d2 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -11,29 +11,61 @@ constexpr size_t offset_of(U T::*member) { return (char*) &((T*) nullptr->*member) - (char*) nullptr; } -constexpr inline Toucan::Matrix4f create_3d_projection_matrix(float z_near, float z_far, float x_left, float x_right, float y_top, float y_bottom) { +inline Toucan::Matrix4f create_3d_orientation_and_handedness_matrix(const Orientation& orientation, const Handedness& handedness) { + Toucan::Vector3f x_vec = Toucan::Vector3f::UnitX(); + Toucan::Vector3f y_vec = Toucan::Vector3f::UnitY(); + Toucan::Vector3f z_vec = Toucan::Vector3f::UnitZ(); + + switch (orientation) { + case Orientation::X_UP : { + x_vec = Toucan::Vector3f::UnitZ(); + y_vec = Toucan::Vector3f::UnitX(); + z_vec = x_vec.cross_product(y_vec); + if (handedness == Handedness::LEFT_HANDED) { z_vec = -z_vec; } + } break; + case Orientation::X_DOWN : { + x_vec = -Toucan::Vector3f::UnitZ(); + y_vec = Toucan::Vector3f::UnitX(); + z_vec = x_vec.cross_product(y_vec); + if (handedness == Handedness::LEFT_HANDED) { z_vec = -z_vec; } + } break; + case Orientation::Y_UP : { + y_vec = Toucan::Vector3f::UnitZ(); + z_vec = Toucan::Vector3f::UnitX(); + x_vec = y_vec.cross_product(z_vec); + if (handedness == Handedness::LEFT_HANDED) { x_vec = -x_vec; } + } break; + case Orientation::Y_DOWN : { + y_vec = -Toucan::Vector3f::UnitZ(); + z_vec = Toucan::Vector3f::UnitX(); + x_vec = y_vec.cross_product(z_vec); + if (handedness == Handedness::LEFT_HANDED) { x_vec = -x_vec; } + } break; + case Orientation::Z_UP : { + z_vec = Toucan::Vector3f::UnitZ(); + x_vec = Toucan::Vector3f::UnitX(); + y_vec = z_vec.cross_product(x_vec); + if (handedness == Handedness::LEFT_HANDED) { y_vec = -y_vec; } + } break; + case Orientation::Z_DOWN : { + z_vec = -Toucan::Vector3f::UnitZ(); + x_vec = Toucan::Vector3f::UnitX(); + y_vec = z_vec.cross_product(x_vec); + if (handedness == Handedness::LEFT_HANDED) { y_vec = -y_vec; } + } break; + } + + Toucan::Matrix4f m( - 2.0f * z_near / (x_right - x_left), 0.0f, (x_right + x_left) / (x_right - x_left), 0.0f, - 0.0f, 2.0f * z_near / (y_bottom - y_top), (y_bottom + y_top) / (y_bottom - y_top), 0.0f, - 0.0f, 0.0f, (z_far + z_near) / (z_far - z_near), -2.0f * z_far * z_near / (z_far - z_near), - 0.0f, 0.0f, 1.0f, 0.0f - ); + x_vec.x(), y_vec.x(), z_vec.x(), 0.0f, + x_vec.y(), y_vec.y(), z_vec.y(), 0.0f, + x_vec.z(), y_vec.z(), z_vec.z(), 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); return m; } -constexpr inline Toucan::Matrix4f create_3d_projection_matrix(float z_near, float z_far, float fx, float fy, float cx, float cy, Vector2i image_size) { - float x_min = -z_near * cx / fx; - float x_max = z_near * (image_size.x() - cx) / fx; - float y_min = -z_near * cy / fy; - float y_max = z_near * (image_size.y() - cy) / fy; - return create_3d_projection_matrix(z_near, z_far, x_min, x_max, y_min, y_max); -} - -constexpr inline Toucan::Matrix4f create_3d_projection_matrix(float z_min, float z_max, float f, Vector2i image_size) { - return create_3d_projection_matrix(z_min, z_max, f, f, 0.5f * image_size.x(), 0.5f * image_size.y(), image_size); -} - inline Toucan::Matrix4f create_2d_view_matrix(const Rectangle& draw_view, YAxisDirection y_axis_direction) { const float& a = draw_view.min.x(); const float& b = draw_view.min.y(); diff --git a/src/gl/geometry.cpp b/src/gl/geometry.cpp index 08e6aae..0ff53f6 100644 --- a/src/gl/geometry.cpp +++ b/src/gl/geometry.cpp @@ -144,15 +144,15 @@ IndexedGeometryHandles generate_sphere(int number_of_sectors, int number_of_stac // TODO(Matias): Use a single vertex for top and bottom for (int stack_index = 0; stack_index <= number_of_stacks; ++stack_index) { const float stack_angle = 0.5f*static_cast(M_PI) - static_cast(stack_index)*stack_step; - const float xz = radius * std::cos(stack_angle); - const float y = radius * std::sin(stack_angle); + const float xy = radius * std::cos(stack_angle); + const float z = radius * std::sin(stack_angle); for (int sector_index = 0; sector_index <= number_of_sectors; ++sector_index) { const float sector_angle = static_cast(sector_index) * sector_step; - const float x = xz * std::cos(sector_angle); - const float z = xz * std::sin(sector_angle); + const float x = xy * std::cos(sector_angle); + const float y = xy * std::sin(sector_angle); const float u = static_cast(sector_index) / static_cast(number_of_sectors); const float v = static_cast(stack_index) / static_cast(number_of_stacks); @@ -275,20 +275,20 @@ IndexedGeometryHandles generate_cylinder(int number_of_sectors) { int vertex_index = 2; // TODO(Matias): Fix UV coordinates - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(0.0f, -0.5f, 0.0f), -Toucan::Vector3f::UnitY(), Toucan::Vector2f::Zero()}); - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(0.0f, 0.5f, 0.0f), Toucan::Vector3f::UnitY(), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(0.0f, 0.0f, -0.5f), -Toucan::Vector3f::UnitZ(), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(0.0f, 0.0f, 0.5f), Toucan::Vector3f::UnitZ(), Toucan::Vector2f::Zero()}); // Top cap for (int sector_index = 0; sector_index <= number_of_sectors; ++sector_index) { const float angle = sector_angle * sector_index; const float x = std::cos(angle); - const float z = std::sin(angle); + const float y = std::sin(angle); - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, -0.5f, radius*z), -Toucan::Vector3f::UnitY(), Toucan::Vector2f::Zero()}); - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, -0.5f, radius*z), Toucan::Vector3f(x, 0.0f, z), Toucan::Vector2f::Zero()}); - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, 0.5f, radius*z), Toucan::Vector3f(x, 0.0f, z), Toucan::Vector2f::Zero()}); - geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, 0.5f, radius*z), Toucan::Vector3f::UnitY(), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, radius * y, -0.5f), -Toucan::Vector3f::UnitZ(), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, radius * y, -0.5f), Toucan::Vector3f(x, y, 0.0f), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, radius * y, 0.5f), Toucan::Vector3f(x, y, 0.0f), Toucan::Vector2f::Zero()}); + geometry_data.vertices.emplace_back(TexturedVertex{Toucan::Vector3f(radius*x, radius * y, 0.5f), Toucan::Vector3f::UnitZ(), Toucan::Vector2f::Zero()}); vertex_index += 4; if (sector_index != 0) { diff --git a/src/gl/projection.h b/src/gl/projection.h new file mode 100644 index 0000000..aa22a1e --- /dev/null +++ b/src/gl/projection.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +template +constexpr inline Toucan::Matrix4 create_3d_projection_matrix(scalar_t z_near, scalar_t z_far, scalar_t x_left, scalar_t x_right, scalar_t y_top, scalar_t y_bottom) { + Toucan::Matrix4 m( + scalar_t(2) * z_near / (x_right - x_left), scalar_t(0), (x_right + x_left) / (x_right - x_left), scalar_t(0), + scalar_t(0), scalar_t(2) * z_near / (y_bottom - y_top), (y_bottom + y_top) / (y_bottom - y_top), scalar_t(0), + scalar_t(0), scalar_t(0), (z_far + z_near) / (z_far - z_near), -scalar_t(2) * z_far * z_near / (z_far - z_near), + scalar_t(0), scalar_t(0), scalar_t(1), scalar_t(0) + ); + + return m; +} + +template +constexpr inline Toucan::Matrix4 create_3d_projection_matrix(scalar_t z_near, scalar_t z_far, scalar_t fx, scalar_t fy, scalar_t cx, scalar_t cy, Toucan::Vector2i image_size) { + scalar_t x_min = -z_near * cx / fx; + scalar_t x_max = z_near * (image_size.x() - cx) / fx; + scalar_t y_min = -z_near * cy / fy; + scalar_t y_max = z_near * (image_size.y() - cy) / fy; + return create_3d_projection_matrix(z_near, z_far, x_min, x_max, y_min, y_max); +} + +template +constexpr inline Toucan::Matrix4 create_3d_projection_matrix(float z_min, float z_max, float f, Toucan::Vector2i image_size) { + return create_3d_projection_matrix(z_min, z_max, f, f, scalar_t(0.5) * image_size.x(), scalar_t(0.5) * image_size.y(), image_size); +} diff --git a/src/internal.h b/src/internal.h index e1ee822..66805b4 100644 --- a/src/internal.h +++ b/src/internal.h @@ -59,7 +59,7 @@ struct Element2D { union { LinePlot2DMetadata line_plot_2d_metadata; Point2DMetadata point_2d_metadata; - Image2DMetadata image_2d_metadata ; + Image2DMetadata image_2d_metadata; }; }; @@ -157,6 +157,105 @@ struct Figure3D { Vector2i framebuffer_size = Vector2i(128, 128); }; +enum class ElementInputType { + BUTTON, CHECKBOX, + SLIDER_FLOAT, SLIDER_FLOAT2, SLIDER_FLOAT3, SLIDER_FLOAT4, + SLIDER_INT, SLIDER_INT2, SLIDER_INT3, SLIDER_INT4, + COLOR_PICKER +}; + +struct ShowButtonMetadata { + int number_of_click_events = 0; + ShowButtonSettings settings; +}; + +struct ShowCheckboxMetadata { + bool value; + bool value_changed; + ShowCheckboxSettings settings; +}; + +struct ShowSliderFloatMetadata { + float value; + bool value_changed; + ShowSliderFloatSettings settings; +}; + +struct ShowSliderFloat2Metadata { + Vector2f value; + bool value_changed; + ShowSliderFloatSettings settings; +}; + +struct ShowSliderFloat3Metadata { + Vector3f value; + bool value_changed; + ShowSliderFloatSettings settings; +}; + +struct ShowSliderFloat4Metadata { + Vector4f value; + bool value_changed; + ShowSliderFloatSettings settings; +}; + +struct ShowSliderIntMetadata { + int value; + bool value_changed; + ShowSliderIntSettings settings; +}; + +struct ShowSliderInt2Metadata { + Vector2i value; + bool value_changed; + ShowSliderIntSettings settings; +}; + +struct ShowSliderInt3Metadata { + Vector3i value; + bool value_changed; + ShowSliderIntSettings settings; +}; + +struct ShowSliderInt4Metadata { + Vector4i value; + bool value_changed; + ShowSliderIntSettings settings; +}; + +struct ShowColorPickerMetadata { + Color value; + bool value_changed; + ShowColorPickerSettings settings; +}; + +struct ElementInput { + std::string name; + ElementInputType type; + + union { + ShowButtonMetadata show_button_metadata; + ShowCheckboxMetadata show_checkbox_metadata; + ShowSliderFloatMetadata show_slider_float_metadata; + ShowSliderFloat2Metadata show_slider_float2_metadata; + ShowSliderFloat3Metadata show_slider_float3_metadata; + ShowSliderFloat4Metadata show_slider_float4_metadata; + ShowSliderIntMetadata show_slider_int_metadata; + ShowSliderInt2Metadata show_slider_int2_metadata; + ShowSliderInt3Metadata show_slider_int3_metadata; + ShowSliderInt4Metadata show_slider_int4_metadata; + ShowColorPickerMetadata show_color_picker_metadata; + }; +}; + +struct InputWindow { + std::string name; + InputSettings settings = {}; + + std::mutex mutex; + std::vector elements; +}; + struct ToucanContext { std::atomic_bool should_render = true; std::atomic_bool window_open = true; @@ -177,6 +276,9 @@ struct ToucanContext { std::list figures_3d; Toucan::Figure3D* current_figure_3d = nullptr; + std::list input_windows; + Toucan::InputWindow* current_input_window = nullptr; + AssetContext asset_context = {}; }; diff --git a/src/render.cpp b/src/render.cpp index 4c77a9a..4f41860 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -12,6 +12,7 @@ #include "asset.h" #include "gl/shader.h" #include "gl/geometry.h" +#include "gl/projection.h" void recreate_framebuffer(unsigned int* framebuffer, unsigned int* framebuffer_color_texture, unsigned int* framebuffer_depth_texture, Toucan::Vector2i size) { @@ -291,7 +292,8 @@ bool Toucan::update_framebuffer_3d(Figure3D& figure_3d, Toucan::Vector2i size) { } -void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& model_to_world_matrix, const Matrix4f& world_to_camera_matrix, const Matrix4f& projection_matrix, Toucan::ToucanContext* context) { +void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& model_to_world_matrix, const Matrix4f& orientation_and_handedness_matrix, const Matrix4f& world_to_camera_matrix, const Matrix4f& projection_matrix, + Toucan::ToucanContext* context) { switch (element_3d.type) { case Toucan::ElementType3D::Grid3D: { @@ -315,92 +317,48 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode line_vertices_minor.reserve(number_of_vertices); // TODO(Matias): Define these colors with a setting - const Color line_color_major(0.5, 0.5, 0.5); - const Color line_color_minor(0.4, 0.4, 0.4); - const Color x_axis_color(1.0, 0.0, 0.0); - const Color y_axis_color(0.0, 1.0, 0.0); - const Color z_axis_color(0.0, 0.0, 1.0); + const Color line_color_origin(0.8, 0.8, 0.8); + const Color line_color_major(0.4, 0.4, 0.4); + const Color line_color_minor(0.3, 0.3, 0.3); for (int line_index = -line_extent; line_index <= line_extent; ++line_index) { if (line_index == 0) { // Special case for lines crossing the origin - constexpr float axis_line_length = 1.0; // X-axis - line_vertices_major.emplace_back(Vector3f(-line_extent_position, 0.0f, 0.0f), line_color_major); - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, 0.0f), line_color_major); - - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, 0.0f), x_axis_color); - line_vertices_major.emplace_back(Vector3f(axis_line_length, 0.0f, 0.0f), x_axis_color); - - line_vertices_major.emplace_back(Vector3f(axis_line_length, 0.0f, 0.0f), line_color_major); - line_vertices_major.emplace_back(Vector3f(line_extent_position, 0.0f, 0.0f), line_color_major); + line_vertices_major.emplace_back(Vector3f(spacing*line_index, -line_extent_position, 0.0f), line_color_origin); + line_vertices_major.emplace_back(Vector3f(spacing*line_index, line_extent_position, 0.0f), line_color_origin); // Y-axis - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, 0.0f), y_axis_color); - line_vertices_major.emplace_back(Vector3f(0.0f, axis_line_length, 0.0f), y_axis_color); - - // Z-axis - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, -line_extent_position), line_color_major); - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, 0.0f), line_color_major); - - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, 0.0f), z_axis_color); - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, axis_line_length), z_axis_color); - - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, axis_line_length), line_color_major); - line_vertices_major.emplace_back(Vector3f(0.0f, 0.0f, line_extent_position), line_color_major); + line_vertices_major.emplace_back(Vector3f(-line_extent_position, spacing*line_index, 0.0f), line_color_origin); + line_vertices_major.emplace_back(Vector3f(line_extent_position, spacing*line_index, 0.0f), line_color_origin); } else if (line_index % 5 == 0) { // Major lines // X-axis - line_vertices_major.emplace_back( - Vector3f(spacing*line_index, 0.0f, -line_extent_position), - line_color_major - ); - line_vertices_major.emplace_back( - Vector3f(spacing*line_index, 0.0f, line_extent_position), - line_color_major - ); + line_vertices_major.emplace_back(Vector3f(spacing*line_index, -line_extent_position, 0.0f), line_color_major); + line_vertices_major.emplace_back(Vector3f(spacing*line_index, line_extent_position, 0.0f), line_color_major); - // Z-axis - line_vertices_major.emplace_back( - Vector3f(-line_extent_position, 0.0f, spacing*line_index), - line_color_major - ); - line_vertices_major.emplace_back( - Vector3f(line_extent_position, 0.0f, spacing*line_index), - line_color_major - ); + // Y-axis + line_vertices_major.emplace_back(Vector3f(-line_extent_position, spacing*line_index, 0.0f), line_color_major); + line_vertices_major.emplace_back(Vector3f(line_extent_position, spacing*line_index, 0.0f), line_color_major); } else { // Minor lines // X-axis - line_vertices_minor.emplace_back( - Vector3f(spacing*line_index, 0.0f, -line_extent_position), - line_color_minor - ); - line_vertices_minor.emplace_back( - Vector3f(spacing*line_index, 0.0f, line_extent_position), - line_color_minor - ); + line_vertices_minor.emplace_back(Vector3f(spacing*line_index, -line_extent_position, 0.0f), line_color_minor); + line_vertices_minor.emplace_back(Vector3f(spacing*line_index, line_extent_position, 0.0f), line_color_minor); - // Z-axis - line_vertices_minor.emplace_back( - Vector3f(-line_extent_position, 0.0f, spacing*line_index), - line_color_minor - ); - line_vertices_minor.emplace_back( - Vector3f(line_extent_position, 0.0f, spacing*line_index), - line_color_minor - ); + // Y-axis + line_vertices_minor.emplace_back(Vector3f(-line_extent_position, spacing*line_index, 0.0f), line_color_minor); + line_vertices_minor.emplace_back(Vector3f(line_extent_position, spacing*line_index, 0.0f), line_color_minor); } } - - // Major lines - glBindVertexArray(element_3d.grid_3d_metadata.vao_major); - - glBindBuffer(GL_ARRAY_BUFFER, element_3d.grid_3d_metadata.vbo_major); - glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(LineVertex3D) * line_vertices_major.size()), line_vertices_major.data(), GL_STATIC_DRAW); - element_3d.grid_3d_metadata.number_of_major_vertices = line_vertices_major.size(); - constexpr auto position_location = 0; constexpr auto color_location = 1; + // Minor lines + glBindVertexArray(element_3d.grid_3d_metadata.vao_minor); + + glBindBuffer(GL_ARRAY_BUFFER, element_3d.grid_3d_metadata.vbo_minor); + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(LineVertex3D) * line_vertices_minor.size()), line_vertices_minor.data(), GL_STATIC_DRAW); + element_3d.grid_3d_metadata.number_of_minor_vertices = line_vertices_minor.size(); + // Position glVertexAttribPointer(position_location, 3, GL_FLOAT, GL_FALSE, sizeof(LineVertex3D), reinterpret_cast(offset_of(&LineVertex3D::position))); glEnableVertexAttribArray(position_location); @@ -411,12 +369,13 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode glCheckError(); - // Minor lines - glBindVertexArray(element_3d.grid_3d_metadata.vao_minor); + // Major lines + glBindVertexArray(element_3d.grid_3d_metadata.vao_major); + + glBindBuffer(GL_ARRAY_BUFFER, element_3d.grid_3d_metadata.vbo_major); + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(LineVertex3D) * line_vertices_major.size()), line_vertices_major.data(), GL_STATIC_DRAW); + element_3d.grid_3d_metadata.number_of_major_vertices = line_vertices_major.size(); - glBindBuffer(GL_ARRAY_BUFFER, element_3d.grid_3d_metadata.vbo_minor); - glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(LineVertex3D) * line_vertices_minor.size()), line_vertices_minor.data(), GL_STATIC_DRAW); - element_3d.grid_3d_metadata.number_of_minor_vertices = line_vertices_minor.size(); // Position glVertexAttribPointer(position_location, 3, GL_FLOAT, GL_FALSE, sizeof(LineVertex3D), reinterpret_cast(offset_of(&LineVertex3D::position))); @@ -440,8 +399,10 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode unsigned int line_3d_shader = get_line_3d_shader(&context->asset_context); glUseProgram(line_3d_shader); + const Toucan::Matrix4f model_matrix = model_to_world_matrix; + // TODO(Matias): Use uniform buffer object to set the matrix once for all objects that are rendered - set_shader_uniform(line_3d_shader, "model", model_to_world_matrix); + set_shader_uniform(line_3d_shader, "model", model_matrix); set_shader_uniform(line_3d_shader, "view", world_to_camera_matrix); set_shader_uniform(line_3d_shader, "projection", projection_matrix); @@ -462,7 +423,7 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode unsigned int line_3d_shader = get_line_3d_shader(&context->asset_context); glUseProgram(line_3d_shader); - const Toucan::Matrix4f model_matrix = model_to_world_matrix * Toucan::ScaledTransform3Df( + const Toucan::Matrix4f model_matrix = model_to_world_matrix * orientation_and_handedness_matrix * Toucan::ScaledTransform3Df( Quaternionf::Identity(), Toucan::Vector3f::Zero(), Toucan::Vector3f::Ones()*element_3d.axis_3d_metadata.settings.size ).transformation_matrix(); @@ -516,7 +477,7 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode glUseProgram(point_3d_shader); // TODO(Matias): Use uniform buffer object to set the matrix once for all objects that are rendered - const Toucan::Matrix4f model_matrix = model_to_world_matrix * element_3d.point_3d_metadata.settings.scaled_transform.transformation_matrix(); + const Toucan::Matrix4f model_matrix = model_to_world_matrix * orientation_and_handedness_matrix * element_3d.point_3d_metadata.settings.scaled_transform.transformation_matrix(); set_shader_uniform(point_3d_shader, "model", model_matrix); set_shader_uniform(point_3d_shader, "view", world_to_camera_matrix); set_shader_uniform(point_3d_shader, "projection", projection_matrix); @@ -555,7 +516,7 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode glUseProgram(line_3d_shader); // TODO(Matias): Use uniform buffer object to set the matrix once for all objects that are rendered - const Toucan::Matrix4f model_matrix = model_to_world_matrix * element_3d.line_3d_metadata.settings.scaled_transform.transformation_matrix(); + const Toucan::Matrix4f model_matrix = model_to_world_matrix * orientation_and_handedness_matrix * element_3d.line_3d_metadata.settings.scaled_transform.transformation_matrix(); set_shader_uniform(line_3d_shader, "model", model_matrix); set_shader_uniform(line_3d_shader, "view", world_to_camera_matrix); set_shader_uniform(line_3d_shader, "projection", projection_matrix); @@ -610,7 +571,7 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode case PrimitiveType::Cylinder: { geometry_handles_ptr = get_cylinder_handles_ptr(&context->asset_context); } break; } - const Toucan::Matrix4f model_matrix = model_to_world_matrix * primitive.scaled_transform.transformation_matrix(); + const Toucan::Matrix4f model_matrix = model_to_world_matrix * orientation_and_handedness_matrix * primitive.scaled_transform.transformation_matrix() * orientation_and_handedness_matrix.transpose(); set_shader_uniform(mesh_3d_shader, "model", model_matrix); set_shader_uniform(mesh_3d_shader, "color", primitive.color); @@ -627,3 +588,86 @@ void Toucan::draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& mode } } +void Toucan::draw_axis_gizmo_3d(const Toucan::RigidTransform3Df& camera_transform, const Toucan::Vector2i& framebuffer_size, const Toucan::Matrix4f& orientation_and_handedness_matrix, Toucan::ToucanContext* context) { + // TODO(Matias): Make these user editable + constexpr float gizmo_size_fraction = 0.15f; + constexpr int gizmo_max_absolute_size = 180; + constexpr int gizmo_min_absolute_size = 75; + + const int width = static_cast(framebuffer_size.x()*gizmo_size_fraction); + const int height = static_cast(framebuffer_size.y()*gizmo_size_fraction); + + int size = std::min(width, height); + size = std::min(size, gizmo_max_absolute_size); + size = std::max(size, gizmo_min_absolute_size); + + glClear(GL_DEPTH_BUFFER_BIT); + + // TODO(Matias): Make gizmo corner user editable + glViewport(0, 0, size, size); + + unsigned int mesh_3d_shader = get_mesh_3d_shader(&context->asset_context); + glUseProgram(mesh_3d_shader); + + const Toucan::Matrix4f world_to_camera_matrix = camera_transform.transformation_matrix(); + const Toucan::Matrix4f projection_matrix = create_3d_projection_matrix(0.01f, 150.0f, 4.0f*size, Toucan::Vector2i(size, size)); + + set_shader_uniform(mesh_3d_shader, "view", world_to_camera_matrix); + set_shader_uniform(mesh_3d_shader, "projection", projection_matrix); + set_shader_uniform(mesh_3d_shader, "light_vector", Toucan::Vector3f(1.0f, 1.5f, 1.8f).normalized()); + + { + set_shader_uniform(mesh_3d_shader, "color", Toucan::Color(0.8f, 0.8f, 0.8f)); + const Toucan::Matrix4f model_matrix = Toucan::ScaledTransform3Df(Quaternionf::Identity(), Vector3f::Zero(), Vector3f::Ones()).transformation_matrix() * orientation_and_handedness_matrix; + set_shader_uniform(mesh_3d_shader, "model", model_matrix); + + const IndexedGeometryHandles* geometry_handles_ptr = get_cube_handles_ptr(&context->asset_context); + glBindVertexArray(geometry_handles_ptr->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometry_handles_ptr->ebo); + glDrawElements(GL_TRIANGLES, geometry_handles_ptr->number_of_indices, GL_UNSIGNED_INT, nullptr); + } + + { // X-axis + set_shader_uniform(mesh_3d_shader, "color", Toucan::Color::Red()); + const Toucan::Matrix4f model_matrix = orientation_and_handedness_matrix * Toucan::ScaledTransform3Df( + Quaternionf(Vector3f::UnitY(), M_PI/2), + Vector3f(5.5f, 0.0f, 0.0f), + Vector3f(0.8f, 0.8f, 10.0f)).transformation_matrix(); + set_shader_uniform(mesh_3d_shader, "model", model_matrix); + + const IndexedGeometryHandles* geometry_handles_ptr = get_cylinder_handles_ptr(&context->asset_context); + glBindVertexArray(geometry_handles_ptr->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometry_handles_ptr->ebo); + glDrawElements(GL_TRIANGLES, geometry_handles_ptr->number_of_indices, GL_UNSIGNED_INT, nullptr); + } + + { // Y-axis + set_shader_uniform(mesh_3d_shader, "color", Toucan::Color::Green()); + const Toucan::Matrix4f model_matrix = orientation_and_handedness_matrix * Toucan::ScaledTransform3Df( + Quaternionf(Vector3f::UnitX(), M_PI/2), + Vector3f(0.0f, 5.5f, 0.0f), + Vector3f(0.8f, 0.8f, 10.0f)).transformation_matrix(); + set_shader_uniform(mesh_3d_shader, "model", model_matrix); + + const IndexedGeometryHandles* geometry_handles_ptr = get_cylinder_handles_ptr(&context->asset_context); + glBindVertexArray(geometry_handles_ptr->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometry_handles_ptr->ebo); + glDrawElements(GL_TRIANGLES, geometry_handles_ptr->number_of_indices, GL_UNSIGNED_INT, nullptr); + } + + { // Z-axis + set_shader_uniform(mesh_3d_shader, "color", Toucan::Color::Blue()); + const Toucan::Matrix4f model_matrix = orientation_and_handedness_matrix * Toucan::ScaledTransform3Df( + Quaternionf(Vector3f::UnitZ(), M_PI/2), + Vector3f(0.0f, 0.0f, 5.5f), + Vector3f(0.8f, 0.8f, 10.0f)).transformation_matrix(); + set_shader_uniform(mesh_3d_shader, "model", model_matrix); + + const IndexedGeometryHandles* geometry_handles_ptr = get_cylinder_handles_ptr(&context->asset_context); + glBindVertexArray(geometry_handles_ptr->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometry_handles_ptr->ebo); + glDrawElements(GL_TRIANGLES, geometry_handles_ptr->number_of_indices, GL_UNSIGNED_INT, nullptr); + } + + glViewport(0, 0, framebuffer_size.x(), framebuffer_size.y()); +} diff --git a/src/render.h b/src/render.h index eb3a8cd..271c1f4 100644 --- a/src/render.h +++ b/src/render.h @@ -8,6 +8,9 @@ bool update_framebuffer_2d(Figure2D& figure_2d, Toucan::Vector2i size); void draw_element_2d(Element2D& element_2d, const Matrix4f& model_to_world_matrix, const Matrix4f& world_to_camera_matrix, ToucanContext* context); bool update_framebuffer_3d(Figure3D& figure_3d, Toucan::Vector2i size); -void draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& model_to_world_matrix, const Matrix4f& world_to_camera_matrix, const Matrix4f& projection_matrix, Toucan::ToucanContext* context); +void draw_element_3d(Toucan::Element3D& element_3d, const Matrix4f& model_to_world_matrix, const Matrix4f& orientation_and_handedness_matrix, const Matrix4f& world_to_camera_matrix, const Matrix4f& projection_matrix, + Toucan::ToucanContext* context); + +void draw_axis_gizmo_3d(const Toucan::RigidTransform3Df& camera_transform, const Toucan::Vector2i& framebuffer_size, const Toucan::Matrix4f& orientation_and_handedness_matrix, Toucan::ToucanContext* context); } // namespace Toucan diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2051035..15968a7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,32 +1,2 @@ -add_subdirectory(Catch2) - -set( - Toucan_test_source - tests.cpp - LinAlg_test.cpp -) - -add_executable(Toucan_test ${Toucan_test_source}) - -target_include_directories( - Toucan_test PRIVATE - ../src ../include) - -target_compile_features( - Toucan_test PRIVATE - cxx_std_17 -) - -target_compile_options( - Toucan_test PRIVATE - -Wall -Wextra -Wpedantic -Werror -) - -include(CTest) - -find_package(Eigen3 REQUIRED NO_MODULE) -target_link_libraries( - Toucan_test PRIVATE - Eigen3::Eigen - Catch2::Catch2 -) +add_subdirectory(example-tests) +add_subdirectory(unit-tests) diff --git a/test/example-tests/CMakeLists.txt b/test/example-tests/CMakeLists.txt new file mode 100644 index 0000000..7f9d7bd --- /dev/null +++ b/test/example-tests/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(Image2D-Example) +add_subdirectory(OrientationHandedness-Example) diff --git a/test/example-tests/Image2D-Example/CMakeLists.txt b/test/example-tests/Image2D-Example/CMakeLists.txt new file mode 100644 index 0000000..01fc433 --- /dev/null +++ b/test/example-tests/Image2D-Example/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) +project(image2d-example) + +add_executable( + image2d-example + main.cpp +) + +set_target_properties( + image2d-example PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) + +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) + +target_link_libraries( + image2d-example + PRIVATE Toucan::Toucan +) diff --git a/examples/Image2D-Example/main.cpp b/test/example-tests/Image2D-Example/main.cpp similarity index 100% rename from examples/Image2D-Example/main.cpp rename to test/example-tests/Image2D-Example/main.cpp diff --git a/test/example-tests/OrientationHandedness-Example/CMakeLists.txt b/test/example-tests/OrientationHandedness-Example/CMakeLists.txt new file mode 100644 index 0000000..6bbe62d --- /dev/null +++ b/test/example-tests/OrientationHandedness-Example/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) +project(orientation-handedness-example) + +add_executable( + orientation-handedness-example + main.cpp +) + +set_target_properties( + orientation-handedness-example PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) + +# This if check is used to work better with a local copy of Toucan. +# In a regular project the 'if' can be omitted, as the 'find_package' command should always be called. +if (NOT Toucan_FOUND) + find_package(Toucan REQUIRED) +endif (NOT Toucan_FOUND) + +target_link_libraries( + orientation-handedness-example + PRIVATE Toucan::Toucan +) diff --git a/test/example-tests/OrientationHandedness-Example/main.cpp b/test/example-tests/OrientationHandedness-Example/main.cpp new file mode 100644 index 0000000..be1df6f --- /dev/null +++ b/test/example-tests/OrientationHandedness-Example/main.cpp @@ -0,0 +1,125 @@ +#include + +#include + +void draw_figure3d(const std::string& figure_name, const Toucan::Figure3DSettings& settings) { + std::array primitive_array = { + Toucan::Primitive3D( + Toucan::PrimitiveType::Cylinder, + Toucan::ScaledTransform3Df(Toucan::Quaternionf::Identity(), 2.0f*Toucan::Vector3f::UnitX()), + Toucan::Color::Red() + ), + Toucan::Primitive3D( + Toucan::PrimitiveType::Cube, + Toucan::ScaledTransform3Df(Toucan::Quaternionf::Identity(), 2.0f*Toucan::Vector3f::UnitY()), + Toucan::Color::Green() + ), + Toucan::Primitive3D( + Toucan::PrimitiveType::Sphere, + Toucan::ScaledTransform3Df(Toucan::Quaternionf::Identity(), 2.0f*Toucan::Vector3f::UnitZ()), + Toucan::Color::Blue() + ), + }; + + Toucan::BeginFigure3D(figure_name, settings); + { + Toucan::ShowPrimitives3D("Primitives", primitive_array); + } + Toucan::EndFigure3D(); +} + +int main() { + + Toucan::Initialize(); + + // X + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::X_UP; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("X up:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::X_UP; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("X up:L", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::X_DOWN; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("X down:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::X_DOWN; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("X down:L", settings); + } + + // Y + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Y_UP; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("Y up:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Y_UP; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("Y up:L", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Y_DOWN; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("Y down:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Y_DOWN; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("Y down:L", settings); + } + + // Z + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Z_UP; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("Z up:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Z_UP; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("Z up:L", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Z_DOWN; + settings.handedness = Toucan::Handedness::RIGHT_HANDED; + draw_figure3d("Z down:R", settings); + } + + { + Toucan::Figure3DSettings settings; + settings.orientation = Toucan::Orientation::Z_DOWN; + settings.handedness = Toucan::Handedness::LEFT_HANDED; + draw_figure3d("Z down:L", settings); + } + + Toucan::SleepUntilWindowClosed(); + + Toucan::Destroy(); +} diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt new file mode 100644 index 0000000..2051035 --- /dev/null +++ b/test/unit-tests/CMakeLists.txt @@ -0,0 +1,32 @@ +add_subdirectory(Catch2) + +set( + Toucan_test_source + tests.cpp + LinAlg_test.cpp +) + +add_executable(Toucan_test ${Toucan_test_source}) + +target_include_directories( + Toucan_test PRIVATE + ../src ../include) + +target_compile_features( + Toucan_test PRIVATE + cxx_std_17 +) + +target_compile_options( + Toucan_test PRIVATE + -Wall -Wextra -Wpedantic -Werror +) + +include(CTest) + +find_package(Eigen3 REQUIRED NO_MODULE) +target_link_libraries( + Toucan_test PRIVATE + Eigen3::Eigen + Catch2::Catch2 +) diff --git a/test/Catch2 b/test/unit-tests/Catch2 similarity index 100% rename from test/Catch2 rename to test/unit-tests/Catch2 diff --git a/test/LinAlg_test.cpp b/test/unit-tests/LinAlg_test.cpp similarity index 100% rename from test/LinAlg_test.cpp rename to test/unit-tests/LinAlg_test.cpp diff --git a/test/tests.cpp b/test/unit-tests/tests.cpp similarity index 100% rename from test/tests.cpp rename to test/unit-tests/tests.cpp