diff --git a/source/blender/blenkernel/BKE_scene.hh b/source/blender/blenkernel/BKE_scene.hh index 7b6986ada01..2273ca70e50 100644 --- a/source/blender/blenkernel/BKE_scene.hh +++ b/source/blender/blenkernel/BKE_scene.hh @@ -138,7 +138,7 @@ const char *BKE_scene_find_marker_name(const Scene *scene, int frame); */ const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame); -int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int frame); +float BKE_scene_frame_snap_by_seconds(const Scene *scene, double interval_in_seconds, float frame); /** * Checks for cycle, returns true if it's all OK. diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 8e1dee7613a..c3fdc0bfddb 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -814,6 +814,7 @@ if(WITH_GTESTS) intern/main_test.cc intern/nla_test.cc intern/path_templates_test.cc + intern/scene_test.cc intern/subdiv_ccg_test.cc intern/tracking_test.cc intern/volume_test.cc diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index 2adeea30ee2..82015441756 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -2291,14 +2291,19 @@ const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame) return best_marker ? best_marker->name : nullptr; } -int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int frame) +float BKE_scene_frame_snap_by_seconds(const Scene *scene, + const double interval_in_seconds, + const float frame) { - const int fps = round_db_to_int(scene->frames_per_second() * interval_in_seconds); - const int second_prev = frame - mod_i(frame, fps); - const int second_next = second_prev + fps; - const int delta_prev = frame - second_prev; - const int delta_next = second_next - frame; - return (delta_prev < delta_next) ? second_prev : second_next; + BLI_assert(interval_in_seconds > 0); + BLI_assert(scene->frames_per_second() > 0); + + const double interval_in_frames = scene->frames_per_second() * interval_in_seconds; + const double second_prev = interval_in_frames * floor(frame / interval_in_frames); + const double second_next = second_prev + ceil(interval_in_frames); + const double delta_prev = frame - second_prev; + const double delta_next = second_next - frame; + return float((delta_prev < delta_next) ? second_prev : second_next); } void BKE_scene_remove_rigidbody_object(Main *bmain, Scene *scene, Object *ob, const bool free_us) diff --git a/source/blender/blenkernel/intern/scene_test.cc b/source/blender/blenkernel/intern/scene_test.cc new file mode 100644 index 00000000000..965f0855cf4 --- /dev/null +++ b/source/blender/blenkernel/intern/scene_test.cc @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_scene.hh" + +#include "DNA_scene_types.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(scene, frame_snap_by_seconds) +{ + Scene fake_scene = {}; + + /* Regular 24 FPS snapping. */ + fake_scene.r.frs_sec = 24; + fake_scene.r.frs_sec_base = 1.0; + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 47)); + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 49)); + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 59)); + EXPECT_FLOAT_EQ(72.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 60)); + EXPECT_FLOAT_EQ(9984.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 10000.0)); + + /* 12 FPS snapping by incrementing the base. */ + fake_scene.r.frs_sec = 24; + fake_scene.r.frs_sec_base = 2.0; + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 47)); + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 49)); + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 53)); + EXPECT_FLOAT_EQ(60.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 54)); + EXPECT_FLOAT_EQ(9996.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 10000.0)); + + /* 0.1 FPS snapping to 2-second intervals. */ + fake_scene.r.frs_sec = 1; + fake_scene.r.frs_sec_base = 10.0; + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.0)); + EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.1)); + EXPECT_FLOAT_EQ(48.2, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.2)); + EXPECT_FLOAT_EQ(10000.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 10000.0)); +} + +} // namespace blender::bke::tests