diff --git a/include/neural-graphics-primitives/camera_path.h b/include/neural-graphics-primitives/camera_path.h index b520b69576be19a8e0dde37792d06cc70cf36a24..16a33e7db85937c39d2858f6e8d1f811ba393ae7 100644 --- a/include/neural-graphics-primitives/camera_path.h +++ b/include/neural-graphics-primitives/camera_path.h @@ -72,12 +72,25 @@ struct CameraPath { bool m_update_cam_from_path = false; float m_playtime = 0.f; float m_autoplayspeed = 0.f; - - const CameraKeyframe& get_keyframe(int i) { return m_keyframes[tcnn::clamp(i, 0, (int)m_keyframes.size()-1)]; } + // If m_loop is set true, the last frame set will be more like "next to last," + // with animation then returning back to the first frame, making a continuous loop. + // Note that the user does not have to (and should not normally) duplicate the first frame to be the last frame. + bool m_loop = false; + + const CameraKeyframe& get_keyframe(int i) { + if (m_loop) { + int size = (int)m_keyframes.size(); + // add size to ensure no negative value is generated by modulo + return m_keyframes[(i + size) % size]; + } else { + return m_keyframes[tcnn::clamp(i, 0, (int)m_keyframes.size()-1)]; + } + } CameraKeyframe eval_camera_path(float t) { if (m_keyframes.empty()) return {}; - t *= (float)(m_keyframes.size()-1); + // make room for last frame == first frame when looping + t *= (float)(m_loop ? m_keyframes.size() : m_keyframes.size()-1); int t1 = (int)floorf(t); return spline(t-floorf(t), get_keyframe(t1-1), get_keyframe(t1), get_keyframe(t1+1), get_keyframe(t1+2)); } diff --git a/include/neural-graphics-primitives/testbed.h b/include/neural-graphics-primitives/testbed.h index 521e8a3cd31911e028e3d04ec405376c42fd30cc..227ffec9d1de1751915fe5145ba456249b8cb77e 100644 --- a/include/neural-graphics-primitives/testbed.h +++ b/include/neural-graphics-primitives/testbed.h @@ -388,6 +388,8 @@ public: void set_camera_from_time(float t); void update_loss_graph(); void load_camera_path(const std::string& filepath_string); + bool loop_animation(); + void set_loop_animation(bool value); float compute_image_mse(bool quantize_to_byte); diff --git a/scripts/run.py b/scripts/run.py index aed15f889de280c55967dd7a38cebc133b2bd62c..151e924b096d441805187d056289460148701a14 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -46,6 +46,7 @@ def parse_args(): parser.add_argument("--video_camera_path", default="", help="The camera path to render, e.g., base_cam.json.") parser.add_argument("--video_camera_smoothing", action="store_true", help="Applies additional smoothing to the camera trajectory with the caveat that the endpoint of the camera path may not be reached.") + parser.add_argument("--video_loop_animation", action="store_true", help="Connect the last and first keyframes in a continuous loop.") parser.add_argument("--video_fps", type=int, default=60, help="Number of frames per second.") parser.add_argument("--video_n_seconds", type=int, default=1, help="Number of seconds the rendered video should be long.") parser.add_argument("--video_spp", type=int, default=8, help="Number of samples per pixel. A larger number means less noise, but slower rendering.") @@ -336,6 +337,7 @@ if __name__ == "__main__": if args.video_camera_path: testbed.load_camera_path(args.video_camera_path) + testbed.loop_animation = args.video_loop_animation resolution = [args.width or 1920, args.height or 1080] n_frames = args.video_n_seconds * args.video_fps diff --git a/src/camera_path.cu b/src/camera_path.cu index 88d8be0bdd865791827726d9d1505ba05bcafe2b..281544880f8fb8fc40913285c73564afeb8288e8 100644 --- a/src/camera_path.cu +++ b/src/camera_path.cu @@ -60,7 +60,7 @@ CameraKeyframe spline(float t, const CameraKeyframe& p0, const CameraKeyframe& p CameraKeyframe r1 = lerp(q1, q2, t, 0.f, 2.f); return lerp(r0, r1, t, 0.f, 1.f); } else { - // cublic bspline + // cubic bspline float tt=t*t; float ttt=t*t*t; float a = (1-t)*(1-t)*(1-t)*(1.f/6.f); diff --git a/src/python_api.cu b/src/python_api.cu index 6d4427d8b3928ae5dbe04b3c8688e40f9a3b5728..ef41fa63fb4955b6533396e261541bfa66aa086b 100644 --- a/src/python_api.cu +++ b/src/python_api.cu @@ -386,6 +386,7 @@ PYBIND11_MODULE(pyngp, m) { .def("save_snapshot", &Testbed::save_snapshot, py::arg("path"), py::arg("include_optimizer_state")=false, "Save a snapshot of the currently trained model") .def("load_snapshot", &Testbed::load_snapshot, py::arg("path"), "Load a previously saved snapshot") .def("load_camera_path", &Testbed::load_camera_path, "Load a camera path", py::arg("path")) + .def_property("loop_animation", &Testbed::loop_animation, &Testbed::set_loop_animation) .def("compute_and_save_png_slices", &Testbed::compute_and_save_png_slices, py::arg("filename"), py::arg("resolution") = Eigen::Vector3i::Constant(256), diff --git a/src/testbed.cu b/src/testbed.cu index 8052e506f00c4e114b8640864931d24d08295fba..78c1ac2fd66d7481e0ced7c56c7ada1366617f7d 100644 --- a/src/testbed.cu +++ b/src/testbed.cu @@ -3008,5 +3008,13 @@ void Testbed::load_camera_path(const std::string& filepath_string) { m_camera_path.load(filepath_string, Matrix<float, 3, 4>::Identity()); } +bool Testbed::loop_animation() { + return m_camera_path.m_loop; +} + +void Testbed::set_loop_animation(bool value) { + m_camera_path.m_loop = value; +} + NGP_NAMESPACE_END