Kaynağa Gözat

Implemented augment render.

jcsyshc 2 yıl önce
ebeveyn
işleme
a7f584ae0c

+ 15 - 2
CMakeLists.txt

@@ -108,8 +108,21 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${Boost_INCLUDE_DIRS})
 target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
 
 # VTK config
-set(VTK_DIR /home/tpx/src/VTK-9.3.0/Build/lib/cmake/vtk-9.3)
 find_package(VTK REQUIRED)
 target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
 vtk_module_autoinit(TARGETS ${PROJECT_NAME} MODULES ${VTK_LIBRARIES})
-target_sources(${PROJECT_NAME} PRIVATE src/vtk_viewer.cpp)
+target_sources(${PROJECT_NAME} PRIVATE src/vtk_viewer.cpp)
+
+# Eigen3 config
+find_package(Eigen3 REQUIRED)
+target_link_libraries(${PROJECT_NAME} Eigen3::Eigen)
+
+# JSON config
+find_package(nlohmann_json REQUIRED)
+target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json)
+
+# Sophiar2 config
+set(Sophiar2DIR /home/tpx/project/Sophiar2)
+add_subdirectory(${Sophiar2DIR}/src Sophiar2Lib)
+target_include_directories(${PROJECT_NAME} PRIVATE ${Sophiar2DIR}/src)
+target_link_libraries(${PROJECT_NAME} Sophiar2Lib)

+ 21 - 2
data/config.yaml

@@ -2,6 +2,13 @@ camera:
   names:
     left: LeftEye
     right: RightEye
+  remap:
+    width: 2491
+    height: 2077
+    angle: 32.351172
+    data:
+      left: ./left_proj.dat
+      right: ./right_proj.dat
   capture:
     frame_rate: 40
     expo_time_ms: 12
@@ -18,5 +25,17 @@ output:
 
 sender:
   mtu: 1400
-  port: 5277
-  parity: 0.2
+  port: 5279
+  parity: 0.2
+
+sophiar:
+  config: ./sophiar_config.json
+  left_camera_trans_var: left_camera_in_tracker
+  right_camera_trans_var: right_camera_in_tracker
+  augment:
+    - name: Femur
+      trans_var: femur_in_tracker
+      stl_file: /home/tpx/project/RemoteAR3/data/femur.stl
+    - name: Tibia
+      trans_var: tibia_in_tracker
+      stl_file: /home/tpx/project/RemoteAR3/data/tibia.stl

+ 606 - 0
data/sophiar_config.json

