cesium-native/CesiumGltf/test/TestPropertyAttributeView.cpp

2271 lines
82 KiB
C++

#include <CesiumGltf/Accessor.h>
#include <CesiumGltf/Buffer.h>
#include <CesiumGltf/BufferView.h>
#include <CesiumGltf/Class.h>
#include <CesiumGltf/ClassProperty.h>
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
#include <CesiumGltf/Mesh.h>
#include <CesiumGltf/MeshPrimitive.h>
#include <CesiumGltf/Model.h>
#include <CesiumGltf/PropertyAttribute.h>
#include <CesiumGltf/PropertyAttributeProperty.h>
#include <CesiumGltf/PropertyAttributePropertyView.h>
#include <CesiumGltf/PropertyAttributeView.h>
#include <CesiumGltf/PropertyTransformations.h>
#include <CesiumGltf/PropertyType.h>
#include <CesiumGltf/PropertyTypeTraits.h>
#include <CesiumGltf/Schema.h>
#include <CesiumUtility/Assert.h>
#include <doctest/doctest.h>
#include <glm/ext/matrix_double2x2.hpp>
#include <glm/ext/matrix_float2x2.hpp>
#include <glm/ext/vector_double2.hpp>
#include <glm/ext/vector_float2.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/ext/vector_int2_sized.hpp>
#include <glm/ext/vector_uint2_sized.hpp>
#include <glm/ext/vector_uint3_sized.hpp>
#include <glm/fwd.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>
#include <vector>
using namespace CesiumGltf;
namespace {
template <typename T, bool Normalized = false>
void addAttributeToModel(
Model& model,
MeshPrimitive& primitive,
const std::string& name,
const std::vector<T>& values) {
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(values.size() * sizeof(T));
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
std::memcpy(
buffer.cesium.data.data(),
values.data(),
buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
bufferView.byteOffset = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = static_cast<int32_t>(model.bufferViews.size() - 1);
accessor.count = static_cast<int64_t>(values.size());
accessor.byteOffset = 0;
PropertyType type = TypeToPropertyType<T>::value;
switch (type) {
case PropertyType::Scalar:
accessor.type = Accessor::Type::SCALAR;
break;
case PropertyType::Vec2:
accessor.type = Accessor::Type::VEC2;
break;
case PropertyType::Vec3:
accessor.type = Accessor::Type::VEC3;
break;
case PropertyType::Vec4:
accessor.type = Accessor::Type::VEC4;
break;
case PropertyType::Mat2:
accessor.type = Accessor::Type::MAT2;
break;
case PropertyType::Mat3:
accessor.type = Accessor::Type::MAT3;
break;
case PropertyType::Mat4:
accessor.type = Accessor::Type::MAT4;
break;
default:
CESIUM_ASSERT(false && "Input type is not supported as an accessor type");
break;
}
PropertyComponentType componentType = TypeToPropertyType<T>::component;
switch (componentType) {
case PropertyComponentType::Int8:
accessor.componentType = Accessor::ComponentType::BYTE;
break;
case PropertyComponentType::Uint8:
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
break;
case PropertyComponentType::Int16:
accessor.componentType = Accessor::ComponentType::SHORT;
break;
case PropertyComponentType::Uint16:
accessor.componentType = Accessor::ComponentType::UNSIGNED_SHORT;
break;
case PropertyComponentType::Float32:
accessor.componentType = Accessor::ComponentType::FLOAT;
break;
default:
CESIUM_ASSERT(
false &&
"Input component type is not supported as an accessor component type");
break;
}
accessor.normalized = Normalized;
primitive.attributes[name] = static_cast<int32_t>(model.accessors.size() - 1);
}
} // namespace
TEST_CASE("Test PropertyAttributeView on model without EXT_structural_metadata "
"extension") {
Model model;
// Create an erroneously isolated property Attribute.
PropertyAttribute propertyAttribute;
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = "_ATTRIBUTE";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(
view.status() ==
PropertyAttributeViewStatus::ErrorMissingMetadataExtension);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test PropertyAttributeView on model without metadata schema") {
Model model;
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = "_ATTRIBUTE";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::ErrorMissingSchema);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test property attribute 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::UINT8;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "I Don't Exist";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = "_ATTRIBUTE";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::ErrorClassNotFound);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
}
TEST_CASE("Test scalar PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
std::vector<uint16_t> data = {12, 34, 30, 11};
addAttributeToModel(model, primitive, attributeName, data);
size_t accessorIndex = model.accessors.size() - 1;
size_t bufferIndex = model.buffers.size() - 1;
size_t bufferViewIndex = 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;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<uint16_t> uint16Property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
uint16Property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(uint16Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(uint16Property.get(static_cast<int64_t>(i)) == data[i]);
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<glm::u16vec2> u16vec2Invalid =
view.getPropertyView<glm::u16vec2>(primitive, "TestClassProperty");
REQUIRE(
u16vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<uint8_t> uint8Invalid =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyAttributePropertyView<int32_t> int32Invalid =
view.getPropertyView<int32_t>(primitive, "TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyAttributePropertyView<float> uint64Invalid =
view.getPropertyView<float>(primitive, "TestClassProperty");
REQUIRE(
uint64Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyAttributePropertyView<uint16_t, true> normalizedInvalid =
view.getPropertyView<uint16_t, true>(primitive, "TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[bufferIndex].cesium.data.resize(4);
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[bufferViewIndex].buffer = 2;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBuffer);
}
SUBCASE("Accessor view points outside of buffer viwe length") {
model.accessors[accessorIndex].count = 10;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorOutOfBounds);
}
SUBCASE("Wrong buffer view index") {
model.accessors[accessorIndex].bufferView = -1;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBufferView);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = true;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
SUBCASE("Wrong accessor component type") {
model.accessors[accessorIndex].componentType =
Accessor::ComponentType::SHORT;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorComponentTypeMismatch);
}
SUBCASE("Wrong accessor type") {
model.accessors[accessorIndex].type = Accessor::Type::VEC2;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorTypeMismatch);
}
SUBCASE("Wrong accessor index") {
primitive.attributes[attributeName] = -1;
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidAccessor);
}
SUBCASE("Missing attribute") {
primitive.attributes.clear();
PropertyAttributePropertyView<uint16_t> property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorMissingAttribute);
}
}
TEST_CASE("Test scalar PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
std::vector<uint8_t> data = {12, 34, 30, 11};
addAttributeToModel<uint8_t, true>(model, primitive, attributeName, data);
size_t accessorIndex = model.accessors.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::UINT8;
testClassProperty.normalized = true;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<uint8_t, true> uint8Property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(
uint8Property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(uint8Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(uint8Property.get(static_cast<int64_t>(i)) == normalize(data[i]));
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<glm::u8vec2, true> u8vec2Invalid =
view.getPropertyView<glm::u8vec2, true>(primitive, "TestClassProperty");
REQUIRE(
u8vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<uint16_t, true> uint16Invalid =
view.getPropertyView<uint16_t, true>(primitive, "TestClassProperty");
REQUIRE(
uint16Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyAttributePropertyView<int32_t> int32Invalid =
view.getPropertyView<int32_t>(primitive, "TestClassProperty");
REQUIRE(
int32Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyAttributePropertyView<uint8_t> normalizedInvalid =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as double") {
PropertyAttributePropertyView<double> doubleInvalid =
view.getPropertyView<double>(primitive, "TestClassProperty");
REQUIRE(
doubleInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = false;
PropertyAttributePropertyView<uint8_t, true> property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
}
TEST_CASE("Test vecN PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
std::vector<glm::u8vec2> data = {
glm::u8vec2(12, 34),
glm::u8vec2(10, 3),
glm::u8vec2(40, 0),
glm::u8vec2(30, 11)};
addAttributeToModel(model, primitive, attributeName, data);
size_t accessorIndex = model.accessors.size() - 1;
size_t bufferIndex = model.buffers.size() - 1;
size_t bufferViewIndex = 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::VEC2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT8;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<glm::u8vec2> u8vec2Property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
u8vec2Property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(u8vec2Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(u8vec2Property.get(static_cast<int64_t>(i)) == data[i]);
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<uint8_t> uint8Invalid =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u8vec3> u8vec3Invalid =
view.getPropertyView<glm::u8vec3>(primitive, "TestClassProperty");
REQUIRE(
u8vec3Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<glm::vec2> vec2Invalid =
view.getPropertyView<glm::vec2>(primitive, "TestClassProperty");
REQUIRE(
vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyAttributePropertyView<glm::u8vec2, true> normalizedInvalid =
view.getPropertyView<glm::u8vec2, true>(primitive, "TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[bufferIndex].cesium.data.resize(4);
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[bufferViewIndex].buffer = 2;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBuffer);
}
SUBCASE("Accessor view points outside of buffer viwe length") {
model.accessors[accessorIndex].count = 10;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorOutOfBounds);
}
SUBCASE("Wrong buffer view index") {
model.accessors[accessorIndex].bufferView = -1;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBufferView);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = true;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
SUBCASE("Wrong accessor component type") {
model.accessors[accessorIndex].componentType =
Accessor::ComponentType::BYTE;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorComponentTypeMismatch);
}
SUBCASE("Wrong accessor type") {
model.accessors[accessorIndex].type = Accessor::Type::SCALAR;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorTypeMismatch);
}
SUBCASE("Wrong accessor index") {
primitive.attributes[attributeName] = -1;
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidAccessor);
}
SUBCASE("Missing attribute") {
primitive.attributes.clear();
PropertyAttributePropertyView<glm::u8vec2> property =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorMissingAttribute);
}
}
TEST_CASE("Test vecN PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
std::vector<glm::u8vec2> data = {
glm::u8vec2(12, 34),
glm::u8vec2(10, 3),
glm::u8vec2(40, 0),
glm::u8vec2(30, 11)};
addAttributeToModel<glm::u8vec2, true>(model, primitive, attributeName, data);
size_t accessorIndex = model.accessors.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::VEC2;
testClassProperty.componentType = ClassProperty::ComponentType::UINT8;
testClassProperty.normalized = true;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<glm::u8vec2, true> u8vec2Property =
view.getPropertyView<glm::u8vec2, true>(primitive, "TestClassProperty");
REQUIRE(
u8vec2Property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(u8vec2Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
u8vec2Property.get(static_cast<int64_t>(i)) == normalize(data[i]));
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<uint8_t, true> uint8Invalid =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u8vec3, true> u8vec3Invalid =
view.getPropertyView<glm::u8vec3, true>(primitive, "TestClassProperty");
REQUIRE(
u8vec3Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<glm::u16vec2, true> u16vec2Invalid =
view.getPropertyView<glm::u16vec2, true>(
primitive,
"TestClassProperty");
REQUIRE(
u16vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
PropertyAttributePropertyView<glm::i8vec2, true> i8vec2Invalid =
view.getPropertyView<glm::i8vec2, true>(primitive, "TestClassProperty");
REQUIRE(
i8vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyAttributePropertyView<glm::u8vec2> normalizedInvalid =
view.getPropertyView<glm::u8vec2>(primitive, "TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as dvec2") {
PropertyAttributePropertyView<glm::dvec2> dvec2Invalid =
view.getPropertyView<glm::dvec2>(primitive, "TestClassProperty");
REQUIRE(
dvec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = false;
PropertyAttributePropertyView<glm::u8vec2, true> property =
view.getPropertyView<glm::u8vec2, true>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
}
TEST_CASE("Test matN PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
// clang-format off
std::vector<glm::u16mat2x2> data = {
glm::u16mat2x2(
12, 34,
30, 1),
glm::u16mat2x2(
11, 8,
73, 102),
glm::u16mat2x2(
1, 0,
63, 2),
glm::u16mat2x2(
4, 8,
3, 23)};
// clang-format on
addAttributeToModel(model, primitive, attributeName, data);
size_t accessorIndex = model.accessors.size() - 1;
size_t bufferIndex = model.buffers.size() - 1;
size_t bufferViewIndex = 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::UINT16;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<glm::u16mat2x2> u16mat2x2Property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
u16mat2x2Property.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(u16mat2x2Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(u16mat2x2Property.get(static_cast<int64_t>(i)) == data[i]);
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<uint16_t> uint16Invalid =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
uint16Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u16vec2> u16vec2Invalid =
view.getPropertyView<glm::u16vec2>(primitive, "TestClassProperty");
REQUIRE(
u16vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u16mat4x4> u16mat4x4Invalid =
view.getPropertyView<glm::u16mat4x4>(primitive, "TestClassProperty");
REQUIRE(
u16mat4x4Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<glm::mat2> mat2Invalid =
view.getPropertyView<glm::mat2>(primitive, "TestClassProperty");
REQUIRE(
mat2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyAttributePropertyView<glm::u16mat2x2, true> normalizedInvalid =
view.getPropertyView<glm::u16mat2x2, true>(
primitive,
"TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Buffer view points outside of the real buffer length") {
model.buffers[bufferIndex].cesium.data.resize(4);
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorBufferViewOutOfBounds);
}
SUBCASE("Wrong buffer index") {
model.bufferViews[bufferViewIndex].buffer = 2;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBuffer);
}
SUBCASE("Accessor view points outside of buffer viwe length") {
model.accessors[accessorIndex].count = 10;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorOutOfBounds);
}
SUBCASE("Wrong buffer view index") {
model.accessors[accessorIndex].bufferView = -1;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidBufferView);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = true;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
SUBCASE("Wrong accessor component type") {
model.accessors[accessorIndex].componentType =
Accessor::ComponentType::BYTE;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorComponentTypeMismatch);
}
SUBCASE("Wrong accessor type") {
model.accessors[accessorIndex].type = Accessor::Type::SCALAR;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorAccessorTypeMismatch);
}
SUBCASE("Wrong accessor index") {
primitive.attributes[attributeName] = -1;
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidAccessor);
}
SUBCASE("Missing attribute") {
primitive.attributes.clear();
PropertyAttributePropertyView<glm::u16mat2x2> property =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
property.status() ==
PropertyAttributePropertyViewStatus::ErrorMissingAttribute);
}
}
TEST_CASE("Test matN PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
const std::string attributeName = "_ATTRIBUTE";
// clang-format off
std::vector<glm::u16mat2x2> data = {
glm::u16mat2x2(
12, 34,
30, 1),
glm::u16mat2x2(
11, 8,
73, 102),
glm::u16mat2x2(
1, 0,
63, 2),
glm::u16mat2x2(
4, 8,
3, 23)};
// clang-format on
addAttributeToModel<glm::u16mat2x2, true>(
model,
primitive,
attributeName,
data);
size_t accessorIndex = model.accessors.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::UINT16;
testClassProperty.normalized = true;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<glm::u16mat2x2, true> u16mat2x2Property =
view.getPropertyView<glm::u16mat2x2, true>(
primitive,
"TestClassProperty");
REQUIRE(
u16mat2x2Property.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(u16mat2x2Property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
u16mat2x2Property.get(static_cast<int64_t>(i)) == normalize(data[i]));
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<uint16_t, true> uint16Invalid =
view.getPropertyView<uint16_t, true>(primitive, "TestClassProperty");
REQUIRE(
uint16Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u16vec2, true> u16vec2Invalid =
view.getPropertyView<glm::u16vec2, true>(
primitive,
"TestClassProperty");
REQUIRE(
u16vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
PropertyAttributePropertyView<glm::u16mat4x4, true> u16mat4x4Invalid =
view.getPropertyView<glm::u16mat4x4, true>(
primitive,
"TestClassProperty");
REQUIRE(
u16mat4x4Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<glm::imat2x2, true> imat2Invalid =
view.getPropertyView<glm::imat2x2, true>(
primitive,
"TestClassProperty");
REQUIRE(
imat2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as non-normalized") {
PropertyAttributePropertyView<glm::u16mat2x2> nonNormalizedInvalid =
view.getPropertyView<glm::u16mat2x2>(primitive, "TestClassProperty");
REQUIRE(
nonNormalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Access incorrectly as dmat2") {
PropertyAttributePropertyView<glm::dmat2> dmat2Invalid =
view.getPropertyView<glm::dmat2>(primitive, "TestClassProperty");
REQUIRE(
dmat2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Wrong accessor normalization") {
model.accessors[accessorIndex].normalized = false;
PropertyAttributePropertyView<glm::u16mat2x2, true> property =
view.getPropertyView<glm::u16mat2x2, true>(
primitive,
"TestClassProperty");
REQUIRE(
property.status() == PropertyAttributePropertyViewStatus::
ErrorAccessorNormalizationMismatch);
}
}
TEST_CASE("Test with PropertyAttributeProperty offset, scale, min, max") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<float> data{1.0f, 2.0f, 3.0f, 4.0f};
const float offset = 1.0f;
const float scale = 2.0f;
const float min = 3.0f;
const float max = 9.0f;
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel(model, primitive, attributeName, data);
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;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
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") {
PropertyAttributePropertyView<float> property =
view.getPropertyView<float>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
REQUIRE(property.offset() == offset);
REQUIRE(property.scale() == scale);
REQUIRE(property.min() == min);
REQUIRE(property.max() == max);
std::vector<float> expected{3.0f, 5.0f, 7.0f, 9.0f};
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(property.get(static_cast<int64_t>(i)) == expected[i]);
}
}
SUBCASE("Use own property values") {
const float newOffset = 0.5f;
const float newScale = -1.0f;
const float newMin = -3.5f;
const float newMax = -0.5f;
propertyAttributeProperty.offset = newOffset;
propertyAttributeProperty.scale = newScale;
propertyAttributeProperty.min = newMin;
propertyAttributeProperty.max = newMax;
PropertyAttributePropertyView<float> property =
view.getPropertyView<float>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
REQUIRE(property.offset() == newOffset);
REQUIRE(property.scale() == newScale);
REQUIRE(property.min() == newMin);
REQUIRE(property.max() == newMax);
std::vector<float> expected{-0.5f, -1.5f, -2.5f, -3.5f};
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(property.get(static_cast<int64_t>(i)) == expected[i]);
}
}
}
TEST_CASE("Test with PropertyAttributeProperty offset, scale, min, max "
"(normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<uint8_t> data{0, 128, 255, 32};
const double offset = 1.0;
const double scale = 2.0;
const double min = 1.0;
const double max = 3.0;
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel<uint8_t, true>(model, primitive, attributeName, data);
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::UINT8;
testClassProperty.normalized = true;
testClassProperty.offset = offset;
testClassProperty.scale = scale;
testClassProperty.min = min;
testClassProperty.max = max;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
std::vector<glm::dvec2> texCoords{
glm::dvec2(0, 0),
glm::dvec2(0.5, 0),
glm::dvec2(0, 0.5),
glm::dvec2(0.5, 0.5)};
SUBCASE("Use class property values") {
PropertyAttributePropertyView<uint8_t, true> property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
REQUIRE(property.offset() == offset);
REQUIRE(property.scale() == scale);
REQUIRE(property.min() == min);
REQUIRE(property.max() == max);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
property.get(static_cast<int64_t>(i)) ==
normalize(data[i]) * scale + offset);
}
}
SUBCASE("Use own property values") {
const double newOffset = 2.0;
const double newScale = 5.0;
const double newMin = 10.0;
const double newMax = 11.0;
propertyAttributeProperty.offset = newOffset;
propertyAttributeProperty.scale = newScale;
propertyAttributeProperty.min = newMin;
propertyAttributeProperty.max = newMax;
PropertyAttributePropertyView<uint8_t, true> property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
REQUIRE(property.offset() == newOffset);
REQUIRE(property.scale() == newScale);
REQUIRE(property.min() == newMin);
REQUIRE(property.max() == newMax);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(property.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
property.get(static_cast<int64_t>(i)) ==
normalize(data[i]) * newScale + newOffset);
}
}
}
TEST_CASE("Test with PropertyAttributeProperty noData") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<uint8_t> data = {12, 34, 30, 11};
const uint8_t noData = 34;
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel(model, primitive, attributeName, data);
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::UINT8;
testClassProperty.noData = noData;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
SUBCASE("Without default value") {
PropertyAttributePropertyView<uint8_t> property =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
auto value = property.getRaw(static_cast<int64_t>(i));
REQUIRE(value == data[i]);
auto maybeValue = property.get(static_cast<int64_t>(i));
if (value == noData) {
REQUIRE(!maybeValue);
} else {
REQUIRE(maybeValue == data[i]);
}
}
}
SUBCASE("With default value") {
const uint8_t defaultValue = 255;
testClassProperty.defaultProperty = defaultValue;
PropertyAttributePropertyView<uint8_t> property =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
auto value = property.getRaw(static_cast<int64_t>(i));
REQUIRE(value == data[i]);
auto maybeValue = property.get(static_cast<int64_t>(i));
if (value == noData) {
REQUIRE(maybeValue == defaultValue);
} else {
REQUIRE(maybeValue == data[i]);
}
}
}
}
TEST_CASE("Test with PropertyAttributeProperty noData (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<uint8_t> data = {12, 34, 30, 11};
const uint8_t noData = 34;
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel<uint8_t, true>(model, primitive, attributeName, data);
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::UINT8;
testClassProperty.normalized = true;
testClassProperty.noData = noData;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
SUBCASE("Without default value") {
PropertyAttributePropertyView<uint8_t, true> property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
auto value = property.getRaw(static_cast<int64_t>(i));
REQUIRE(value == data[i]);
auto maybeValue = property.get(static_cast<int64_t>(i));
if (value == noData) {
REQUIRE(!maybeValue);
} else {
REQUIRE(maybeValue == normalize(data[i]));
}
}
}
SUBCASE("With default value") {
const double defaultValue = -1.0;
testClassProperty.defaultProperty = defaultValue;
PropertyAttributePropertyView<uint8_t, true> property =
view.getPropertyView<uint8_t, true>(primitive, "TestClassProperty");
REQUIRE(property.status() == PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
auto value = property.getRaw(static_cast<int64_t>(i));
REQUIRE(value == data[i]);
auto maybeValue = property.get(static_cast<int64_t>(i));
if (value == noData) {
REQUIRE(maybeValue == defaultValue);
} else {
REQUIRE(maybeValue == normalize(data[i]));
}
}
}
}
TEST_CASE(
"Test nonexistent PropertyAttributeProperty with class property default") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
// Add a position attribute so it can be used to substitute the property
// accessor size.
const std::string attributeName = "POSITION";
std::vector<glm::vec3> data = {
glm::vec3(0),
glm::vec3(1, 2, 3),
glm::vec3(0, 1, 0)};
addAttributeToModel(model, primitive, attributeName, data);
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;
const uint16_t defaultValue = 10;
testClassProperty.defaultProperty = defaultValue;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->defaultProperty);
SUBCASE("Access correct type") {
PropertyAttributePropertyView<uint16_t> uint16Property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
uint16Property.status() ==
PropertyAttributePropertyViewStatus::EmptyPropertyWithDefault);
REQUIRE(uint16Property.size() == static_cast<int64_t>(data.size()));
REQUIRE(uint16Property.defaultValue() == defaultValue);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(uint16Property.get(static_cast<int64_t>(i)) == defaultValue);
}
}
SUBCASE("Access wrong type") {
PropertyAttributePropertyView<glm::u16vec2> u16vec2Invalid =
view.getPropertyView<glm::u16vec2>(primitive, "TestClassProperty");
REQUIRE(
u16vec2Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorTypeMismatch);
}
SUBCASE("Access wrong component type") {
PropertyAttributePropertyView<uint8_t> uint8Invalid =
view.getPropertyView<uint8_t>(primitive, "TestClassProperty");
REQUIRE(
uint8Invalid.status() ==
PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch);
}
SUBCASE("Access incorrectly as normalized") {
PropertyAttributePropertyView<uint16_t, true> normalizedInvalid =
view.getPropertyView<uint16_t, true>(primitive, "TestClassProperty");
REQUIRE(
normalizedInvalid.status() ==
PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch);
}
SUBCASE("Invalid default value") {
testClassProperty.defaultProperty = "not a number";
PropertyAttributePropertyView<uint16_t> uint16Property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
uint16Property.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidDefaultValue);
}
SUBCASE("No default value") {
testClassProperty.defaultProperty.reset();
PropertyAttributePropertyView<uint16_t> uint16Property =
view.getPropertyView<uint16_t>(primitive, "TestClassProperty");
REQUIRE(
uint16Property.status() ==
PropertyAttributePropertyViewStatus::ErrorNonexistentProperty);
}
}
TEST_CASE("Test callback on invalid property Attribute view") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
metadata.schema.emplace();
// Property Attribute has a nonexistent class.
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = "_INVALID";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::ErrorClassNotFound);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(!classProperty);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidPropertyAttribute);
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback on invalid PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
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::UINT8;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["InvalidProperty"];
propertyAttributeProperty.attribute = "_INVALID";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
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() != PropertyAttributePropertyViewStatus::Valid);
};
view.getPropertyView(primitive, "InvalidProperty", testCallback);
view.getPropertyView(primitive, "NonexistentProperty", testCallback);
REQUIRE(invokedCallbackCount == 2);
}
TEST_CASE("Test callback on invalid normalized PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
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.
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = "_INVALID";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
uint32_t invokedCallbackCount = 0;
auto testCallback = [&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::ErrorInvalidNormalization);
};
view.getPropertyView(primitive, "TestClassProperty", testCallback);
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<int16_t> data{-1, 268, 542, -256};
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel(model, primitive, attributeName, data);
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;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
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->array);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<int16_t>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(propertyValue.get(static_cast<int64_t>(i)) == data[i]);
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for scalar PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<int16_t> data{-1, 268, 542, -256};
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel<int16_t, true>(model, primitive, attributeName, data);
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;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
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->array);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<int16_t, true>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
propertyValue.get(static_cast<int64_t>(i)) ==
normalize(data[i]));
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<glm::i8vec2> data{
glm::i8vec2(-1, -1),
glm::i8vec2(12, 1),
glm::i8vec2(30, 2),
glm::i8vec2(0, -1)};
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel(model, primitive, attributeName, data);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC2;
testClassProperty.componentType = ClassProperty::ComponentType::INT8;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<glm::i8vec2>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(propertyValue.get(static_cast<int64_t>(i)) == data[i]);
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for vecN PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
std::vector<glm::i8vec2> data{
glm::i8vec2(-1, -1),
glm::i8vec2(12, 1),
glm::i8vec2(30, 2),
glm::i8vec2(0, -1)};
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel<glm::i8vec2, true>(model, primitive, attributeName, data);
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
testClassProperty.type = ClassProperty::Type::VEC2;
testClassProperty.componentType = ClassProperty::ComponentType::INT8;
testClassProperty.normalized = true;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::VEC2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::INT8);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<glm::i8vec2, true>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
propertyValue.get(static_cast<int64_t>(i)) ==
normalize(data[i]));
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for matN PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
// clang-format off
std::vector<glm::u16mat2x2> data = {
glm::u16mat2x2(
12, 34,
30, 1),
glm::u16mat2x2(
11, 8,
73, 102),
glm::u16mat2x2(
1, 0,
63, 2),
glm::u16mat2x2(
4, 8,
3, 23)};
// clang-format on
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel(model, primitive, attributeName, data);
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::UINT16;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(!classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<glm::u16mat2x2>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(propertyValue.get(static_cast<int64_t>(i)) == data[i]);
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback for matN PropertyAttributeProperty (normalized)") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
// clang-format off
std::vector<glm::u16mat2x2> data = {
glm::u16mat2x2(
12, 34,
30, 1),
glm::u16mat2x2(
11, 8,
73, 102),
glm::u16mat2x2(
1, 0,
63, 2),
glm::u16mat2x2(
4, 8,
3, 23)};
// clang-format on
const std::string attributeName = "_ATTRIBUTE";
addAttributeToModel<glm::u16mat2x2, true>(
model,
primitive,
attributeName,
data);
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::UINT16;
testClassProperty.normalized = true;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& propertyAttributeProperty =
propertyAttribute.properties["TestClassProperty"];
propertyAttributeProperty.attribute = attributeName;
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("TestClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::MAT2);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT16);
REQUIRE(classProperty->count == std::nullopt);
REQUIRE(!classProperty->array);
REQUIRE(classProperty->normalized);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[&data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<glm::u16mat2x2, true>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::Valid);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.getRaw(static_cast<int64_t>(i)) == data[i]);
REQUIRE(
propertyValue.get(static_cast<int64_t>(i)) ==
normalize(data[i]));
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}
TEST_CASE("Test callback on unsupported PropertyAttributeProperty") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
ExtensionModelExtStructuralMetadata& metadata =
model.addExtension<ExtensionModelExtStructuralMetadata>();
Schema& schema = metadata.schema.emplace();
Class& testClass = schema.classes["TestClass"];
ClassProperty& doubleClassProperty =
testClass.properties["DoubleClassProperty"];
doubleClassProperty.type = ClassProperty::Type::SCALAR;
doubleClassProperty.componentType = ClassProperty::ComponentType::FLOAT64;
ClassProperty& arrayClassProperty =
testClass.properties["ArrayClassProperty"];
arrayClassProperty.type = ClassProperty::Type::SCALAR;
arrayClassProperty.componentType = ClassProperty::ComponentType::UINT8;
arrayClassProperty.array = true;
arrayClassProperty.count = 2;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeProperty& doubleProperty =
propertyAttribute.properties["DoubleClassProperty"];
doubleProperty.attribute = "_DOUBLE";
PropertyAttributeProperty& arrayProperty =
propertyAttribute.properties["ArrayClassProperty"];
arrayProperty.attribute = "_ARRAY";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
const ClassProperty* classProperty =
view.getClassProperty("DoubleClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(
classProperty->componentType == ClassProperty::ComponentType::FLOAT64);
REQUIRE(!classProperty->array);
classProperty = view.getClassProperty("ArrayClassProperty");
REQUIRE(classProperty);
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
REQUIRE(classProperty->array);
REQUIRE(classProperty->count == 2);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"DoubleClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty);
});
REQUIRE(invokedCallbackCount == 1);
view.getPropertyView(
primitive,
"ArrayClassProperty",
[&invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty);
});
REQUIRE(invokedCallbackCount == 2);
}
TEST_CASE(
"Test callback for empty PropertyAttributeProperty with default value") {
Model model;
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive& primitive = mesh.primitives.emplace_back();
// Add a position attribute so it can be used to substitute the property
// accessor size.
const std::string attributeName = "POSITION";
std::vector<glm::vec3> data = {
glm::vec3(0),
glm::vec3(1, 2, 3),
glm::vec3(0, 1, 0)};
addAttributeToModel(model, primitive, attributeName, data);
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;
const int16_t defaultValue = 10;
testClassProperty.defaultProperty = defaultValue;
PropertyAttribute& propertyAttribute =
metadata.propertyAttributes.emplace_back();
propertyAttribute.classProperty = "TestClass";
PropertyAttributeView view(model, propertyAttribute);
REQUIRE(view.status() == PropertyAttributeViewStatus::Valid);
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->array);
REQUIRE(!classProperty->normalized);
REQUIRE(classProperty->defaultProperty);
uint32_t invokedCallbackCount = 0;
view.getPropertyView(
primitive,
"TestClassProperty",
[defaultValue, &data, &invokedCallbackCount](
const std::string& /*propertyId*/,
auto propertyValue) mutable {
invokedCallbackCount++;
if constexpr (std::is_same_v<
PropertyAttributePropertyView<int16_t>,
decltype(propertyValue)>) {
REQUIRE(
propertyValue.status() ==
PropertyAttributePropertyViewStatus::EmptyPropertyWithDefault);
REQUIRE(propertyValue.size() == static_cast<int64_t>(data.size()));
REQUIRE(propertyValue.defaultValue() == defaultValue);
for (size_t i = 0; i < data.size(); ++i) {
REQUIRE(propertyValue.get(static_cast<int64_t>(i)) == defaultValue);
}
} else {
FAIL("getPropertyView returned PropertyAttributePropertyView of "
"incorrect type for TestClassProperty.");
}
});
REQUIRE(invokedCallbackCount == 1);
}