Alembic: simplified sub-frame sampling
It's now less confusing (for example, using nr_of_samples directly, instead of using 1 / 1 / nr_of_samples). Might also have fixed a bug. Also added unittests.
This commit is contained in:
parent
4e5440686d
commit
35f4abcf53
|
@ -47,8 +47,8 @@ struct AlembicExportParams {
|
|||
double frame_start;
|
||||
double frame_end;
|
||||
|
||||
double frame_step_xform;
|
||||
double frame_step_shape;
|
||||
unsigned int frame_samples_xform;
|
||||
unsigned int frame_samples_shape;
|
||||
|
||||
double shutter_open;
|
||||
double shutter_close;
|
||||
|
|
|
@ -72,8 +72,8 @@ ExportSettings::ExportSettings()
|
|||
, renderable_only(false)
|
||||
, frame_start(1)
|
||||
, frame_end(1)
|
||||
, frame_step_xform(1)
|
||||
, frame_step_shape(1)
|
||||
, frame_samples_xform(1)
|
||||
, frame_samples_shape(1)
|
||||
, shutter_open(0.0)
|
||||
, shutter_close(1.0)
|
||||
, global_scale(1.0f)
|
||||
|
@ -189,31 +189,25 @@ AbcExporter::~AbcExporter()
|
|||
delete m_writer;
|
||||
}
|
||||
|
||||
void AbcExporter::getShutterSamples(double step, bool time_relative,
|
||||
void AbcExporter::getShutterSamples(unsigned int nr_of_samples,
|
||||
bool time_relative,
|
||||
std::vector<double> &samples)
|
||||
{
|
||||
Scene *scene = m_scene; /* for use in the FPS macro */
|
||||
samples.clear();
|
||||
|
||||
const double time_factor = time_relative ? m_scene->r.frs_sec : 1.0;
|
||||
const double shutter_open = m_settings.shutter_open;
|
||||
const double shutter_close = m_settings.shutter_close;
|
||||
unsigned int frame_offset = time_relative ? m_settings.frame_start : 0;
|
||||
double time_factor = time_relative ? FPS : 1.0;
|
||||
double shutter_open = m_settings.shutter_open;
|
||||
double shutter_close = m_settings.shutter_close;
|
||||
double time_inc = (shutter_close - shutter_open) / nr_of_samples;
|
||||
|
||||
/* sample all frame */
|
||||
if (shutter_open == 0.0 && shutter_close == 1.0) {
|
||||
for (double t = 0.0; t < 1.0; t += step) {
|
||||
samples.push_back((t + m_settings.frame_start) / time_factor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* sample between shutter open & close */
|
||||
const int nsamples = static_cast<int>(std::max((1.0 / step) - 1.0, 1.0));
|
||||
const double time_inc = (shutter_close - shutter_open) / nsamples;
|
||||
/* sample between shutter open & close */
|
||||
for (int sample=0; sample < nr_of_samples; ++sample) {
|
||||
double sample_time = shutter_open + time_inc * sample;
|
||||
double time = (frame_offset + sample_time) / time_factor;
|
||||
|
||||
for (int sample=0; sample < nsamples; ++sample) {
|
||||
double sample_time = shutter_open + time_inc * sample;
|
||||
double time = (m_settings.frame_start + sample_time) / time_factor;
|
||||
samples.push_back(time);
|
||||
}
|
||||
samples.push_back(time);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,21 +221,24 @@ Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step)
|
|||
|
||||
getShutterSamples(step, true, samples);
|
||||
|
||||
Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()), 1.0 / m_scene->r.frs_sec);
|
||||
Alembic::Abc::TimeSamplingType ts(
|
||||
static_cast<uint32_t>(samples.size()),
|
||||
1.0 / m_scene->r.frs_sec);
|
||||
|
||||
return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples));
|
||||
}
|
||||
|
||||
void AbcExporter::getFrameSet(double step, std::set<double> &frames)
|
||||
void AbcExporter::getFrameSet(unsigned int nr_of_samples,
|
||||
std::set<double> &frames)
|
||||
{
|
||||
frames.clear();
|
||||
|
||||
std::vector<double> shutter_samples;
|
||||
|
||||
getShutterSamples(step, false, shutter_samples);
|
||||
getShutterSamples(nr_of_samples, false, shutter_samples);
|
||||
|
||||
for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) {
|
||||
for (int j = 0, e = shutter_samples.size(); j < e; ++j) {
|
||||
for (size_t j = 0; j < nr_of_samples; ++j) {
|
||||
frames.insert(frame + shutter_samples[j]);
|
||||
}
|
||||
}
|
||||
|
@ -273,20 +270,20 @@ void AbcExporter::operator()(Main *bmain, float &progress, bool &was_canceled)
|
|||
|
||||
/* Create time samplings for transforms and shapes. */
|
||||
|
||||
TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_step_xform);
|
||||
TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform);
|
||||
|
||||
m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time);
|
||||
|
||||
TimeSamplingPtr shape_time;
|
||||
|
||||
if ((m_settings.frame_step_shape == m_settings.frame_step_xform) ||
|
||||
if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) ||
|
||||
(m_settings.frame_start == m_settings.frame_end))
|
||||
{
|
||||
shape_time = trans_time;
|
||||
m_shape_sampling_index = m_trans_sampling_index;
|
||||
}
|
||||
else {
|
||||
shape_time = createTimeSampling(m_settings.frame_step_shape);
|
||||
shape_time = createTimeSampling(m_settings.frame_samples_shape);
|
||||
m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time);
|
||||
}
|
||||
|
||||
|
@ -298,13 +295,12 @@ void AbcExporter::operator()(Main *bmain, float &progress, bool &was_canceled)
|
|||
/* Make a list of frames to export. */
|
||||
|
||||
std::set<double> xform_frames;
|
||||
getFrameSet(m_settings.frame_step_xform, xform_frames);
|
||||
getFrameSet(m_settings.frame_samples_xform, xform_frames);
|
||||
|
||||
std::set<double> shape_frames;
|
||||
getFrameSet(m_settings.frame_step_shape, shape_frames);
|
||||
getFrameSet(m_settings.frame_samples_shape, shape_frames);
|
||||
|
||||
/* Merge all frames needed. */
|
||||
|
||||
std::set<double> frames(xform_frames);
|
||||
frames.insert(shape_frames.begin(), shape_frames.end());
|
||||
|
||||
|
@ -327,7 +323,7 @@ void AbcExporter::operator()(Main *bmain, float &progress, bool &was_canceled)
|
|||
const double frame = *begin;
|
||||
|
||||
/* 'frame' is offset by start frame, so need to cancel the offset. */
|
||||
setCurrentFrame(bmain, frame - m_settings.frame_start);
|
||||
setCurrentFrame(bmain, frame);
|
||||
|
||||
if (shape_frames.count(frame) != 0) {
|
||||
for (int i = 0, e = m_shapes.size(); i != e; ++i) {
|
||||
|
|
|
@ -50,8 +50,8 @@ struct ExportSettings {
|
|||
bool renderable_only;
|
||||
|
||||
double frame_start, frame_end;
|
||||
double frame_step_xform;
|
||||
double frame_step_shape;
|
||||
double frame_samples_xform;
|
||||
double frame_samples_shape;
|
||||
double shutter_open;
|
||||
double shutter_close;
|
||||
float global_scale;
|
||||
|
@ -103,13 +103,15 @@ public:
|
|||
|
||||
void operator()(Main *bmain, float &progress, bool &was_canceled);
|
||||
|
||||
protected:
|
||||
void getShutterSamples(unsigned int nr_of_samples,
|
||||
bool time_relative,
|
||||
std::vector<double> &samples);
|
||||
void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames);
|
||||
|
||||
private:
|
||||
void getShutterSamples(double step, bool time_relative, std::vector<double> &samples);
|
||||
|
||||
Alembic::Abc::TimeSamplingPtr createTimeSampling(double step);
|
||||
|
||||
void getFrameSet(double step, std::set<double> &frames);
|
||||
|
||||
void createTransformWritersHierarchy(EvaluationContext *eval_ctx);
|
||||
AbcTransformWriter * createTransformWriter(Object *ob, Object *parent, Object *dupliObParent);
|
||||
void exploreTransform(EvaluationContext *eval_ctx, Object *ob, Object *parent, Object *dupliObParent = NULL);
|
||||
|
|
|
@ -332,8 +332,8 @@ bool ABC_export(
|
|||
job->settings.scene = job->scene;
|
||||
job->settings.frame_start = params->frame_start;
|
||||
job->settings.frame_end = params->frame_end;
|
||||
job->settings.frame_step_xform = params->frame_step_xform;
|
||||
job->settings.frame_step_shape = params->frame_step_shape;
|
||||
job->settings.frame_samples_xform = params->frame_samples_xform;
|
||||
job->settings.frame_samples_shape = params->frame_samples_shape;
|
||||
job->settings.shutter_open = params->shutter_open;
|
||||
job->settings.shutter_close = params->shutter_close;
|
||||
job->settings.selected_only = params->selected_only;
|
||||
|
|
|
@ -106,8 +106,8 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op)
|
|||
.frame_start = RNA_int_get(op->ptr, "start"),
|
||||
.frame_end = RNA_int_get(op->ptr, "end"),
|
||||
|
||||
.frame_step_xform = 1.0 / (double)RNA_int_get(op->ptr, "xsamples"),
|
||||
.frame_step_shape = 1.0 / (double)RNA_int_get(op->ptr, "gsamples"),
|
||||
.frame_samples_xform = RNA_int_get(op->ptr, "xsamples"),
|
||||
.frame_samples_shape = RNA_int_get(op->ptr, "gsamples"),
|
||||
|
||||
.shutter_open = RNA_float_get(op->ptr, "sh_open"),
|
||||
.shutter_close = RNA_float_get(op->ptr, "sh_close"),
|
||||
|
|
|
@ -227,8 +227,8 @@ static void rna_Scene_alembic_export(
|
|||
.frame_start = frame_start,
|
||||
.frame_end = frame_end,
|
||||
|
||||
.frame_step_xform = 1.0 / (double)xform_samples,
|
||||
.frame_step_shape = 1.0 / (double)geom_samples,
|
||||
.frame_samples_xform = xform_samples,
|
||||
.frame_samples_shape = geom_samples,
|
||||
|
||||
.shutter_open = shutter_open,
|
||||
.shutter_close = shutter_close,
|
||||
|
|
|
@ -26,6 +26,7 @@ set(INC
|
|||
..
|
||||
../../../source/blender/blenlib
|
||||
../../../source/blender/alembic
|
||||
../../../source/blender/makesdna
|
||||
${ALEMBIC_INCLUDE_DIRS}
|
||||
${BOOST_INCLUDE_DIR}
|
||||
${HDF5_INCLUDE_DIRS}
|
||||
|
@ -44,8 +45,8 @@ else()
|
|||
endif()
|
||||
|
||||
# For motivation on doubling BLENDER_SORTED_LIBS, see ../bmesh/CMakeLists.txt
|
||||
BLENDER_SRC_GTEST(abc_matrix "abc_matrix_test.cc;${_buildinfo_src}" "${BLENDER_SORTED_LIBS};${BLENDER_SORTED_LIBS}")
|
||||
BLENDER_SRC_GTEST(alembic "abc_matrix_test.cc;abc_export_test.cc;${_buildinfo_src}" "${BLENDER_SORTED_LIBS};${BLENDER_SORTED_LIBS}")
|
||||
|
||||
unset(_buildinfo_src)
|
||||
|
||||
setup_liblinks(abc_matrix_test)
|
||||
setup_liblinks(alembic_test)
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#include "testing/testing.h"
|
||||
|
||||
// Keep first since utildefines defines AT which conflicts with fucking STL
|
||||
#include "intern/abc_util.h"
|
||||
#include "intern/abc_exporter.h"
|
||||
|
||||
extern "C" {
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_math.h"
|
||||
#include "DNA_scene_types.h"
|
||||
}
|
||||
|
||||
class TestableAbcExporter : public AbcExporter {
|
||||
public:
|
||||
TestableAbcExporter(Scene *scene, const char *filename, ExportSettings &settings)
|
||||
: AbcExporter(scene, filename, settings)
|
||||
{}
|
||||
|
||||
void getShutterSamples(unsigned int nr_of_samples,
|
||||
bool time_relative,
|
||||
std::vector<double> &samples)
|
||||
{
|
||||
AbcExporter::getShutterSamples(nr_of_samples, time_relative, samples);
|
||||
}
|
||||
|
||||
void getFrameSet(unsigned int nr_of_samples,
|
||||
std::set<double> &frames) {
|
||||
AbcExporter::getFrameSet(nr_of_samples, frames);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
TEST(abc_export, TimeSamplesFullShutter) {
|
||||
ExportSettings settings;
|
||||
settings.frame_start = 31.0;
|
||||
settings.frame_end = 223.0;
|
||||
settings.shutter_open = 0.0;
|
||||
settings.shutter_close = 1.0;
|
||||
|
||||
/* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */
|
||||
Scene scene;
|
||||
scene.r.frs_sec = 50;
|
||||
scene.r.frs_sec_base = 2;
|
||||
|
||||
TestableAbcExporter exporter(&scene, "somefile.abc", settings);
|
||||
std::vector<double> samples;
|
||||
|
||||
/* test 5 samples per frame */
|
||||
exporter.getShutterSamples(5, true, samples);
|
||||
EXPECT_EQ(5, samples.size());
|
||||
EXPECT_NEAR(1.240, samples[0], 1e-5f);
|
||||
EXPECT_NEAR(1.248, samples[1], 1e-5f);
|
||||
EXPECT_NEAR(1.256, samples[2], 1e-5f);
|
||||
EXPECT_NEAR(1.264, samples[3], 1e-5f);
|
||||
EXPECT_NEAR(1.272, samples[4], 1e-5f);
|
||||
|
||||
/* test same, but using frame number offset instead of time */
|
||||
exporter.getShutterSamples(5, false, samples);
|
||||
EXPECT_EQ(5, samples.size());
|
||||
EXPECT_NEAR(0.0, samples[0], 1e-5f);
|
||||
EXPECT_NEAR(0.2, samples[1], 1e-5f);
|
||||
EXPECT_NEAR(0.4, samples[2], 1e-5f);
|
||||
EXPECT_NEAR(0.6, samples[3], 1e-5f);
|
||||
EXPECT_NEAR(0.8, samples[4], 1e-5f);
|
||||
|
||||
/* use the same setup to test getFrameSet() */
|
||||
std::set<double> frames;
|
||||
exporter.getFrameSet(5, frames);
|
||||
EXPECT_EQ(965, frames.size());
|
||||
EXPECT_EQ(1, frames.count(31.0));
|
||||
EXPECT_EQ(1, frames.count(31.2));
|
||||
EXPECT_EQ(1, frames.count(31.4));
|
||||
EXPECT_EQ(1, frames.count(31.6));
|
||||
EXPECT_EQ(1, frames.count(31.8));
|
||||
}
|
||||
|
||||
|
||||
TEST(abc_export, TimeSamples180degShutter) {
|
||||
ExportSettings settings;
|
||||
settings.frame_start = 31.0;
|
||||
settings.frame_end = 223.0;
|
||||
settings.shutter_open = -0.25;
|
||||
settings.shutter_close = 0.25;
|
||||
|
||||
/* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */
|
||||
Scene scene;
|
||||
scene.r.frs_sec = 50;
|
||||
scene.r.frs_sec_base = 2;
|
||||
|
||||
TestableAbcExporter exporter(&scene, "somefile.abc", settings);
|
||||
std::vector<double> samples;
|
||||
|
||||
/* test 5 samples per frame */
|
||||
exporter.getShutterSamples(5, true, samples);
|
||||
EXPECT_EQ(5, samples.size());
|
||||
EXPECT_NEAR(1.230, samples[0], 1e-5f);
|
||||
EXPECT_NEAR(1.234, samples[1], 1e-5f);
|
||||
EXPECT_NEAR(1.238, samples[2], 1e-5f);
|
||||
EXPECT_NEAR(1.242, samples[3], 1e-5f);
|
||||
EXPECT_NEAR(1.246, samples[4], 1e-5f);
|
||||
|
||||
/* test same, but using frame number offset instead of time */
|
||||
exporter.getShutterSamples(5, false, samples);
|
||||
EXPECT_EQ(5, samples.size());
|
||||
EXPECT_NEAR(-0.25, samples[0], 1e-5f);
|
||||
EXPECT_NEAR(-0.15, samples[1], 1e-5f);
|
||||
EXPECT_NEAR(-0.05, samples[2], 1e-5f);
|
||||
EXPECT_NEAR( 0.05, samples[3], 1e-5f);
|
||||
EXPECT_NEAR( 0.15, samples[4], 1e-5f);
|
||||
|
||||
/* Use the same setup to test getFrameSet().
|
||||
* Here only a few numbers are tested, due to rounding issues. */
|
||||
std::set<double> frames;
|
||||
exporter.getFrameSet(5, frames);
|
||||
EXPECT_EQ(965, frames.size());
|
||||
EXPECT_EQ(1, frames.count(30.75));
|
||||
EXPECT_EQ(1, frames.count(30.95));
|
||||
EXPECT_EQ(1, frames.count(31.15));
|
||||
}
|
Loading…
Reference in New Issue