@@ -0,0 +1,606 @@
+{
+  "variable_list": [
+    {
+      "name": "probe_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "femur_ref_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "femur_in_femur_ref",
+      "type": "transform_obj",
+      "value": [
+        49.7073,
+        -72.6173,
+        -9.09289,
+        0.440928,
+        0.542853,
+        0.490098,
+        0.520286
+      ]
+    },
+    {
+      "name": "tibia_ref_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "tibia_in_tibia_ref",
+      "type": "transform_obj",
+      "value": [
+        -3.91566,
+        -85.3472,
+        -42.0667,
+        0.360607,
+        0.661265,
+        -0.283426,
+        -0.5936
+      ]
+    },
+    {
+      "name": "camera_ref_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "left_camera_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "right_camera_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "femur_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "tibia_in_tracker",
+      "type": "transform_obj"
+    },
+    {
+      "name": "probe_in_femur_ref",
+      "type": "transform_obj"
+    },
+    {
+      "name": "probe_in_tibia_ref",
+      "type": "transform_obj"
+    },
+    {
+      "name": "probe_in_femur",
+      "type": "transform_obj"
+    },
+    {
+      "name": "probe_in_tibia",
+      "type": "transform_obj"
+    },
+    {
+      "name": "probe_tip_in_femur_ref",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "probe_tip_in_tibia_ref",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "picked_point_in_femur_ref",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "picked_point_in_tibia_ref",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "femur_in_femur_ref_error",
+      "type": "double_obj"
+    },
+    {
+      "name": "tibia_in_tibia_ref_error",
+      "type": "double_obj"
+    },
+    {
+      "name": "probe_tip_in_femur",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "probe_tip_in_tibia",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "picked_point_in_femur",
+      "type": "scalarxyz_obj"
+    },
+    {
+      "name": "picked_point_in_tibia",
+      "type": "scalarxyz_obj"
+    }
+  ],
+  "object_list": [
+    {
+      "type": "transform_tree",
+      "name": "transform_tree",
+      "init_config": {
+        "node_list": [
+          {
+            "name": "tracker"
+          },
+          {
+            "name": "probe_ref",
+            "parent": "tracker",
+            "transform_var_name": "probe_in_tracker"
+          },
+          {
+            "name": "probe",
+            "parent": "probe_ref",
+            "transform": [
+              -0.23,
+              -13.98,
+              -119.65,
+              1,
+              0,
+              0,
+              0
+            ]
+          },
+          {
+            "name": "femur_ref",
+            "parent": "tracker",
+            "transform_var_name": "femur_ref_in_tracker"
+          },
+          {
+            "name": "femur",
+            "parent": "femur_ref",
+            "transform_var_name": "femur_in_femur_ref"
+          },
+          {
+            "name": "tibia_ref",
+            "parent": "tracker",
+            "transform_var_name": "tibia_ref_in_tracker"
+          },
+          {
+            "name": "tibia",
+            "parent": "tibia_ref",
+            "transform_var_name": "tibia_in_tibia_ref"
+          },
+          {
+            "name": "camera_ref",
+            "parent": "tracker",
+            "transform_var_name": "camera_ref_in_tracker"
+          },
+          {
+            "name": "left_camera",
+            "parent": "camera_ref",
+            "transform": [
+              91.0962,
+              35.5978,
+              -25.9658,
+              0.6130,
+              -0.3530,
+              0.6086,
+              0.3595
+            ]
+          },
+          {
+            "name": "right_camera",
+            "parent": "camera_ref",
+            "transform": [
+              90.1815,
+              35.1063,
+              -91.4749,
+              0.6089,
+              -0.3537,
+              0.6133,
+              0.3577
+            ]
+          }
+        ]
+      },
+      "start_config": {
+        "watch_list": [
+          {
+            "target": "left_camera",
+            "observer": "tracker",
+            "transform_var_name": "left_camera_in_tracker"
+          },
+          {
+            "target": "right_camera",
+            "observer": "tracker",
+            "transform_var_name": "right_camera_in_tracker"
+          },
+          {
+            "target": "femur",
+            "observer": "tracker",
+            "transform_var_name": "femur_in_tracker"
+          },
+          {
+            "target": "tibia",
+            "observer": "tracker",
+            "transform_var_name": "tibia_in_tracker"
+          },
+          {
+            "target": "probe",
+            "observer": "femur_ref",
+            "transform_var_name": "probe_in_femur_ref"
+          },
+          {
+            "target": "probe",
+            "observer": "tibia_ref",
+            "transform_var_name": "probe_in_tibia_ref"
+          },
+          {
+            "target": "probe",
+            "observer": "femur",
+            "transform_var_name": "probe_in_femur"
+          },
+          {
+            "target": "probe",
+            "observer": "tibia",
+            "transform_var_name": "probe_in_tibia"
+          }
+        ]
+      }
+    },
+    {
+      "type": "ndi_interface",
+      "name": "ndi",
+      "init_config": {
+        "address_type": "ethernet",
+        "ip": "10.0.0.5",
+        "tcp_port": 8765,
+        "com_port": "/dev/ttyUSB0",
+        "tool_list": [
+          {
+            "rom_path": "/home/tpx/data/roms/GlassProbe_4Ball_4.rom",
+            "outputs": {
+              "transform": "probe_in_tracker"
+            }
+          },
+          {
+            "rom_path": "/home/tpx/data/roms/Glass_4Ball_1.rom",
+            "outputs": {
+              "transform": "camera_ref_in_tracker"
+            }
+          },
+          {
+            "rom_path": "/home/tpx/data/roms/Glass_3Ball_6.rom",
+            "outputs": {
+              "transform": "femur_ref_in_tracker"
+            }
+          },
+          {
+            "rom_path": "/home/tpx/data/roms/Glass_3Ball_5.rom",
+            "outputs": {
+              "transform": "tibia_ref_in_tracker"
+            }
+          }
+        ]
+      },
+      "start_config": {
+        "allow_unreliable": true,
+        "prefer_stream_tracking": false
+      }
+    },
+    {
+      "type": "object_validity_watcher",
+      "name": "probe_visibility_watcher",
+      "start_config": {
+        "variable_name": "probe_in_tracker"
+      }
+    },
+    {
+      "type": "transform_obj_validity_watcher",
+      "name": "camera_visibility_watcher",
+      "start_config": {
+        "variable_name": "camera_ref_in_tracker"
+      }
+    },
+    {
+      "type": "transform_obj_validity_watcher",
+      "name": "femur_visibility_watcher",
+      "start_config": {
+        "variable_name": "femur_ref_in_tracker"
+      }
+    },
+    {
+      "type": "transform_obj_validity_watcher",
+      "name": "tibia_visibility_watcher",
+      "start_config": {
+        "variable_name": "tibia_ref_in_tracker"
+      }
+    },
+    {
+      "type": "scalarxyz_transformer",
+      "name": "probe_tip_in_femur_ref_transformer",
+      "start_config": {
+        "transform_type": "point",
+        "transform_var_name": "probe_in_femur_ref",
+        "target_value": [
+          0,
+          0,
+          0
+        ],
+        "output_var_name": "probe_tip_in_femur_ref"
+      },
+      "dependencies": [
+        "ndi",
+        "transform_tree"
+      ]
+    },
+    {
+      "type": "scalarxyz_transformer",
+      "name": "probe_tip_in_tibia_ref_transformer",
+      "start_config": {
+        "transform_type": "point",
+        "transform_var_name": "probe_in_tibia_ref",
+        "target_value": [
+          0,
+          0,
+          0
+        ],
+        "output_var_name": "probe_tip_in_tibia_ref"
+      },
+      "dependencies": [
+        "ndi",
+        "transform_tree"
+      ]
+    },
+    {
+      "type": "transform_stabilizer",
+      "name": "point_picker_in_femur_ref",
+      "start_config": {
+        "stable_type": "point",
+        "input_var_name": "probe_tip_in_femur_ref",
+        "output_var_name": "picked_point_in_femur_ref",
+        "linear_tolerance_mm": 0.05,
+        "temporal_interval_s": 3
+      },
+      "dependencies": [
+        "probe_tip_in_femur_ref_transformer"
+      ]
+    },
+    {
+      "type": "transform_stabilizer",
+      "name": "point_picker_in_tibia_ref",
+      "start_config": {
+        "stable_type": "point",
+        "input_var_name": "probe_tip_in_tibia_ref",
+        "output_var_name": "picked_point_in_tibia_ref",
+        "linear_tolerance_mm": 0.05,
+        "temporal_interval_s": 3
+      },
+      "dependencies": [
+        "probe_tip_in_tibia_ref_transformer"
+      ]
+    },
+    {
+      "type": "scalarxyz_obj_watcher",
+      "name": "picked_point_watcher_for_femur_ref",
+      "start_config": {
+        "variable_name": "picked_point_in_femur_ref"
+      }
+    },
+    {
+      "type": "scalarxyz_obj_watcher",
+      "name": "picked_point_watcher_for_tibia_ref",
+      "start_config": {
+        "variable_name": "picked_point_in_tibia_ref"
+      }
+    },
+    {
+      "type": "landmark_registration",
+      "name": "femur_landmark",
+      "start_config": {
+        "fiducial_points": [
+          [
+            1.68412,
+            22.5766,
+            202.831
+          ],
+          [
+            13.5083,
+            -24.1831,
+            200.386
+          ],
+          [
+            26.621,
+            2.0151,
+            191.341
+          ]
+        ],
+        "point_var_name": "picked_point_in_femur_ref",
+        "transform_var_name": "femur_in_femur_ref",
+        "error_var_name": "femur_in_femur_ref_error"
+      },
+      "dependencies": [
+        "point_picker_in_femur_ref"
+      ]
+    },
+    {
+      "type": "landmark_registration",
+      "name": "tibia_landmark",
+      "start_config": {
+        "fiducial_points": [
+          [
+            47.351,
+            46.6799,
+            -70.5804
+          ],
+          [
+            -45.3668,
+            23.3452,
+            1.94304
+          ],
+          [
+            -55.3382,
+            -4.55638,
+            54.7446
+          ]
+        ],
+        "point_var_name": "picked_point_in_tibia_ref",
+        "transform_var_name": "tibia_in_tibia_ref",
+        "error_var_name": "tibia_in_tibia_ref_error"
+      },
+      "dependencies": [
+        "point_picker_in_tibia_ref"
+      ]
+    },
+    {
+      "type": "transform_obj_watcher",
+      "name": "femur_registration_result_watcher",
+      "start_config": {
+        "variable_name": "femur_in_femur_ref"
+      }
+    },
+    {
+      "type": "transform_obj_watcher",
+      "name": "tibia_registration_result_watcher",
+      "start_config": {
+        "variable_name": "tibia_in_tibia_ref"
+      }
+    },
+    {
+      "type": "double_obj_watcher",
+      "name": "femur_registration_error_watcher",
+      "start_config": {
+        "variable_name": "femur_in_femur_ref_error"
+      }
+    },
+    {
+      "type": "double_obj_watcher",
+      "name": "tibia_registration_error_watcher",
+      "start_config": {
+        "variable_name": "tibia_in_tibia_ref_error"
+      }
+    },
+    {
+      "type": "scalarxyz_transformer",
+      "name": "probe_tip_in_femur_transformer",
+      "start_config": {
+        "transform_type": "point",
+        "transform_var_name": "probe_in_femur",
+        "target_value": [
+          0,
+          0,
+          0
+        ],
+        "output_var_name": "probe_tip_in_femur"
+      },
+      "dependencies": [
+        "ndi",
+        "transform_tree"
+      ]
+    },
+    {
+      "type": "scalarxyz_transformer",
+      "name": "probe_tip_in_tibia_transformer",
+      "start_config": {
+        "transform_type": "point",
+        "transform_var_name": "probe_in_tibia",
+        "target_value": [
+          0,
+          0,
+          0
+        ],
+        "output_var_name": "probe_tip_in_tibia"
+      },
+      "dependencies": [
+        "ndi",
+        "transform_tree"
+      ]
+    },
+    {
+      "type": "transform_stabilizer",
+      "name": "point_picker_in_femur",
+      "start_config": {
+        "stable_type": "point",
+        "input_var_name": "probe_tip_in_femur",
+        "output_var_name": "picked_point_in_femur",
+        "linear_tolerance_mm": 0.05,
+        "temporal_interval_s": 3
+      },
+      "dependencies": [
+        "probe_tip_in_femur_transformer"
+      ]
+    },
+    {
+      "type": "transform_stabilizer",
+      "name": "point_picker_in_tibia",
+      "start_config": {
+        "stable_type": "point",
+        "input_var_name": "probe_tip_in_tibia",
+        "output_var_name": "picked_point_in_tibia",
+        "linear_tolerance_mm": 0.05,
+        "temporal_interval_s": 3
+      },
+      "dependencies": [
+        "probe_tip_in_tibia_transformer"
+      ]
+    },
+    {
+      "type": "scalarxyz_obj_watcher",
+      "name": "picked_point_watcher_for_femur",
+      "start_config": {
+        "variable_name": "picked_point_in_femur"
+      }
+    },
+    {
+      "type": "scalarxyz_obj_watcher",
+      "name": "picked_point_watcher_for_tibia",
+      "start_config": {
+        "variable_name": "picked_point_in_tibia"
+      }
+    },
+    {
+      "type": "empty_object",
+      "name": "tracker_all",
+      "dependencies": [
+        "transform_tree",
+        "ndi",
+        "probe_visibility_watcher",
+        "camera_visibility_watcher",
+        "femur_visibility_watcher",
+        "tibia_visibility_watcher"
+      ]
+    },
+    {
+      "type": "empty_object",
+      "name": "femur_landmark_all",
+      "dependencies": [
+        "tracker_all",
+        "femur_landmark",
+        "picked_point_watcher_for_femur_ref",
+        "femur_registration_result_watcher",
+        "femur_registration_error_watcher"
+      ]
+    },
+    {
+      "type": "empty_object",
+      "name": "tibia_landmark_all",
+      "dependencies": [
+        "tracker_all",
+        "tibia_landmark",
+        "picked_point_watcher_for_tibia_ref",
+        "tibia_registration_result_watcher",
+        "tibia_registration_error_watcher"
+      ]
+    },
+    {
+      "type": "empty_object",
+      "name": "femur_icp_all",
+      "dependencies": [
+        "tracker_all",
+        "point_picker_in_femur",
+        "picked_point_watcher_for_femur"
+      ]
+    },
+    {
+      "type": "empty_object",
+      "name": "tibia_icp_all",
+      "dependencies": [
+        "tracker_all",
+        "point_picker_in_tibia",
+        "picked_point_watcher_for_tibia"
+      ]
+    }
+  ]
+}

