cesium-native/CesiumGltfContent/test/TestIntersectRayGltf.cpp

233 lines
6.7 KiB
C++

#include <CesiumGeometry/IntersectionTests.h>
#include <CesiumGeometry/Ray.h>
#include <CesiumGltf/Accessor.h>
#include <CesiumGltf/Mesh.h>
#include <CesiumGltf/MeshPrimitive.h>
#include <CesiumGltfContent/GltfUtilities.h>
#include <CesiumGltfReader/GltfReader.h>
#include <CesiumNativeTests/readFile.h>
#include <CesiumUtility/Math.h>
#include <doctest/doctest.h>
#include <glm/common.hpp>
#include <glm/ext/matrix_double4x4.hpp>
#include <glm/ext/vector_double3.hpp>
#include <glm/vector_relational.hpp>
#include <cstddef>
#include <filesystem>
#include <string>
using namespace CesiumUtility;
using namespace CesiumGltf;
using namespace CesiumGeometry;
using namespace CesiumGltfReader;
using namespace CesiumGltfContent;
void checkIntersection(
const Ray& ray,
const Model& model,
bool cullBackFaces,
const glm::dmat4x4& modelToWorld,
bool shouldHit,
const glm::dvec3& expectedHit) {
GltfUtilities::IntersectResult hitResult =
GltfUtilities::intersectRayGltfModel(
ray,
model,
cullBackFaces,
modelToWorld);
if (shouldHit) {
CHECK(hitResult.hit.has_value());
if (!hitResult.hit.has_value())
return;
} else {
CHECK(!hitResult.hit.has_value());
return;
}
// Validate hit point
CHECK(glm::all(glm::lessThan(
glm::abs(hitResult.hit->worldPoint - expectedHit),
glm::dvec3(CesiumUtility::Math::Epsilon6))));
// Use results to dive into model
CHECK(hitResult.hit->meshId > -1);
CHECK(static_cast<size_t>(hitResult.hit->meshId) < model.meshes.size());
const CesiumGltf::Mesh& mesh =
model.meshes[static_cast<size_t>(hitResult.hit->meshId)];
CHECK(hitResult.hit->primitiveId > -1);
CHECK(
static_cast<size_t>(hitResult.hit->primitiveId) < mesh.primitives.size());
const CesiumGltf::MeshPrimitive& primitive =
mesh.primitives[static_cast<size_t>(hitResult.hit->primitiveId)];
bool modeIsValid =
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES ||
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP ||
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN;
CHECK(modeIsValid);
// There should be positions
auto positionAccessorIt = primitive.attributes.find("POSITION");
CHECK(positionAccessorIt != primitive.attributes.end());
// And a way to access them
int positionAccessorID = positionAccessorIt->second;
const Accessor* pPositionAccessor =
Model::getSafe(&model.accessors, positionAccessorID);
CHECK(pPositionAccessor);
}
void checkBadUnitCube(const std::string& testModelName, bool shouldHitAnyway) {
GltfReader reader;
Model testModel =
*reader
.readGltf(readFile(
std::filesystem::path(CesiumGltfContent_TEST_DATA_DIR) /
testModelName))
.model;
// Do an intersection with top side of the cube
GltfUtilities::IntersectResult hitResult =
GltfUtilities::intersectRayGltfModel(
Ray(glm::dvec3(0.0, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
glm::dmat4x4(1.0));
// We're expecting a bad model, so it shouldn't crash or assert
// and we should get some warnings about that
CHECK(hitResult.warnings.size() > 0);
// Check for a bad model that is mostly good, and should produce good results
if (shouldHitAnyway) {
CHECK(hitResult.hit.has_value());
} else {
CHECK(!hitResult.hit.has_value());
}
}
void checkValidUnitCube(const std::string& testModelName) {
GltfReader reader;
Model testModel =
*reader
.readGltf(readFile(
std::filesystem::path(CesiumGltfContent_TEST_DATA_DIR) /
testModelName))
.model;
// intersects the top side of the cube
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
glm::dmat4x4(1.0),
true,
glm::dvec3(0.0, 0.0, 0.5));
// misses the top side of the cube to the right
checkIntersection(
Ray(glm::dvec3(0.6, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
glm::dmat4x4(1.0),
false,
{});
// misses the top side of the cube because it's behind it (avoid backfaces)
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 0.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
glm::dmat4x4(1.0),
false,
{});
// hits backface triangles
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 0.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
false,
glm::dmat4x4(1.0),
true,
glm::dvec3(0.0, 0.0, -0.5));
// tests against backfaces, and picks first hit (top)
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
false,
glm::dmat4x4(1.0),
true,
glm::dvec3(0.0, 0.0, 0.5));
// tests against backfaces, and picks first hit (bottom)
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, -2.0), glm::dvec3(0.0, 0.0, 1.0)),
testModel,
false,
glm::dmat4x4(1.0),
true,
glm::dvec3(0.0, 0.0, -0.5));
// just misses the top side of a cube translated to the right
glm::dmat4x4 translationMatrix(1.0);
translationMatrix[3] = glm::dvec4(0.6, 0.0, 0.0, 1.0);
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
translationMatrix,
false,
{});
// just hits the top side of a cube translated to the right
checkIntersection(
Ray(glm::dvec3(0.6, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
translationMatrix,
true,
glm::dvec3(0.6, 0.0, 0.5));
// correctly hits a scaled cube
glm::dmat4x4 scaleMatrix = glm::dmat4(
glm::dvec4(2.0, 0.0, 0.0, 0.0),
glm::dvec4(0.0, 2.0, 0.0, 0.0),
glm::dvec4(0.0, 0.0, 2.0, 0.0),
glm::dvec4(0.0, 0.0, 0.0, 1.0));
checkIntersection(
Ray(glm::dvec3(0.0, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
scaleMatrix,
true,
glm::dvec3(0.0, 0.0, 1.0));
// just misses a scaled cube to the right
checkIntersection(
Ray(glm::dvec3(1.1, 0.0, 2.0), glm::dvec3(0.0, 0.0, -1.0)),
testModel,
true,
scaleMatrix,
false,
{});
}
TEST_CASE("GltfUtilities::intersectRayGltfModel") {
checkValidUnitCube("cube.glb");
checkValidUnitCube("cubeIndexed.glb");
checkValidUnitCube("cubeStrip.glb");
checkValidUnitCube("cubeStripIndexed.glb");
checkValidUnitCube("cubeFan.glb");
checkValidUnitCube("cubeFanIndexed.glb");
checkValidUnitCube("cubeQuantized.glb");
checkValidUnitCube("cubeTranslated.glb");
checkBadUnitCube("cubeInvalidVertCount.glb", false);
checkBadUnitCube("cubeSomeBadIndices.glb", true);
}