Spaces:
Build error
Build error
/* | |
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
* SPDX-License-Identifier: Apache-2.0 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
/** @file camera_path.cpp | |
* @author Thomas Müller & Alex Evans, NVIDIA | |
*/ | |
using namespace nlohmann; | |
namespace ngp { | |
CameraKeyframe lerp(const CameraKeyframe& p0, const CameraKeyframe& p1, float t, float t0, float t1) { | |
t = (t - t0) / (t1 - t0); | |
quat R1 = p1.R; | |
// take the short path | |
if (dot(R1, p0.R) < 0.0f) { | |
R1 = -R1; | |
} | |
return { | |
normalize(slerp(p0.R, R1, t)), | |
p0.T + (p1.T - p0.T) * t, | |
p0.fov + (p1.fov - p0.fov) * t, | |
p0.timestamp + (p1.timestamp - p0.timestamp) * t, | |
}; | |
} | |
CameraKeyframe normalize(const CameraKeyframe& p0) { | |
CameraKeyframe result = p0; | |
result.R = normalize(result.R); | |
return result; | |
} | |
CameraKeyframe spline_cm(float t, const CameraKeyframe& p0, const CameraKeyframe& p1, const CameraKeyframe& p2, const CameraKeyframe& p3) { | |
CameraKeyframe q0 = lerp(p0, p1, t, -1.f, 0.f); | |
CameraKeyframe q1 = lerp(p1, p2, t, 0.f, 1.f); | |
CameraKeyframe q2 = lerp(p2, p3, t, 1.f, 2.f); | |
CameraKeyframe r0 = lerp(q0, q1, t, -1.f, 1.f); | |
CameraKeyframe r1 = lerp(q1, q2, t, 0.f, 2.f); | |
return lerp(r0, r1, t, 0.f, 1.f); | |
} | |
CameraKeyframe spline_cubic(float t, const CameraKeyframe& p0, const CameraKeyframe& p1, const CameraKeyframe& p2, const CameraKeyframe& p3) { | |
float tt = t * t; | |
float ttt = t * t * t; | |
float a = (1 - t) * (1 - t) * (1 - t) * (1.f / 6.f); | |
float b = (3.f * ttt - 6.f * tt + 4.f) * (1.f / 6.f); | |
float c = (-3.f * ttt + 3.f * tt + 3.f * t + 1.f) * (1.f / 6.f); | |
float d = ttt * (1.f / 6.f); | |
return normalize(p0 * a + p1 * b + p2 * c + p3 * d); | |
} | |
CameraKeyframe spline_quadratic(float t, const CameraKeyframe& p0, const CameraKeyframe& p1, const CameraKeyframe& p2) { | |
float tt = t * t; | |
float a = (1 - t) * (1 - t) * 0.5f; | |
float b = (-2.f * tt + 2.f * t + 1.f) * 0.5f; | |
float c = tt * 0.5f; | |
return normalize(p0 * a + p1 * b + p2 * c); | |
} | |
CameraKeyframe spline_linear(float t, const CameraKeyframe& p0, const CameraKeyframe& p1) { return normalize(p0 * (1.0f - t) + p1 * t); } | |
void to_json(json& j, const CameraKeyframe& p) { | |
j = json{ | |
{"R", p.R }, | |
{"T", p.T }, | |
{"fov", p.fov }, | |
{"timestamp", p.timestamp}, | |
}; | |
} | |
bool load_relative_to_first = false; // set to true when using a camera path that is aligned with the first training image, such that it is | |
// invariant to changes in the space of the training data | |
void from_json(bool is_first, const json& j, CameraKeyframe& p, const CameraKeyframe& first, const mat4x3& ref) { | |
if (is_first && load_relative_to_first) { | |
p.from_m(ref); | |
} else { | |
p.R = j.at("R"); | |
p.T = j.at("T"); | |
if (load_relative_to_first) { | |
mat4 ref4 = {ref}; | |
mat4 first4 = {first.m()}; | |
mat4 p4 = {p.m()}; | |
p.from_m(mat4x3(ref4 * inverse(first4) * p4)); | |
} | |
} | |
j.at("fov").get_to(p.fov); | |
if (j.contains("timestamp")) { | |
j.at("timestamp").get_to(p.timestamp); | |
} else { | |
p.timestamp = 0.f; | |
} | |
} | |
void CameraPath::save(const fs::path& path) { | |
json j = { | |
{"loop", loop }, | |
{"time", play_time }, | |
{"path", keyframes }, | |
{"duration_seconds", duration_seconds()}, | |
{"spline_order", spline_order }, | |
}; | |
std::ofstream f(native_string(path)); | |
f << j; | |
} | |
void CameraPath::load(const fs::path& path, const mat4x3& first_xform) { | |
std::ifstream f{native_string(path)}; | |
if (!f) { | |
throw std::runtime_error{fmt::format("Camera path {} does not exist.", path.str())}; | |
} | |
json j; | |
f >> j; | |
CameraKeyframe first; | |
keyframes.clear(); | |
if (j.contains("loop")) { | |
loop = j["loop"]; | |
} | |
if (j.contains("time")) { | |
play_time = j["time"]; | |
} | |
if (j.contains("path")) { | |
for (auto& el : j["path"]) { | |
CameraKeyframe p; | |
bool is_first = keyframes.empty(); | |
from_json(is_first, el, p, first, first_xform); | |
if (is_first) { | |
first = p; | |
} | |
keyframes.push_back(p); | |
} | |
} | |
spline_order = j.value("spline_order", 3); | |
sanitize_keyframes(); | |
play_time = 0.0f; | |
if (keyframes.size() >= 16) { | |
keyframe_subsampling = keyframes.size() - 1; | |
editing_kernel_type = EEditingKernel::Gaussian; | |
} | |
} | |
void CameraPath::add_camera(const mat4x3& camera, float fov, float timestamp) { | |
int n = std::max(0, int(keyframes.size()) - 1); | |
int i = (int)ceil(play_time * (float)n + 0.001f); | |
if (i > keyframes.size()) { | |
i = keyframes.size(); | |
} | |
if (i < 0) { | |
i = 0; | |
} | |
keyframes.insert(keyframes.begin() + i, CameraKeyframe(camera, fov, timestamp)); | |
update_cam_from_path = false; | |
play_time = get_playtime(i); | |
sanitize_keyframes(); | |
} | |
float editing_kernel(float x, EEditingKernel kernel) { | |
x = kernel == EEditingKernel::Gaussian ? x : clamp(x, -1.0f, 1.0f); | |
switch (kernel) { | |
case EEditingKernel::Gaussian: return expf(-2.0f * x * x); | |
case EEditingKernel::Quartic: return (1.0f - x * x) * (1.0f - x * x); | |
case EEditingKernel::Hat: return 1.0f - fabsf(x); | |
case EEditingKernel::Box: return x > -1.0f && x < 1.0f ? 1.0f : 0.0f; | |
case EEditingKernel::None: return fabs(x) < 0.0001f ? 1.0f : 0.0f; | |
default: throw std::runtime_error{"Unknown editing kernel"}; | |
} | |
} | |
int CameraPath::imgui(char path_filename_buf[1024], float frame_milliseconds, const mat4x3& camera, float fov, const mat4x3& first_xform) { | |
int n = std::max(0, int(keyframes.size()) - 1); | |
int read = 0; // 1=smooth, 2=hard | |
ImGui::InputText("##PathFile", path_filename_buf, 1024); | |
ImGui::SameLine(); | |
static std::string camera_path_load_error_string = ""; | |
if (rendering) { | |
ImGui::BeginDisabled(); | |
} | |
if (ImGui::Button("Load")) { | |
try { | |
load(path_filename_buf, first_xform); | |
} catch (const std::exception& e) { | |
ImGui::OpenPopup("Camera path load error"); | |
camera_path_load_error_string = std::string{"Failed to load camera path: "} + e.what(); | |
} | |
} | |
if (rendering) { | |
ImGui::EndDisabled(); | |
} | |
if (ImGui::BeginPopupModal("Camera path load error", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { | |
ImGui::Text("%s", camera_path_load_error_string.c_str()); | |
if (ImGui::Button("OK", ImVec2(120, 0))) { | |
ImGui::CloseCurrentPopup(); | |
} | |
ImGui::EndPopup(); | |
} | |
if (!keyframes.empty()) { | |
ImGui::SameLine(); | |
if (ImGui::Button("Save")) { | |
save(path_filename_buf); | |
} | |
} | |
if (rendering) { | |
ImGui::BeginDisabled(); | |
} | |
if (ImGui::Button("Add from cam")) { | |
const float duration = duration_seconds(); | |
add_camera(camera, fov, 0.0f); | |
make_keyframe_timestamps_equidistant(duration); | |
read = 2; | |
} | |
auto p = get_pos(play_time); | |
if (!keyframes.empty()) { | |
ImGui::SameLine(); | |
if (ImGui::Button("Split")) { | |
update_cam_from_path = false; | |
int i = clamp(p.kfidx + 1, 0, (int)keyframes.size()); | |
const float duration = duration_seconds(); | |
keyframes.insert(keyframes.begin() + i, eval_camera_path(play_time)); | |
make_keyframe_timestamps_equidistant(duration); | |
play_time = get_playtime(i); | |
read = 2; | |
} | |
ImGui::SameLine(); | |
int i = p.kfidx; | |
if (!loop) { | |
i += (int)round(p.t); | |
} | |
if (ImGui::Button("|<")) { | |
play_time = 0.f; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("<")) { | |
play_time = n ? (get_playtime(i - 1) + 0.0001f) : 0.f; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button(update_cam_from_path ? "Stop" : "Read")) { | |
update_cam_from_path = !update_cam_from_path; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button(">")) { | |
play_time = n ? (get_playtime(i + 1) + 0.0001f) : 1.0f; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button(">|")) { | |
play_time = 1.0f; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("Dup")) { | |
update_cam_from_path = false; | |
const float duration = duration_seconds(); | |
keyframes.insert(keyframes.begin() + i, keyframes[i]); | |
make_keyframe_timestamps_equidistant(duration); | |
play_time = get_playtime(i); | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("Del")) { | |
update_cam_from_path = false; | |
const float duration = duration_seconds(); | |
keyframes.erase(keyframes.begin() + i); | |
make_keyframe_timestamps_equidistant(duration); | |
play_time = get_playtime(i - 1); | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("Set")) { | |
keyframes[i] = CameraKeyframe(camera, fov, keyframes[i].timestamp); | |
read = 2; | |
if (n) { | |
play_time = get_playtime(i); | |
} | |
} | |
if (ImGui::RadioButton("Translate", m_gizmo_op == ImGuizmo::TRANSLATE)) { | |
m_gizmo_op = ImGuizmo::TRANSLATE; | |
} | |
ImGui::SameLine(); | |
if (ImGui::RadioButton("Rotate", m_gizmo_op == ImGuizmo::ROTATE)) { | |
m_gizmo_op = ImGuizmo::ROTATE; | |
} | |
ImGui::SameLine(); | |
if (ImGui::RadioButton("Local", m_gizmo_mode == ImGuizmo::LOCAL)) { | |
m_gizmo_mode = ImGuizmo::LOCAL; | |
} | |
ImGui::SameLine(); | |
if (ImGui::RadioButton("World", m_gizmo_mode == ImGuizmo::WORLD)) { | |
m_gizmo_mode = ImGuizmo::WORLD; | |
} | |
ImGui::SameLine(); | |
ImGui::Checkbox("Loop path", &loop); | |
if (ImGui::Button("Start") && !keyframes.empty()) { | |
auto_play_speed = 0.0f; | |
play_time = 0.0f; | |
read = 2; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("Rev") && !keyframes.empty()) { | |
auto_play_speed = -1.0f / duration_seconds(); | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button(auto_play_speed != 0 ? "Pause" : "Play") && !keyframes.empty()) { | |
auto_play_speed = auto_play_speed == 0.0f ? (1.0f / duration_seconds()) : 0.0f; | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("End") && !keyframes.empty()) { | |
auto_play_speed = 0.0f; | |
play_time = 1.0f; | |
read = 2; | |
} | |
ImGui::SliderFloat("Playback speed", &auto_play_speed, -1.0f, 1.0f); | |
if (auto_play_speed != 0.0f) { | |
float prev = play_time; | |
play_time = clamp(play_time + auto_play_speed * (frame_milliseconds / 1000.f), 0.0f, 1.0f); | |
if (play_time != prev) { | |
read = 1; | |
} | |
} | |
if (ImGui::SliderFloat("Camera path time", &play_time, 0.0f, 1.0f)) { | |
read = 1; | |
} | |
ImGui::Text("Current keyframe %d/%d:", i, n + 1); | |
if (ImGui::SliderFloat("Field of view", &keyframes[i].fov, 0.0f, 120.0f)) { | |
read = 2; | |
} | |
if (ImGui::Button("Apply to all keyframes")) { | |
for (auto& k : keyframes) { | |
k.fov = keyframes[i].fov; | |
} | |
} | |
if (ImGui::TreeNodeEx("Batch keyframe editing")) { | |
ImGui::Combo("Editing kernel", (int*)&editing_kernel_type, EditingKernelStr); | |
ImGui::SliderFloat( | |
"Editing kernel radius", &editing_kernel_radius, 0.001f, 10.0f, "%.4f", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoRoundToFormat | |
); | |
ImGui::TreePop(); | |
} | |
if (ImGui::TreeNodeEx("Advanced camera path settings")) { | |
ImGui::SliderInt("Spline order", &spline_order, 0, 3); | |
ImGui::SliderInt("Keyframe subsampling", &keyframe_subsampling, 1, max((int)keyframes.size() - 1, 1)); | |
ImGui::TreePop(); | |
} | |
} | |
if (rendering) { | |
ImGui::EndDisabled(); | |
} | |
return keyframes.empty() ? 0 : read; | |
} | |
bool debug_project(const mat4& proj, vec3 p, ImVec2& o) { | |
vec4 ph{p.x, p.y, p.z, 1.0f}; | |
vec4 pa = proj * ph; | |
if (pa.w <= 0.f) { | |
return false; | |
} | |
o.x = pa.x / pa.w; | |
o.y = pa.y / pa.w; | |
return true; | |
} | |
void add_debug_line(ImDrawList* list, const mat4& proj, vec3 a, vec3 b, uint32_t col, float thickness) { | |
ImVec2 aa, bb; | |
if (debug_project(proj, a, aa) && debug_project(proj, b, bb)) { | |
list->AddLine(aa, bb, col, thickness * 2.0f); | |
} | |
} | |
void visualize_cube(ImDrawList* list, const mat4& world2proj, const vec3& a, const vec3& b, const mat3& render_aabb_to_local) { | |
mat3 m = transpose(render_aabb_to_local); | |
add_debug_line(list, world2proj, m * vec3{a.x, a.y, a.z}, m * vec3{a.x, a.y, b.z}, 0xffff4040); // Z | |
add_debug_line(list, world2proj, m * vec3{b.x, a.y, a.z}, m * vec3{b.x, a.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, b.y, a.z}, m * vec3{a.x, b.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{b.x, b.y, a.z}, m * vec3{b.x, b.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, a.y, a.z}, m * vec3{b.x, a.y, a.z}, 0xff4040ff); // X | |
add_debug_line(list, world2proj, m * vec3{a.x, b.y, a.z}, m * vec3{b.x, b.y, a.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, a.y, b.z}, m * vec3{b.x, a.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, b.y, b.z}, m * vec3{b.x, b.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, a.y, a.z}, m * vec3{a.x, b.y, a.z}, 0xff40ff40); // Y | |
add_debug_line(list, world2proj, m * vec3{b.x, a.y, a.z}, m * vec3{b.x, b.y, a.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{a.x, a.y, b.z}, m * vec3{a.x, b.y, b.z}, 0xffffffff); | |
add_debug_line(list, world2proj, m * vec3{b.x, a.y, b.z}, m * vec3{b.x, b.y, b.z}, 0xffffffff); | |
} | |
void visualize_camera(ImDrawList* list, const mat4& world2proj, const mat4x3& xform, float aspect, uint32_t col, float thickness) { | |
const float axis_size = 0.025f; | |
const vec3* xforms = (const vec3*)&xform; | |
vec3 pos = xforms[3]; | |
add_debug_line(list, world2proj, pos, pos + axis_size * xforms[0], 0xff4040ff, thickness); | |
add_debug_line(list, world2proj, pos, pos + axis_size * xforms[1], 0xff40ff40, thickness); | |
add_debug_line(list, world2proj, pos, pos + axis_size * xforms[2], 0xffff4040, thickness); | |
float xs = axis_size * aspect; | |
float ys = axis_size; | |
float zs = axis_size * 2.0f * aspect; | |
vec3 a = pos + xs * xforms[0] + ys * xforms[1] + zs * xforms[2]; | |
vec3 b = pos - xs * xforms[0] + ys * xforms[1] + zs * xforms[2]; | |
vec3 c = pos - xs * xforms[0] - ys * xforms[1] + zs * xforms[2]; | |
vec3 d = pos + xs * xforms[0] - ys * xforms[1] + zs * xforms[2]; | |
add_debug_line(list, world2proj, pos, a, col, thickness); | |
add_debug_line(list, world2proj, pos, b, col, thickness); | |
add_debug_line(list, world2proj, pos, c, col, thickness); | |
add_debug_line(list, world2proj, pos, d, col, thickness); | |
add_debug_line(list, world2proj, a, b, col, thickness); | |
add_debug_line(list, world2proj, b, c, col, thickness); | |
add_debug_line(list, world2proj, c, d, col, thickness); | |
add_debug_line(list, world2proj, d, a, col, thickness); | |
} | |
bool CameraPath::has_valid_timestamps() const { | |
float prev_timestamp = 0.0f; | |
for (size_t i = 0; i < keyframes.size(); ++i) { | |
if (!(keyframes[i].timestamp > prev_timestamp)) { | |
return false; | |
} | |
prev_timestamp = keyframes[i].timestamp; | |
} | |
return true; | |
} | |
void CameraPath::make_keyframe_timestamps_equidistant(const float duration_seconds) { | |
const float sanitized_duration = duration_seconds > 0.0f ? duration_seconds : default_duration_seconds; | |
for (size_t i = 0; i < keyframes.size(); ++i) { | |
keyframes[i].timestamp = sanitized_duration * (i + 1) / (float)keyframes.size(); | |
} | |
} | |
void CameraPath::sanitize_keyframes() { | |
if (has_valid_timestamps()) { | |
return; | |
} | |
// Timestamps are invalid. Best effort is to equally space all frames. Default to 3 seconds duration. | |
make_keyframe_timestamps_equidistant(default_duration_seconds); | |
} | |
float CameraPath::duration_seconds() const { | |
if (keyframes.empty()) { | |
return 0.0f; | |
} | |
return keyframes.back().timestamp; | |
} | |
void CameraPath::set_duration_seconds(const float duration) { | |
const float old_duration = duration_seconds(); | |
if (!(old_duration > 0.0f)) { | |
make_keyframe_timestamps_equidistant(duration); | |
return; | |
} | |
const float multiplier = duration / old_duration; | |
for (auto& kf : keyframes) { | |
kf.timestamp *= multiplier; | |
} | |
} | |
CameraPath::Pos CameraPath::get_pos(float playtime) { | |
if (keyframes.empty()) { | |
return {-1, 0.0f}; | |
} else if (keyframes.size() == 1) { | |
return {0, playtime}; | |
} | |
const float duration = loop ? keyframes.back().timestamp : keyframes[keyframes.size() - 2].timestamp; | |
playtime *= duration; | |
CameraKeyframe dummy; | |
dummy.timestamp = playtime; | |
// Binary search to obtain relevant keyframe in O(log(n_keyframes)) time | |
auto it = std::upper_bound(keyframes.begin(), keyframes.end(), dummy, [](const auto& a, const auto& b) { | |
return a.timestamp < b.timestamp; | |
}); | |
int i = clamp((int)std::distance(keyframes.begin(), it), 0, (int)keyframes.size() - (loop ? 1 : 2)); | |
float prev_timestamp = i == 0 ? 0.0f : keyframes[i - 1].timestamp; | |
return { | |
i, | |
(playtime - prev_timestamp) / (keyframes[i].timestamp - prev_timestamp), | |
}; | |
} | |
bool CameraPath::imgui_viz( | |
ImDrawList* list, | |
mat4& view2proj, | |
mat4& world2proj, | |
mat4& world2view, | |
vec2 focal, | |
float aspect, | |
float znear, | |
float zfar | |
) { | |
bool changed = false; | |
// float flx = focal.x; | |
float fly = focal.y; | |
mat4 view2proj_guizmo = transpose( | |
mat4{ | |
fly * 2.0f / aspect, | |
0.0f, | |
0.0f, | |
0.0f, | |
0.0f, | |
-fly * 2.0f, | |
0.0f, | |
0.0f, | |
0.0f, | |
0.0f, | |
(zfar + znear) / (zfar - znear), | |
-(2.0f * zfar * znear) / (zfar - znear), | |
0.0f, | |
0.0f, | |
1.0f, | |
0.0f, | |
} | |
); | |
if (!update_cam_from_path && !keyframes.empty()) { | |
auto p = get_pos(play_time); | |
int cur_cam_i = p.kfidx; | |
if (!loop) { | |
cur_cam_i += (int)round(p.t); | |
} | |
vec3 prevp; | |
for (int i = 0; i < keyframes.size(); i += max(min(keyframe_subsampling, (int)keyframes.size() - 1 - i), 1)) { | |
visualize_camera(list, world2proj, keyframes[i].m(), aspect, (i == cur_cam_i) ? 0xff80c0ff : 0x8080c0ff); | |
vec3 p = keyframes[i].T; | |
if (i && keyframe_subsampling == 1) { | |
add_debug_line(list, world2proj, prevp, p, 0xccffc040); | |
} | |
prevp = p; | |
} | |
ImGuiIO& io = ImGui::GetIO(); | |
mat4 matrix = keyframes[cur_cam_i].m(); | |
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); | |
if (ImGuizmo::Manipulate( | |
(const float*)&world2view, | |
(const float*)&view2proj_guizmo, | |
(ImGuizmo::OPERATION)m_gizmo_op, | |
(ImGuizmo::MODE)m_gizmo_mode, | |
(float*)&matrix, | |
NULL, | |
NULL | |
)) { | |
// Find overlapping keypoints... | |
int i0 = cur_cam_i; | |
while (i0 > 0 && keyframes[cur_cam_i].same_pos_as(keyframes[i0 - 1])) { | |
i0--; | |
} | |
int i1 = cur_cam_i; | |
while (i1 < keyframes.size() - 1 && keyframes[cur_cam_i].same_pos_as(keyframes[i1 + 1])) { | |
i1++; | |
} | |
vec3 tdiff = matrix[3].xyz() - keyframes[cur_cam_i].T; | |
mat3 rdiff = mat_log(mat3(matrix) * inverse(to_mat3(normalize(keyframes[cur_cam_i].R)))); | |
for (int i = 0; i < keyframes.size(); ++i) { | |
float x = (get_playtime(i) - get_playtime(cur_cam_i)) / editing_kernel_radius; | |
float w = editing_kernel(x, editing_kernel_type); | |
keyframes[i].T += w * tdiff; | |
keyframes[i].R = quat(mat_exp(w * rdiff) * to_mat3(normalize(keyframes[i].R))); | |
} | |
// ...and ensure overlapping keypoints were edited exactly in tandem | |
for (int i = i0; i <= i1; ++i) { | |
keyframes[i].T = keyframes[cur_cam_i].T; | |
keyframes[i].R = keyframes[cur_cam_i].R; | |
} | |
changed = true; | |
} | |
visualize_camera(list, world2proj, eval_camera_path(play_time).m(), aspect, 0xff80ff80); | |
float dt = 0.001f; | |
float total_length = 0.0f; | |
for (float t = 0.0f;; t += dt) { | |
if (t > 1.0f) { | |
t = 1.0f; | |
} | |
vec3 p = eval_camera_path(t).T; | |
if (t) { | |
total_length += distance(prevp, p); | |
} | |
prevp = p; | |
if (t >= 1.0f) { | |
break; | |
} | |
} | |
dt = 0.001f / total_length; | |
static const uint32_t N_DASH_STEPS = 10; | |
uint32_t i = 0; | |
for (float t = 0.0f;; t += dt, ++i) { | |
if (t > 1.0f) { | |
t = 1.0f; | |
} | |
vec3 p = eval_camera_path(t).T; | |
if (t && (i / N_DASH_STEPS) % 2 == 0) { | |
float thickness = 1.0f; | |
if (editing_kernel_type != EEditingKernel::None) { | |
float x = (t + dt / 2.0f - get_playtime(cur_cam_i)) / editing_kernel_radius; | |
thickness += 4.0f * editing_kernel(x, editing_kernel_type); | |
} | |
add_debug_line(list, world2proj, prevp, p, 0xff80c0ff, thickness); | |
} | |
prevp = p; | |
if (t >= 1.0f) { | |
break; | |
} | |
} | |
} | |
return changed; | |
} | |
} // namespace ngp | |