cesium-native/CesiumGltf/test/TestPropertyTableView.cpp

5812 lines
214 KiB
C++

#include "makeEnumValue.h"
#include <CesiumGltf/Buffer.h>
#include <CesiumGltf/BufferView.h>
#include <CesiumGltf/Class.h>
#include <CesiumGltf/ClassProperty.h>
#include <CesiumGltf/EnumValue.h>
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
#include <CesiumGltf/Model.h>
#include <CesiumGltf/PropertyArrayView.h>
#include <CesiumGltf/PropertyTable.h>
#include <CesiumGltf/PropertyTableProperty.h>
#include <CesiumGltf/PropertyTablePropertyView.h>
#include <CesiumGltf/PropertyTableView.h>
#include <CesiumGltf/PropertyTransformations.h>
#include <CesiumGltf/Schema.h>
#include <doctest/doctest.h>
#include <glm/common.hpp>
#include <glm/ext/matrix_double2x2.hpp>
#include <glm/ext/matrix_float2x2.hpp>
#include <glm/ext/vector_double3.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/ext/vector_int2.hpp>
#include <glm/ext/vector_int3.hpp>
#include <glm/ext/vector_int3_sized.hpp>
#include <glm/ext/vector_uint2.hpp>
#include <glm/ext/vector_uint3.hpp>
#include <glm/ext/vector_uint3_sized.hpp>
#include <glm/fwd.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string_view>
#include <vector>
using namespace CesiumGltf;
using namespace CesiumNativeTests;
namespace {
template <typename T>
void addBufferToModel(Model& model, const std::vector<T>& values) {
Buffer& valueBuffer = model.buffers.emplace_back();
valueBuffer.cesium.data.resize(values.size() * sizeof(T));
valueBuffer.byteLength = static_cast<int64_t>(valueBuffer.cesium.data.size());
std::memcpy(
valueBuffer.cesium.data.data(),
values.data(),
valueBuffer.cesium.data.size());
BufferView& valueBufferView = model.bufferViews.emplace_back();
valueBufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
valueBufferView.byteOffset = 0;
valueBufferView.byteLength = valueBuffer.byteLength;
}
} // namespace
TEST_CASE("Test PropertyTableView on model without EXT_structural_metadata "
"extension") {
Model model;
// Create an erroneously isolated property table.
PropertyTable propertyTable;
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(10);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(0);
PropertyTableView view(model, propertyTable);
REQUIRE(
view.status() == PropertyTableViewStatus::ErrorMissingMetadataExtension);
REQUIRE(view.size() == 0);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test PropertyTableView on model without metadata schema") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(10);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(0);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::ErrorMissingSchema);
REQUIRE(view.size() == 0);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test property table with nonexistent class") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "I Don't Exist";
propertyTable.count = static_cast<int64_t>(10);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(0);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::ErrorClassNotFound);
REQUIRE(view.size() == 0);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test scalar PropertyTableProperty") {
Model model;
std::vector<uint32_t> values = {12, 34, 30, 11, 34, 34, 11, 33, 122, 33};
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(uint32Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(uint32Property.size() > 0);
for (int64_t i = 0; i < uint32Property.size(); ++i) {
REQUIRE(uint32Property.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(uint32Property.get(i) == uint32Property.getRaw(i));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<glm::uvec3> uvec3Invalid =
view.getPropertyView<glm::uvec3>("TestClassProperty");
REQUIRE(
uvec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::umat3x3> umat3x3Invalid =
view.getPropertyView<glm::umat3x3>("TestClassProperty");
REQUIRE(
umat3x3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<bool> boolInvalid =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(
boolInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<std::string_view> stringInvalid =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<uint8_t> uint8Invalid =
view.getPropertyView<uint8_t>("TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<int32_t> int32Invalid =
view.getPropertyView<int32_t>("TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<uint64_t> uint64Invalid =
view.getPropertyView<uint64_t>("TestClassProperty");
REQUIRE(
uint64Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyTablePropertyView<uint32_t, true> uint32NormalizedInvalid =
view.getPropertyView<uint32_t, true>("TestClassProperty");
REQUIRE(
uint32NormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[valueBufferViewIndex].buffer = 2;
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBuffer);
}
SUBCASE("Wrong buffer view index") {
propertyTableProperty.values = -1;
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBufferView);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[valueBufferIndex].cesium.data.resize(12);
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Buffer view length isn't multiple of sizeof(T)") {
model.bufferViews[valueBufferViewIndex].byteLength = 13;
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Buffer view length doesn't match with propertyTableCount") {
model.bufferViews[valueBufferViewIndex].byteLength = 12;
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test scalar PropertyTableProperty (normalized)") {
Model model;
std::vector<int16_t> values = {-128, 0, 32, 2340, -1234, 127};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::INT16;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(classProperty->normalized);
REQUIRE(!classProperty->array);
SUBCASE("Access correct type") {
PropertyTablePropertyView<int16_t, true> int16Property =
view.getPropertyView<int16_t, true>("TestClassProperty");
REQUIRE(int16Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(int16Property.size() > 0);
for (int64_t i = 0; i < int16Property.size(); ++i) {
auto value = int16Property.getRaw(i);
REQUIRE(value == values[static_cast<size_t>(i)]);
REQUIRE(int16Property.get(i) == normalize(value));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<glm::i16vec3, true> i16vec3Invalid =
view.getPropertyView<glm::i16vec3, true>("TestClassProperty");
REQUIRE(
i16vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::i16mat3x3, true> i16mat3x3Invalid =
view.getPropertyView<glm::i16mat3x3, true>("TestClassProperty");
REQUIRE(
i16mat3x3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<uint16_t, true> uint16Invalid =
view.getPropertyView<uint16_t, true>("TestClassProperty");
REQUIRE(
uint16Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<int32_t, true> int32Invalid =
view.getPropertyView<int32_t, true>("TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as array") {
PropertyTablePropertyView<PropertyArrayView<int16_t>, true> arrayInvalid =
view.getPropertyView<PropertyArrayView<int16_t>, true>(
"TestClassProperty");
REQUIRE(
arrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyTablePropertyView<int16_t> nonNormalizedInvalid =
view.getPropertyView<int16_t>("TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as double") {
PropertyTablePropertyView<double> doubleInvalid =
view.getPropertyView<double>("TestClassProperty");
REQUIRE(
doubleInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
}
TEST_CASE("Test vecN PropertyTableProperty") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(-12, 34, 30),
glm::ivec3(11, 73, 0),
glm::ivec3(-2, 6, 12),
glm::ivec3(-4, 8, -13)};
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(ivec3Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(ivec3Property.size() > 0);
for (int64_t i = 0; i < ivec3Property.size(); ++i) {
REQUIRE(ivec3Property.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(ivec3Property.get(i) == ivec3Property.getRaw(i));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<int32_t> int32Invalid =
view.getPropertyView<int32_t>("TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::ivec2> ivec2Invalid =
view.getPropertyView<glm::ivec2>("TestClassProperty");
REQUIRE(
ivec2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::i32mat3x3> i32mat3x3Invalid =
view.getPropertyView<glm::i32mat3x3>("TestClassProperty");
REQUIRE(
i32mat3x3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<bool> boolInvalid =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(
boolInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<std::string_view> stringInvalid =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<glm::u8vec3> u8vec3Invalid =
view.getPropertyView<glm::u8vec3>("TestClassProperty");
REQUIRE(
u8vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::i16vec3> i16vec3Invalid =
view.getPropertyView<glm::i16vec3>("TestClassProperty");
REQUIRE(
i16vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::vec3> vec3Invalid =
view.getPropertyView<glm::vec3>("TestClassProperty");
REQUIRE(
vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as array") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> ivec3ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
ivec3ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyTablePropertyView<glm::ivec3, true> normalizedInvalid =
view.getPropertyView<glm::ivec3, true>("TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[valueBufferViewIndex].buffer = 2;
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBuffer);
}
SUBCASE("Wrong buffer view index") {
propertyTableProperty.values = -1;
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBufferView);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[valueBufferIndex].cesium.data.resize(12);
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Property.status() ==
PropertyTablePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Buffer view length isn't multiple of sizeof(T)") {
model.bufferViews[valueBufferViewIndex].byteLength = 11;
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Buffer view length doesn't match with propertyTableCount") {
model.bufferViews[valueBufferViewIndex].byteLength = 12;
PropertyTablePropertyView<glm::ivec3> ivec3Property =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test vecN PropertyTableProperty (normalized)") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(-12, 34, 30),
glm::ivec3(11, 73, 0),
glm::ivec3(-2, 6, 12),
glm::ivec3(-4, 8, -13)};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<glm::ivec3, true> ivec3Property =
view.getPropertyView<glm::ivec3, true>("TestClassProperty");
REQUIRE(ivec3Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(ivec3Property.size() > 0);
for (int64_t i = 0; i < ivec3Property.size(); ++i) {
auto value = ivec3Property.getRaw(i);
REQUIRE(value == values[static_cast<size_t>(i)]);
REQUIRE(ivec3Property.get(i) == normalize(value));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<int32_t, true> int32Invalid =
view.getPropertyView<int32_t, true>("TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::ivec2, true> ivec2Invalid =
view.getPropertyView<glm::ivec2, true>("TestClassProperty");
REQUIRE(
ivec2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::i32mat3x3, true> i32mat3x3Invalid =
view.getPropertyView<glm::i32mat3x3, true>("TestClassProperty");
REQUIRE(
i32mat3x3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<glm::u8vec3, true> u8vec3Invalid =
view.getPropertyView<glm::u8vec3, true>("TestClassProperty");
REQUIRE(
u8vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::i16vec3, true> i16vec3Invalid =
view.getPropertyView<glm::i16vec3, true>("TestClassProperty");
REQUIRE(
i16vec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as array") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> ivec3ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
ivec3ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyTablePropertyView<glm::ivec3> nonNormalizedInvalid =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as dvec3") {
PropertyTablePropertyView<glm::dvec3> dvec3Invalid =
view.getPropertyView<glm::dvec3>("TestClassProperty");
REQUIRE(
dvec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
}
TEST_CASE("Test matN PropertyTableProperty") {
Model model;
// clang-format off
std::vector<glm::umat2x2> values = {
glm::umat2x2(
12, 34,
30, 1),
glm::umat2x2(
11, 8,
73, 102),
glm::umat2x2(
1, 0,
63, 2),
glm::umat2x2(
4, 8,
3, 23)};
// clang-format on
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(umat2x2Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(umat2x2Property.size() > 0);
for (int64_t i = 0; i < umat2x2Property.size(); ++i) {
REQUIRE(umat2x2Property.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(umat2x2Property.get(i) == umat2x2Property.getRaw(i));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<uint32_t> uint32Invalid =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::uvec2> uvec2Invalid =
view.getPropertyView<glm::uvec2>("TestClassProperty");
REQUIRE(
uvec2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::umat4x4> umat4x4Invalid =
view.getPropertyView<glm::umat4x4>("TestClassProperty");
REQUIRE(
umat4x4Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<bool> boolInvalid =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(
boolInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<std::string_view> stringInvalid =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<glm::u8mat2x2> u8mat2x2Invalid =
view.getPropertyView<glm::u8mat2x2>("TestClassProperty");
REQUIRE(
u8mat2x2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::imat2x2> imat2x2Invalid =
view.getPropertyView<glm::imat2x2>("TestClassProperty");
REQUIRE(
imat2x2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::mat2> mat2Invalid =
view.getPropertyView<glm::mat2>("TestClassProperty");
REQUIRE(
mat2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as array") {
PropertyTablePropertyView<PropertyArrayView<glm::umat2x2>> arrayInvalid =
view.getPropertyView<PropertyArrayView<glm::umat2x2>>(
"TestClassProperty");
REQUIRE(
arrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyTablePropertyView<glm::umat2x2, true> normalizedInvalid =
view.getPropertyView<glm::umat2x2, true>("TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[valueBufferViewIndex].buffer = 2;
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
umat2x2Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBuffer);
}
SUBCASE("Wrong buffer view index") {
propertyTableProperty.values = -1;
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
umat2x2Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidValueBufferView);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[valueBufferIndex].cesium.data.resize(sizeof(glm::umat2x2));
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
umat2x2Property.status() ==
PropertyTablePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Buffer view length isn't multiple of sizeof(T)") {
model.bufferViews[valueBufferViewIndex].byteLength =
sizeof(glm::umat2x2) * 4 - 1;
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
umat2x2Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Buffer view length doesn't match with propertyTableCount") {
model.bufferViews[valueBufferViewIndex].byteLength = sizeof(glm::umat2x2);
PropertyTablePropertyView<glm::umat2x2> umat2x2Property =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
umat2x2Property.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test matN PropertyTableProperty (normalized)") {
Model model;
// clang-format off
std::vector<glm::umat2x2> values = {
glm::umat2x2(
12, 34,
30, 1),
glm::umat2x2(
11, 8,
73, 102),
glm::umat2x2(
1, 0,
63, 2),
glm::umat2x2(
4, 8,
3, 23)};
// clang-format on
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<glm::umat2x2, true> umat2x2Property =
view.getPropertyView<glm::umat2x2, true>("TestClassProperty");
REQUIRE(umat2x2Property.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(umat2x2Property.size() > 0);
for (int64_t i = 0; i < umat2x2Property.size(); ++i) {
auto value = umat2x2Property.getRaw(i);
REQUIRE(value == values[static_cast<size_t>(i)]);
REQUIRE(umat2x2Property.get(i) == normalize(value));
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<uint32_t, true> uint32Invalid =
view.getPropertyView<uint32_t, true>("TestClassProperty");
REQUIRE(
uint32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::uvec2, true> uvec2Invalid =
view.getPropertyView<glm::uvec2, true>("TestClassProperty");
REQUIRE(
uvec2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<glm::umat4x4, true> umat4x4Invalid =
view.getPropertyView<glm::umat4x4, true>("TestClassProperty");
REQUIRE(
umat4x4Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<glm::u8mat2x2, true> u8mat2x2Invalid =
view.getPropertyView<glm::u8mat2x2, true>("TestClassProperty");
REQUIRE(
u8mat2x2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyTablePropertyView<glm::imat2x2, true> imat2x2Invalid =
view.getPropertyView<glm::imat2x2, true>("TestClassProperty");
REQUIRE(
imat2x2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as array") {
PropertyTablePropertyView<PropertyArrayView<glm::umat2x2>, true>
arrayInvalid =
view.getPropertyView<PropertyArrayView<glm::umat2x2>, true>(
"TestClassProperty");
REQUIRE(
arrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyTablePropertyView<glm::umat2x2> nonNormalizedInvalid =
view.getPropertyView<glm::umat2x2>("TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as dmat2") {
PropertyTablePropertyView<glm::dmat2> dmat2Invalid =
view.getPropertyView<glm::dmat2>("TestClassProperty");
REQUIRE(
dmat2Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
}
TEST_CASE("Test boolean PropertyTableProperty") {
Model model;
int64_t instanceCount = 21;
std::vector<bool> expected;
std::vector<uint8_t> values;
values.resize(3);
for (int64_t i = 0; i < instanceCount; ++i) {
if (i % 2 == 0) {
expected.emplace_back(true);
} else {
expected.emplace_back(false);
}
uint8_t expectedValue = expected.back();
int64_t byteIndex = i / 8;
int64_t bitIndex = i % 8;
values[static_cast<size_t>(byteIndex)] = static_cast<uint8_t>(
(expectedValue << bitIndex) | values[static_cast<size_t>(byteIndex)]);
}
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::BOOLEAN;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(instanceCount);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::BOOLEAN);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
SUBCASE("Access correct type") {
PropertyTablePropertyView<bool> boolProperty =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(boolProperty.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(boolProperty.size() == instanceCount);
for (int64_t i = 0; i < boolProperty.size(); ++i) {
bool expectedValue = expected[static_cast<size_t>(i)];
REQUIRE(boolProperty.getRaw(i) == expectedValue);
REQUIRE(boolProperty.get(i) == expectedValue);
}
}
SUBCASE("Buffer size doesn't match with propertyTableCount") {
propertyTable.count = 66;
PropertyTablePropertyView<bool> boolProperty =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(
boolProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test string PropertyTableProperty") {
Model model;
std::vector<std::string> expected{"What's up", "Test_0", "Test_1", "", "Hi"};
size_t totalBytes = 0;
for (const std::string& expectedValue : expected) {
totalBytes += expectedValue.size();
}
std::vector<std::byte> stringOffsets(
(expected.size() + 1) * sizeof(uint32_t));
std::vector<std::byte> values(totalBytes);
uint32_t* offsetValue = reinterpret_cast<uint32_t*>(stringOffsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
const std::string& expectedValue = expected[i];
std::memcpy(
values.data() + offsetValue[i],
expectedValue.c_str(),
expectedValue.size());
offsetValue[i + 1] =
offsetValue[i] + static_cast<uint32_t>(expectedValue.size());
}
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, stringOffsets);
size_t offsetBufferIndex = model.buffers.size() - 1;
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::STRING;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.stringOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::STRING);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
SUBCASE("Access correct type") {
PropertyTablePropertyView<std::string_view> stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(stringProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
REQUIRE(stringProperty.getRaw(static_cast<int64_t>(i)) == expected[i]);
REQUIRE(stringProperty.get(static_cast<int64_t>(i)) == expected[i]);
}
}
SUBCASE("Wrong array type") {
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringArrayInvalid =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
stringArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Wrong offset type") {
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT8;
PropertyTablePropertyView<std::string_view> stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT64;
stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.stringOffsetType = "NONSENSE";
stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetType);
propertyTableProperty.stringOffsetType = "";
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetType);
}
SUBCASE("Offset values are not sorted ascending") {
uint32_t* offset = reinterpret_cast<uint32_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[2] =
static_cast<uint32_t>(model.buffers[valueBufferIndex].byteLength + 4);
PropertyTablePropertyView<std::string_view> stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorStringOffsetsNotSorted);
}
SUBCASE("Offset value points outside of value buffer") {
uint32_t* offset = reinterpret_cast<uint32_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] =
static_cast<uint32_t>(model.buffers[valueBufferIndex].byteLength + 4);
PropertyTablePropertyView<std::string_view> stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorStringOffsetOutOfBounds);
}
}
TEST_CASE("Test enum PropertyTableProperty") {
Model model;
const uint64_t expectedUint = 0xABADCAFEDEADBEEF;
std::vector<int64_t> expected{0, 1, 2, static_cast<int64_t>(expectedUint)};
std::vector<std::string> expectedNames{"Foo", "Bar", "Baz", "Uint64"};
addBufferToModel(model, expected);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
schema.enums.emplace("TestEnum", Enum{});
Enum& enumDef = schema.enums["TestEnum"];
enumDef.name = "Test";
enumDef.description = "An example enum";
enumDef.values = std::vector<EnumValue>{
makeEnumValue("Foo", 0),
makeEnumValue("Bar", 1),
makeEnumValue("Baz", 2),
makeEnumValue("Uint64", static_cast<int64_t>(expectedUint))};
enumDef.valueType = Enum::ValueType::UINT64;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::ENUM;
testClassProperty.enumType = "TestEnum";
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::ENUM);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->enumType == "TestEnum");
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyTablePropertyView<uint64_t> enumProperty =
view.getPropertyView<uint64_t>("TestClassProperty");
REQUIRE(enumProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
REQUIRE(enumProperty.getRaw(static_cast<int64_t>(i)) == expected[i]);
REQUIRE(enumProperty.get(static_cast<int64_t>(i)) == expected[i]);
REQUIRE(
enumDef.getName(*enumProperty.get(static_cast<int64_t>(i))) ==
expectedNames[i]);
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<int64_t> enumProperty =
view.getPropertyView<int64_t>("TestClassProperty");
REQUIRE(
enumProperty.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Wrong array type") {
PropertyTablePropertyView<PropertyArrayView<uint64_t>> enumArrayInvalid =
view.getPropertyView<PropertyArrayView<uint64_t>>("TestClassProperty");
REQUIRE(
enumArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Valid uint64_t -> int64_t -> uint64_t round trip") {
PropertyTablePropertyView<uint64_t> enumProperty =
view.getPropertyView<uint64_t>("TestClassProperty");
REQUIRE(enumProperty.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(enumProperty.getRaw(3) == static_cast<int64_t>(expectedUint));
REQUIRE(static_cast<uint64_t>(enumProperty.getRaw(3)) == expectedUint);
}
}
TEST_CASE("Test fixed-length enum array") {
Model model;
std::vector<uint16_t> values = {0, 1, 2, 1, 2, 3, 3, 4, 5, 5, 0, 1};
std::vector<std::string> names = {
"Scarlet",
"Mustard",
"Green",
"Mustard",
"Green",
"White",
"White",
"Peacock",
"Plum",
"Plum",
"Scarlet",
"Mustard"};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
schema.enums.emplace("TestEnum", Enum{});
Enum& enumDef = schema.enums["TestEnum"];
enumDef.name = "Test";
enumDef.description = "An example enum";
enumDef.values = std::vector<EnumValue>{
makeEnumValue("Scarlet", 0),
makeEnumValue("Mustard", 1),
makeEnumValue("Green", 2),
makeEnumValue("White", 3),
makeEnumValue("Peacock", 4),
makeEnumValue("Plum", 5)};
enumDef.valueType = Enum::ValueType::UINT16;
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::ENUM;
testClassProperty.enumType = "TestEnum";
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::ENUM);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<uint16_t> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayInvalid =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<int8_t>> uvec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<int8_t>>("TestClassProperty");
REQUIRE(
uvec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<uint32_t> uint32Invalid =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Buffer size is not a multiple of type size") {
model.bufferViews[valueBufferViewIndex].byteLength = 13;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Negative count") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() == PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
SUBCASE("Value buffer doesn't fit into property table count") {
testClassProperty.count = 55;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test fixed-length scalar array") {
Model model;
std::vector<uint32_t> values =
{12, 34, 30, 11, 34, 34, 11, 33, 122, 33, 223, 11};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<uint32_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint32_t>>("TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<uint32_t> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayInvalid =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<glm::uvec2>> uvec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::uvec2>>(
"TestClassProperty");
REQUIRE(
uvec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>> int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>>("TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<uint32_t> uint32Invalid =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrectly normalized") {
PropertyTablePropertyView<PropertyArrayView<uint32_t>, true>
normalizedInvalid =
view.getPropertyView<PropertyArrayView<uint32_t>, true>(
"TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer size is not a multiple of type size") {
model.bufferViews[valueBufferViewIndex].byteLength = 13;
PropertyTablePropertyView<PropertyArrayView<uint32_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint32_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Negative count") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<uint32_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint32_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() == PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
SUBCASE("Value buffer doesn't fit into property table count") {
testClassProperty.count = 55;
PropertyTablePropertyView<PropertyArrayView<uint32_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint32_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test fixed-length scalar array (normalized)") {
Model model;
std::vector<uint32_t> values =
{12, 34, 30, 11, 34, 34, 11, 33, 122, 33, 223, 11};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.array = true;
testClassProperty.count = 3;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
REQUIRE(classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<uint32_t>, true> arrayProperty =
view.getPropertyView<PropertyArrayView<uint32_t>, true>(
"TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<uint32_t> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == normalize(array[j]));
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<glm::uvec2>, true>
uvec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::uvec2>, true>(
"TestClassProperty");
REQUIRE(
uvec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>, true>
int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>, true>(
"TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<uint32_t, true> uint32Invalid =
view.getPropertyView<uint32_t, true>("TestClassProperty");
REQUIRE(
uint32Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrectly non-normalized") {
PropertyTablePropertyView<PropertyArrayView<uint32_t>>
nonNormalizedInvalid =
view.getPropertyView<PropertyArrayView<uint32_t>>(
"TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test variable-length scalar array") {
Model model;
std::vector<std::vector<uint16_t>> expected{
{12, 33, 11, 344, 112, 444, 1},
{},
{},
{122, 23, 333, 12},
{},
{333, 311, 22, 34},
{},
{33, 1888, 233, 33019}};
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(uint16_t));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(uint16_t),
expected[i].data(),
expected[i].size() * sizeof(uint16_t));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferIndex = model.buffers.size() - 1;
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT16;
testClassProperty.array = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<uint16_t>> property =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<uint16_t> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
REQUIRE(maybeArray->size() == array.size());
for (size_t j = 0; j < expected[i].size(); ++j) {
REQUIRE(expected[i][j] == array[static_cast<int64_t>(j)]);
REQUIRE(
(*maybeArray)[static_cast<int64_t>(j)] ==
array[static_cast<int64_t>(j)]);
}
}
}
SUBCASE("Incorrectly normalized") {
PropertyTablePropertyView<PropertyArrayView<uint16_t>, true> property =
view.getPropertyView<PropertyArrayView<uint16_t>, true>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong offset type") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT16;
arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType = "NONSENSE";
arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
propertyTableProperty.arrayOffsetType = "";
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT64;
arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
}
SUBCASE("Offset values are not sorted ascending") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] = 0;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetsNotSorted);
}
SUBCASE("Offset value points outside of value buffer") {
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] =
static_cast<uint32_t>(model.buffers[valueBufferIndex].byteLength + 4);
PropertyTablePropertyView<PropertyArrayView<uint16_t>> arrayProperty =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetOutOfBounds);
}
SUBCASE("Count and offset buffer are both present") {
testClassProperty.count = 3;
PropertyTablePropertyView<PropertyArrayView<uint16_t>> property =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorArrayCountAndOffsetBufferCoexist);
}
}
TEST_CASE("Test variable-length scalar array (normalized)") {
Model model;
std::vector<std::vector<uint16_t>> expected{
{12, 33, 11, 344, 112, 444, 1},
{},
{},
{122, 23, 333, 12},
{},
{333, 311, 22, 34},
{},
{33, 1888, 233, 33019}};
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(uint16_t));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(uint16_t),
expected[i].data(),
expected[i].size() * sizeof(uint16_t));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT16;
testClassProperty.array = true;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<uint16_t>, true> property =
view.getPropertyView<PropertyArrayView<uint16_t>, true>(
"TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<uint16_t> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
REQUIRE(maybeArray->size() == array.size());
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == normalize(value));
}
}
}
SUBCASE("Incorrectly non-normalized") {
PropertyTablePropertyView<PropertyArrayView<uint16_t>> property =
view.getPropertyView<PropertyArrayView<uint16_t>>("TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test fixed-length vecN array") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(12, 34, -30),
glm::ivec3(-2, 0, 1),
glm::ivec3(1, 2, 8),
glm::ivec3(-100, 84, 6),
glm::ivec3(2, -2, -2),
glm::ivec3(40, 61, 3),
};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<glm::ivec3> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 2 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>> int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>>("TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<glm::ivec2>> ivec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec2>>(
"TestClassProperty");
REQUIRE(
ivec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<glm::uvec3>> uvec3ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::uvec3>>(
"TestClassProperty");
REQUIRE(
uvec3ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<glm::ivec3> ivec3Invalid =
view.getPropertyView<glm::ivec3>("TestClassProperty");
REQUIRE(
ivec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrect normalization") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>, true>
normalizedInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec3>, true>(
"TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer size is not a multiple of type size") {
model.bufferViews[valueBufferViewIndex].byteLength = 13;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Negative count") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() == PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
SUBCASE("Value buffer doesn't fit into property table count") {
testClassProperty.count = 55;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test fixed-length vecN array (normalized)") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(12, 34, -30),
glm::ivec3(-2, 0, 1),
glm::ivec3(1, 2, 8),
glm::ivec3(-100, 84, 6),
glm::ivec3(2, -2, -2),
glm::ivec3(40, 61, 3),
};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>, true>
arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>, true>(
"TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<glm::ivec3> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 2 + j)]);
REQUIRE((*maybeArray)[j] == normalize(array[j]));
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>, true>
int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>, true>(
"TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<glm::ivec2>, true>
ivec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec2>, true>(
"TestClassProperty");
REQUIRE(
ivec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<glm::uvec3>, true>
uvec3ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::uvec3>, true>(
"TestClassProperty");
REQUIRE(
uvec3ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<glm::ivec3, true> ivec3Invalid =
view.getPropertyView<glm::ivec3, true>("TestClassProperty");
REQUIRE(
ivec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrect non-normalization") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>>
nonNormalizedInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test variable-length vecN array") {
Model model;
// clang-format off
std::vector<std::vector<glm::ivec3>> expected{
{ glm::ivec3(12, 34, -30), glm::ivec3(-2, 0, 1) },
{ glm::ivec3(1, 2, 8), },
{},
{ glm::ivec3(-100, 84, 6), glm::ivec3(2, -2, -2), glm::ivec3(40, 61, 3) },
{ glm::ivec3(-1, 4, -7) },
};
// clang-format on
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(glm::ivec3));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(glm::ivec3),
expected[i].data(),
expected[i].size() * sizeof(glm::ivec3));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferIndex = model.buffers.size() - 1;
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> property =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<glm::ivec3> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == value);
}
}
}
SUBCASE("Incorrectly normalized") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>, true> property =
view.getPropertyView<PropertyArrayView<glm::ivec3>, true>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong offset type") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT16;
arrayProperty = view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType = "NONSENSE";
arrayProperty = view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
propertyTableProperty.arrayOffsetType = "";
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT64;
arrayProperty = view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
}
SUBCASE("Offset values are not sorted ascending") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] = 0;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetsNotSorted);
}
SUBCASE("Offset value points outside of value buffer") {
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] =
static_cast<uint32_t>(model.buffers[valueBufferIndex].byteLength + 4);
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetOutOfBounds);
}
SUBCASE("Count and offset buffer are both present") {
testClassProperty.count = 3;
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> property =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorArrayCountAndOffsetBufferCoexist);
}
}
TEST_CASE("Test variable-length vecN array (normalized)") {
Model model;
// clang-format off
std::vector<std::vector<glm::ivec3>> expected{
{ glm::ivec3(12, 34, -30), glm::ivec3(-2, 0, 1) },
{ glm::ivec3(1, 2, 8), },
{},
{ glm::ivec3(-100, 84, 6), glm::ivec3(2, -2, -2), glm::ivec3(40, 61, 3) },
{ glm::ivec3(-1, 4, -7) },
};
// clang-format on
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(glm::ivec3));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(glm::ivec3),
expected[i].data(),
expected[i].size() * sizeof(glm::ivec3));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>, true> property =
view.getPropertyView<PropertyArrayView<glm::ivec3>, true>(
"TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<glm::ivec3> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == normalize(value));
}
}
}
SUBCASE("Incorrectly non-normalized") {
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>> property =
view.getPropertyView<PropertyArrayView<glm::ivec3>>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test fixed-length matN array") {
Model model;
// clang-format off
std::vector<glm::imat2x2> values = {
glm::imat2x2(
12, 34,
-30, 20),
glm::imat2x2(
-2, -2,
0, 1),
glm::imat2x2(
1, 2,
8, 5),
glm::imat2x2(
-100, 3,
84, 6),
glm::imat2x2(
2, 12,
-2, -2),
glm::imat2x2(
40, 61,
7, -3),
};
// clang-format on
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<glm::imat2x2> member = arrayProperty.getRaw(i);
for (int64_t j = 0; j < member.size(); ++j) {
REQUIRE(member[j] == values[static_cast<size_t>(i * 2 + j)]);
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>> int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>>("TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<glm::ivec2>> ivec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec2>>(
"TestClassProperty");
REQUIRE(
ivec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<glm::umat2x2>>
umat2x2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::umat2x2>>(
"TestClassProperty");
REQUIRE(
umat2x2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<glm::imat2x2> ivec3Invalid =
view.getPropertyView<glm::imat2x2>("TestClassProperty");
REQUIRE(
ivec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrect normalization") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>, true>
normalizedInvalid =
view.getPropertyView<PropertyArrayView<glm::imat2x2>, true>(
"TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer size is not a multiple of type size") {
model.bufferViews[valueBufferViewIndex].byteLength = 13;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
}
SUBCASE("Negative count") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() == PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
SUBCASE("Value buffer doesn't fit into property table count") {
testClassProperty.count = 55;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
}
TEST_CASE("Test fixed-length matN array (normalized)") {
Model model;
// clang-format off
std::vector<glm::imat2x2> values = {
glm::imat2x2(
12, 34,
-30, 20),
glm::imat2x2(
-2, -2,
0, 1),
glm::imat2x2(
1, 2,
8, 5),
glm::imat2x2(
-100, 3,
84, 6),
glm::imat2x2(
2, 12,
-2, -2),
glm::imat2x2(
40, 61,
7, -3),
};
// clang-format on
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(classProperty->normalized);
SUBCASE("Access the right type") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>, true>
arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>, true>(
"TestClassProperty");
REQUIRE(arrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (int64_t i = 0; i < arrayProperty.size(); ++i) {
PropertyArrayView<glm::imat2x2> array = arrayProperty.getRaw(i);
auto maybeArray = arrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 2 + j)]);
REQUIRE((*maybeArray)[j] == normalize(array[j]));
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<int32_t>, true>
int32ArrayInvalid =
view.getPropertyView<PropertyArrayView<int32_t>, true>(
"TestClassProperty");
REQUIRE(
int32ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
PropertyTablePropertyView<PropertyArrayView<glm::ivec2>, true>
ivec2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::ivec2>, true>(
"TestClassProperty");
REQUIRE(
ivec2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Wrong component type") {
PropertyTablePropertyView<PropertyArrayView<glm::umat2x2>, true>
umat2x2ArrayInvalid =
view.getPropertyView<PropertyArrayView<glm::umat2x2>, true>(
"TestClassProperty");
REQUIRE(
umat2x2ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Not an array type") {
PropertyTablePropertyView<glm::imat2x2, true> ivec3Invalid =
view.getPropertyView<glm::imat2x2, true>("TestClassProperty");
REQUIRE(
ivec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Incorrect non-normalization") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>>
nonNormalizedInvalid =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test variable-length matN array") {
Model model;
// clang-format off
std::vector<glm::imat2x2> data0{
glm::imat2x2(
3, -2,
1, 0),
glm::imat2x2(
40, 3,
8, -9)
};
std::vector<glm::imat2x2> data1{
glm::imat2x2(
1, 10,
7, 8),
};
std::vector<glm::imat2x2> data2{
glm::imat2x2(
18, 0,
1, 17),
glm::imat2x2(
-4, -2,
-9, 1),
glm::imat2x2(
1, 8,
-99, 3),
};
// clang-format on
std::vector<std::vector<glm::imat2x2>> expected{data0, {}, data1, data2, {}};
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(glm::imat2x2));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(glm::imat2x2),
expected[i].data(),
expected[i].size() * sizeof(glm::imat2x2));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferIndex = model.buffers.size() - 1;
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(!classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> property =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<glm::imat2x2> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == value);
}
}
}
SUBCASE("Incorrectly normalized") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>, true> property =
view.getPropertyView<PropertyArrayView<glm::imat2x2>, true>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Wrong offset type") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT16;
arrayProperty = view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType = "NONSENSE";
arrayProperty = view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
propertyTableProperty.arrayOffsetType = "";
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT64;
arrayProperty = view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
}
SUBCASE("Offset values are not sorted ascending") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] = 0;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetsNotSorted);
}
SUBCASE("Offset value points outside of value buffer") {
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] =
static_cast<uint32_t>(model.buffers[valueBufferIndex].byteLength + 4);
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> arrayProperty =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetOutOfBounds);
}
SUBCASE("Count and offset buffer are both present") {
testClassProperty.count = 3;
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> property =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorArrayCountAndOffsetBufferCoexist);
}
}
TEST_CASE("Test variable-length matN array (normalized)") {
Model model;
// clang-format off
std::vector<glm::imat2x2> data0{
glm::imat2x2(
3, -2,
1, 0),
glm::imat2x2(
40, 3,
8, -9)
};
std::vector<glm::imat2x2> data1{
glm::imat2x2(
1, 10,
7, 8),
};
std::vector<glm::imat2x2> data2{
glm::imat2x2(
18, 0,
1, 17),
glm::imat2x2(
-4, -2,
-9, 1),
glm::imat2x2(
1, 8,
-99, 3),
};
// clang-format on
std::vector<std::vector<glm::imat2x2>> expected{data0, {}, data1, data2, {}};
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
std::vector<std::byte> values(numOfElements * sizeof(glm::imat2x2));
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
std::memcpy(
values.data() + offsetValue[i] * sizeof(glm::imat2x2),
expected[i].data(),
expected[i].size() * sizeof(glm::imat2x2));
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
REQUIRE(classProperty->normalized);
SUBCASE("Access the correct type") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>, true> property =
view.getPropertyView<PropertyArrayView<glm::imat2x2>, true>(
"TestClassProperty");
REQUIRE(property.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<glm::imat2x2> array =
property.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = property.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == normalize(value));
}
}
}
SUBCASE("Incorrectly non-normalized") {
PropertyTablePropertyView<PropertyArrayView<glm::imat2x2>> property =
view.getPropertyView<PropertyArrayView<glm::imat2x2>>(
"TestClassProperty");
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
}
TEST_CASE("Test fixed-length boolean array") {
Model model;
std::vector<bool> expected = {
true,
false,
false,
true,
false,
false,
true,
true,
true,
false,
false,
true};
std::vector<uint8_t> values;
size_t requiredBytesSize = static_cast<size_t>(
glm::ceil(static_cast<double>(expected.size()) / 8.0));
values.resize(requiredBytesSize);
for (size_t i = 0; i < expected.size(); ++i) {
uint8_t expectedValue = expected[i];
size_t byteIndex = i / 8;
size_t bitIndex = i % 8;
values[byteIndex] =
static_cast<uint8_t>((expectedValue << bitIndex) | values[byteIndex]);
}
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::BOOLEAN;
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
expected.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::BOOLEAN);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
SUBCASE("Access correct type") {
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(boolArrayProperty.size() == propertyTable.count);
REQUIRE(boolArrayProperty.size() > 0);
for (int64_t i = 0; i < boolArrayProperty.size(); ++i) {
PropertyArrayView<bool> array = boolArrayProperty.getRaw(i);
auto maybeArray = boolArrayProperty.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == expected[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
}
SUBCASE("Wrong type") {
PropertyTablePropertyView<PropertyArrayView<uint8_t>> uint8ArrayInvalid =
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
REQUIRE(
uint8ArrayInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("View is not array type") {
PropertyTablePropertyView<bool> boolInvalid =
view.getPropertyView<bool>("TestClassProperty");
REQUIRE(
boolInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Value buffer doesn't have enough required bytes") {
testClassProperty.count = 11;
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
}
SUBCASE("Count is negative") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
}
TEST_CASE("Test variable-length boolean array") {
Model model;
std::vector<std::vector<bool>> expected{
{true, true, true, true, true},
{},
{},
{},
{false},
{true, true},
{false},
{true, true, true, true, true}};
size_t numOfElements = 0;
for (const auto& expectedMember : expected) {
numOfElements += expectedMember.size();
}
size_t requiredBytesSize =
static_cast<size_t>(glm::ceil(static_cast<double>(numOfElements) / 8.0));
std::vector<std::byte> values(requiredBytesSize);
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint64_t));
uint64_t* offsetValue = reinterpret_cast<uint64_t*>(offsets.data());
size_t indexSoFar = 0;
for (size_t i = 0; i < expected.size(); ++i) {
for (size_t j = 0; j < expected[i].size(); ++j) {
uint8_t expectedValue = expected[i][j];
size_t byteIndex = indexSoFar / 8;
size_t bitIndex = indexSoFar % 8;
values[byteIndex] = static_cast<std::byte>(
(expectedValue << bitIndex) | static_cast<int>(values[byteIndex]));
++indexSoFar;
}
offsetValue[i + 1] = offsetValue[i] + expected[i].size();
}
addBufferToModel(model, values);
size_t valueBufferIndex = model.buffers.size() - 1;
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferIndex = model.buffers.size() - 1;
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::BOOLEAN;
testClassProperty.array = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::BOOLEAN);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->count);
SUBCASE("Access correct type") {
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<bool> array =
boolArrayProperty.getRaw(static_cast<int64_t>(i));
REQUIRE(array.size() == static_cast<int64_t>(expected[i].size()));
auto maybeArray = boolArrayProperty.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
auto value = array[static_cast<int64_t>(j)];
REQUIRE(expected[i][j] == value);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == value);
}
}
}
SUBCASE("Wrong offset type") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<bool>> arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT16;
arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType = "NONSENSE";
arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
propertyTableProperty.arrayOffsetType = "";
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT64;
arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
}
SUBCASE("Offset values are not sorted ascending") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT64;
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] = 0;
PropertyTablePropertyView<PropertyArrayView<bool>> arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetsNotSorted);
}
SUBCASE("Offset value points outside of value buffer") {
uint64_t* offset = reinterpret_cast<uint64_t*>(
model.buffers[offsetBufferIndex].cesium.data.data());
offset[propertyTable.count] = static_cast<uint32_t>(
model.buffers[valueBufferIndex].byteLength * 8 + 20);
PropertyTablePropertyView<PropertyArrayView<bool>> arrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetOutOfBounds);
}
SUBCASE("Count and offset buffer both present") {
testClassProperty.count = 3;
PropertyTablePropertyView<PropertyArrayView<bool>> boolArrayProperty =
view.getPropertyView<PropertyArrayView<bool>>("TestClassProperty");
REQUIRE(
boolArrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayCountAndOffsetBufferCoexist);
}
}
TEST_CASE("Test fixed-length arrays of strings") {
Model model;
std::vector<std::string> expected{
"What's up",
"Breaking news!!! Aliens no longer attacks the US first",
"But they still abduct my cows! Those milk thiefs! 👽 🐮",
"I'm not crazy. My mother had me tested 🤪",
"I love you, meat bags! ❤️",
"Book in the freezer"};
size_t totalBytes = 0;
for (const std::string& expectedValue : expected) {
totalBytes += expectedValue.size();
}
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint32_t));
std::vector<std::byte> values(totalBytes);
uint32_t* offsetValue = reinterpret_cast<uint32_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
const std::string& expectedValue = expected[i];
std::memcpy(
values.data() + offsetValue[i],
expectedValue.c_str(),
expectedValue.size());
offsetValue[i + 1] =
offsetValue[i] + static_cast<uint32_t>(expectedValue.size());
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::STRING;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
expected.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.stringOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::STRING);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
SUBCASE("Access correct type") {
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(stringProperty.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(stringProperty.size() == 3);
PropertyArrayView<std::string_view> v0 = stringProperty.getRaw(0);
REQUIRE(v0.size() == 2);
REQUIRE(v0[0] == "What's up");
REQUIRE(v0[1] == "Breaking news!!! Aliens no longer attacks the US first");
PropertyArrayView<std::string_view> v1 = stringProperty.getRaw(1);
REQUIRE(v1.size() == 2);
REQUIRE(v1[0] == "But they still abduct my cows! Those milk thiefs! 👽 🐮");
REQUIRE(v1[1] == "I'm not crazy. My mother had me tested 🤪");
PropertyArrayView<std::string_view> v2 = stringProperty.getRaw(2);
REQUIRE(v2.size() == 2);
REQUIRE(v2[0] == "I love you, meat bags! ❤️");
REQUIRE(v2[1] == "Book in the freezer");
for (int64_t i = 0; i < stringProperty.size(); i++) {
auto maybeValue = stringProperty.get(i);
REQUIRE(maybeValue);
auto value = stringProperty.getRaw(i);
REQUIRE(maybeValue->size() == value.size());
for (int64_t j = 0; j < value.size(); j++) {
REQUIRE((*maybeValue)[j] == value[j]);
}
}
}
SUBCASE("Array type mismatch") {
PropertyTablePropertyView<std::string_view> stringProperty =
view.getPropertyView<std::string_view>("TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayTypeMismatch);
}
SUBCASE("Count is negative") {
testClassProperty.count = -1;
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
stringProperty.status() == PropertyTablePropertyViewStatus::
ErrorArrayCountAndOffsetBufferDontExist);
}
SUBCASE("Offset type is unknown") {
propertyTableProperty.stringOffsetType = "NONSENSE";
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetType);
propertyTableProperty.stringOffsetType = "";
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT32;
stringProperty = view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetType);
}
SUBCASE("String offsets don't exist") {
propertyTableProperty.stringOffsets = -1;
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
stringProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetBufferView);
}
}
TEST_CASE("Test variable-length arrays of strings") {
Model model;
std::vector<std::vector<std::string>> expected{
{"What's up"},
{"Breaking news!!! Aliens no longer attacks the US first",
"But they still abduct my cows! Those milk thiefs! 👽 🐮"},
{"I'm not crazy. My mother had me tested 🤪",
"I love you, meat bags! ❤️",
"Book in the freezer"}};
size_t totalBytes = 0;
size_t numOfElements = 0;
for (const auto& expectedValues : expected) {
for (const auto& value : expectedValues) {
totalBytes += value.size();
}
numOfElements += expectedValues.size();
}
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint32_t));
std::vector<std::byte> stringOffsets((numOfElements + 1) * sizeof(uint16_t));
std::vector<std::byte> values(totalBytes);
uint32_t* offsetValue = reinterpret_cast<uint32_t*>(offsets.data());
uint16_t* stringOffsetValue =
reinterpret_cast<uint16_t*>(stringOffsets.data());
size_t strOffsetIdx = 0;
for (size_t i = 0; i < expected.size(); ++i) {
for (size_t j = 0; j < expected[i].size(); ++j) {
const std::string& expectedValue = expected[i][j];
std::memcpy(
values.data() + stringOffsetValue[strOffsetIdx],
expectedValue.c_str(),
expectedValue.size());
stringOffsetValue[strOffsetIdx + 1] =
stringOffsetValue[strOffsetIdx] +
static_cast<uint16_t>(expectedValue.size());
++strOffsetIdx;
}
offsetValue[i + 1] =
offsetValue[i] + static_cast<uint32_t>(expected[i].size());
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t arrayOffsetBuffer = model.buffers.size() - 1;
size_t arrayOffsetBufferView = model.bufferViews.size() - 1;
addBufferToModel(model, stringOffsets);
size_t stringOffsetBuffer = model.buffers.size() - 1;
size_t stringOffsetBufferView = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::STRING;
testClassProperty.array = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT32;
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT16;
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.arrayOffsets =
static_cast<int32_t>(arrayOffsetBufferView);
propertyTableProperty.stringOffsets =
static_cast<int32_t>(stringOffsetBufferView);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::STRING);
REQUIRE(classProperty->array);
REQUIRE(!classProperty->componentType);
REQUIRE(!classProperty->count);
SUBCASE("Access correct type") {
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
stringProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(stringProperty.status() == PropertyTablePropertyViewStatus::Valid);
for (size_t i = 0; i < expected.size(); ++i) {
PropertyArrayView<std::string_view> stringArray =
stringProperty.getRaw(static_cast<int64_t>(i));
auto maybeArray = stringProperty.get(static_cast<int64_t>(i));
REQUIRE(maybeArray);
for (size_t j = 0; j < expected[i].size(); ++j) {
REQUIRE(stringArray[static_cast<int64_t>(j)] == expected[i][j]);
REQUIRE((*maybeArray)[static_cast<int64_t>(j)] == expected[i][j]);
}
}
}
SUBCASE("Wrong array offset type") {
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT16;
arrayProperty = view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.arrayOffsetType = "NONSENSE";
arrayProperty = view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidArrayOffsetType);
propertyTableProperty.arrayOffsetType =
PropertyTableProperty::ArrayOffsetType::UINT32;
}
SUBCASE("Wrong string offset type") {
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT8;
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeDoesNotMatchPropertyTableCount);
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
arrayProperty = view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::
ErrorBufferViewSizeNotDivisibleByTypeSize);
propertyTableProperty.stringOffsetType = "NONSENSE";
arrayProperty = view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidStringOffsetType);
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT16;
}
SUBCASE("Array offset values are not sorted ascending") {
uint32_t* offset = reinterpret_cast<uint32_t*>(
model.buffers[arrayOffsetBuffer].cesium.data.data());
offset[0] = static_cast<uint32_t>(1000);
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetsNotSorted);
offset[0] = 0;
}
SUBCASE("String offset values are not sorted ascending") {
uint32_t* offset = reinterpret_cast<uint32_t*>(
model.buffers[stringOffsetBuffer].cesium.data.data());
offset[0] = static_cast<uint32_t>(1000);
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorStringOffsetsNotSorted);
offset[0] = 0;
}
SUBCASE("Array offset value points outside of value buffer") {
uint32_t* offset = reinterpret_cast<uint32_t*>(
model.buffers[arrayOffsetBuffer].cesium.data.data());
uint32_t previousValue = offset[propertyTable.count];
offset[propertyTable.count] = static_cast<uint32_t>(100000);
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayOffsetOutOfBounds);
offset[propertyTable.count] = previousValue;
}
SUBCASE("String offset value points outside of value buffer") {
uint16_t* offset = reinterpret_cast<uint16_t*>(
model.buffers[stringOffsetBuffer].cesium.data.data());
uint16_t previousValue = offset[6];
offset[6] = static_cast<uint16_t>(10000);
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
arrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
arrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorStringOffsetOutOfBounds);
offset[6] = previousValue;
}
SUBCASE("Count and offset buffer both present") {
testClassProperty.count = 3;
PropertyTablePropertyView<PropertyArrayView<std::string_view>>
boolArrayProperty =
view.getPropertyView<PropertyArrayView<std::string_view>>(
"TestClassProperty");
REQUIRE(
boolArrayProperty.status() ==
PropertyTablePropertyViewStatus::ErrorArrayCountAndOffsetBufferCoexist);
}
}
TEST_CASE("Test with PropertyTableProperty offset, scale, min, max") {
Model model;
std::vector<float> values = {1.0f, 2.0f, 3.0f, 4.0f};
const float offset = 0.5f;
const float scale = 2.0f;
const float min = 3.5f;
const float max = 8.5f;
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::FLOAT32;
testClassProperty.offset = offset;
testClassProperty.scale = scale;
testClassProperty.min = min;
testClassProperty.max = max;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(
classProperty->componentType == ClassProperty::ComponentType::FLOAT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->offset);
REQUIRE(classProperty->scale);
REQUIRE(classProperty->min);
REQUIRE(classProperty->max);
SUBCASE("Use class property values") {
PropertyTablePropertyView<float> propertyView =
view.getPropertyView<float>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.offset() == offset);
REQUIRE(propertyView.scale() == scale);
REQUIRE(propertyView.min() == min);
REQUIRE(propertyView.max() == max);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(propertyView.get(i) == propertyView.getRaw(i) * scale + offset);
}
}
SUBCASE("Use own property values") {
const float newOffset = 1.0f;
const float newScale = -1.0f;
const float newMin = -3.0f;
const float newMax = 0.0f;
propertyTableProperty.offset = newOffset;
propertyTableProperty.scale = newScale;
propertyTableProperty.min = newMin;
propertyTableProperty.max = newMax;
PropertyTablePropertyView<float> propertyView =
view.getPropertyView<float>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.offset() == newOffset);
REQUIRE(propertyView.scale() == newScale);
REQUIRE(propertyView.min() == newMin);
REQUIRE(propertyView.max() == newMax);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(
propertyView.get(i) == propertyView.getRaw(i) * newScale + newOffset);
}
}
}
TEST_CASE(
"Test with PropertyTableProperty offset, scale, min, max (normalized)") {
Model model;
std::vector<int8_t> values = {-128, 0, 32, 127};
const double offset = 0.5;
const double scale = 2.0;
const double min = 1.5;
const double max = 2.5;
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::INT8;
testClassProperty.normalized = true;
testClassProperty.offset = offset;
testClassProperty.scale = scale;
testClassProperty.min = min;
testClassProperty.max = max;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
REQUIRE(classProperty->offset);
REQUIRE(classProperty->scale);
REQUIRE(classProperty->min);
REQUIRE(classProperty->max);
SUBCASE("Use class property values") {
PropertyTablePropertyView<int8_t, true> propertyView =
view.getPropertyView<int8_t, true>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.offset() == offset);
REQUIRE(propertyView.scale() == scale);
REQUIRE(propertyView.min() == min);
REQUIRE(propertyView.max() == max);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(
propertyView.get(i) ==
normalize(propertyView.getRaw(i)) * scale + offset);
}
}
SUBCASE("Use own property values") {
const double newOffset = -0.5;
const double newScale = 1.0;
const double newMin = -1.5;
const double newMax = 0.5;
propertyTableProperty.offset = newOffset;
propertyTableProperty.scale = newScale;
propertyTableProperty.min = newMin;
propertyTableProperty.max = newMax;
PropertyTablePropertyView<int8_t, true> propertyView =
view.getPropertyView<int8_t, true>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.offset() == newOffset);
REQUIRE(propertyView.scale() == newScale);
REQUIRE(propertyView.min() == newMin);
REQUIRE(propertyView.max() == newMax);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
REQUIRE(
propertyView.get(i) ==
normalize(propertyView.getRaw(i)) * newScale + newOffset);
}
}
}
TEST_CASE("Test with PropertyTableProperty noData value") {
Model model;
std::vector<int8_t> values = {-128, 0, 32, -128, 127};
const int8_t noData = -128;
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::INT8;
testClassProperty.noData = noData;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->noData);
SUBCASE("Without default value") {
PropertyTablePropertyView<int8_t> propertyView =
view.getPropertyView<int8_t>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.noData() == noData);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
if (propertyView.getRaw(i) == noData) {
REQUIRE(!propertyView.get(i));
} else {
REQUIRE(propertyView.get(i) == propertyView.getRaw(i));
}
}
}
SUBCASE("With default value") {
const int8_t defaultValue = 100;
testClassProperty.defaultProperty = defaultValue;
classProperty = view.getClassProperty("TestClassProperty");
REQUIRE(classProperty->defaultProperty);
PropertyTablePropertyView<int8_t> propertyView =
view.getPropertyView<int8_t>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.noData() == noData);
REQUIRE(propertyView.defaultValue() == defaultValue);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
if (propertyView.getRaw(i) == noData) {
REQUIRE(propertyView.get(i) == defaultValue);
} else {
REQUIRE(propertyView.get(i) == propertyView.getRaw(i));
}
}
}
}
TEST_CASE("Test with PropertyTableProperty noData value (normalized)") {
Model model;
std::vector<int8_t> values = {-128, 0, 32, -128, 127};
const int8_t noData = -128;
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::INT8;
testClassProperty.normalized = true;
testClassProperty.noData = noData;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
REQUIRE(classProperty->noData);
SUBCASE("Without default value") {
PropertyTablePropertyView<int8_t, true> propertyView =
view.getPropertyView<int8_t, true>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.noData() == noData);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
if (propertyView.getRaw(i) == noData) {
REQUIRE(!propertyView.get(i));
} else {
REQUIRE(propertyView.get(i) == normalize(propertyView.getRaw(i)));
}
}
}
SUBCASE("With default value") {
const double defaultValue = 10.5;
testClassProperty.defaultProperty = defaultValue;
classProperty = view.getClassProperty("TestClassProperty");
REQUIRE(classProperty->defaultProperty);
PropertyTablePropertyView<int8_t, true> propertyView =
view.getPropertyView<int8_t, true>("TestClassProperty");
REQUIRE(propertyView.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyView.size() > 0);
REQUIRE(propertyView.noData() == noData);
REQUIRE(propertyView.defaultValue() == defaultValue);
for (int64_t i = 0; i < propertyView.size(); ++i) {
REQUIRE(propertyView.getRaw(i) == values[static_cast<size_t>(i)]);
if (propertyView.getRaw(i) == noData) {
REQUIRE(propertyView.get(i) == defaultValue);
} else {
REQUIRE(propertyView.get(i) == normalize(propertyView.getRaw(i)));
}
}
}
}
TEST_CASE(
"Test nonexistent PropertyTableProperty with class property default") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
const uint32_t defaultValue = 10;
testClassProperty.defaultProperty = defaultValue;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = 4;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->defaultProperty);
SUBCASE("Access correct type") {
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::EmptyPropertyWithDefault);
REQUIRE(uint32Property.size() == propertyTable.count);
REQUIRE(uint32Property.defaultValue() == defaultValue);
for (int64_t i = 0; i < uint32Property.size(); ++i) {
REQUIRE(uint32Property.get(i) == defaultValue);
}
}
SUBCASE("Access wrong type") {
PropertyTablePropertyView<glm::uvec3> uvec3Invalid =
view.getPropertyView<glm::uvec3>("TestClassProperty");
REQUIRE(
uvec3Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyTablePropertyView<uint8_t> uint8Invalid =
view.getPropertyView<uint8_t>("TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyTablePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyTablePropertyView<uint32_t, true> uint32NormalizedInvalid =
view.getPropertyView<uint32_t, true>("TestClassProperty");
REQUIRE(
uint32NormalizedInvalid.status() ==
PropertyTablePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Invalid default value") {
testClassProperty.defaultProperty = "not a number";
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidDefaultValue);
}
SUBCASE("No default value") {
testClassProperty.defaultProperty.reset();
PropertyTablePropertyView<uint32_t> uint32Property =
view.getPropertyView<uint32_t>("TestClassProperty");
REQUIRE(
uint32Property.status() ==
PropertyTablePropertyViewStatus::ErrorNonexistentProperty);
}
}
TEST_CASE("Test callback on invalid property table view") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
metadata.schema.emplace();
// Property table has a nonexistent class.
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(5);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(-1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::ErrorClassNotFound);
REQUIRE(view.size() == 0);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
uint32_t invokedCallbackCount = 0;
auto callback = [&invokedCallbackCount](
const std::string& /*propertyId*/,
auto property) mutable {
invokedCallbackCount++;
REQUIRE(
property.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidPropertyTable);
REQUIRE(property.size() == 0);
};
view.getPropertyView("TestClassProperty", callback);
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for invalid PropertyTableProperty") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["InvalidProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(5);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["InvalidProperty"];
propertyTableProperty.values = static_cast<int32_t>(-1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty = view.getClassProperty("InvalidProperty");
REQUIRE(classProperty);
classProperty = view.getClassProperty("NonexistentProperty");
REQUIRE(!classProperty);
uint32_t invokedCallbackCount = 0;
auto testCallback = [&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(propertyValue.status() != PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() == 0);
};
view.getPropertyView("InvalidProperty", testCallback);
view.getPropertyView("NonexistentProperty", testCallback);
REQUIRE(invokedCallbackCount == 2);
}
TEST_CASE("Test callback for invalid normalized PropertyTableProperty") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::FLOAT32;
testClassProperty.normalized = true; // This is erroneous.
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(5);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = 0;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(
classProperty->componentType == ClassProperty::ComponentType::FLOAT32);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() ==
PropertyTablePropertyViewStatus::ErrorInvalidNormalization);
REQUIRE(propertyValue.size() == 0);
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar PropertyTableProperty") {
Model model;
std::vector<uint32_t> values = {12, 34, 30, 11, 34, 34, 11, 33, 122, 33};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyTablePropertyView<uint32_t>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(
static_cast<uint32_t>(propertyValue.getRaw(i)) ==
expectedValue);
REQUIRE(propertyValue.get(i) == expectedValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar PropertyTableProperty (normalized)") {
Model model;
std::vector<uint32_t> values = {12, 34, 30, 11, 34, 34, 11, 33, 122, 33};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyTablePropertyView<uint32_t, true>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(
static_cast<uint32_t>(propertyValue.getRaw(i)) ==
expectedValue);
REQUIRE(propertyValue.get(i) == normalize(expectedValue));
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN PropertyTableProperty") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(-12, 34, 30),
glm::ivec3(11, 73, 0),
glm::ivec3(-2, 6, 12),
glm::ivec3(-4, 8, -13)};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<glm::ivec3>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(
static_cast<glm::ivec3>(propertyValue.getRaw(i)) ==
expectedValue);
REQUIRE(propertyValue.get(i) == expectedValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN PropertyTableProperty (normalized)") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(-12, 34, 30),
glm::ivec3(11, 73, 0),
glm::ivec3(-2, 6, 12),
glm::ivec3(-4, 8, -13)};
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<glm::ivec3, true>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(propertyValue.getRaw(i) == expectedValue);
REQUIRE(propertyValue.get(i) == normalize(expectedValue));
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for matN PropertyTableProperty") {
Model model;
// clang-format off
std::vector<glm::umat2x2> values = {
glm::umat2x2(
12, 34,
30, 1),
glm::umat2x2(
11, 8,
73, 102),
glm::umat2x2(
1, 0,
63, 2),
glm::umat2x2(
4, 8,
3, 23)};
// clang-format on
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<glm::umat2x2>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(propertyValue.getRaw(i) == expectedValue);
REQUIRE(propertyValue.get(i) == expectedValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
invokedCallbackCount++;
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for matN PropertyTableProperty (normalized)") {
Model model;
// clang-format off
std::vector<glm::umat2x2> values = {
glm::umat2x2(
12, 34,
30, 1),
glm::umat2x2(
11, 8,
73, 102),
glm::umat2x2(
1, 0,
63, 2),
glm::umat2x2(
4, 8,
3, 23)};
// clang-format on
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(values.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<glm::umat2x2, true>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = values[static_cast<size_t>(i)];
REQUIRE(
static_cast<glm::umat2x2>(propertyValue.getRaw(i)) ==
expectedValue);
REQUIRE(propertyValue.get(i) == normalize(expectedValue));
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
invokedCallbackCount++;
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for boolean PropertyTableProperty") {
Model model;
int64_t instanceCount = 21;
std::vector<bool> expected;
std::vector<uint8_t> values;
values.resize(3);
for (int64_t i = 0; i < instanceCount; ++i) {
if (i % 2 == 0) {
expected.emplace_back(true);
} else {
expected.emplace_back(false);
}
uint8_t expectedValue = expected.back();
int64_t byteIndex = i / 8;
int64_t bitIndex = i % 8;
values[static_cast<size_t>(byteIndex)] = static_cast<uint8_t>(
(expectedValue << bitIndex) | values[static_cast<size_t>(byteIndex)]);
}
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::BOOLEAN;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(instanceCount);
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::BOOLEAN);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&expected, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<bool>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = expected[static_cast<size_t>(i)];
REQUIRE(
static_cast<bool>(propertyValue.getRaw(i)) == expectedValue);
REQUIRE(propertyValue.get(i) == expectedValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for string PropertyTableProperty") {
Model model;
std::vector<std::string> expected{"What's up", "Test_0", "Test_1", "", "Hi"};
size_t totalBytes = 0;
for (const std::string& expectedValue : expected) {
totalBytes += expectedValue.size();
}
std::vector<std::byte> stringOffsets(
(expected.size() + 1) * sizeof(uint32_t));
std::vector<std::byte> values(totalBytes);
uint32_t* offsetValue = reinterpret_cast<uint32_t*>(stringOffsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
const std::string& expectedValue = expected[i];
std::memcpy(
values.data() + offsetValue[i],
expectedValue.c_str(),
expectedValue.size());
offsetValue[i + 1] =
offsetValue[i] + static_cast<uint32_t>(expectedValue.size());
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, stringOffsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::STRING;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.stringOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::STRING);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&expected, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<std::string_view>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = expected[static_cast<size_t>(i)];
REQUIRE(
static_cast<std::string_view>(propertyValue.getRaw(i)) ==
expectedValue);
REQUIRE(propertyValue.get(i) == expectedValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for enum PropertyTableProperty") {
Model model;
const uint64_t expectedUint = 0xABADCAFEDEADBEEF;
std::vector<int64_t> expected{0, 1, 2, static_cast<int64_t>(expectedUint)};
std::vector<std::string> expectedNames{"Foo", "Bar", "Baz", "Uint64"};
addBufferToModel(model, expected);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
schema.enums.emplace("TestEnum", Enum{});
Enum& enumDef = schema.enums["TestEnum"];
enumDef.name = "Test";
enumDef.description = "An example enum";
enumDef.values = std::vector<EnumValue>{
makeEnumValue("Foo", 0),
makeEnumValue("Bar", 1),
makeEnumValue("Baz", 2),
makeEnumValue("Uint64", static_cast<int64_t>(expectedUint))};
enumDef.valueType = Enum::ValueType::UINT64;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(expected.size());
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::ENUM;
testClassProperty.enumType = "TestEnum";
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::ENUM);
REQUIRE(classProperty->componentType == std::nullopt);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&expected, &expectedNames, &enumDef, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<uint64_t>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
auto expectedValue = expected[static_cast<size_t>(i)];
REQUIRE(propertyValue.getRaw(i) == expectedValue);
REQUIRE(propertyValue.get(i).value() == expectedValue);
REQUIRE(
enumDef.getName(*propertyValue.get(i)) ==
expectedNames[static_cast<size_t>(i)]);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar array PropertyTableProperty") {
Model model;
std::vector<uint32_t> values =
{12, 34, 30, 11, 34, 34, 11, 33, 122, 33, 223, 11};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<
PropertyArrayView<uint32_t>>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<uint32_t> array = propertyValue.getRaw(i);
auto maybeArray = propertyValue.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar array PropertyTableProperty (normalized)") {
Model model;
std::vector<uint32_t> values =
{12, 34, 30, 11, 34, 34, 11, 33, 122, 33, 223, 11};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
testClassProperty.array = true;
testClassProperty.count = 3;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (
std::is_same_v<
PropertyTablePropertyView<PropertyArrayView<uint32_t>, true>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<uint32_t> array = propertyValue.getRaw(i);
auto maybeArray = propertyValue.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == normalize(array[j]));
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN array PropertyTableProperty") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(12, 34, -30),
glm::ivec3(-2, 0, 1),
glm::ivec3(1, 2, 8),
glm::ivec3(-100, 84, 6),
glm::ivec3(2, -2, -2),
glm::ivec3(40, 61, 3),
};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<
PropertyArrayView<glm::ivec3>>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<glm::ivec3> array = propertyValue.getRaw(i);
auto maybeArray = propertyValue.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 2 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN array PropertyTableProperty (normalized)") {
Model model;
std::vector<glm::ivec3> values = {
glm::ivec3(12, 34, -30),
glm::ivec3(-2, 0, 1),
glm::ivec3(1, 2, 8),
glm::ivec3(-100, 84, 6),
glm::ivec3(2, -2, -2),
glm::ivec3(40, 61, 3),
};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC3;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
testClassProperty.normalized = true;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC3);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (
std::is_same_v<
PropertyTablePropertyView<PropertyArrayView<glm::ivec3>, true>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<glm::ivec3> array = propertyValue.getRaw(i);
auto maybeArray = propertyValue.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == values[static_cast<size_t>(i * 2 + j)]);
REQUIRE((*maybeArray)[j] == normalize(array[j]));
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for matN array PropertyTableProperty") {
Model model;
// clang-format off
std::vector<glm::imat2x2> values = {
glm::imat2x2(
12, 34,
-30, 20),
glm::imat2x2(
-2, -2,
0, 1),
glm::imat2x2(
1, 2,
8, 5),
glm::imat2x2(
-100, 3,
84, 6),
glm::imat2x2(
2, 12,
-2, -2),
glm::imat2x2(
40, 61,
7, -3),
};
// clang-format on
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::MAT2;
testClassProperty.componentType = ClassProperty::ComponentType::INT32;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT32);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&values, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<
PropertyArrayView<glm::imat2x2>>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<glm::imat2x2> member = propertyValue.getRaw(i);
for (int64_t j = 0; j < member.size(); ++j) {
REQUIRE(member[j] == values[static_cast<size_t>(i * 2 + j)]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for boolean array PropertyTableProperty") {
Model model;
std::vector<bool> expected = {
true,
false,
false,
true,
false,
false,
true,
true,
true,
false,
false,
true};
std::vector<uint8_t> values;
size_t requiredBytesSize = static_cast<size_t>(
glm::ceil(static_cast<double>(expected.size()) / 8.0));
values.resize(requiredBytesSize);
for (size_t i = 0; i < expected.size(); ++i) {
uint8_t expectedValue = expected[i];
size_t byteIndex = i / 8;
size_t bitIndex = i % 8;
values[byteIndex] =
static_cast<uint8_t>((expectedValue << bitIndex) | values[byteIndex]);
}
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::BOOLEAN;
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
expected.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::BOOLEAN);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&expected, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() > 0);
if constexpr (std::is_same_v<
PropertyTablePropertyView<PropertyArrayView<bool>>,
decltype(propertyValue)>) {
for (int64_t i = 0; i < propertyValue.size(); ++i) {
PropertyArrayView<bool> array = propertyValue.getRaw(i);
auto maybeArray = propertyValue.get(i);
REQUIRE(maybeArray);
for (int64_t j = 0; j < array.size(); ++j) {
REQUIRE(array[j] == expected[static_cast<size_t>(i * 3 + j)]);
REQUIRE((*maybeArray)[j] == array[j]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for string array PropertyTableProperty") {
Model model;
std::vector<std::string> expected{
"What's up",
"Breaking news!!! Aliens no longer attacks the US first",
"But they still abduct my cows! Those milk thiefs! 👽 🐮",
"I'm not crazy. My mother had me tested 🤪",
"I love you, meat bags! ❤️",
"Book in the freezer"};
size_t totalBytes = 0;
for (const std::string& expectedValue : expected) {
totalBytes += expectedValue.size();
}
std::vector<std::byte> offsets((expected.size() + 1) * sizeof(uint32_t));
std::vector<std::byte> values(totalBytes);
uint32_t* offsetValue = reinterpret_cast<uint32_t*>(offsets.data());
for (size_t i = 0; i < expected.size(); ++i) {
const std::string& expectedValue = expected[i];
std::memcpy(
values.data() + offsetValue[i],
expectedValue.c_str(),
expectedValue.size());
offsetValue[i + 1] =
offsetValue[i] + static_cast<uint32_t>(expectedValue.size());
}
addBufferToModel(model, values);
size_t valueBufferViewIndex = model.bufferViews.size() - 1;
addBufferToModel(model, offsets);
size_t offsetBufferViewIndex = model.bufferViews.size() - 1;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::STRING;
testClassProperty.array = true;
testClassProperty.count = 2;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
expected.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.stringOffsetType =
PropertyTableProperty::StringOffsetType::UINT32;
propertyTableProperty.values = static_cast<int32_t>(valueBufferViewIndex);
propertyTableProperty.stringOffsets =
static_cast<int32_t>(offsetBufferViewIndex);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::STRING);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() == 3);
if constexpr (std::is_same_v<
PropertyTablePropertyView<
PropertyArrayView<std::string_view>>,
decltype(propertyValue)>) {
PropertyArrayView<std::string_view> v0 = propertyValue.getRaw(0);
REQUIRE(v0.size() == 2);
REQUIRE(v0[0] == "What's up");
REQUIRE(
v0[1] ==
"Breaking news!!! Aliens no longer attacks the US first");
PropertyArrayView<std::string_view> v1 = propertyValue.getRaw(1);
REQUIRE(v1.size() == 2);
REQUIRE(
v1[0] == "But they still abduct my cows! Those milk thiefs! 👽 🐮");
REQUIRE(v1[1] == "I'm not crazy. My mother had me tested 🤪");
PropertyArrayView<std::string_view> v2 = propertyValue.getRaw(2);
REQUIRE(v2.size() == 2);
REQUIRE(v2[0] == "I love you, meat bags! ❤️");
REQUIRE(v2[1] == "Book in the freezer");
for (int64_t i = 0; i < propertyValue.size(); i++) {
auto maybeValue = propertyValue.get(i);
REQUIRE(maybeValue);
auto value = propertyValue.getRaw(i);
REQUIRE(maybeValue->size() == value.size());
for (int64_t j = 0; j < value.size(); j++) {
REQUIRE((*maybeValue)[j] == value[j]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for enum array PropertyTableProperty") {
Model model;
std::vector<uint16_t> values = {0, 1, 2, 1, 2, 3, 3, 4, 5, 5, 0, 1};
std::vector<std::string> names = {
"Scarlet",
"Mustard",
"Green",
"Mustard",
"Green",
"White",
"White",
"Peacock",
"Plum",
"Plum",
"Scarlet",
"Mustard"};
addBufferToModel(model, values);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
schema.enums.emplace("TestEnum", Enum{});
Enum& enumDef = schema.enums["TestEnum"];
enumDef.name = "Test";
enumDef.description = "An example enum";
enumDef.values = std::vector<EnumValue>{
makeEnumValue("Scarlet", 0),
makeEnumValue("Mustard", 1),
makeEnumValue("Green", 2),
makeEnumValue("White", 3),
makeEnumValue("Peacock", 4),
makeEnumValue("Plum", 5)};
enumDef.valueType = Enum::ValueType::UINT16;
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::ENUM;
testClassProperty.enumType = "TestEnum";
testClassProperty.array = true;
testClassProperty.count = 3;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = static_cast<int64_t>(
values.size() / static_cast<size_t>(testClassProperty.count.value()));
PropertyTableProperty& propertyTableProperty =
propertyTable.properties["TestClassProperty"];
propertyTableProperty.values =
static_cast<int32_t>(model.bufferViews.size() - 1);
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::ENUM);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 3);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() == PropertyTablePropertyViewStatus::Valid);
REQUIRE(propertyValue.size() == 4);
if constexpr (std::is_same_v<
PropertyTablePropertyView<
PropertyArrayView<uint16_t>>,
decltype(propertyValue)>) {
PropertyArrayView<uint16_t> v0 = propertyValue.getRaw(0);
REQUIRE(v0.size() == 3);
REQUIRE(v0[0] == 0);
REQUIRE(v0[1] == 1);
REQUIRE(v0[2] == 2);
PropertyArrayView<uint16_t> v1 = propertyValue.getRaw(1);
REQUIRE(v1.size() == 3);
REQUIRE(v1[0] == 1);
REQUIRE(v1[1] == 2);
REQUIRE(v1[2] == 3);
PropertyArrayView<uint16_t> v2 = propertyValue.getRaw(2);
REQUIRE(v2.size() == 3);
REQUIRE(v2[0] == 3);
REQUIRE(v2[1] == 4);
REQUIRE(v2[2] == 5);
PropertyArrayView<uint16_t> v3 = propertyValue.getRaw(3);
REQUIRE(v3.size() == 3);
REQUIRE(v3[0] == 5);
REQUIRE(v3[1] == 0);
REQUIRE(v3[2] == 1);
for (int64_t i = 0; i < propertyValue.size(); i++) {
auto maybeValue = propertyValue.get(i);
REQUIRE(maybeValue);
auto value = propertyValue.getRaw(i);
REQUIRE(maybeValue->size() == value.size());
for (int64_t j = 0; j < value.size(); j++) {
REQUIRE((*maybeValue)[j] == value[j]);
}
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for empty PropertyTableProperty with default value") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::SCALAR;
testClassProperty.componentType = ClassProperty::ComponentType::UINT32;
const uint32_t defaultValue = 10;
testClassProperty.defaultProperty = defaultValue;
PropertyTable& propertyTable = metadata.propertyTables.emplace_back();
propertyTable.classProperty = "TestClass";
propertyTable.count = 4;
PropertyTableView view(model, propertyTable);
REQUIRE(view.status() == PropertyTableViewStatus::Valid);
REQUIRE(view.size() == propertyTable.count);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT32);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->defaultProperty);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
"TestClassProperty",
[defaultValue, count = propertyTable.count, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyTablePropertyView<uint32_t>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyTablePropertyViewStatus::EmptyPropertyWithDefault);
REQUIRE(propertyValue.size() == count);
REQUIRE(propertyValue.defaultValue() == defaultValue);
for (int64_t i = 0; i < propertyValue.size(); ++i) {
REQUIRE(propertyValue.get(i) == defaultValue);
}
} else {
FAIL("getPropertyView returned PropertyTablePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}