cesium-native/CesiumGltf/test/TestAccessorUtility.cpp

1083 lines
36 KiB
C++

#include <CesiumGltf/Accessor.h>
#include <CesiumGltf/AccessorUtility.h>
#include <CesiumGltf/AccessorView.h>
#include <CesiumGltf/Buffer.h>
#include <CesiumGltf/BufferView.h>
#include <CesiumGltf/ExtensionExtMeshGpuInstancing.h>
#include <CesiumGltf/Mesh.h>
#include <CesiumGltf/MeshPrimitive.h>
#include <CesiumGltf/Model.h>
#include <CesiumGltf/Node.h>
#include <doctest/doctest.h>
#include <glm/ext/vector_float2.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/ext/vector_uint2_sized.hpp>
#include <cstdint>
#include <cstring>
#include <variant>
#include <vector>
using namespace CesiumGltf;
TEST_CASE("Test CountFromAccessor") {
Model model;
std::vector<uint8_t> featureIds{1, 2, 3, 4};
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(featureIds.size() * sizeof(uint8_t));
std::memcpy(
buffer.cesium.data.data(),
featureIds.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
accessor.type = Accessor::Type::SCALAR;
accessor.count = bufferView.byteLength;
SUBCASE("Handles invalid accessor") {
// Wrong type
TexCoordAccessorType texcoordAccessor =
AccessorView<AccessorTypes::VEC2<uint8_t>>(model, accessor);
REQUIRE(
std::visit(StatusFromAccessor{}, texcoordAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, texcoordAccessor) == 0);
// Wrong component type
FeatureIdAccessorType featureIdAccessor =
AccessorView<int16_t>(model, accessor);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIdAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIdAccessor) == 0);
}
SUBCASE("Retrieves from valid accessor") {
FeatureIdAccessorType featureIdAccessor =
AccessorView<uint8_t>(model, accessor);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIdAccessor) ==
AccessorViewStatus::Valid);
int64_t count = std::visit(CountFromAccessor{}, featureIdAccessor);
REQUIRE(count == static_cast<int64_t>(featureIds.size()));
}
}
TEST_CASE("Test getPositionAccessorView") {
Model model;
std::vector<glm::vec3> positions{
glm::vec3(0, 1, 2),
glm::vec3(3, 4, 5),
glm::vec3(6, 7, 8)};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(positions.size() * sizeof(glm::vec3));
std::memcpy(
buffer.cesium.data.data(),
positions.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::FLOAT;
accessor.type = Accessor::Type::VEC3;
accessor.count = static_cast<int64_t>(positions.size());
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.attributes.insert({"POSITION", 0});
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::SCALAR;
PositionAccessorType positionAccessor =
getPositionAccessorView(model, primitive);
REQUIRE(positionAccessor.status() != AccessorViewStatus::Valid);
REQUIRE(positionAccessor.size() == 0);
model.accessors[0].type = Accessor::Type::VEC3;
}
SUBCASE("Handles unsupported accessor component type") {
model.accessors[0].componentType = Accessor::ComponentType::BYTE;
PositionAccessorType positionAccessor =
getPositionAccessorView(model, primitive);
REQUIRE(positionAccessor.status() != AccessorViewStatus::Valid);
REQUIRE(positionAccessor.size() == 0);
model.accessors[0].componentType = Accessor::ComponentType::FLOAT;
}
SUBCASE("Creates from valid accessor") {
PositionAccessorType positionAccessor =
getPositionAccessorView(model, primitive);
REQUIRE(positionAccessor.status() == AccessorViewStatus::Valid);
REQUIRE(static_cast<size_t>(positionAccessor.size()) == positions.size());
}
}
TEST_CASE("Test getNormalAccessorView") {
Model model;
std::vector<glm::vec3> normals{
glm::vec3(0, 1, 0),
glm::vec3(1, 0, 0),
glm::vec3(0, 0, 1)};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(normals.size() * sizeof(glm::vec3));
std::memcpy(
buffer.cesium.data.data(),
normals.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::FLOAT;
accessor.type = Accessor::Type::VEC3;
accessor.count = static_cast<int64_t>(normals.size());
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.attributes.insert({"NORMAL", 0});
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::SCALAR;
NormalAccessorType normalAccessor = getNormalAccessorView(model, primitive);
REQUIRE(normalAccessor.status() != AccessorViewStatus::Valid);
REQUIRE(normalAccessor.size() == 0);
model.accessors[0].type = Accessor::Type::VEC3;
}
SUBCASE("Handles unsupported accessor component type") {
model.accessors[0].componentType = Accessor::ComponentType::BYTE;
NormalAccessorType normalAccessor = getNormalAccessorView(model, primitive);
REQUIRE(normalAccessor.status() != AccessorViewStatus::Valid);
REQUIRE(normalAccessor.size() == 0);
model.accessors[0].componentType = Accessor::ComponentType::FLOAT;
}
SUBCASE("Creates from valid accessor") {
NormalAccessorType normalAccessor = getNormalAccessorView(model, primitive);
REQUIRE(normalAccessor.status() == AccessorViewStatus::Valid);
REQUIRE(static_cast<size_t>(normalAccessor.size()) == normals.size());
}
}
TEST_CASE("Test getFeatureIdAccessorView") {
Model model;
std::vector<uint8_t> featureIds0{1, 2, 3, 4};
// First _FEATURE_ID set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(featureIds0.size() * sizeof(uint8_t));
std::memcpy(
buffer.cesium.data.data(),
featureIds0.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
accessor.type = Accessor::Type::SCALAR;
accessor.count = bufferView.byteLength;
}
std::vector<uint16_t> featureIds1{5, 6, 7, 8};
// Second _FEATURE_ID set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(featureIds1.size() * sizeof(uint16_t));
std::memcpy(
buffer.cesium.data.data(),
featureIds1.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 1;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 1;
accessor.componentType = Accessor::ComponentType::UNSIGNED_SHORT;
accessor.type = Accessor::Type::SCALAR;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(uint16_t));
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.attributes.insert({"_FEATURE_ID_0", 0});
primitive.attributes.insert({"_FEATURE_ID_1", 1});
SUBCASE("Handles invalid feature ID set index") {
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, primitive, 2);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
}
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::VEC2;
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, primitive, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
model.accessors[0].type = Accessor::Type::SCALAR;
}
SUBCASE("Handles invalid normalized accessor") {
model.accessors[1].normalized = true;
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, primitive, 1);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
model.accessors[1].normalized = false;
}
SUBCASE("Creates from valid feature ID sets") {
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, primitive, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) ==
AccessorViewStatus::Valid);
REQUIRE(
std::visit(CountFromAccessor{}, featureIDAccessor) ==
static_cast<int64_t>(featureIds0.size()));
featureIDAccessor = getFeatureIdAccessorView(model, primitive, 1);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) ==
AccessorViewStatus::Valid);
REQUIRE(
std::visit(CountFromAccessor{}, featureIDAccessor) ==
static_cast<int64_t>(featureIds1.size()));
}
}
TEST_CASE("Test getFeatureIdAccessorView for instances") {
Model model;
std::vector<int8_t> featureIds{1, 2, 3, 4};
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(featureIds.size() * sizeof(int8_t));
std::memcpy(
buffer.cesium.data.data(),
featureIds.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::BYTE;
accessor.type = Accessor::Type::SCALAR;
accessor.count = bufferView.byteLength;
Node& node = model.nodes.emplace_back();
ExtensionExtMeshGpuInstancing& instancingExtension =
node.addExtension<ExtensionExtMeshGpuInstancing>();
instancingExtension.attributes["_FEATURE_ID_0"] = 0;
SUBCASE("Handles missing extension") {
Node temporaryNode;
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, temporaryNode, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
model.accessors[0].type = Accessor::Type::SCALAR;
}
SUBCASE("Handles invalid feature ID set index") {
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, node, 2);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
}
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::VEC2;
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, node, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
model.accessors[0].type = Accessor::Type::SCALAR;
}
SUBCASE("Handles invalid normalized accessor") {
model.accessors[0].normalized = true;
FeatureIdAccessorType featureIDAccessor =
getFeatureIdAccessorView(model, node, 1);
REQUIRE(
std::visit(StatusFromAccessor{}, featureIDAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, featureIDAccessor) == 0);
model.accessors[0].normalized = false;
}
SUBCASE("Retrieves from valid accessor") {
FeatureIdAccessorType featureIdAccessor =
getFeatureIdAccessorView(model, node, 0);
for (size_t i = 0; i < featureIds.size(); i++) {
int64_t featureID = std::visit(
FeatureIdFromAccessor{static_cast<int64_t>(i)},
featureIdAccessor);
REQUIRE(featureID == featureIds[i]);
}
}
}
TEST_CASE("FeatureIdFromAccessor") {
Model model;
std::vector<int8_t> featureIds{1, 2, 3, 4};
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(featureIds.size() * sizeof(int8_t));
std::memcpy(
buffer.cesium.data.data(),
featureIds.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::BYTE;
accessor.type = Accessor::Type::SCALAR;
accessor.count = bufferView.byteLength;
SUBCASE("Handles invalid accessor") {
// Wrong component type
FeatureIdAccessorType featureIdAccessor =
AccessorView<int16_t>(model, accessor);
REQUIRE(std::visit(FeatureIdFromAccessor{0}, featureIdAccessor) == -1);
}
SUBCASE("Retrieves from valid accessor") {
FeatureIdAccessorType featureIdAccessor =
AccessorView<int8_t>(model, accessor);
for (size_t i = 0; i < featureIds.size(); i++) {
int64_t featureID = std::visit(
FeatureIdFromAccessor{static_cast<int64_t>(i)},
featureIdAccessor);
REQUIRE(featureID == featureIds[i]);
}
}
}
TEST_CASE("Test getIndexAccessorView") {
Model model;
std::vector<uint8_t> indices{0, 1, 2, 0, 2, 3};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(indices.size() * sizeof(uint8_t));
std::memcpy(
buffer.cesium.data.data(),
indices.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
accessor.type = Accessor::Type::SCALAR;
accessor.count = bufferView.byteLength;
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.indices = 0;
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::VEC2;
IndexAccessorType indexAccessor = getIndexAccessorView(model, primitive);
REQUIRE(
std::visit(StatusFromAccessor{}, indexAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, indexAccessor) == 0);
model.accessors[0].type = Accessor::Type::SCALAR;
}
SUBCASE("Handles unsupported accessor component type") {
model.accessors[0].componentType = Accessor::ComponentType::BYTE;
IndexAccessorType indexAccessor = getIndexAccessorView(model, primitive);
REQUIRE(
std::visit(StatusFromAccessor{}, indexAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, indexAccessor) == 0);
model.accessors[0].componentType = Accessor::ComponentType::UNSIGNED_BYTE;
}
SUBCASE("Handles invalid normalized accessor") {
model.accessors[0].normalized = true;
IndexAccessorType indexAccessor = getIndexAccessorView(model, primitive);
REQUIRE(
std::visit(StatusFromAccessor{}, indexAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, indexAccessor) == 0);
model.accessors[0].normalized = false;
}
SUBCASE("Creates from valid accessor") {
IndexAccessorType indexAccessor = getIndexAccessorView(model, primitive);
REQUIRE(
std::visit(StatusFromAccessor{}, indexAccessor) ==
AccessorViewStatus::Valid);
REQUIRE(
std::visit(CountFromAccessor{}, indexAccessor) ==
static_cast<int64_t>(indices.size()));
}
SUBCASE("Creates from nonexistent accessor") {
primitive.indices = -1;
IndexAccessorType indexAccessor = getIndexAccessorView(model, primitive);
REQUIRE(std::get_if<std::monostate>(&indexAccessor));
}
}
TEST_CASE("Test IndicesForFaceFromAccessor") {
Model model;
int64_t vertexCount = 9;
// Triangle mode indices
std::vector<uint32_t>
triangleIndices{0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 6, 7, 8};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(triangleIndices.size() * sizeof(uint32_t));
std::memcpy(
buffer.cesium.data.data(),
triangleIndices.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = static_cast<int32_t>(model.bufferViews.size() - 1);
accessor.componentType = Accessor::ComponentType::UNSIGNED_INT;
accessor.type = Accessor::Type::SCALAR;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(uint32_t));
}
// Triangle strip and fan indices
std::vector<uint32_t> specialIndices{1, 2, 3, 4, 5, 6, 7, 8, 0};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(specialIndices.size() * sizeof(uint32_t));
std::memcpy(
buffer.cesium.data.data(),
specialIndices.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = static_cast<int32_t>(model.bufferViews.size() - 1);
accessor.componentType = Accessor::ComponentType::UNSIGNED_INT;
accessor.type = Accessor::Type::SCALAR;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(uint32_t));
}
SUBCASE("Handles invalid accessor") {
REQUIRE(model.accessors.size() > 0);
// Wrong component type
IndexAccessorType indexAccessor =
AccessorView<uint8_t>(model, model.accessors[0]);
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
0,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
}
SUBCASE("Handles invalid face index") {
REQUIRE(model.accessors.size() > 0);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[0]);
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
-1,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
indicesForFace = std::visit(
IndicesForFaceFromAccessor{
10,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
}
SUBCASE("Handles invalid primitive modes") {
REQUIRE(model.accessors.size() > 0);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[0]);
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
-1,
vertexCount,
MeshPrimitive::Mode::POINTS},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
indicesForFace = std::visit(
IndicesForFaceFromAccessor{10, vertexCount, MeshPrimitive::Mode::LINES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
indicesForFace = std::visit(
IndicesForFaceFromAccessor{
10,
vertexCount,
MeshPrimitive::Mode::LINE_LOOP},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
}
SUBCASE("Retrieves from valid accessor and face index; triangles mode") {
REQUIRE(model.accessors.size() > 0);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[0]);
const size_t numFaces =
static_cast<size_t>(std::visit(CountFromAccessor{}, indexAccessor) / 3);
for (size_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
static_cast<int64_t>(i),
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (size_t j = 0; j < indicesForFace.size(); j++) {
int64_t expected = static_cast<int64_t>(triangleIndices[i * 3 + j]);
REQUIRE(indicesForFace[j] == expected);
}
}
}
SUBCASE("Retrieves from valid accessor and face index; triangle strip mode") {
REQUIRE(model.accessors.size() > 1);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[1]);
const size_t numFaces =
static_cast<size_t>(std::visit(CountFromAccessor{}, indexAccessor) - 2);
for (size_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
static_cast<int64_t>(i),
vertexCount,
MeshPrimitive::Mode::TRIANGLE_STRIP},
indexAccessor);
for (size_t j = 0; j < indicesForFace.size(); j++) {
int64_t expected = static_cast<int64_t>(specialIndices[i + j]);
REQUIRE(indicesForFace[j] == expected);
}
}
}
SUBCASE("Retrieves from valid accessor and face index; triangle fan mode") {
REQUIRE(model.accessors.size() > 1);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[1]);
const size_t numFaces =
static_cast<size_t>(std::visit(CountFromAccessor{}, indexAccessor) - 2);
for (size_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
static_cast<int64_t>(i),
vertexCount,
MeshPrimitive::Mode::TRIANGLE_FAN},
indexAccessor);
REQUIRE(indicesForFace[0] == specialIndices[0]);
REQUIRE(indicesForFace[1] == specialIndices[i + 1]);
REQUIRE(indicesForFace[2] == specialIndices[i + 2]);
}
}
SUBCASE("Handles invalid face index for nonexistent accessor") {
IndexAccessorType indexAccessor;
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
-1,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
indicesForFace = std::visit(
IndicesForFaceFromAccessor{
10,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (int64_t index : indicesForFace) {
REQUIRE(index == -1);
}
}
SUBCASE("Retrieves from valid face index for nonexistent accessor; triangles "
"mode") {
IndexAccessorType indexAccessor;
const int64_t numFaces = vertexCount / 3;
for (int64_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
i,
vertexCount,
MeshPrimitive::Mode::TRIANGLES},
indexAccessor);
for (size_t j = 0; j < indicesForFace.size(); j++) {
int64_t expected = i * 3 + static_cast<int64_t>(j);
REQUIRE(indicesForFace[j] == expected);
}
}
}
SUBCASE("Retrieves from valid face index for nonexistent accessor; triangle "
"strip mode") {
IndexAccessorType indexAccessor;
const int64_t numFaces = vertexCount - 2;
for (int64_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
i,
vertexCount,
MeshPrimitive::Mode::TRIANGLE_STRIP},
indexAccessor);
for (size_t j = 0; j < indicesForFace.size(); j++) {
int64_t expected = i + static_cast<int64_t>(j);
REQUIRE(indicesForFace[j] == expected);
}
}
}
SUBCASE("Retrieves from valid face index for nonexistent accessor; triangle "
"fan mode") {
IndexAccessorType indexAccessor;
const int64_t numFaces = vertexCount - 2;
for (int64_t i = 0; i < numFaces; i++) {
auto indicesForFace = std::visit(
IndicesForFaceFromAccessor{
i,
vertexCount,
MeshPrimitive::Mode::TRIANGLE_FAN},
indexAccessor);
REQUIRE(indicesForFace[0] == 0);
REQUIRE(indicesForFace[1] == i + 1);
REQUIRE(indicesForFace[2] == i + 2);
}
}
}
TEST_CASE("Test IndexFromAccessor") {
Model model;
std::vector<uint32_t> indices{0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 6, 7, 8};
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(indices.size() * sizeof(uint32_t));
std::memcpy(
buffer.cesium.data.data(),
indices.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = static_cast<int32_t>(model.buffers.size() - 1);
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = static_cast<int32_t>(model.bufferViews.size() - 1);
accessor.componentType = Accessor::ComponentType::UNSIGNED_INT;
accessor.type = Accessor::Type::SCALAR;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(uint32_t));
}
SUBCASE("Handles invalid accessor") {
REQUIRE(model.accessors.size() > 0);
// Wrong component type
IndexAccessorType indexAccessor =
AccessorView<uint8_t>(model, model.accessors[0]);
auto index = std::visit(IndexFromAccessor{0}, indexAccessor);
REQUIRE(index == -1);
}
SUBCASE("Handles invalid index") {
REQUIRE(model.accessors.size() > 0);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[0]);
auto index = std::visit(IndexFromAccessor{-1}, indexAccessor);
REQUIRE(index == -1);
index = std::visit(
IndexFromAccessor{static_cast<int64_t>(indices.size())},
indexAccessor);
REQUIRE(index == -1);
}
SUBCASE("Retrieves from valid accessor and index") {
REQUIRE(model.accessors.size() > 0);
IndexAccessorType indexAccessor =
AccessorView<uint32_t>(model, model.accessors[0]);
for (size_t i = 0; i < indices.size(); i++) {
auto index =
std::visit(IndexFromAccessor{static_cast<int64_t>(i)}, indexAccessor);
REQUIRE(index == indices[i]);
}
}
}
TEST_CASE("Test getTexCoordAccessorView") {
Model model;
std::vector<glm::vec2> texCoords0{
glm::vec2(0, 0),
glm::vec2(1, 0),
glm::vec2(0, 1),
glm::vec2(1, 1)};
// First TEXCOORD set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(texCoords0.size() * sizeof(glm::vec2));
std::memcpy(
buffer.cesium.data.data(),
texCoords0.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::FLOAT;
accessor.type = Accessor::Type::VEC2;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(glm::vec2));
}
std::vector<glm::u8vec2> texCoords1{
glm::u8vec2(0, 0),
glm::u8vec2(0, 255),
glm::u8vec2(255, 255),
glm::u8vec2(255, 0)};
// Second TEXCOORD set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(texCoords1.size() * sizeof(glm::u8vec2));
std::memcpy(
buffer.cesium.data.data(),
texCoords1.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 1;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 1;
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
accessor.type = Accessor::Type::VEC2;
accessor.normalized = true;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(glm::u8vec2));
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.attributes.insert({"TEXCOORD_0", 0});
primitive.attributes.insert({"TEXCOORD_1", 1});
SUBCASE("Handles invalid texture coordinate set index") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 2);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, texCoordAccessor) == 0);
}
SUBCASE("Handles invalid accessor type") {
model.accessors[0].type = Accessor::Type::SCALAR;
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, texCoordAccessor) == 0);
model.accessors[0].type = Accessor::Type::VEC2;
}
SUBCASE("Handles unsupported accessor component type") {
model.accessors[0].componentType = Accessor::ComponentType::BYTE;
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, texCoordAccessor) == 0);
model.accessors[0].componentType = Accessor::ComponentType::FLOAT;
}
SUBCASE("Handles invalid un-normalized texcoord") {
model.accessors[1].normalized = false;
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 2);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) !=
AccessorViewStatus::Valid);
REQUIRE(std::visit(CountFromAccessor{}, texCoordAccessor) == 0);
model.accessors[1].normalized = true;
}
SUBCASE("Creates from valid texture coordinate sets") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 0);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) ==
AccessorViewStatus::Valid);
REQUIRE(
std::visit(CountFromAccessor{}, texCoordAccessor) ==
static_cast<int64_t>(texCoords0.size()));
texCoordAccessor = getTexCoordAccessorView(model, primitive, 1);
REQUIRE(
std::visit(StatusFromAccessor{}, texCoordAccessor) ==
AccessorViewStatus::Valid);
REQUIRE(
std::visit(CountFromAccessor{}, texCoordAccessor) ==
static_cast<int64_t>(texCoords1.size()));
}
}
TEST_CASE("Test TexCoordFromAccessor") {
Model model;
std::vector<glm::vec2> texCoords0{
glm::vec2(0, 0),
glm::vec2(1, 0),
glm::vec2(0, 1),
glm::vec2(1, 1)};
// First TEXCOORD set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(texCoords0.size() * sizeof(glm::vec2));
std::memcpy(
buffer.cesium.data.data(),
texCoords0.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 0;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 0;
accessor.componentType = Accessor::ComponentType::FLOAT;
accessor.type = Accessor::Type::VEC2;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(glm::vec2));
}
std::vector<glm::u8vec2> texCoords1{
glm::u8vec2(0, 0),
glm::u8vec2(0, 255),
glm::u8vec2(255, 255),
glm::u8vec2(255, 0)};
// Second TEXCOORD set
{
Buffer& buffer = model.buffers.emplace_back();
buffer.cesium.data.resize(texCoords1.size() * sizeof(glm::u8vec2));
std::memcpy(
buffer.cesium.data.data(),
texCoords1.data(),
buffer.cesium.data.size());
buffer.byteLength = static_cast<int64_t>(buffer.cesium.data.size());
BufferView& bufferView = model.bufferViews.emplace_back();
bufferView.buffer = 1;
bufferView.byteLength = buffer.byteLength;
Accessor& accessor = model.accessors.emplace_back();
accessor.bufferView = 1;
accessor.componentType = Accessor::ComponentType::UNSIGNED_BYTE;
accessor.type = Accessor::Type::VEC2;
accessor.normalized = true;
accessor.count =
bufferView.byteLength / static_cast<int64_t>(sizeof(glm::u8vec2));
}
Mesh& mesh = model.meshes.emplace_back();
MeshPrimitive primitive = mesh.primitives.emplace_back();
primitive.attributes.insert({"TEXCOORD_0", 0});
primitive.attributes.insert({"TEXCOORD_1", 1});
SUBCASE("Handles invalid accessor") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 2);
REQUIRE(!std::visit(TexCoordFromAccessor{0}, texCoordAccessor));
}
SUBCASE("Handles invalid index") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 0);
REQUIRE(!std::visit(TexCoordFromAccessor{-1}, texCoordAccessor));
REQUIRE(!std::visit(TexCoordFromAccessor{10}, texCoordAccessor));
}
SUBCASE("Retrieves from valid accessor and index") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 0);
for (size_t i = 0; i < texCoords0.size(); i++) {
auto maybeTexCoord = std::visit(
TexCoordFromAccessor{static_cast<int64_t>(i)},
texCoordAccessor);
REQUIRE(maybeTexCoord);
auto expected = glm::dvec2(texCoords0[i][0], texCoords0[i][1]);
REQUIRE(*maybeTexCoord == expected);
}
}
SUBCASE("Retrieves from valid normalized accessor and index") {
TexCoordAccessorType texCoordAccessor =
getTexCoordAccessorView(model, primitive, 1);
for (size_t i = 0; i < texCoords1.size(); i++) {
auto maybeTexCoord = std::visit(
TexCoordFromAccessor{static_cast<int64_t>(i)},
texCoordAccessor);
REQUIRE(maybeTexCoord);
auto expected = glm::dvec2(texCoords1[i][0], texCoords1[i][1]);
expected /= 255;
REQUIRE(*maybeTexCoord == expected);
}
}
}