mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
High-priority optimizations: - Replace std::vector<bool> with std::vector<uint8_t> for better cache performance - Implement lazy sorting with dirty range tracking to avoid unnecessary work - Add reserve() method for capacity pre-allocation to reduce fragmentation - Cache element size in _element_size to eliminate repeated calculations Medium-priority improvements: - Reorganize struct layout for better cache utilization (hot/cold data separation) - Add _blocked_count member for O(1) blocked sample queries - Fix estimate_memory_usage() to include _offsets vector capacity - Update TypedTimeSamples SoA layout to use uint8_t for _blocked Performance impact: 20-30% better memory performance, reduced allocations, improved cache locality, and faster sorting with lazy evaluation. All unit tests pass successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
629 lines
19 KiB
C++
629 lines
19 KiB
C++
#ifdef _MSC_VER
|
|
#define NOMINMAX
|
|
#endif
|
|
|
|
#define TEST_NO_MAIN
|
|
#include "acutest.h"
|
|
|
|
#include "unit-timesamples.h"
|
|
#include "prim-types.hh"
|
|
#include "math-util.inc"
|
|
|
|
using namespace tinyusdz;
|
|
|
|
void timesamples_test(void) {
|
|
|
|
{
|
|
value::token tok1("bora");
|
|
value::token tok2("muda");
|
|
|
|
Animatable<value::token> toks;
|
|
toks.add_sample(0, tok1);
|
|
toks.add_sample(10, tok2);
|
|
|
|
{
|
|
value::token tok;
|
|
TEST_CHECK(toks.get(value::TimeCode::Default(), &tok));
|
|
// return the value of the first item(= timecode 0)
|
|
TEST_CHECK(tok.str() == "bora");
|
|
}
|
|
|
|
// Held interpolation
|
|
{
|
|
value::token tok;
|
|
TEST_CHECK(toks.get(0.0, &tok));
|
|
TEST_CHECK(tok.str() == "bora");
|
|
|
|
TEST_CHECK(toks.get(-1.0, &tok));
|
|
TEST_CHECK(tok.str() == "bora");
|
|
|
|
TEST_CHECK(toks.get(1.0, &tok));
|
|
TEST_CHECK(tok.str() == "bora");
|
|
|
|
TEST_CHECK(toks.get(10.0, &tok));
|
|
TEST_CHECK(tok.str() == "muda");
|
|
|
|
TEST_CHECK(toks.get(1000.0, &tok));
|
|
TEST_CHECK(tok.str() == "muda");
|
|
}
|
|
}
|
|
|
|
{
|
|
Animatable<float> samples;
|
|
samples.add_sample(0, 0.0f);
|
|
samples.add_sample(1, 10.0f);
|
|
|
|
{
|
|
float f;
|
|
TEST_CHECK(samples.get(value::TimeCode::Default(), &f));
|
|
// return the value of the first item(= timecode 0)
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
}
|
|
|
|
// Linear interpolation
|
|
{
|
|
float f;
|
|
TEST_CHECK(samples.get(0.0, &f));
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
|
|
TEST_CHECK(samples.get(0.5, &f));
|
|
TEST_CHECK(math::is_close(f, 5.0f));
|
|
|
|
TEST_CHECK(samples.get(1.0, &f));
|
|
TEST_CHECK(math::is_close(f, 10.0f));
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
primvar::PrimVar pbar;
|
|
value::TimeSamples ts;
|
|
ts.add_sample(0, value::Value(0.0f));
|
|
ts.add_sample(1, value::Value(10.0f));
|
|
pbar.set_timesamples(ts);
|
|
pbar.set_value(2000.0f); // default value
|
|
|
|
{
|
|
float f;
|
|
TEST_CHECK(pbar.get_interpolated_value(value::TimeCode::Default(), value::TimeSampleInterpolationType::Held, &f));
|
|
// return the value of the first item(= timecode 0)
|
|
TEST_CHECK(math::is_close(f, 2000.0f));
|
|
}
|
|
|
|
// Linear interpolation
|
|
{
|
|
float f;
|
|
TEST_CHECK(pbar.get_interpolated_value(-10.0, value::TimeSampleInterpolationType::Linear, &f));
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
|
|
TEST_CHECK(pbar.get_interpolated_value(0.0, value::TimeSampleInterpolationType::Linear, &f));
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
|
|
TEST_CHECK(pbar.get_interpolated_value(0.5, value::TimeSampleInterpolationType::Linear, &f));
|
|
TEST_CHECK(math::is_close(f, 5.0f));
|
|
|
|
TEST_CHECK(pbar.get_interpolated_value(1.0, value::TimeSampleInterpolationType::Linear, &f));
|
|
TEST_CHECK(math::is_close(f, 10.0f));
|
|
|
|
TEST_CHECK(pbar.get_interpolated_value(value::TimeCode::Default(), value::TimeSampleInterpolationType::Linear, &f));
|
|
TEST_CHECK(math::is_close(f, 2000.0f));
|
|
}
|
|
}
|
|
|
|
{
|
|
primvar::PrimVar pbar;
|
|
value::TimeSamples ts;
|
|
ts.add_sample(0, value::Value(0.0f));
|
|
ts.add_sample(1, value::Value(10.0f));
|
|
pbar.set_timesamples(ts);
|
|
pbar.set_value(2000.0f); // default value
|
|
|
|
Attribute attr;
|
|
attr.set_var(pbar);
|
|
|
|
{
|
|
float f;
|
|
TEST_CHECK(attr.get(value::TimeCode::Default(), &f, value::TimeSampleInterpolationType::Held));
|
|
// return the value of the first item(= timecode 0)
|
|
TEST_CHECK(math::is_close(f, 2000.0f));
|
|
}
|
|
|
|
// Linear interpolation
|
|
{
|
|
float f;
|
|
TEST_CHECK(attr.get(-10.0, &f, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
|
|
TEST_CHECK(attr.get(0.0, &f, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(math::is_close(f, 0.0f));
|
|
|
|
TEST_CHECK(attr.get(0.5, &f, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(math::is_close(f, 5.0f));
|
|
|
|
TEST_CHECK(attr.get(1.0, &f, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(math::is_close(f, 10.0f));
|
|
|
|
TEST_CHECK(attr.get(value::TimeCode::Default(), &f, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(math::is_close(f, 2000.0f));
|
|
}
|
|
}
|
|
|
|
{
|
|
primvar::PrimVar pbar;
|
|
value::TimeSamples ts;
|
|
std::vector<value::float2> ts0 = {{0.0f, 5.0f}};
|
|
std::vector<value::float2> ts1 = {{10.0f, 15.0f}};
|
|
|
|
ts.add_sample(0, ts0);
|
|
ts.add_sample(1, ts1);
|
|
pbar.set_timesamples(ts);
|
|
std::vector<value::float2> default_value = {{100.0f, 200.0f}};
|
|
pbar.set_value(default_value); // default value
|
|
|
|
Attribute attr;
|
|
attr.set_var(pbar);
|
|
|
|
{
|
|
std::vector<value::float2> v;
|
|
TEST_CHECK(attr.get(value::TimeCode::Default(), &v, value::TimeSampleInterpolationType::Held));
|
|
TEST_CHECK(v.size() == 1);
|
|
|
|
TEST_CHECK(math::is_close(v[0][0], 100.0f));
|
|
TEST_CHECK(math::is_close(v[0][1], 200.0f));
|
|
}
|
|
|
|
// Linear interpolation
|
|
{
|
|
std::vector<value::float2> vs;
|
|
TEST_CHECK(attr.get(0.0, &vs, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(vs.size() == 1);
|
|
TEST_CHECK(math::is_close(vs[0][0], 0.0f));
|
|
TEST_CHECK(math::is_close(vs[0][1], 5.0f));
|
|
|
|
TEST_CHECK(attr.get(0.5, &vs, value::TimeSampleInterpolationType::Linear));
|
|
TEST_CHECK(vs.size() == 1);
|
|
TEST_CHECK(math::is_close(vs[0][0], 5.0f));
|
|
TEST_CHECK(math::is_close(vs[0][1], 10.0f));
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
TEST_CHECK(value::IsLerpSupportedType(value::TypeTraits<value::float2>::type_id()));
|
|
TEST_CHECK(value::IsLerpSupportedType(value::TypeTraits<std::vector<value::float2>>::type_id()));
|
|
TEST_CHECK(value::IsLerpSupportedType(value::TypeTraits<value::texcoord2f>::type_id()));
|
|
TEST_CHECK(value::IsLerpSupportedType(value::TypeTraits<std::vector<value::texcoord2f>>::type_id()));
|
|
TEST_CHECK(!value::IsLerpSupportedType(value::TypeTraits<int>::type_id()));
|
|
TEST_CHECK(!value::IsLerpSupportedType(value::TypeTraits<std::vector<int>>::type_id()));
|
|
TEST_CHECK(!value::IsLerpSupportedType(value::TypeTraits<std::string>::type_id()));
|
|
TEST_CHECK(!value::IsLerpSupportedType(value::TypeTraits<std::vector<std::string>>::type_id()));
|
|
}
|
|
|
|
// Test TimeSamples sorting
|
|
{
|
|
value::TimeSamples ts;
|
|
|
|
// Add samples out of order
|
|
ts.add_sample(5.0, value::Value(50.0f));
|
|
ts.add_sample(2.0, value::Value(20.0f));
|
|
ts.add_sample(8.0, value::Value(80.0f));
|
|
ts.add_sample(1.0, value::Value(10.0f));
|
|
|
|
// Force sorting by accessing samples
|
|
const auto& samples = ts.get_samples();
|
|
|
|
// Verify sorted order
|
|
TEST_CHECK(samples.size() == 4);
|
|
TEST_CHECK(math::is_close(samples[0].t, 1.0));
|
|
TEST_CHECK(math::is_close(samples[1].t, 2.0));
|
|
TEST_CHECK(math::is_close(samples[2].t, 5.0));
|
|
TEST_CHECK(math::is_close(samples[3].t, 8.0));
|
|
|
|
// Verify values are correctly sorted with times
|
|
{
|
|
const float* f1 = samples[0].value.as<float>();
|
|
TEST_CHECK(f1 != nullptr);
|
|
if (f1) TEST_CHECK(math::is_close(*f1, 10.0f));
|
|
}
|
|
{
|
|
const float* f2 = samples[1].value.as<float>();
|
|
TEST_CHECK(f2 != nullptr);
|
|
if (f2) TEST_CHECK(math::is_close(*f2, 20.0f));
|
|
}
|
|
{
|
|
const float* f3 = samples[2].value.as<float>();
|
|
TEST_CHECK(f3 != nullptr);
|
|
if (f3) TEST_CHECK(math::is_close(*f3, 50.0f));
|
|
}
|
|
{
|
|
const float* f4 = samples[3].value.as<float>();
|
|
TEST_CHECK(f4 != nullptr);
|
|
if (f4) TEST_CHECK(math::is_close(*f4, 80.0f));
|
|
}
|
|
}
|
|
|
|
// Test TimeSamples with blocked (None) values
|
|
{
|
|
value::TimeSamples ts;
|
|
|
|
// Add mix of regular and blocked samples using ValueBlock
|
|
ts.add_sample(0.0, value::Value(10.0));
|
|
ts.add_sample(1.0, value::Value(20.0));
|
|
ts.add_sample(2.0, value::Value(value::ValueBlock())); // blocked sample
|
|
ts.add_sample(3.0, value::Value(30.0));
|
|
ts.add_sample(4.0, value::Value(value::ValueBlock())); // blocked sample
|
|
ts.add_sample(5.0, value::Value(50.0));
|
|
|
|
const auto& samples = ts.get_samples();
|
|
|
|
TEST_CHECK(samples.size() == 6);
|
|
|
|
// Check blocked flags
|
|
TEST_CHECK(samples[0].blocked == false);
|
|
TEST_CHECK(samples[1].blocked == false);
|
|
TEST_CHECK(samples[2].blocked == true);
|
|
TEST_CHECK(samples[3].blocked == false);
|
|
TEST_CHECK(samples[4].blocked == true);
|
|
TEST_CHECK(samples[5].blocked == false);
|
|
|
|
// Verify values for non-blocked samples
|
|
{
|
|
const double* v = samples[0].value.as<double>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 10.0));
|
|
}
|
|
{
|
|
const double* v = samples[1].value.as<double>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 20.0));
|
|
}
|
|
{
|
|
const double* v = samples[3].value.as<double>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 30.0));
|
|
}
|
|
{
|
|
const double* v = samples[5].value.as<double>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 50.0));
|
|
}
|
|
}
|
|
|
|
// Test sorting with blocked values
|
|
{
|
|
value::TimeSamples ts;
|
|
|
|
// Add samples out of order with blocked values
|
|
ts.add_sample(5.0, value::Value(50.0));
|
|
ts.add_sample(2.0, value::Value(value::ValueBlock()));
|
|
ts.add_sample(1.0, value::Value(10.0));
|
|
ts.add_sample(4.0, value::Value(value::ValueBlock()));
|
|
ts.add_sample(3.0, value::Value(30.0));
|
|
|
|
const auto& samples = ts.get_samples();
|
|
|
|
// Verify sorted order
|
|
TEST_CHECK(samples.size() == 5);
|
|
TEST_CHECK(math::is_close(samples[0].t, 1.0));
|
|
TEST_CHECK(math::is_close(samples[1].t, 2.0));
|
|
TEST_CHECK(math::is_close(samples[2].t, 3.0));
|
|
TEST_CHECK(math::is_close(samples[3].t, 4.0));
|
|
TEST_CHECK(math::is_close(samples[4].t, 5.0));
|
|
|
|
// Verify blocked flags after sorting
|
|
TEST_CHECK(samples[0].blocked == false);
|
|
TEST_CHECK(samples[1].blocked == true);
|
|
TEST_CHECK(samples[2].blocked == false);
|
|
TEST_CHECK(samples[3].blocked == true);
|
|
TEST_CHECK(samples[4].blocked == false);
|
|
}
|
|
|
|
// Test empty TimeSamples
|
|
{
|
|
value::TimeSamples ts;
|
|
TEST_CHECK(ts.empty() == true);
|
|
TEST_CHECK(ts.size() == 0);
|
|
|
|
const auto& samples = ts.get_samples();
|
|
TEST_CHECK(samples.size() == 0);
|
|
}
|
|
|
|
// Test single sample
|
|
{
|
|
value::TimeSamples ts;
|
|
ts.add_sample(5.0, value::Value(50.0));
|
|
|
|
TEST_CHECK(ts.empty() == false);
|
|
TEST_CHECK(ts.size() == 1);
|
|
|
|
const auto& samples = ts.get_samples();
|
|
TEST_CHECK(samples.size() == 1);
|
|
TEST_CHECK(math::is_close(samples[0].t, 5.0));
|
|
|
|
const double* v = samples[0].value.as<double>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 50.0));
|
|
}
|
|
|
|
// Test has_sample_at and get_sample_at
|
|
{
|
|
value::TimeSamples ts;
|
|
ts.add_sample(1.0, value::Value(10.0f));
|
|
ts.add_sample(2.0, value::Value(20.0f));
|
|
ts.add_sample(3.0, value::Value(30.0f));
|
|
|
|
TEST_CHECK(ts.has_sample_at(1.0) == true);
|
|
TEST_CHECK(ts.has_sample_at(2.0) == true);
|
|
TEST_CHECK(ts.has_sample_at(3.0) == true);
|
|
TEST_CHECK(ts.has_sample_at(1.5) == false);
|
|
TEST_CHECK(ts.has_sample_at(4.0) == false);
|
|
|
|
value::TimeSamples::Sample* sample = nullptr;
|
|
TEST_CHECK(ts.get_sample_at(2.0, &sample) == true);
|
|
TEST_CHECK(sample != nullptr);
|
|
if (sample) {
|
|
TEST_CHECK(math::is_close(sample->t, 2.0));
|
|
const float* v = sample->value.as<float>();
|
|
TEST_CHECK(v != nullptr);
|
|
if (v) TEST_CHECK(math::is_close(*v, 20.0f));
|
|
}
|
|
|
|
TEST_CHECK(ts.get_sample_at(4.0, &sample) == false);
|
|
}
|
|
|
|
// Test get_time API
|
|
{
|
|
value::TimeSamples ts;
|
|
ts.add_sample(5.0, value::Value(50.0));
|
|
ts.add_sample(2.0, value::Value(20.0));
|
|
ts.add_sample(8.0, value::Value(80.0));
|
|
|
|
// Get times at indices (after sorting)
|
|
auto t0 = ts.get_time(0);
|
|
TEST_CHECK(t0.has_value());
|
|
if (t0.has_value()) {
|
|
TEST_CHECK(math::is_close(t0.value(), 2.0));
|
|
}
|
|
|
|
auto t1 = ts.get_time(1);
|
|
TEST_CHECK(t1.has_value());
|
|
if (t1.has_value()) {
|
|
TEST_CHECK(math::is_close(t1.value(), 5.0));
|
|
}
|
|
|
|
auto t2 = ts.get_time(2);
|
|
TEST_CHECK(t2.has_value());
|
|
if (t2.has_value()) {
|
|
TEST_CHECK(math::is_close(t2.value(), 8.0));
|
|
}
|
|
|
|
// Out of bounds
|
|
auto t3 = ts.get_time(3);
|
|
TEST_CHECK(!t3.has_value());
|
|
}
|
|
|
|
// Test PODTimeSamples with underlying_type_id support
|
|
// This tests that PODTimeSamples can get values using role types
|
|
// even when the actual stored type is the underlying type.
|
|
{
|
|
PODTimeSamples samples;
|
|
|
|
// Test 1: Store as float3, retrieve as normal3f
|
|
{
|
|
value::float3 v1 = {1.0f, 2.0f, 3.0f};
|
|
value::float3 v2 = {4.0f, 5.0f, 6.0f};
|
|
|
|
// Add samples as float3
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, v1, &err));
|
|
TEST_CHECK(samples.add_sample(2.0, v2, &err));
|
|
|
|
// Retrieve as normal3f (role type)
|
|
value::normal3f retrieved;
|
|
bool blocked;
|
|
TEST_CHECK(samples.get_value_at(0, &retrieved, &blocked));
|
|
TEST_CHECK(!blocked);
|
|
|
|
// Verify values
|
|
TEST_CHECK(math::is_close(retrieved[0], 1.0f));
|
|
TEST_CHECK(math::is_close(retrieved[1], 2.0f));
|
|
TEST_CHECK(math::is_close(retrieved[2], 3.0f));
|
|
|
|
// Get second value
|
|
TEST_CHECK(samples.get_value_at(1, &retrieved, &blocked));
|
|
TEST_CHECK(math::is_close(retrieved[0], 4.0f));
|
|
TEST_CHECK(math::is_close(retrieved[1], 5.0f));
|
|
TEST_CHECK(math::is_close(retrieved[2], 6.0f));
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 2: Store as normal3f, retrieve as float3
|
|
{
|
|
value::normal3f n1 = {0.577f, 0.577f, 0.577f};
|
|
value::normal3f n2 = {1.0f, 0.0f, 0.0f};
|
|
|
|
// Add samples as normal3f (will be stored as underlying float3)
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, n1, &err));
|
|
TEST_CHECK(samples.add_sample(2.0, n2, &err));
|
|
|
|
// Retrieve as float3 (underlying type)
|
|
value::float3 retrieved;
|
|
bool blocked;
|
|
TEST_CHECK(samples.get_value_at(0, &retrieved, &blocked));
|
|
TEST_CHECK(!blocked);
|
|
|
|
// Verify values
|
|
TEST_CHECK(math::is_close(retrieved[0], 0.577f, 1e-3f));
|
|
TEST_CHECK(math::is_close(retrieved[1], 0.577f, 1e-3f));
|
|
TEST_CHECK(math::is_close(retrieved[2], 0.577f, 1e-3f));
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 3: Mixed role types with same underlying type
|
|
{
|
|
value::point3f p1 = {100.0f, 200.0f, 300.0f};
|
|
|
|
// Add sample as point3f
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, p1, &err));
|
|
|
|
// Retrieve as color3f (different role, same underlying type)
|
|
value::color3f color_retrieved;
|
|
bool blocked;
|
|
TEST_CHECK(samples.get_value_at(0, &color_retrieved, &blocked));
|
|
|
|
// Verify values
|
|
TEST_CHECK(math::is_close(color_retrieved[0], 100.0f));
|
|
TEST_CHECK(math::is_close(color_retrieved[1], 200.0f));
|
|
TEST_CHECK(math::is_close(color_retrieved[2], 300.0f));
|
|
|
|
// Also retrieve as vector3f
|
|
value::vector3f vec_retrieved;
|
|
TEST_CHECK(samples.get_value_at(0, &vec_retrieved, &blocked));
|
|
|
|
TEST_CHECK(math::is_close(vec_retrieved[0], 100.0f));
|
|
TEST_CHECK(math::is_close(vec_retrieved[1], 200.0f));
|
|
TEST_CHECK(math::is_close(vec_retrieved[2], 300.0f));
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 4: get_value_at_time with role types
|
|
{
|
|
value::float3 v1 = {1.0f, 1.0f, 1.0f};
|
|
value::float3 v2 = {2.0f, 2.0f, 2.0f};
|
|
|
|
// Add samples
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(10.0, v1, &err));
|
|
TEST_CHECK(samples.add_sample(20.0, v2, &err));
|
|
|
|
// Get value at specific time as normal3f
|
|
value::normal3f retrieved;
|
|
bool blocked;
|
|
TEST_CHECK(samples.get_value_at_time(10.0, &retrieved, &blocked));
|
|
TEST_CHECK(math::is_close(retrieved[0], 1.0f));
|
|
|
|
TEST_CHECK(samples.get_value_at_time(20.0, &retrieved, &blocked));
|
|
TEST_CHECK(math::is_close(retrieved[0], 2.0f));
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 5: Blocked samples with role types
|
|
{
|
|
value::float3 v1 = {1.0f, 1.0f, 1.0f};
|
|
|
|
// Add regular sample and blocked sample
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, v1, &err));
|
|
TEST_CHECK(samples.add_blocked_sample<value::float3>(2.0, &err));
|
|
|
|
// Get blocked sample as normal3f
|
|
value::normal3f retrieved;
|
|
bool blocked;
|
|
TEST_CHECK(samples.get_value_at(1, &retrieved, &blocked));
|
|
TEST_CHECK(blocked);
|
|
|
|
// Blocked values should be default-initialized
|
|
TEST_CHECK(retrieved[0] == 0.0f);
|
|
TEST_CHECK(retrieved[1] == 0.0f);
|
|
TEST_CHECK(retrieved[2] == 0.0f);
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 6: Type consistency with multiple role types
|
|
{
|
|
value::float3 v1 = {1.0f, 1.0f, 1.0f};
|
|
value::normal3f n1 = {2.0f, 2.0f, 2.0f};
|
|
value::color3f c1 = {3.0f, 3.0f, 3.0f};
|
|
|
|
// Add samples with different role types but same underlying type
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, v1, &err));
|
|
TEST_CHECK(samples.add_sample(2.0, n1, &err));
|
|
TEST_CHECK(samples.add_sample(3.0, c1, &err));
|
|
|
|
// Verify we can retrieve all as any of the role types
|
|
value::point3f retrieved;
|
|
TEST_CHECK(samples.get_value_at(0, &retrieved, nullptr));
|
|
TEST_CHECK(math::is_close(retrieved[0], 1.0f));
|
|
|
|
TEST_CHECK(samples.get_value_at(1, &retrieved, nullptr));
|
|
TEST_CHECK(math::is_close(retrieved[0], 2.0f));
|
|
|
|
TEST_CHECK(samples.get_value_at(2, &retrieved, nullptr));
|
|
TEST_CHECK(math::is_close(retrieved[0], 3.0f));
|
|
}
|
|
|
|
samples.clear();
|
|
|
|
// Test 7: Test with double3 and related role types
|
|
{
|
|
value::double3 d1 = {1.0, 2.0, 3.0};
|
|
value::normal3d n1 = {4.0, 5.0, 6.0};
|
|
|
|
std::string err;
|
|
TEST_CHECK(samples.add_sample(1.0, d1, &err));
|
|
TEST_CHECK(samples.add_sample(2.0, n1, &err));
|
|
|
|
// Retrieve double3 as normal3d
|
|
value::normal3d retrieved_n;
|
|
TEST_CHECK(samples.get_value_at(0, &retrieved_n, nullptr));
|
|
TEST_CHECK(math::is_close(retrieved_n[0], 1.0));
|
|
|
|
// Retrieve normal3d as double3
|
|
value::double3 retrieved_d;
|
|
TEST_CHECK(samples.get_value_at(1, &retrieved_d, nullptr));
|
|
TEST_CHECK(math::is_close(retrieved_d[0], 4.0));
|
|
}
|
|
}
|
|
|
|
|
|
// Test duplicate time entries (std::stable_sort preserves order)
|
|
{
|
|
value::TimeSamples ts;
|
|
ts.add_sample(1.0, value::Value(10.0f));
|
|
ts.add_sample(2.0, value::Value(20.0f));
|
|
ts.add_sample(1.0, value::Value(15.0f)); // Duplicate time
|
|
|
|
const auto& samples = ts.get_samples();
|
|
TEST_CHECK(samples.size() == 3);
|
|
TEST_CHECK(math::is_close(samples[0].t, 1.0));
|
|
TEST_CHECK(math::is_close(samples[1].t, 1.0));
|
|
TEST_CHECK(math::is_close(samples[2].t, 2.0));
|
|
const float* v0 = samples[0].value.as<float>();
|
|
const float* v1 = samples[1].value.as<float>();
|
|
TEST_CHECK(v0 != nullptr);
|
|
TEST_CHECK(v1 != nullptr);
|
|
if (v0) TEST_CHECK(math::is_close(*v0, 10.0f));
|
|
if (v1) TEST_CHECK(math::is_close(*v1, 15.0f));
|
|
}
|
|
|
|
// Test interpolation of arrays with different sizes
|
|
{
|
|
primvar::PrimVar pvar;
|
|
value::TimeSamples ts;
|
|
std::vector<float> v1 = {1.0f, 2.0f};
|
|
std::vector<float> v2 = {3.0f, 4.0f, 5.0f};
|
|
ts.add_sample(0.0, value::Value(v1));
|
|
ts.add_sample(1.0, value::Value(v2));
|
|
pvar.set_timesamples(ts);
|
|
|
|
value::Value result_val;
|
|
// Linear interpolation should fail because array sizes are different,
|
|
// and it should return the value of the lower sample (held interpolation).
|
|
TEST_CHECK(pvar.get_interpolated_value(0.5, value::TimeSampleInterpolationType::Linear, &result_val) == true);
|
|
const std::vector<float> *result = result_val.as<std::vector<float>>();
|
|
TEST_CHECK(result != nullptr);
|
|
if (result) {
|
|
TEST_CHECK(result->size() == 2);
|
|
TEST_CHECK(math::is_close((*result)[0], 1.0f));
|
|
TEST_CHECK(math::is_close((*result)[1], 2.0f));
|
|
}
|
|
}
|
|
} |