3689 lines
127 KiB
C++
3689 lines
127 KiB
C++
#include "makeEnumValue.h"
|
|
|
|
#include <CesiumGltf/Class.h>
|
|
#include <CesiumGltf/ClassProperty.h>
|
|
#include <CesiumGltf/EnumValue.h>
|
|
#include <CesiumGltf/ExtensionKhrTextureTransform.h>
|
|
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
|
|
#include <CesiumGltf/Image.h>
|
|
#include <CesiumGltf/Model.h>
|
|
#include <CesiumGltf/PropertyArrayView.h>
|
|
#include <CesiumGltf/PropertyTexture.h>
|
|
#include <CesiumGltf/PropertyTextureProperty.h>
|
|
#include <CesiumGltf/PropertyTexturePropertyView.h>
|
|
#include <CesiumGltf/PropertyTextureView.h>
|
|
#include <CesiumGltf/PropertyTransformations.h>
|
|
#include <CesiumGltf/Sampler.h>
|
|
#include <CesiumGltf/Schema.h>
|
|
#include <CesiumGltf/Texture.h>
|
|
#include <CesiumGltf/TextureView.h>
|
|
#include <CesiumUtility/Math.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <glm/ext/vector_int2_sized.hpp>
|
|
#include <glm/ext/vector_uint2_sized.hpp>
|
|
#include <glm/ext/vector_uint3_sized.hpp>
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
using namespace CesiumGltf;
|
|
using namespace CesiumNativeTests;
|
|
|
|
namespace {
|
|
void addTextureToModel(
|
|
Model& model,
|
|
int32_t wrapS,
|
|
int32_t wrapT,
|
|
int32_t width,
|
|
int32_t height,
|
|
int32_t channels,
|
|
const std::vector<uint8_t>& data) {
|
|
Image& image = model.images.emplace_back();
|
|
image.pAsset.emplace();
|
|
image.pAsset->width = width;
|
|
image.pAsset->height = height;
|
|
image.pAsset->channels = channels;
|
|
image.pAsset->bytesPerChannel = 1;
|
|
|
|
std::vector<std::byte>& imageData = image.pAsset->pixelData;
|
|
imageData.resize(data.size());
|
|
std::memcpy(imageData.data(), data.data(), data.size());
|
|
|
|
Sampler& sampler = model.samplers.emplace_back();
|
|
sampler.wrapS = wrapS;
|
|
sampler.wrapT = wrapT;
|
|
|
|
Texture& texture = model.textures.emplace_back();
|
|
texture.sampler = static_cast<int32_t>(model.samplers.size() - 1);
|
|
texture.source = static_cast<int32_t>(model.images.size() - 1);
|
|
}
|
|
|
|
template <typename T, bool Normalized>
|
|
void verifyTextureTransformConstruction(
|
|
const PropertyTexturePropertyView<T, Normalized>& propertyView,
|
|
const ExtensionKhrTextureTransform& extension) {
|
|
auto textureTransform = propertyView.getTextureTransform();
|
|
REQUIRE(textureTransform != std::nullopt);
|
|
REQUIRE(
|
|
textureTransform->offset() ==
|
|
glm::dvec2{extension.offset[0], extension.offset[1]});
|
|
REQUIRE(textureTransform->rotation() == extension.rotation);
|
|
REQUIRE(
|
|
textureTransform->scale() ==
|
|
glm::dvec2{extension.scale[0], extension.scale[1]});
|
|
|
|
// Texcoord is overridden by value in KHR_texture_transform.
|
|
if (extension.texCoord) {
|
|
REQUIRE(
|
|
propertyView.getTexCoordSetIndex() ==
|
|
textureTransform->getTexCoordSetIndex());
|
|
REQUIRE(textureTransform->getTexCoordSetIndex() == *extension.texCoord);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("Test PropertyTextureView on model without EXT_structural_metadata "
|
|
"extension") {
|
|
Model model;
|
|
|
|
// Create an erroneously isolated property texture.
|
|
PropertyTexture propertyTexture;
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = 0;
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(
|
|
view.status() ==
|
|
PropertyTextureViewStatus::ErrorMissingMetadataExtension);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(!classProperty);
|
|
}
|
|
|
|
TEST_CASE("Test PropertyTextureView on model without metadata schema") {
|
|
Model model;
|
|
|
|
ExtensionModelExtStructuralMetadata& metadata =
|
|
model.addExtension<ExtensionModelExtStructuralMetadata>();
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = 0;
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::ErrorMissingSchema);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(!classProperty);
|
|
}
|
|
|
|
TEST_CASE("Test property texture 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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "I Don't Exist";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = 0;
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::ErrorClassNotFound);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(!classProperty);
|
|
}
|
|
|
|
TEST_CASE("Test scalar PropertyTextureProperty") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 30, 11};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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") {
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == data[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty", options);
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(uint8Property, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<uint8_t> expected{data[3], data[1], data[2], data[0]};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty", options);
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == data[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Invalid =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<uint16_t> uint16Invalid =
|
|
view.getPropertyView<uint16_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint16Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<int32_t> int32Invalid =
|
|
view.getPropertyView<int32_t>("TestClassProperty");
|
|
REQUIRE(
|
|
int32Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<float> uint64Invalid =
|
|
view.getPropertyView<float>("TestClassProperty");
|
|
REQUIRE(
|
|
uint64Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as array") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> arrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
arrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as normalized") {
|
|
PropertyTexturePropertyView<uint8_t, true> normalizedInvalid =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 2;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid channel values") {
|
|
propertyTextureProperty.channels = {5};
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Zero channel values") {
|
|
propertyTextureProperty.channels.clear();
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Invalid bytes per channel") {
|
|
model.images[imageIndex].pAsset->bytesPerChannel = 2;
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel);
|
|
}
|
|
|
|
SUBCASE("Empty image") {
|
|
model.images[imageIndex].pAsset->width = 0;
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorEmptyImage);
|
|
}
|
|
|
|
SUBCASE("Wrong image index") {
|
|
model.textures[textureIndex].source = 1;
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidImage);
|
|
}
|
|
|
|
SUBCASE("Wrong sampler index") {
|
|
model.textures[textureIndex].sampler = 1;
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidSampler);
|
|
}
|
|
|
|
SUBCASE("Wrong texture index") {
|
|
propertyTextureProperty.index = 1;
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidTexture);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test scalar PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 30, 11};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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") {
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == normalize(data[i]));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty", options);
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(uint8Property, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<uint8_t> expected{data[3], data[1], data[2], data[0]};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty", options);
|
|
REQUIRE(uint8Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == normalize(data[i]));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<glm::u8vec2, true> u8vec2Invalid =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<uint16_t, true> uint16Invalid =
|
|
view.getPropertyView<uint16_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
uint16Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<int32_t> int32Invalid =
|
|
view.getPropertyView<int32_t>("TestClassProperty");
|
|
REQUIRE(
|
|
int32Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as array") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true> arrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
arrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as non-normalized") {
|
|
PropertyTexturePropertyView<uint8_t> normalizedInvalid =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as double") {
|
|
PropertyTexturePropertyView<double> doubleInvalid =
|
|
view.getPropertyView<double>("TestClassProperty");
|
|
REQUIRE(
|
|
doubleInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 2;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test vecN PropertyTextureProperty") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 10, 3, 40, 0, 30, 11};
|
|
std::vector<glm::u8vec2> expected{
|
|
glm::u8vec2(data[0], data[1]),
|
|
glm::u8vec2(data[2], data[3]),
|
|
glm::u8vec2(data[4], data[5]),
|
|
glm::u8vec2(data[6], data[7])};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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") {
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(u8vec2Property.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty", options);
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(u8vec2Property, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<glm::u8vec2> expectedTransformed{
|
|
expected[3],
|
|
expected[1],
|
|
expected[2],
|
|
expected[0]};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expectedTransformed[i]);
|
|
REQUIRE(u8vec2Property.get(uv[0], uv[1]) == expectedTransformed[i]);
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty", options);
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(u8vec2Property.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<uint8_t> uint8Invalid =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::u8vec3> u8vec3Invalid =
|
|
view.getPropertyView<glm::u8vec3>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec3Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<glm::u16vec2> u16vec2Invalid =
|
|
view.getPropertyView<glm::u16vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u16vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::i8vec2> i8vec2Invalid =
|
|
view.getPropertyView<glm::i8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
i8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as array") {
|
|
PropertyTexturePropertyView<PropertyArrayView<glm::u8vec2>> arrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<glm::u8vec2>>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
arrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as normalized") {
|
|
PropertyTexturePropertyView<glm::u8vec2, true> normalizedInvalid =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 4;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid channel values") {
|
|
propertyTextureProperty.channels = {0, 4};
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Invalid bytes per channel") {
|
|
model.images[imageIndex].pAsset->bytesPerChannel = 2;
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test vecN PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 10, 3, 40, 0, 30, 11};
|
|
std::vector<glm::u8vec2> expected{
|
|
glm::u8vec2(data[0], data[1]),
|
|
glm::u8vec2(data[2], data[3]),
|
|
glm::u8vec2(data[4], data[5]),
|
|
glm::u8vec2(data[6], data[7])};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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") {
|
|
PropertyTexturePropertyView<glm::u8vec2, true> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(u8vec2Property.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<glm::u8vec2, true> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty", options);
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(u8vec2Property, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<glm::u8vec2> expectedTransformed{
|
|
expected[3],
|
|
expected[1],
|
|
expected[2],
|
|
expected[0]};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expectedTransformed[i]);
|
|
REQUIRE(
|
|
u8vec2Property.get(uv[0], uv[1]) ==
|
|
normalize(expectedTransformed[i]));
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<glm::u8vec2, true> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty", options);
|
|
REQUIRE(
|
|
u8vec2Property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(u8vec2Property.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(u8vec2Property.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Invalid =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::u8vec3, true> u8vec3Invalid =
|
|
view.getPropertyView<glm::u8vec3, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec3Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<glm::u16vec2, true> u16vec2Invalid =
|
|
view.getPropertyView<glm::u16vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u16vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::i8vec2, true> i8vec2Invalid =
|
|
view.getPropertyView<glm::i8vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
i8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as array") {
|
|
PropertyTexturePropertyView<PropertyArrayView<glm::u8vec2>, true>
|
|
arrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<glm::u8vec2>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
arrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as non-normalized") {
|
|
PropertyTexturePropertyView<glm::u8vec2> normalizedInvalid =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as dvec2") {
|
|
PropertyTexturePropertyView<glm::dvec2> dvec2Invalid =
|
|
view.getPropertyView<glm::dvec2>("TestClassProperty");
|
|
REQUIRE(
|
|
dvec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 4;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
PropertyTexturePropertyView<glm::u8vec2, true> u8vec2Property =
|
|
view.getPropertyView<glm::u8vec2, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test array PropertyTextureProperty") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
12, 34, 10,
|
|
40, 0, 30,
|
|
80, 4, 2,
|
|
6, 3, 4,
|
|
};
|
|
// clang-format on
|
|
std::vector<std::array<uint8_t, 3>> expected = {
|
|
{data[0], data[1], data[2]},
|
|
{data[3], data[4], data[5]},
|
|
{data[6], data[7], data[8]},
|
|
{data[9], data[10], data[11]},
|
|
};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
3,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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.array = true;
|
|
testClassProperty.count = 3;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(classProperty);
|
|
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
|
|
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
|
|
REQUIRE(classProperty->array);
|
|
REQUIRE(classProperty->count == 3);
|
|
REQUIRE(!classProperty->normalized);
|
|
|
|
SUBCASE("Access correct type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
auto maybeValue = uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(uint8ArrayProperty, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<std::array<uint8_t, 3>> expectedTransformed{
|
|
expected[3],
|
|
expected[1],
|
|
expected[2],
|
|
expected[0]};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expectedTransformed[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<uint8_t>> maybeValue =
|
|
uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<uint8_t>> maybeValue =
|
|
uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<int8_t>> int8ArrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<int8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
int8ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint16_t>>
|
|
uint16ArrayInvalid = view.getPropertyView<PropertyArrayView<uint16_t>>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
uint16ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as non-array") {
|
|
PropertyTexturePropertyView<uint8_t> uint8Invalid =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::u8vec3> u8vec3Invalid =
|
|
view.getPropertyView<glm::u8vec3>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec3Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as normalized") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true>
|
|
normalizedInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 4;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid channel values") {
|
|
propertyTextureProperty.channels = {0, 4, 1};
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Invalid bytes per channel") {
|
|
model.images[imageIndex].pAsset->bytesPerChannel = 2;
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test array PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
12, 34, 10,
|
|
40, 0, 30,
|
|
80, 4, 2,
|
|
6, 3, 4,
|
|
};
|
|
// clang-format on
|
|
|
|
std::vector<std::array<uint8_t, 3>> expected = {
|
|
{data[0], data[1], data[2]},
|
|
{data[3], data[4], data[5]},
|
|
{data[6], data[7], data[8]},
|
|
{data[9], data[10], data[11]},
|
|
};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
2,
|
|
3,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.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.array = true;
|
|
testClassProperty.count = 3;
|
|
testClassProperty.normalized = true;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(classProperty);
|
|
REQUIRE(classProperty->type == ClassProperty::Type::SCALAR);
|
|
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
|
|
REQUIRE(classProperty->array);
|
|
REQUIRE(classProperty->count == 3);
|
|
REQUIRE(classProperty->normalized);
|
|
|
|
SUBCASE("Access correct type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true>
|
|
uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<double>> maybeValue =
|
|
uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == normalize(value[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true>
|
|
uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(uint8ArrayProperty, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<std::array<uint8_t, 3>> expectedTransformed{
|
|
expected[3],
|
|
expected[1],
|
|
expected[2],
|
|
expected[0]};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expectedTransformed[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<double>> maybeValue =
|
|
uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == normalize(value[j]));
|
|
}
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true>
|
|
uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value =
|
|
uint8ArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<double>> maybeValue =
|
|
uint8ArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == normalize(value[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<int8_t>, true>
|
|
int8ArrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<int8_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
int8ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint16_t>, true>
|
|
uint16ArrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint16_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
uint16ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as non-array") {
|
|
PropertyTexturePropertyView<uint8_t, true> uint8Invalid =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::u8vec3, true> u8vec3Invalid =
|
|
view.getPropertyView<glm::u8vec3, true>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec3Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as normalized") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> normalizedInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 4;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>, true>
|
|
uint8ArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>, true>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
uint8ArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test with PropertyTextureProperty offset, scale, min, max") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data{
|
|
0, 0, 0, 1,
|
|
9, 0, 1, 0,
|
|
20, 2, 2, 0,
|
|
8, 1, 0, 1};
|
|
// clang-format on
|
|
|
|
std::vector<uint32_t> expectedUint{16777216, 65545, 131604, 16777480};
|
|
|
|
const float offset = 1.0f;
|
|
const float scale = 2.0f;
|
|
const float min = -10.0f;
|
|
const float max = 10.0f;
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
4,
|
|
data);
|
|
size_t textureIndex = model.textures.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
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") {
|
|
PropertyTexturePropertyView<float> property =
|
|
view.getPropertyView<float>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
REQUIRE(property.offset() == offset);
|
|
REQUIRE(property.scale() == scale);
|
|
REQUIRE(property.min() == min);
|
|
REQUIRE(property.max() == max);
|
|
|
|
std::vector<float> expectedRaw(expectedUint.size());
|
|
std::vector<float> expectedTransformed(expectedUint.size());
|
|
for (size_t i = 0; i < expectedUint.size(); i++) {
|
|
float value = *reinterpret_cast<float*>(&expectedUint[i]);
|
|
expectedRaw[i] = value;
|
|
expectedTransformed[i] = value * scale + offset;
|
|
}
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(property.getRaw(uv[0], uv[1]) == expectedRaw[i]);
|
|
REQUIRE(property.get(uv[0], uv[1]) == expectedTransformed[i]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
propertyTextureProperty.offset = newOffset;
|
|
propertyTextureProperty.scale = newScale;
|
|
propertyTextureProperty.min = newMin;
|
|
propertyTextureProperty.max = newMax;
|
|
|
|
PropertyTexturePropertyView<float> property =
|
|
view.getPropertyView<float>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
REQUIRE(property.offset() == newOffset);
|
|
REQUIRE(property.scale() == newScale);
|
|
REQUIRE(property.min() == newMin);
|
|
REQUIRE(property.max() == newMax);
|
|
|
|
std::vector<float> expectedRaw(expectedUint.size());
|
|
std::vector<float> expectedTransformed(expectedUint.size());
|
|
for (size_t i = 0; i < expectedUint.size(); i++) {
|
|
float value = *reinterpret_cast<float*>(&expectedUint[i]);
|
|
expectedRaw[i] = value;
|
|
expectedTransformed[i] = value * newScale + newOffset;
|
|
}
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(property.getRaw(uv[0], uv[1]) == expectedRaw[i]);
|
|
REQUIRE(property.get(uv[0], uv[1]) == expectedTransformed[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Test with PropertyTextureProperty offset, scale, min, max (normalized)") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 30, 11};
|
|
|
|
const double offset = 1.0;
|
|
const double scale = 2.0;
|
|
const double min = 1.0;
|
|
const double max = 3.0;
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.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;
|
|
testClassProperty.offset = offset;
|
|
testClassProperty.scale = scale;
|
|
testClassProperty.min = min;
|
|
testClassProperty.max = max;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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") {
|
|
PropertyTexturePropertyView<uint8_t, true> property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
REQUIRE(property.offset() == offset);
|
|
REQUIRE(property.scale() == scale);
|
|
REQUIRE(property.min() == min);
|
|
REQUIRE(property.max() == max);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(
|
|
property.get(uv[0], uv[1]) == 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;
|
|
propertyTextureProperty.offset = newOffset;
|
|
propertyTextureProperty.scale = newScale;
|
|
propertyTextureProperty.min = newMin;
|
|
propertyTextureProperty.max = newMax;
|
|
|
|
PropertyTexturePropertyView<uint8_t, true> property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
REQUIRE(property.offset() == newOffset);
|
|
REQUIRE(property.scale() == newScale);
|
|
REQUIRE(property.min() == newMin);
|
|
REQUIRE(property.max() == newMax);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(property.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(
|
|
property.get(uv[0], uv[1]) ==
|
|
normalize(data[i]) * newScale + newOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test with PropertyTextureProperty noData") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 30, 11};
|
|
const uint8_t noData = 34;
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.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.noData = noData;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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("Without default value") {
|
|
PropertyTexturePropertyView<uint8_t> property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
auto value = property.getRaw(uv[0], uv[1]);
|
|
REQUIRE(value == data[i]);
|
|
|
|
auto maybeValue = property.get(uv[0], uv[1]);
|
|
if (value == noData) {
|
|
REQUIRE(!maybeValue);
|
|
} else {
|
|
REQUIRE(maybeValue == data[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("With default value") {
|
|
const uint8_t defaultValue = 255;
|
|
testClassProperty.defaultProperty = defaultValue;
|
|
|
|
PropertyTexturePropertyView<uint8_t> property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
auto value = property.getRaw(uv[0], uv[1]);
|
|
REQUIRE(value == data[i]);
|
|
|
|
auto maybeValue = property.get(uv[0], uv[1]);
|
|
if (value == noData) {
|
|
REQUIRE(maybeValue == defaultValue);
|
|
} else {
|
|
REQUIRE(maybeValue == data[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test with PropertyTextureProperty noData (normalized)") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {12, 34, 30, 11};
|
|
const uint8_t noData = 34;
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.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;
|
|
testClassProperty.noData = noData;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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("Without default value") {
|
|
PropertyTexturePropertyView<uint8_t, true> property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
auto value = property.getRaw(uv[0], uv[1]);
|
|
REQUIRE(value == data[i]);
|
|
|
|
auto maybeValue = property.get(uv[0], uv[1]);
|
|
if (value == noData) {
|
|
REQUIRE(!maybeValue);
|
|
} else {
|
|
REQUIRE(maybeValue == normalize(data[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("With default value") {
|
|
const double defaultValue = -1.0;
|
|
testClassProperty.defaultProperty = defaultValue;
|
|
|
|
PropertyTexturePropertyView<uint8_t, true> property =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(property.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
auto value = property.getRaw(uv[0], uv[1]);
|
|
REQUIRE(value == data[i]);
|
|
|
|
auto maybeValue = property.get(uv[0], uv[1]);
|
|
if (value == noData) {
|
|
REQUIRE(maybeValue == defaultValue);
|
|
} else {
|
|
REQUIRE(maybeValue == normalize(data[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Test nonexistent PropertyTextureProperty 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::UINT8;
|
|
|
|
const uint8_t defaultValue = 10;
|
|
testClassProperty.defaultProperty = defaultValue;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
REQUIRE(classProperty->defaultProperty);
|
|
|
|
SUBCASE("Access correct type") {
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault);
|
|
REQUIRE(uint8Property.defaultValue() == defaultValue);
|
|
|
|
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)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(uint8Property.get(uv[0], uv[1]) == defaultValue);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Invalid =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access wrong component type") {
|
|
PropertyTexturePropertyView<uint16_t> uint16Invalid =
|
|
view.getPropertyView<uint16_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint16Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as normalized") {
|
|
PropertyTexturePropertyView<uint8_t, true> normalizedInvalid =
|
|
view.getPropertyView<uint8_t, true>("TestClassProperty");
|
|
REQUIRE(
|
|
normalizedInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNormalizationMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid default value") {
|
|
testClassProperty.defaultProperty = "not a number";
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidDefaultValue);
|
|
}
|
|
|
|
SUBCASE("No default value") {
|
|
testClassProperty.defaultProperty.reset();
|
|
PropertyTexturePropertyView<uint8_t> uint8Property =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Property.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorNonexistentProperty);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback on invalid property texture view") {
|
|
Model model;
|
|
ExtensionModelExtStructuralMetadata& metadata =
|
|
model.addExtension<ExtensionModelExtStructuralMetadata>();
|
|
metadata.schema.emplace();
|
|
|
|
// Property texture has a nonexistent class.
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = -1;
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::ErrorClassNotFound);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(!classProperty);
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidPropertyTexture);
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
TEST_CASE("Test callback on invalid PropertyTextureProperty") {
|
|
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::UINT8;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["InvalidProperty"];
|
|
propertyTextureProperty.index = -1;
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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() != PropertyTexturePropertyViewStatus::Valid);
|
|
};
|
|
|
|
view.getPropertyView("InvalidProperty", testCallback);
|
|
view.getPropertyView("NonexistentProperty", testCallback);
|
|
|
|
REQUIRE(invokedCallbackCount == 2);
|
|
}
|
|
|
|
TEST_CASE("Test callback on invalid normalized PropertyTextureProperty") {
|
|
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.
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(0);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidNormalization);
|
|
};
|
|
|
|
view.getPropertyView("TestClassProperty", testCallback);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
TEST_CASE("Test callback for scalar PropertyTextureProperty") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {255, 255, 12, 1, 30, 2, 0, 255};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
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)};
|
|
|
|
std::vector<int16_t> expected{-1, 268, 542, -256};
|
|
|
|
SUBCASE("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<int16_t>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(propertyValue.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<int16_t>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(propertyValue.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback for scalar PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {255, 255, 12, 1, 30, 2, 0, 255};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.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;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
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)};
|
|
|
|
std::vector<int16_t> expected{-1, 268, 542, -256};
|
|
|
|
SUBCASE("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<int16_t, true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(
|
|
propertyValue.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<int16_t, true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(
|
|
propertyValue.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback for vecN PropertyTextureProperty") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
255, 255,
|
|
12, 1,
|
|
30, 2,
|
|
0, 255};
|
|
// clang-format on
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.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::INT8;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
std::vector<glm::i8vec2> expected{
|
|
glm::i8vec2(-1, -1),
|
|
glm::i8vec2(12, 1),
|
|
glm::i8vec2(30, 2),
|
|
glm::i8vec2(0, -1)};
|
|
|
|
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("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<glm::i8vec2>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(propertyValue.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<glm::i8vec2>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(propertyValue.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback for vecN PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
255, 255,
|
|
12, 1,
|
|
30, 2,
|
|
0, 255};
|
|
// clang-format on
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
2,
|
|
data);
|
|
size_t textureIndex = model.textures.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::INT8;
|
|
testClassProperty.normalized = true;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
std::vector<glm::i8vec2> expected{
|
|
glm::i8vec2(-1, -1),
|
|
glm::i8vec2(12, 1),
|
|
glm::i8vec2(30, 2),
|
|
glm::i8vec2(0, -1)};
|
|
|
|
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("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<glm::i8vec2, true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(
|
|
propertyValue.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<glm::i8vec2, true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(
|
|
propertyValue.get(uv[0], uv[1]) == normalize(expected[i]));
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback for array PropertyTextureProperty") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
254, 0, 253, 1,
|
|
10, 2, 40, 3,
|
|
30, 0, 0, 2,
|
|
10, 2, 255, 4};
|
|
// clang-format on
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
4,
|
|
data);
|
|
size_t textureIndex = model.textures.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.count = 2;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
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 == 2);
|
|
REQUIRE(!classProperty->normalized);
|
|
|
|
std::vector<std::vector<uint16_t>> expected{
|
|
{254, 509},
|
|
{522, 808},
|
|
{30, 512},
|
|
{522, 1279}};
|
|
|
|
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("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<
|
|
PropertyArrayView<uint16_t>>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
std::vector<uint16_t>& expectedArray = expected[i];
|
|
glm::dvec2& uv = texCoords[i];
|
|
PropertyArrayCopy<uint16_t> array =
|
|
propertyValue.getRaw(uv[0], uv[1]);
|
|
|
|
REQUIRE(
|
|
static_cast<size_t>(array.size()) == expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(array[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<uint16_t>> maybeArray =
|
|
propertyValue.get(uv[0], uv[1]);
|
|
REQUIRE(maybeArray);
|
|
REQUIRE(
|
|
static_cast<size_t>(maybeArray->size()) ==
|
|
expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(
|
|
(*maybeArray)[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<
|
|
PropertyArrayView<uint16_t>>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
std::vector<uint16_t>& expectedArray = expected[i];
|
|
glm::dvec2& uv = texCoords[i];
|
|
auto array = propertyValue.getRaw(uv[0], uv[1]);
|
|
|
|
REQUIRE(
|
|
static_cast<size_t>(array.size()) == expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(array[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
auto maybeArray = propertyValue.get(uv[0], uv[1]);
|
|
REQUIRE(maybeArray);
|
|
REQUIRE(
|
|
static_cast<size_t>(maybeArray->size()) ==
|
|
expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(
|
|
(*maybeArray)[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback for array PropertyTextureProperty (normalized)") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
254, 0, 253, 1,
|
|
10, 2, 40, 3,
|
|
30, 0, 0, 2,
|
|
10, 2, 255, 4};
|
|
// clang-format on
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
2,
|
|
4,
|
|
data);
|
|
size_t textureIndex = model.textures.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.count = 2;
|
|
testClassProperty.normalized = true;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
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 == 2);
|
|
REQUIRE(classProperty->normalized);
|
|
|
|
std::vector<std::vector<uint16_t>> expected{
|
|
{254, 509},
|
|
{522, 808},
|
|
{30, 512},
|
|
{522, 1279}};
|
|
|
|
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("Works") {
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<
|
|
PropertyArrayView<uint16_t>,
|
|
true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
std::vector<uint16_t>& expectedArray = expected[i];
|
|
glm::dvec2& uv = texCoords[i];
|
|
PropertyArrayCopy<uint16_t> array =
|
|
propertyValue.getRaw(uv[0], uv[1]);
|
|
|
|
REQUIRE(
|
|
static_cast<size_t>(array.size()) == expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(array[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
auto maybeArray = propertyValue.get(uv[0], uv[1]);
|
|
REQUIRE(maybeArray);
|
|
REQUIRE(
|
|
static_cast<size_t>(maybeArray->size()) ==
|
|
expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
auto rawValue = expectedArray[static_cast<size_t>(j)];
|
|
REQUIRE((*maybeArray)[j] == normalize(rawValue));
|
|
}
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
SUBCASE("Works with options") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[&expected, &texCoords, &invokedCallbackCount, &model](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<
|
|
PropertyArrayView<uint16_t>,
|
|
true>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(
|
|
emptyData);
|
|
|
|
for (size_t i = 0; i < expected.size(); ++i) {
|
|
std::vector<uint16_t>& expectedArray = expected[i];
|
|
glm::dvec2& uv = texCoords[i];
|
|
PropertyArrayCopy<uint16_t> array =
|
|
propertyValue.getRaw(uv[0], uv[1]);
|
|
|
|
REQUIRE(
|
|
static_cast<size_t>(array.size()) == expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
REQUIRE(array[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
auto maybeArray = propertyValue.get(uv[0], uv[1]);
|
|
REQUIRE(maybeArray);
|
|
REQUIRE(
|
|
static_cast<size_t>(maybeArray->size()) ==
|
|
expectedArray.size());
|
|
for (int64_t j = 0; j < array.size(); j++) {
|
|
auto rawValue = expectedArray[static_cast<size_t>(j)];
|
|
REQUIRE((*maybeArray)[j] == normalize(rawValue));
|
|
}
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
},
|
|
options);
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test callback on unsupported PropertyTextureProperty") {
|
|
Model model;
|
|
// clang-format off
|
|
std::vector<uint8_t> data = {
|
|
254, 0, 253, 1,
|
|
10, 2, 40, 3,
|
|
30, 0, 0, 2,
|
|
10, 2, 255, 4};
|
|
// clang-format on
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
Sampler::WrapS::CLAMP_TO_EDGE,
|
|
2,
|
|
1,
|
|
8,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
|
|
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::VEC4;
|
|
arrayClassProperty.componentType = ClassProperty::ComponentType::UINT8;
|
|
arrayClassProperty.array = true;
|
|
arrayClassProperty.count = 2;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& doubleProperty =
|
|
propertyTexture.properties["DoubleClassProperty"];
|
|
doubleProperty.index = static_cast<int32_t>(textureIndex);
|
|
doubleProperty.texCoord = 0;
|
|
doubleProperty.channels = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
|
|
PropertyTextureProperty& arrayProperty =
|
|
propertyTexture.properties["ArrayClassProperty"];
|
|
arrayProperty.index = static_cast<int32_t>(textureIndex);
|
|
arrayProperty.texCoord = 0;
|
|
arrayProperty.channels = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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::VEC4);
|
|
REQUIRE(classProperty->componentType == ClassProperty::ComponentType::UINT8);
|
|
REQUIRE(classProperty->array);
|
|
REQUIRE(classProperty->count == 2);
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"DoubleClassProperty",
|
|
[&invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorUnsupportedProperty);
|
|
});
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
|
|
view.getPropertyView(
|
|
"ArrayClassProperty",
|
|
[&invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorUnsupportedProperty);
|
|
});
|
|
REQUIRE(invokedCallbackCount == 2);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Test callback for empty PropertyTextureProperty 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::INT16;
|
|
|
|
const int16_t defaultValue = 10;
|
|
testClassProperty.defaultProperty = defaultValue;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::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);
|
|
|
|
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)};
|
|
|
|
uint32_t invokedCallbackCount = 0;
|
|
view.getPropertyView(
|
|
"TestClassProperty",
|
|
[defaultValue, &texCoords, &invokedCallbackCount](
|
|
const std::string& /*propertyId*/,
|
|
auto propertyValue) mutable {
|
|
invokedCallbackCount++;
|
|
if constexpr (std::is_same_v<
|
|
PropertyTexturePropertyView<int16_t>,
|
|
decltype(propertyValue)>) {
|
|
REQUIRE(
|
|
propertyValue.status() ==
|
|
PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault);
|
|
REQUIRE(propertyValue.defaultValue() == defaultValue);
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2& uv = texCoords[i];
|
|
REQUIRE(propertyValue.get(uv[0], uv[1]) == defaultValue);
|
|
}
|
|
} else {
|
|
FAIL("getPropertyView returned PropertyTexturePropertyView of "
|
|
"incorrect type for TestClassProperty.");
|
|
}
|
|
});
|
|
|
|
REQUIRE(invokedCallbackCount == 1);
|
|
}
|
|
|
|
TEST_CASE("Test enum PropertyTextureProperty") {
|
|
Model model;
|
|
std::vector<uint8_t> data = {11, 28, 223, 191, 0, 77, 43, 1};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
4,
|
|
1,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.size() - 1;
|
|
|
|
ExtensionModelExtStructuralMetadata& metadata =
|
|
model.addExtension<ExtensionModelExtStructuralMetadata>();
|
|
|
|
Schema& schema = metadata.schema.emplace();
|
|
Enum& enumDef = schema.enums["TestEnum"];
|
|
enumDef.name = "Test";
|
|
enumDef.description = "An example enum";
|
|
enumDef.values = std::vector<EnumValue>{
|
|
makeEnumValue("Foo", 11),
|
|
makeEnumValue("Bar", 28),
|
|
makeEnumValue("Baz", 223),
|
|
makeEnumValue("Qux", 191),
|
|
makeEnumValue("Quig", 0),
|
|
makeEnumValue("Quag", 77),
|
|
makeEnumValue("Hock", 43),
|
|
makeEnumValue("Hork", 1),
|
|
};
|
|
enumDef.valueType = Enum::ValueType::UINT8;
|
|
|
|
Class& testClass = schema.classes["TestClass"];
|
|
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
|
|
testClassProperty.type = ClassProperty::Type::ENUM;
|
|
testClassProperty.enumType = "TestEnum";
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
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);
|
|
REQUIRE(!classProperty->normalized);
|
|
|
|
SUBCASE("Access correct type") {
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(enumProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(0.5, 0),
|
|
glm::dvec2(0, 0.25),
|
|
glm::dvec2(0.5, 0.25),
|
|
glm::dvec2(0, 0.5),
|
|
glm::dvec2(0.5, 0.5),
|
|
glm::dvec2(0, 0.75),
|
|
glm::dvec2(0.5, 0.75)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(enumProperty.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(enumProperty.get(uv[0], uv[1]) == data[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty", options);
|
|
REQUIRE(enumProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(enumProperty, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<uint8_t> expected{data[5], data[1], data[4], data[0]};
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(enumProperty.getRaw(uv[0], uv[1]) == expected[i]);
|
|
REQUIRE(enumProperty.get(uv[0], uv[1]) == expected[i]);
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty", options);
|
|
REQUIRE(enumProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(0.5, 0),
|
|
glm::dvec2(0, 0.25),
|
|
glm::dvec2(0.5, 0.25),
|
|
glm::dvec2(0, 0.5),
|
|
glm::dvec2(0.5, 0.5),
|
|
glm::dvec2(0, 0.75),
|
|
glm::dvec2(0.5, 0.75)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
REQUIRE(enumProperty.getRaw(uv[0], uv[1]) == data[i]);
|
|
REQUIRE(enumProperty.get(uv[0], uv[1]) == data[i]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<glm::u8vec2> u8vec2Invalid =
|
|
view.getPropertyView<glm::u8vec2>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec2Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as array") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> arrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
arrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 2;
|
|
propertyTextureProperty.channels = {0, 1};
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid channel values") {
|
|
propertyTextureProperty.channels = {5};
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Zero channel values") {
|
|
propertyTextureProperty.channels.clear();
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Invalid bytes per channel") {
|
|
model.images[imageIndex].pAsset->bytesPerChannel = 2;
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel);
|
|
}
|
|
|
|
SUBCASE("Empty image") {
|
|
model.images[imageIndex].pAsset->width = 0;
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorEmptyImage);
|
|
}
|
|
|
|
SUBCASE("Wrong image index") {
|
|
model.textures[textureIndex].source = 1;
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidImage);
|
|
}
|
|
|
|
SUBCASE("Wrong sampler index") {
|
|
model.textures[textureIndex].sampler = 1;
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidSampler);
|
|
}
|
|
|
|
SUBCASE("Wrong texture index") {
|
|
propertyTextureProperty.index = 1;
|
|
PropertyTexturePropertyView<uint8_t> enumProperty =
|
|
view.getPropertyView<uint8_t>("TestClassProperty");
|
|
REQUIRE(
|
|
enumProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidTexture);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Test enum array PropertyTextureProperty") {
|
|
|
|
Model model;
|
|
std::vector<uint8_t> data = {11, 28, 223, 191, 0, 77, 43, 1,
|
|
200, 200, 1, 43, 77, 0, 191, 223,
|
|
28, 11, 1, 200, 43, 77, 28, 0};
|
|
|
|
std::vector<std::array<uint8_t, 3>> expected{
|
|
{11, 28, 223},
|
|
{191, 0, 77},
|
|
{43, 1, 200},
|
|
{200, 1, 43},
|
|
{77, 0, 191},
|
|
{223, 28, 11},
|
|
{1, 200, 43},
|
|
{77, 28, 0}};
|
|
|
|
addTextureToModel(
|
|
model,
|
|
Sampler::WrapS::REPEAT,
|
|
Sampler::WrapT::REPEAT,
|
|
2,
|
|
4,
|
|
3,
|
|
data);
|
|
size_t textureIndex = model.textures.size() - 1;
|
|
size_t imageIndex = model.images.size() - 1;
|
|
|
|
ExtensionModelExtStructuralMetadata& metadata =
|
|
model.addExtension<ExtensionModelExtStructuralMetadata>();
|
|
|
|
Schema& schema = metadata.schema.emplace();
|
|
Enum& enumDef = schema.enums["TestEnum"];
|
|
enumDef.name = "Test";
|
|
enumDef.description = "An example enum";
|
|
enumDef.values = std::vector<EnumValue>{
|
|
makeEnumValue("Foo", 11),
|
|
makeEnumValue("Bar", 28),
|
|
makeEnumValue("Baz", 223),
|
|
makeEnumValue("Qux", 191),
|
|
makeEnumValue("Quig", 0),
|
|
makeEnumValue("Quag", 77),
|
|
makeEnumValue("Hock", 43),
|
|
makeEnumValue("Hork", 1),
|
|
makeEnumValue("Hurk", 200)};
|
|
enumDef.valueType = Enum::ValueType::UINT8;
|
|
|
|
Class& testClass = schema.classes["TestClass"];
|
|
ClassProperty& testClassProperty = testClass.properties["TestClassProperty"];
|
|
testClassProperty.type = ClassProperty::Type::ENUM;
|
|
testClassProperty.enumType = "TestEnum";
|
|
testClassProperty.array = true;
|
|
testClassProperty.count = 3;
|
|
|
|
PropertyTexture& propertyTexture = metadata.propertyTextures.emplace_back();
|
|
propertyTexture.classProperty = "TestClass";
|
|
|
|
PropertyTextureProperty& propertyTextureProperty =
|
|
propertyTexture.properties["TestClassProperty"];
|
|
propertyTextureProperty.index = static_cast<int32_t>(textureIndex);
|
|
propertyTextureProperty.texCoord = 0;
|
|
propertyTextureProperty.channels = {0, 1, 2};
|
|
|
|
PropertyTextureView view(model, propertyTexture);
|
|
REQUIRE(view.status() == PropertyTextureViewStatus::Valid);
|
|
|
|
const ClassProperty* classProperty =
|
|
view.getClassProperty("TestClassProperty");
|
|
REQUIRE(classProperty);
|
|
REQUIRE(classProperty->type == ClassProperty::Type::ENUM);
|
|
REQUIRE(classProperty->componentType == std::nullopt);
|
|
REQUIRE(classProperty->count == 3);
|
|
REQUIRE(classProperty->array);
|
|
REQUIRE(!classProperty->normalized);
|
|
|
|
SUBCASE("Access correct type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
enumArrayProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(0.5, 0),
|
|
glm::dvec2(0, 0.25),
|
|
glm::dvec2(0.5, 0.25),
|
|
glm::dvec2(0, 0.5),
|
|
glm::dvec2(0.5, 0.5),
|
|
glm::dvec2(0, 0.75),
|
|
glm::dvec2(0.5, 0.75)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value = enumArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
auto maybeValue = enumArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access with KHR_texture_transform") {
|
|
TextureViewOptions options;
|
|
options.applyKhrTextureTransformExtension = true;
|
|
|
|
ExtensionKhrTextureTransform& extension =
|
|
propertyTextureProperty.addExtension<ExtensionKhrTextureTransform>();
|
|
extension.offset = {0.5, -0.5};
|
|
extension.rotation = CesiumUtility::Math::PiOverTwo;
|
|
extension.scale = {0.5, 0.5};
|
|
extension.texCoord = 10;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
enumArrayProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
verifyTextureTransformConstruction(enumArrayProperty, extension);
|
|
|
|
// This transforms to the following UV values:
|
|
// (0, 0) -> (0.5, -0.5) -> wraps to (0.5, 0.5)
|
|
// (1, 0) -> (0.5, -1) -> wraps to (0.5, 0)
|
|
// (0, 1) -> (1, -0.5) -> wraps to (0, 0.5)
|
|
// (1, 1) -> (1, -1) -> wraps to (0.0, 0.0)
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(1, 0),
|
|
glm::dvec2(0, 1),
|
|
glm::dvec2(1, 1)};
|
|
|
|
std::vector<std::array<uint8_t, 3>> expectedTransformed{
|
|
expected[5],
|
|
expected[1],
|
|
expected[4],
|
|
expected[0]};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expectedTransformed[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value = enumArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<uint8_t>> maybeValue =
|
|
enumArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
|
|
propertyTextureProperty.extensions.clear();
|
|
}
|
|
|
|
SUBCASE("Access with image copy") {
|
|
TextureViewOptions options;
|
|
options.makeImageCopy = true;
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>(
|
|
"TestClassProperty",
|
|
options);
|
|
REQUIRE(
|
|
enumArrayProperty.status() == PropertyTexturePropertyViewStatus::Valid);
|
|
|
|
// Clear the original image data.
|
|
std::vector<std::byte> emptyData;
|
|
model.images[model.images.size() - 1].pAsset->pixelData.swap(emptyData);
|
|
|
|
std::vector<glm::dvec2> texCoords{
|
|
glm::dvec2(0, 0),
|
|
glm::dvec2(0.5, 0),
|
|
glm::dvec2(0, 0.25),
|
|
glm::dvec2(0.5, 0.25),
|
|
glm::dvec2(0, 0.5),
|
|
glm::dvec2(0.5, 0.5),
|
|
glm::dvec2(0, 0.75),
|
|
glm::dvec2(0.5, 0.75)};
|
|
|
|
for (size_t i = 0; i < texCoords.size(); ++i) {
|
|
glm::dvec2 uv = texCoords[i];
|
|
const std::array<uint8_t, 3>& expectedArray = expected[i];
|
|
|
|
PropertyArrayCopy<uint8_t> value = enumArrayProperty.getRaw(uv[0], uv[1]);
|
|
REQUIRE(static_cast<size_t>(value.size()) == expectedArray.size());
|
|
|
|
for (int64_t j = 0; j < value.size(); j++) {
|
|
REQUIRE(value[j] == expectedArray[static_cast<size_t>(j)]);
|
|
}
|
|
|
|
std::optional<PropertyArrayCopy<uint8_t>> maybeValue =
|
|
enumArrayProperty.get(uv[0], uv[1]);
|
|
REQUIRE(maybeValue);
|
|
for (int64_t j = 0; j < maybeValue->size(); j++) {
|
|
REQUIRE((*maybeValue)[j] == value[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SUBCASE("Access wrong type") {
|
|
PropertyTexturePropertyView<PropertyArrayView<int8_t>> int8ArrayInvalid =
|
|
view.getPropertyView<PropertyArrayView<int8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
int8ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<PropertyArrayView<uint16_t>>
|
|
uint16ArrayInvalid = view.getPropertyView<PropertyArrayView<uint16_t>>(
|
|
"TestClassProperty");
|
|
REQUIRE(
|
|
uint16ArrayInvalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorComponentTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Access incorrectly as non-array") {
|
|
PropertyTexturePropertyView<uint16_t> uint8Invalid =
|
|
view.getPropertyView<uint16_t>("TestClassProperty");
|
|
REQUIRE(
|
|
uint8Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
|
|
PropertyTexturePropertyView<glm::u8vec3> u8vec3Invalid =
|
|
view.getPropertyView<glm::u8vec3>("TestClassProperty");
|
|
REQUIRE(
|
|
u8vec3Invalid.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorArrayTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Channel and type mismatch") {
|
|
model.images[imageIndex].pAsset->channels = 4;
|
|
propertyTextureProperty.channels = {0, 1, 2, 3};
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
enumArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorChannelsAndTypeMismatch);
|
|
}
|
|
|
|
SUBCASE("Invalid channel values") {
|
|
propertyTextureProperty.channels = {0, 4, 1};
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
enumArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidChannels);
|
|
}
|
|
|
|
SUBCASE("Invalid bytes per channel") {
|
|
model.images[imageIndex].pAsset->bytesPerChannel = 2;
|
|
PropertyTexturePropertyView<PropertyArrayView<uint8_t>> enumArrayProperty =
|
|
view.getPropertyView<PropertyArrayView<uint8_t>>("TestClassProperty");
|
|
REQUIRE(
|
|
enumArrayProperty.status() ==
|
|
PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel);
|
|
}
|
|
}
|