+ 1 - 1
src/frame_sender.cpp

@@ -1,7 +1,7 @@
 #include "frame_sender.h"
+#include "core/timestamp_helper.hpp"
 #include "simple_mq.h"
 #include "third_party/scope_guard.hpp"
-#include "third_party/timestamp_helper.hpp"
 #include "utility.hpp"
 #include "variable_defs.h"
 

+ 2 - 0
src/main.cpp

@@ -1,4 +1,5 @@
 #include "simple_mq.h"
+#include "utility.hpp"
 #include "variable_defs.h"
 
 #include <spdlog/spdlog.h>
@@ -51,6 +52,7 @@ int main() {
 
     while (!glfwWindowShouldClose(main_window)) {
 
+        RESET_TIMER;
         prepare_imgui_frame();
         handle_imgui_events();
 

+ 267 - 56
src/main_ext.cpp

@@ -1,10 +1,13 @@
+#include "core/local_connection.h"
+#include "core/timestamp_helper.hpp"
 #include "cuda_helper.hpp"
 #include "frame_sender.h"
 #include "image_process.h"
 #include "mvs_camera.h"
 #include "simple_mq.h"
 #include "simple_opengl.h"
-#include "third_party/timestamp_helper.hpp"
+#include "third_party/scope_guard.hpp"
+#include "utility.hpp"
 #include "variable_defs.h"
 #include "video_encoder.h"
 #include "vtk_viewer.h"
@@ -19,19 +22,25 @@
 #include <opencv2/core/mat.hpp>
 #include <opencv2/core/cuda.hpp>
 
+#include <vtkProperty.h>
+
 #include <imgui.h>
 #include <imgui_impl_glfw.h>
 #include <imgui_impl_opengl3.h>
 
+#include <boost/iostreams/device/mapped_file.hpp>
+
 #include <cassert>
 #include <thread>
 #include <queue>
+#include <vector>
 
 using namespace simple_mq_singleton;
 using namespace sophiar;
 
-// make sophiar happy
-local_time_type sophiar::program_start_time;
+using boost::iostreams::mapped_file;
+
+log_timer global_timer;
 
 // global variable definition
 CUcontext cuda_ctx = nullptr;
@@ -41,14 +50,13 @@ std::string left_camera_name, right_camera_name;
 std::unique_ptr<mvs::camera> left_camera, right_camera;
 mvs::capture_config capture_conf = {};
 int preview_camera_index = 0; // 0 for left, 1 for right
-uint64_t left_raw_cnt = 0, right_raw_cnt = 0;
-std::unique_ptr<cv::cuda::GpuMat> left_img_dev, right_img_dev;
-std::unique_ptr<cv::cuda::Stream> left_stream, right_stream;
-cudaStream_t left_cuda_stream = nullptr, right_cuda_stream = nullptr;
-std::unique_ptr<monocular_processor> left_processor, right_processor;
 std::unique_ptr<simple_render> opengl_render;
 float process_frame_rate = 0;
 bool enhance_image = false;
+bool augment_enable = false;
+cv::Size augment_render_size;
+double augment_render_angle = 0;
+std::unique_ptr<vtk_viewer> augment_viewer;
 
 std::unique_ptr<std::thread> encoder_thread;
 bool output_full_frame = false;
@@ -64,8 +72,89 @@ int sender_listen_port;
 sender_config main_sender_conf;
 std::unique_ptr<frame_sender> sender;
 
+std::string sophiar_config_path;
+std::unique_ptr<std::thread> sophiar_thread;
+local_connection sophiar_conn;
+bool last_command_success = true;
+
+bool show_vtk_debug = false;
+bool show_imgui_demo = false;
+std::unique_ptr<vtk_viewer> vtk_test1, vtk_test2;
+
+struct augment_store_type {
+    std::string name, trans_var;
+    vtkSmartPointer<vtkActor> actor;
+    int opacity = 100;
+};
+
+std::vector<augment_store_type> augment_items;
+
 std::queue<void (*)()> simple_eq;
 
+struct camera_related {
+    uint64_t raw_cnt = 0;
+    std::unique_ptr<cv::cuda::GpuMat> img_dev;
+    std::unique_ptr<cv::cuda::Stream> stream;
+    cudaStream_t cuda_stream = nullptr;
+    std::unique_ptr<monocular_processor> processor;
+    std::string trans_var;
+
+    // remap related
+    std::string remap_data_path;
+    std::unique_ptr<smart_texture> remap_tex;
+    bool augment_available = false;
+    std::unique_ptr<smart_texture> augment_tex;
+
+    void wait_frame(simple_mq::index_type index) const {
+        uint64_t cur_cnt;
+        if (auto ptr = mq().query_variable_ptr<cv::Mat>(index, &cur_cnt);
+                ptr == nullptr || cur_cnt <= raw_cnt) {
+            mq().wait_variable(index, raw_cnt);
+        }
+    }
+
+    void process_frame(simple_mq::index_type index) {
+        uint64_t cur_cnt;
+        auto raw_ptr = mq().query_variable_ptr<cv::Mat>(index, &cur_cnt);
+        assert(cur_cnt > raw_cnt);
+        raw_cnt = cur_cnt;
+        processor->process(*raw_ptr, img_dev.get(), enhance_image, *stream);
+
+        if (augment_enable) {
+            auto trans = sophiar_conn.query_transform_variable(trans_var);
+            augment_available = trans.has_value();
+            if (augment_available) {
+                augment_viewer->set_camera_pose(trans.value());
+                augment_viewer->render(augment_render_size);
+
+                // copy rendered image
+                augment_tex->create(GL_RGBA8, img_dev->size());
+                glCopyImageSubData(augment_viewer->get_tex(), GL_TEXTURE_2D, 0, 0, 0, 0,
+                                   augment_tex->id, GL_TEXTURE_2D, 0, 0, 0, 0, img_dev->cols, img_dev->rows, 1);
+            }
+        }
+    }
+
+    void render(const simple_rect &rect) {
+        assert(img_dev != nullptr);
+        opengl_render->render_rect(*img_dev, rect, false, cuda_stream);
+        if (augment_available) {
+            // create remap file if needed
+            if (remap_tex == nullptr) [[unlikely]] {
+                auto remap_file = mapped_file{remap_data_path, mapped_file::readonly};
+                auto size = img_dev->size();
+                assert(remap_file.size() == size.area() * CV_ELEM_SIZE(CV_32FC2));
+                auto remap_data = cv::Mat{size, CV_32FC2, (void *) remap_file.const_data()};
+                remap_tex = std::make_unique<smart_texture>();
+                upload_remap_data(remap_tex.get(), remap_data);
+            }
+            opengl_render->render_rect(augment_tex->id, rect, remap_tex->id);
+        }
+    }
+};
+
+camera_related left, right;
+
 constexpr auto config_path = "./config.yaml";
 
 void initialize_mq() {
@@ -90,14 +179,14 @@ void initialize_cuda() {
     });
 
     // create some cuda objects
-    left_img_dev = std::make_unique<cv::cuda::GpuMat>();
-    right_img_dev = std::make_unique<cv::cuda::GpuMat>();
-    left_stream = std::make_unique<cv::cuda::Stream>();
-    right_stream = std::make_unique<cv::cuda::Stream>();
-    left_cuda_stream = (cudaStream_t) left_stream->cudaPtr();
-    right_cuda_stream = (cudaStream_t) right_stream->cudaPtr();
-    left_processor = std::make_unique<monocular_processor>();
-    right_processor = std::make_unique<monocular_processor>();
+    left.img_dev = std::make_unique<cv::cuda::GpuMat>();
+    right.img_dev = std::make_unique<cv::cuda::GpuMat>();
+    left.stream = std::make_unique<cv::cuda::Stream>();
+    right.stream = std::make_unique<cv::cuda::Stream>();
+    left.cuda_stream = (cudaStream_t) left.stream->cudaPtr();
+    right.cuda_stream = (cudaStream_t) right.stream->cudaPtr();
+    left.processor = std::make_unique<monocular_processor>();
+    right.processor = std::make_unique<monocular_processor>();
     output_stream = std::make_unique<cv::cuda::Stream>();
     output_cuda_stream = (cudaStream_t) output_stream->cudaPtr();
     output_frame_dev = std::make_shared<cv::cuda::GpuMat>();
@@ -116,6 +205,13 @@ void load_config() {
     main_encoder_conf.frame_rate = capture_conf.frame_rate;
     capture_conf.expo_time_ms = capture_param["expo_time_ms"].as<float>();
     capture_conf.gain_db = capture_param["gain_db"].as<float>();
+    auto remap_conf = camera_conf["remap"];
+    augment_render_size = {remap_conf["width"].as<int>(),
+                           remap_conf["height"].as<int>()};
+    augment_render_angle = remap_conf["angle"].as<double>();
+    auto remap_data_conf = remap_conf["data"];
+    left.remap_data_path = remap_data_conf["left"].as<std::string>();
+    right.remap_data_path = remap_data_conf["right"].as<std::string>();
 
     // load main window config
     auto window_conf = conf["main_window"];
@@ -134,6 +230,20 @@ void load_config() {
     main_sender_conf.parity_rate = sender_conf["parity"].as<float>();
     sender_listen_port = sender_conf["port"].as<int>();
 
+    // load sophiar config
+    auto sophiar_conf = conf["sophiar"];
+    sophiar_config_path = sophiar_conf["config"].as<std::string>();
+    left.trans_var = sophiar_conf["left_camera_trans_var"].as<std::string>();
+    right.trans_var = sophiar_conf["right_camera_trans_var"].as<std::string>();
+
+    // load augment items
+    for (auto item: sophiar_conf["augment"]) {
+        augment_items.emplace_back(
+                item["name"].as<std::string>(),
+                item["trans_var"].as<std::string>(),
+                load_stl(item["stl_file"].as<std::string>()));
+    }
+
     // make variables exist
     mq().update_variable(SENDER_CONNECTED, false);
     mq().update_variable(REQUEST_IDR, false);
@@ -155,6 +265,7 @@ void initialize_main_window() {
                                    "RemoteAR V3.-1", nullptr, nullptr);
     assert(main_window != nullptr);
     glfwMakeContextCurrent(main_window);
+    glfwSwapInterval(0);
 
     // load opengl functions
     auto version = gladLoadGL(glfwGetProcAddress);
@@ -189,6 +300,8 @@ void initialize_main_window() {
     // initialize OpenGL objects
     opengl_render = std::make_unique<simple_render>();
     output_fbo = std::make_unique<smart_frame_buffer>();
+    left.augment_tex = std::make_unique<smart_texture>();
+    right.augment_tex = std::make_unique<smart_texture>();
 
     // elegant cleanup
     std::atexit([] {
@@ -199,6 +312,25 @@ void initialize_main_window() {
         glfwDestroyWindow(main_window);
         glfwTerminate();
     });
+
+    // start sophiar
+    assert(sophiar_thread == nullptr);
+    sophiar_thread = std::make_unique<std::thread>([=] {
+        run_sophiar(sophiar_config_path);
+    });
+
+    // initialize vtk viewers
+    augment_viewer = std::make_unique<vtk_viewer>();
+    augment_viewer->set_camera_view_angle(augment_render_angle);
+    for (auto &item: augment_items) {
+        augment_viewer->add_actor(item.actor);
+    }
+
+    // initialize vtk test viewer
+    vtk_test1 = std::make_unique<vtk_viewer>();
+    vtk_test2 = std::make_unique<vtk_viewer>();
+    vtk_test1->add_actor(load_stl("/home/tpx/data/stls/GlassProbe_4Ball_3.STL"));
+    vtk_test2->add_actor(load_stl("/home/tpx/data/stls/HeadModelDrill.stl"));
 }
 
 bool is_camera_opened() {
@@ -349,11 +481,11 @@ void upload_encoder_config() {
 
 void start_encoder() {
     if (output_full_frame) {
-        assert(!left_img_dev->empty() && !right_img_dev->empty());
-        assert(left_img_dev->size() == right_img_dev->size());
+        assert(!left.img_dev->empty() && !right.img_dev->empty());
+        assert(left.img_dev->size() == right.img_dev->size());
         main_encoder_conf.frame_size = cv::Size{
-                left_img_dev->size().width * 2,
-                left_img_dev->size().height};
+                left.img_dev->size().width * 2,
+                left.img_dev->size().height};
         output_fbo->create(main_encoder_conf.frame_size);
     } else {
         main_encoder_conf.frame_size = cv::Size{output_width, output_height};
@@ -373,6 +505,10 @@ void stop_encoder() {
     encoder_thread.reset();
 }
 
+void start_tracking() {
+    last_command_success = sophiar_conn.start_object("tracker_all");
+}
+
 void cleanup() {
     close_cameras();
     stop_encoder();
@@ -381,8 +517,14 @@ void cleanup() {
     // avoid cudaErrorCudartUnloading
     opengl_render.reset();
     output_fbo.reset();
-    left_processor.reset();
-    right_processor.reset();
+    left.processor.reset();
+    right.processor.reset();
+
+    // stop sophiar
+    assert(sophiar_thread != nullptr);
+    stop_sophiar();
+    sophiar_thread->join();
+    sophiar_thread.reset();
 }
 
 void prepare_imgui_frame() {
@@ -392,7 +534,9 @@ void prepare_imgui_frame() {
     ImGui_ImplGlfw_NewFrame();
     ImGui::NewFrame();
 
-    ImGui::ShowDemoWindow();
+    if (show_imgui_demo) {
+        ImGui::ShowDemoWindow();
+    }
 
     if (ImGui::Begin("Remote AR Control")) {
         ImGui::PushItemWidth(200);
@@ -494,6 +638,65 @@ void prepare_imgui_frame() {
             ImGui::PopID();
         }
 
+        if (ImGui::CollapsingHeader("Navigation")) {
+            ImGui::PushID("Encoder");
+
+            ImGui::SeparatorText("Actions");
+            if (ImGui::Button("Start Tracking")) {
+                simple_eq.push(start_tracking);
+            }
+
+            ImGui::SeparatorText("Infos");
+            if (last_command_success) {
+                ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4) ImColor(0, 255, 0));
+            } else {
+                ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4) ImColor(255, 0, 0));
+            }
+            ImGui::LabelText("Last Command", last_command_success ? "Success" : "Failed");
+            ImGui::PopStyleColor();
+
+            auto helper_func = [&](const std::string &var_name, const std::string &show_name, bool last = false) {
+                auto var = sophiar_conn.query_transform_variable(var_name);
+                bool var_ok = var.has_value();
+                if (var_ok) {
+                    ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4) ImColor(0, 255, 0));
+                } else {
+                    ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4) ImColor(255, 0, 0));
+                }
+                ImGui::Checkbox(show_name.c_str(), &var_ok);
+                ImGui::PopStyleColor();
+                if (!last) {
+                    ImGui::SameLine();
+                }
+            };
+//            ImGui::BeginDisabled();
+            helper_func("camera_ref_in_tracker", "Camera");
+            helper_func("probe_in_tracker", "Probe");
+            helper_func("femur_in_tracker", "Femur");
+            helper_func("tibia_in_tracker", "Tibia", true);
+//            ImGui::EndDisabled();
+
+            ImGui::PopID();
+        }
+
+        if (ImGui::CollapsingHeader("Augment Render")) {
+            ImGui::PushID("Augment");
+
+            ImGui::SeparatorText("Configs");
+            ImGui::Checkbox("Enable", &augment_enable);
+
+            if (augment_enable) {
+                for (auto &item: augment_items) {
+                    auto opacity_name = fmt::format("{} Opacity (%)", item.name);
+                    if (ImGui::DragInt(opacity_name.c_str(), &item.opacity, 1, 0, 100)) {
+                        item.actor->GetProperty()->SetOpacity(0.01 * item.opacity);
+                    }
+                }
+            }
+
+            ImGui::PopID();
+        }
+
 //        if (is_capturing() && ImGui::CollapsingHeader("Video Encoder")) {
         if (ImGui::CollapsingHeader("Video Encoder")) {
             ImGui::PushID("Encoder");
@@ -560,10 +763,28 @@ void prepare_imgui_frame() {
             ImGui::PopID();
         }
 
+        if (ImGui::CollapsingHeader("Debug")) {
+            ImGui::PushID("Debug");
+            ImGui::Checkbox("Debug VTK Viewer", &show_vtk_debug);
+            ImGui::Checkbox("Show ImGui Demo", &show_imgui_demo);
+            ImGui::PopID();
+        }
+
         ImGui::PopItemWidth();
     }
     ImGui::End();
 
+    if (show_vtk_debug) {
+        if (ImGui::Begin("VTK Test A", nullptr, vtk_viewer::no_scroll_flag)) {
+            vtk_test1->show();
+        }
+        ImGui::End();
+        if (ImGui::Begin("VTK Test B", nullptr, vtk_viewer::no_scroll_flag)) {
+            vtk_test2->show();
+        }
+        ImGui::End();
+    }
+
     ImGui::Render();
 }
 
@@ -576,30 +797,21 @@ void handle_imgui_events() {
 
 void wait_camera_frames() {
     assert(is_capturing());
-    uint64_t cur_cnt;
-    if (auto ptr = mq().query_variable_ptr<cv::Mat>(IMG_RAW_HOST_LEFT, &cur_cnt);
-            ptr == nullptr || cur_cnt <= left_raw_cnt) {
-        mq().wait_variable(IMG_RAW_HOST_LEFT, left_raw_cnt);
-    }
-    if (auto ptr = mq().query_variable_ptr<cv::Mat>(IMG_RAW_HOST_RIGHT, &cur_cnt);
-            ptr == nullptr || cur_cnt <= right_raw_cnt) {
-        mq().wait_variable(IMG_RAW_HOST_RIGHT, right_raw_cnt);
-    }
+    left.wait_frame(IMG_RAW_HOST_LEFT);
+    right.wait_frame(IMG_RAW_HOST_RIGHT);
 }
 
 void process_camera_frames() {
-    // retrieve new frames
-    uint64_t cur_cnt;
-    auto left_raw_ptr = mq().query_variable_ptr<cv::Mat>(IMG_RAW_HOST_LEFT, &cur_cnt);
-    assert(cur_cnt > left_raw_cnt);
-    left_raw_cnt = cur_cnt;
-    auto right_raw_ptr = mq().query_variable_ptr<cv::Mat>(IMG_RAW_HOST_RIGHT, &cur_cnt);
-    assert(cur_cnt > right_raw_cnt);
-    right_raw_cnt = cur_cnt;
-
-    // process images
-    left_processor->process(*left_raw_ptr, left_img_dev.get(), enhance_image, *left_stream);
-    right_processor->process(*right_raw_ptr, right_img_dev.get(), enhance_image, *right_stream);
+    // update augment items' position
+    for (auto &item: augment_items) {
+        auto trans = sophiar_conn.query_transform_variable(item.trans_var);
+        if (trans.has_value()) {
+            update_actor_pose(item.actor, trans.value());
+        }
+    }
+
+    left.process_frame(IMG_RAW_HOST_LEFT);
+    right.process_frame(IMG_RAW_HOST_RIGHT);
 }
 
 void render_main_window() {
@@ -611,19 +823,19 @@ void render_main_window() {
 
     if (is_capturing()) {
         // draw preview camera frame
-        assert(left_img_dev->size() == right_img_dev->size());
-        float width_normal = left_img_dev->size().aspectRatio() / frame_size.aspectRatio();
+        assert(left.img_dev->size() == right.img_dev->size());
+        float width_normal = left.img_dev->size().aspectRatio() / frame_size.aspectRatio();
         auto render_rect = simple_rect{
                 -width_normal, 1, 2 * width_normal, -2
         };
         if (preview_camera_index == 0) { // left camera
-            if (!left_img_dev->empty()) {
-                opengl_render->render_rect(*left_img_dev, render_rect, false, left_cuda_stream);
+            if (!left.img_dev->empty()) {
+                left.render(render_rect);
             }
         } else { // right camera
             assert(preview_camera_index == 1);
-            if (!right_img_dev->empty()) {
-                opengl_render->render_rect(*right_img_dev, render_rect, false, right_cuda_stream);
+            if (!right.img_dev->empty()) {
+                right.render(render_rect);
             }
         }
     }
@@ -640,21 +852,20 @@ void render_main_window() {
 
 void generate_output_frame() {
     // offline drawing
-    assert(output_fbo->id != 0);
-    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, output_fbo->id);
-    glViewport(0, 0, output_fbo->size().width, output_fbo->size().height);
+    output_fbo->bind();
+    auto closer = sg::make_scope_guard([&] { output_fbo->unbind(); });
     simple_rect left_rect, right_rect;
     if (output_full_frame) {
         left_rect = simple_rect{-1, -1, 1, 2};
         right_rect = simple_rect{0, -1, 1, 2};
     } else {
-        float width_normal = left_img_dev->size().aspectRatio() /
+        float width_normal = left.img_dev->size().aspectRatio() /
                              output_fbo->size().aspectRatio();
         left_rect = simple_rect{-0.5f - width_normal / 2, -1, width_normal, 2};
         right_rect = simple_rect{0.5f - width_normal / 2, -1, width_normal, 2};
     }
-    opengl_render->render_rect(*left_img_dev, left_rect, false, left_cuda_stream);
-    opengl_render->render_rect(*right_img_dev, right_rect, false, right_cuda_stream);
+    left.render(left_rect);
+    right.render(right_rect);
 
     // wait encoder idle
     for (uint64_t cur_cnt = 0;;) {

+ 31 - 24
src/simple_opengl.cpp

@@ -114,7 +114,7 @@ struct simple_render::impl {
     GLint image_tex_loc = 0, remap_tex_loc = 0;
 
     smart_pixel_buffer image_pbo;
-    smart_texture image_tex, remap_tex;
+    smart_texture image_tex;
 
     impl() {
         create_program();
@@ -207,8 +207,9 @@ struct simple_render::impl {
         glVertexAttribPointer(1, 2, GL_FLOAT, false, 4 * sizeof(GLfloat), (void *) (2 * sizeof(GLfloat)));
     }
 
-    void render_texture(GLuint tex, const simple_rect &rect, bool is_remap = false) {
+    void render_texture(GLuint tex, const simple_rect &rect, GLuint remap_tex) {
         // bind buffers
+        bool is_remap = (remap_tex != 0);
         glUseProgram(is_remap ? remap_program : simple_program);
         glBindVertexArray(vao);
         glBindBuffer(GL_ARRAY_BUFFER, vbo);
@@ -216,13 +217,13 @@ struct simple_render::impl {
 
         // bind textures
         if (is_remap) {
-            assert(remap_tex.id != 0);
+            assert(remap_tex != 0);
             glUniform1i(image_tex_loc, 0);
             glUniform1i(remap_tex_loc, 1);
             glActiveTexture(GL_TEXTURE0 + 0);
             glBindTexture(GL_TEXTURE_2D, tex);
             glActiveTexture(GL_TEXTURE0 + 1);
-            glBindTexture(GL_TEXTURE_2D, remap_tex.id);
+            glBindTexture(GL_TEXTURE_2D, remap_tex);
         } else {
             glActiveTexture(GL_TEXTURE0 + 0);
             glBindTexture(GL_TEXTURE_2D, tex);
@@ -243,18 +244,6 @@ struct simple_render::impl {
         glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
     }
 
-    void upload_remap_data(const cv::Mat &data) {
-        // allocate texture
-        assert(data.type() == CV_32FC2);
-        remap_tex.create(GL_RG32F, data.size());
-
-        // copy data to texture
-        glBindTexture(GL_TEXTURE_2D, remap_tex.id);
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, data.cols, data.rows,
-                     0, GL_RG, GL_FLOAT, data.data);
-        glBindTexture(GL_TEXTURE_2D, 0);
-    }
-
     void upload_gpu_mat(const cv::cuda::GpuMat &img, cudaStream_t stream) {
         // allocate memory if needed
         assert(img.type() == CV_8UC3);
@@ -284,18 +273,14 @@ simple_render::simple_render()
 
 simple_render::~simple_render() = default;
 
-void simple_render::set_remap_data(const cv::Mat &data) {
-    pimpl->upload_remap_data(data);
-}
-
-void simple_render::render_rect(GLuint tex, const simple_rect &rect, bool remap) {
-    pimpl->render_texture(tex, rect, remap);
+void simple_render::render_rect(GLuint tex, const simple_rect &rect, GLuint remap_tex) {
+    pimpl->render_texture(tex, rect, remap_tex);
 }
 
 void simple_render::render_rect(const cv::cuda::GpuMat &img, const simple_rect &rect,
-                                bool remap, cudaStream_t stream) {
+                                GLuint remap_tex, cudaStream_t stream) {
     pimpl->upload_gpu_mat(img, stream);
-    pimpl->render_texture(pimpl->image_tex.id, rect, remap);
+    pimpl->render_texture(pimpl->image_tex.id, rect, remap_tex);
 }
 
 struct smart_frame_buffer::impl {
@@ -383,6 +368,16 @@ void smart_frame_buffer::download(cv::cuda::GpuMat *img, cudaStream_t stream) {
     pimpl->download(img, stream);
 }
 
+void smart_frame_buffer::bind() {
+    assert(id != 0);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, id);
+    glViewport(0, 0, pimpl->last_size.width, pimpl->last_size.height);
+}
+
+void smart_frame_buffer::unbind() {
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+}
+
 struct smart_texture::impl {
 
     smart_texture *q_this = nullptr;
@@ -427,4 +422,16 @@ void smart_texture::create(GLenum format, cv::Size size, GLint min_filter, GLint
 
 cv::Size smart_texture::size() const {
     return pimpl->last_size;
+}
+
+void upload_remap_data(smart_texture *tex, const cv::Mat &data) {
+    // allocate texture
+    assert(data.type() == CV_32FC2);
+    tex->create(GL_RG32F, data.size());
+
+    // copy data to texture
+    glBindTexture(GL_TEXTURE_2D, tex->id);
+    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
+                    data.cols, data.rows, GL_RG, GL_FLOAT, data.data);
+    glBindTexture(GL_TEXTURE_2D, 0);
 }

+ 23 - 19
src/simple_opengl.h

@@ -15,39 +15,39 @@ struct simple_rect {
     GLfloat width, height;
 };
 
-class simple_render {
-public:
-    simple_render();
+struct smart_texture {
+    GLuint id = 0;
 
-    ~simple_render();
+    smart_texture();
 
-    void set_remap_data(const cv::Mat &data);
+    ~smart_texture();
 
-    void render_rect(GLuint tex,
-                     const simple_rect &rect,
-                     bool remap = false);
+    void create(GLenum format, cv::Size size,
+                GLint min_filter = GL_NEAREST, GLint max_filter = GL_NEAREST);
 
-    void render_rect(const cv::cuda::GpuMat &img,
-                     const simple_rect &rect,
-                     bool remap = false,
-                     cudaStream_t stream = nullptr);
+    cv::Size size() const;
 
 private:
     struct impl;
     std::unique_ptr<impl> pimpl;
 };
 
-struct smart_texture {
-    GLuint id = 0;
+void upload_remap_data(smart_texture *tex, const cv::Mat &data);
 
-    smart_texture();
+class simple_render {
+public:
+    simple_render();
 
-    ~smart_texture();
+    ~simple_render();
 
-    void create(GLenum format, cv::Size size,
-                GLint min_filter = GL_NEAREST, GLint max_filter = GL_NEAREST);
+    void render_rect(GLuint tex,
+                     const simple_rect &rect,
+                     GLuint remap_tex = 0);
 
-    cv::Size size() const;
+    void render_rect(const cv::cuda::GpuMat &img,
+                     const simple_rect &rect,
+                     GLuint remap_tex = 0,
+                     cudaStream_t stream = nullptr);
 
 private:
     struct impl;
@@ -69,6 +69,10 @@ public:
     void download(cv::cuda::GpuMat *img,
                   cudaStream_t stream = nullptr);
 
+    void bind();
+
+    static void unbind();
+
 private:
     struct impl;
     std::unique_ptr<impl> pimpl;

+ 0 - 29
src/third_party/timestamp_helper.hpp

@@ -1,29 +0,0 @@
-#ifndef SOPHIAR2_TIMESTAMP_HELPER_HPP
-#define SOPHIAR2_TIMESTAMP_HELPER_HPP
-
-#include <chrono>
-
-namespace sophiar {
-
-    inline auto get_local_time() {
-        return std::chrono::high_resolution_clock::now();
-    }
-
-    using local_time_type = decltype(get_local_time());
-
-    extern local_time_type program_start_time;
-
-    inline auto current_timestamp() {
-        auto time_diff = get_local_time() - program_start_time;
-        return std::chrono::duration_cast<std::chrono::microseconds>(time_diff).count();
-    }
-
-    using timestamp_type = decltype(current_timestamp());
-
-    inline auto get_time_from_timestamp(timestamp_type ts) {
-        return program_start_time + std::chrono::microseconds(ts);
-    }
-
-}
-
-#endif //SOPHIAR2_TIMESTAMP_HELPER_HPP

+ 22 - 0
src/utility.hpp

@@ -35,4 +35,26 @@ inline bool check_function_call(bool function_ret, unsigned int line_number,
 #endif
 }
 
+struct log_timer {
+
+    void reset() {
+        last_ts = clock_type::now();
+    }
+
+    void record(std::string_view name) {
+        SPDLOG_TRACE("{} reached at {}ms", name,
+                     std::chrono::duration_cast<time_res>(clock_type::now() - last_ts).count());
+    }
+
+private:
+    using clock_type = std::chrono::high_resolution_clock;
+    using time_res = std::chrono::milliseconds;
+    clock_type::time_point last_ts;
+};
+
+extern log_timer global_timer;
+
+#define RESET_TIMER global_timer.reset()
+#define RECORD_TIME(name) global_timer.record(name)
+
 #endif //REMOTEAR3_UTILITY_HPP

+ 66 - 41
src/vtk_viewer.cpp

@@ -1,23 +1,26 @@
 #include "vtk_viewer.h"
 
-#include <vtkCallbackCommand.h>
+#include <vtkCamera.h>
 #include <vtkGenericOpenGLRenderWindow.h>
 #include <vtkGenericRenderWindowInteractor.h>
 #include <vtkInteractorStyleTrackballCamera.h>
+#include <vtkMatrix4x4.h>
 #include <vtkNew.h>
 #include <vtkOpenGLFramebufferObject.h>
 #include <vtkPolyDataMapper.h>
 #include <vtkRenderer.h>
 #include <vtkSTLReader.h>
+#include <vtkTextureObject.h>
 
 #include <spdlog/spdlog.h>
 
 struct vtk_viewer::impl {
 
-    // switching between multiple render window will cause block
-    static vtkSmartPointer<vtkGenericOpenGLRenderWindow> window;
+    static constexpr auto default_focal_length = 8; // 8mm
 
-    smart_texture tex;
+    cv::Size last_size;
+    vtkSmartPointer<vtkGenericOpenGLRenderWindow> window;
+    vtkSmartPointer<vtkCamera> camera;
     vtkSmartPointer<vtkRenderWindowInteractor> controller;
     vtkSmartPointer<vtkInteractorStyle> style;
     vtkSmartPointer<vtkRenderer> renderer;
@@ -27,57 +30,52 @@ struct vtk_viewer::impl {
     }
 
     impl() {
-        if (window == nullptr) [[unlikely]] {
-            window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
-            window->InitializeFromCurrentContext();
-            window->SetOffScreenRendering(true);
-            window->SetAlphaBitPlanes(true);
-            window->FramebufferFlipYOn();
-            window->SetIsCurrent(true);
-//            window->SwapBuffersOn();
-//            window->SetFrameBlitModeToNoBlit();
-        }
+        window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
+        window->InitializeFromCurrentContext();
+        window->SetOffScreenRendering(true);
+        window->SetAlphaBitPlanes(true);
+        window->FramebufferFlipYOn();
+        window->SetIsCurrent(true);
+//        window->SwapBuffersOn();
+//        window->SetFrameBlitModeToNoBlit();
+
+        camera = vtkSmartPointer<vtkCamera>::New();
+        camera->SetClippingRange(100, 2000); // 10cm to 2m
 
         renderer = vtkSmartPointer<vtkRenderer>::New();
         renderer->SetUseDepthPeeling(true);
+        renderer->SetActiveCamera(camera);
+        window->AddRenderer(renderer);
 
         style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
         style->SetDefaultRenderer(renderer);
         controller = vtkSmartPointer<vtkGenericRenderWindowInteractor>::New();
         controller->SetInteractorStyle(style);
         controller->EnableRenderOff();
-
-        window->AddRenderer(renderer);
         window->SetInteractor(controller);
     }
 
+    void set_camera_pose(const Eigen::Isometry3d &trans) {
+        auto trans_part = trans.translation();
+        auto rot_part = trans.rotation();
+        auto focal_point = trans_part + rot_part.col(2) * default_focal_length;
+        auto view_up = -rot_part.col(1);
+        camera->SetPosition(trans_part.x(), trans_part.y(), trans_part.z());
+        camera->SetFocalPoint(focal_point.x(), focal_point.y(), focal_point.z());
+        camera->SetViewUp(view_up.x(), view_up.y(), view_up.z());
+        camera->Modified();
+    }
+
     void render(cv::Size size, bool interactive) {
         assert(size.area() > 0);
-        if (size != tex.size()) [[unlikely]] {
-            tex.create(GL_RGBA8, size);
+        if (size != last_size) [[unlikely]] {
             controller->SetSize(size.width, size.height);
-            controller->Modified();
-
+            window->SetSize(size.width, size.height);
         }
-
-        // setup window for render
-        assert(window != nullptr);
-        auto fbo = window->GetDisplayFramebuffer();
-        fbo->Bind();
-        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.id, 0);
-        fbo->UnBind();
-        window->SetSize(size.width, size.height);
-        window->AddRenderer(renderer);
-        window->SetInteractor(controller);
-        window->Modified();
-
         if (interactive) {
             process_event();
         }
         window->Render();
-
-        // cleanup
-        window->RemoveRenderer(renderer);
     }
 
     void process_event() {
@@ -107,20 +105,23 @@ struct vtk_viewer::impl {
         controller->InvokeEvent(vtkCommand::MouseMoveEvent);
     }
 
+    GLuint get_tex() const {
+        return window->GetDisplayFramebuffer()
+                ->GetColorAttachmentAsTextureObject(0)->GetHandle();
+    }
+
     void show_imgui_window(const char *name, ImVec2 req_size) {
         ImGui::BeginChild(name, req_size, 0, no_scroll_flag);
         auto render_size = ImGui::GetContentRegionAvail();
         auto render_size_cv = to_cv_size(render_size);
         if (render_size_cv.area() <= 0)return;
         render(render_size_cv, true);
-        ImGui::Image(reinterpret_cast<void *>(tex.id), render_size);
+        ImGui::Image(reinterpret_cast<void *>(get_tex()), render_size);
         ImGui::EndChild();
     }
 
 };
 
-vtkSmartPointer<vtkGenericOpenGLRenderWindow> vtk_viewer::impl::window;
-
 vtk_viewer::vtk_viewer()
         : pimpl(std::make_unique<impl>()) {}
 
@@ -130,12 +131,13 @@ void vtk_viewer::render(cv::Size size) {
     return pimpl->render(size, false);
 }
 
-smart_texture *vtk_viewer::get_tex() const {
-    return &pimpl->tex;
+GLuint vtk_viewer::get_tex() const {
+    return pimpl->get_tex();
 }
 
 void vtk_viewer::add_actor(vtkActor *actor) {
     pimpl->renderer->AddActor(actor);
+    pimpl->renderer->ResetCamera();
 }
 
 void vtk_viewer::remove_actor(vtkActor *actor) {
@@ -146,7 +148,16 @@ void vtk_viewer::show(const std::string &name) {
     pimpl->show_imgui_window(name.c_str(), ImGui::GetContentRegionAvail());
 }
 
-vtkSmartPointer<vtkActor> vtk_viewer::load_stl(const std::string &path) {
+void vtk_viewer::set_camera_pose(const Eigen::Isometry3d &trans) {
+    pimpl->set_camera_pose(trans);
+}
+
+void vtk_viewer::set_camera_view_angle(double angle) {
+    pimpl->camera->SetViewAngle(angle);
+    pimpl->camera->Modified();
+}
+
+vtkSmartPointer<vtkActor> load_stl(const std::string &path) {
     vtkNew<vtkSTLReader> reader;
     reader->SetFileName(path.c_str());
     reader->Update();
@@ -154,5 +165,19 @@ vtkSmartPointer<vtkActor> vtk_viewer::load_stl(const std::string &path) {
     mapper->SetInputData(reader->GetOutput());
     vtkNew<vtkActor> actor;
     actor->SetMapper(mapper);
+    vtkNew<vtkMatrix4x4> pose;
+    actor->SetUserMatrix(pose);
     return actor;
+}
+
+void update_actor_pose(vtkActor *actor, const Eigen::Isometry3d &trans) {
+    auto matrix = actor->GetUserMatrix();
+    assert(matrix != nullptr);
+    for (int i = 0; i < 4; ++i) {
+        for (int j = 0; j < 4; ++j) {
+            matrix->SetElement(i, j, trans(i, j));
+        }
+    }
+    matrix->Modified();
+    actor->Modified();
 }

+ 11 - 3
src/vtk_viewer.h

@@ -7,11 +7,17 @@
 
 #include <opencv2/core/types.hpp>
 
+#include <Eigen/Geometry>
+
 #include <vtkActor.h>
 #include <vtkSmartPointer.h>
 
 #include <memory>
 
+vtkSmartPointer<vtkActor> load_stl(const std::string &path);
+
+void update_actor_pose(vtkActor *actor, const Eigen::Isometry3d &trans);
+
 class vtk_viewer {
 public:
 
@@ -26,14 +32,16 @@ public:
 
     void remove_actor(vtkActor *actor);
 
+    void set_camera_view_angle(double angle);
+
+    void set_camera_pose(const Eigen::Isometry3d &trans);
+
     void render(cv::Size size);
 
-    smart_texture *get_tex() const;
+    GLuint get_tex() const;
 
     void show(const std::string &name = "VtkViewer"); // Show in ImGui as child
 
-    static vtkSmartPointer<vtkActor> load_stl(const std::string &path);
-
 private:
     struct impl;
     std::unique_ptr<impl> pimpl;