Add unit tests for normals, oct encoded normals, and positions
This commit is contained in:
parent
fc9f633791
commit
cfb7ec7258
|
|
@ -18,6 +18,8 @@
|
|||
#endif
|
||||
|
||||
#include <CesiumGltf/ExtensionCesiumRTC.h>
|
||||
#include <CesiumGltf/ExtensionKhrMaterialsUnlit.h>
|
||||
#include <CesiumUtility/Math.h>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
|
|
@ -93,7 +95,7 @@ struct PntsContent {
|
|||
PntsSemantic position;
|
||||
// required by glTF spec
|
||||
glm::vec3 positionMin = glm::vec3(std::numeric_limits<float>::max());
|
||||
glm::vec3 positionMax = glm::vec3(std::numeric_limits<float>::min());
|
||||
glm::vec3 positionMax = glm::vec3(std::numeric_limits<float>::lowest());
|
||||
|
||||
bool positionQuantized = false;
|
||||
|
||||
|
|
@ -793,7 +795,7 @@ void parsePositionsFromFeatureTableBinary(
|
|||
pointsLength);
|
||||
|
||||
if (parsedContent.positionQuantized) {
|
||||
// PERFORMANCE_IDEA: In the future, it would be more performant to detect if
|
||||
// PERFORMANCE_IDEA: In the future, it might be more performant to detect if
|
||||
// the recipient rendering engine can handle dequantization on its own and
|
||||
// if so, use the KHR_mesh_quantization extension to avoid dequantizing
|
||||
// here.
|
||||
|
|
@ -807,30 +809,34 @@ void parsePositionsFromFeatureTableBinary(
|
|||
const glm::vec3 quantizedVolumeOffset =
|
||||
glm::vec3(parsedContent.quantizedVolumeOffset.value());
|
||||
|
||||
const glm::vec3 quantizedPositionScalar = quantizedVolumeScale / 65535.0f;
|
||||
|
||||
for (size_t i = 0; i < pointsLength; i++) {
|
||||
const glm::vec3 quantizedPosition(
|
||||
quantizedPositions[i].x,
|
||||
quantizedPositions[i].y,
|
||||
quantizedPositions[i].z);
|
||||
|
||||
outPositions[i] = quantizedPosition * quantizedVolumeScale / 65535.0f +
|
||||
quantizedVolumeOffset;
|
||||
const glm::vec3 dequantizedPosition =
|
||||
quantizedPosition * quantizedPositionScalar + quantizedVolumeOffset;
|
||||
outPositions[i] = dequantizedPosition;
|
||||
parsedContent.positionMin =
|
||||
glm::min(parsedContent.positionMin, dequantizedPosition);
|
||||
parsedContent.positionMax =
|
||||
glm::max(parsedContent.positionMax, dequantizedPosition);
|
||||
}
|
||||
} else {
|
||||
// The position accessor min / max is required by the glTF spec, so
|
||||
// a for loop is used instead of std::memcpy.
|
||||
auto binaryByteOffset =
|
||||
featureTableBinaryData.data() + parsedContent.position.byteOffset;
|
||||
const gsl::span<const glm::vec3> positions(
|
||||
reinterpret_cast<const glm::vec3*>(binaryByteOffset),
|
||||
reinterpret_cast<const glm::vec3*>(
|
||||
featureTableBinaryData.data() + parsedContent.position.byteOffset),
|
||||
pointsLength);
|
||||
for (size_t i = 0; i < pointsLength; i++) {
|
||||
outPositions[i] = positions[i];
|
||||
|
||||
parsedContent.positionMin =
|
||||
glm::min(parsedContent.positionMin, positions[i]);
|
||||
parsedContent.positionMax =
|
||||
glm::max(parsedContent.positionMax, positions[i]);
|
||||
const glm::vec3 position = positions[i];
|
||||
outPositions[i] = position;
|
||||
parsedContent.positionMin = glm::min(parsedContent.positionMin, position);
|
||||
parsedContent.positionMax = glm::max(parsedContent.positionMax, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -845,7 +851,7 @@ void parseColorsFromFeatureTableBinary(
|
|||
return;
|
||||
}
|
||||
|
||||
uint32_t pointsLength = parsedContent.pointsLength;
|
||||
const uint32_t pointsLength = parsedContent.pointsLength;
|
||||
|
||||
if (parsedContent.colorType == PntsColorType::RGBA) {
|
||||
const size_t colorsByteStride = sizeof(glm::u8vec4);
|
||||
|
|
@ -887,11 +893,13 @@ void parseColorsFromFeatureTableBinary(
|
|||
const uint16_t mask6 = (1 << 6) - 1;
|
||||
const float normalize5 = 1.0f / 31.0f; // normalize [0, 31] to [0, 1]
|
||||
const float normalize6 = 1.0f / 63.0f; // normalize [0, 63] to [0, 1]
|
||||
const uint16_t shift11 = 11;
|
||||
const uint16_t shift5 = 5;
|
||||
|
||||
for (size_t i = 0; i < pointsLength; i++) {
|
||||
const uint16_t compressedColor = compressedColors[i];
|
||||
const uint16_t red = compressedColor >> 11;
|
||||
const uint16_t green = (compressedColor >> 5) & mask6;
|
||||
const uint16_t red = compressedColor >> shift11;
|
||||
const uint16_t green = (compressedColor >> shift5) & mask6;
|
||||
const uint16_t blue = compressedColor & mask5;
|
||||
|
||||
outColors[i] =
|
||||
|
|
@ -900,6 +908,63 @@ void parseColorsFromFeatureTableBinary(
|
|||
}
|
||||
}
|
||||
|
||||
void parseNormalsFromFeatureTableBinary(
|
||||
const gsl::span<const std::byte>& featureTableBinaryData,
|
||||
PntsContent& parsedContent) {
|
||||
PntsSemantic& normal = parsedContent.normal.value();
|
||||
std::vector<std::byte>& normalData = normal.data;
|
||||
if (normalData.size() > 0) {
|
||||
// If data isn't empty, it must have been decoded from Draco.
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t pointsLength = parsedContent.pointsLength;
|
||||
const size_t normalsByteStride = sizeof(glm::vec3);
|
||||
const size_t normalsByteLength = pointsLength * normalsByteStride;
|
||||
normalData.resize(normalsByteLength);
|
||||
|
||||
if (parsedContent.normalOctEncoded) {
|
||||
const gsl::span<const glm::u8vec2> encodedNormals(
|
||||
reinterpret_cast<const glm::u8vec2*>(
|
||||
featureTableBinaryData.data() + normal.byteOffset),
|
||||
pointsLength);
|
||||
|
||||
gsl::span<glm::vec3> outNormals(
|
||||
reinterpret_cast<glm::vec3*>(normalData.data()),
|
||||
pointsLength);
|
||||
|
||||
constexpr uint8_t rangeMax = 255;
|
||||
|
||||
for (size_t i = 0; i < pointsLength; i++) {
|
||||
const glm::u8vec2 encodedNormal = encodedNormals[i];
|
||||
|
||||
// TODO: This is copied from QuantizedMeshLoader. It should really
|
||||
// be put in its own module, e.g. CesiumUtility::AttributeCompression
|
||||
glm::dvec3 decodedNormal;
|
||||
decodedNormal.x =
|
||||
CesiumUtility::Math::fromSNorm(encodedNormal.x, rangeMax);
|
||||
decodedNormal.y =
|
||||
CesiumUtility::Math::fromSNorm(encodedNormal.y, rangeMax);
|
||||
decodedNormal.z =
|
||||
1.0 - (glm::abs(decodedNormal.x) + glm::abs(decodedNormal.y));
|
||||
|
||||
if (decodedNormal.z < 0.0) {
|
||||
const double oldVX = decodedNormal.x;
|
||||
decodedNormal.x = (1.0 - glm::abs(decodedNormal.y)) *
|
||||
CesiumUtility::Math::signNotZero(oldVX);
|
||||
decodedNormal.y = (1.0 - glm::abs(oldVX)) *
|
||||
CesiumUtility::Math::signNotZero(decodedNormal.y);
|
||||
}
|
||||
outNormals[i] = glm::vec3(glm::normalize(decodedNormal));
|
||||
}
|
||||
} else {
|
||||
std::memcpy(
|
||||
normalData.data(),
|
||||
featureTableBinaryData.data() + normal.byteOffset,
|
||||
normalsByteLength);
|
||||
}
|
||||
}
|
||||
|
||||
void parseFeatureTableBinary(
|
||||
const gsl::span<const std::byte>& featureTableBinaryData,
|
||||
PntsContent& parsedContent) {
|
||||
|
|
@ -908,6 +973,9 @@ void parseFeatureTableBinary(
|
|||
if (parsedContent.color) {
|
||||
parseColorsFromFeatureTableBinary(featureTableBinaryData, parsedContent);
|
||||
}
|
||||
if (parsedContent.normal) {
|
||||
parseNormalsFromFeatureTableBinary(featureTableBinaryData, parsedContent);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
|
|
@ -955,25 +1023,20 @@ int32_t createAccessorInGltf(
|
|||
|
||||
void addPositionsToGltf(PntsContent& parsedContent, CesiumGltf::Model& gltf) {
|
||||
const int64_t count = static_cast<int64_t>(parsedContent.pointsLength);
|
||||
const int64_t positionsByteStride = static_cast<int64_t>(sizeof(glm ::vec3));
|
||||
const int64_t positionsByteLength =
|
||||
static_cast<int64_t>(positionsByteStride * count);
|
||||
int32_t positionsBufferId =
|
||||
createBufferInGltf(gltf, parsedContent.position.data);
|
||||
int32_t positionsBufferViewId = createBufferViewInGltf(
|
||||
const int64_t byteStride = static_cast<int64_t>(sizeof(glm ::vec3));
|
||||
const int64_t byteLength = static_cast<int64_t>(byteStride * count);
|
||||
int32_t bufferId = createBufferInGltf(gltf, parsedContent.position.data);
|
||||
int32_t bufferViewId =
|
||||
createBufferViewInGltf(gltf, bufferId, byteLength, byteStride);
|
||||
int32_t accessorId = createAccessorInGltf(
|
||||
gltf,
|
||||
positionsBufferId,
|
||||
positionsByteLength,
|
||||
positionsByteStride);
|
||||
int32_t positionAccessorId = createAccessorInGltf(
|
||||
gltf,
|
||||
positionsBufferViewId,
|
||||
bufferViewId,
|
||||
CesiumGltf::Accessor::ComponentType::FLOAT,
|
||||
count,
|
||||
CesiumGltf::Accessor::Type::VEC3);
|
||||
|
||||
CesiumGltf::Accessor& accessor =
|
||||
gltf.accessors[static_cast<uint32_t>(positionAccessorId)];
|
||||
gltf.accessors[static_cast<uint32_t>(accessorId)];
|
||||
accessor.min = {
|
||||
parsedContent.positionMin.x,
|
||||
parsedContent.positionMin.y,
|
||||
|
|
@ -986,7 +1049,7 @@ void addPositionsToGltf(PntsContent& parsedContent, CesiumGltf::Model& gltf) {
|
|||
};
|
||||
|
||||
CesiumGltf::MeshPrimitive& primitive = gltf.meshes[0].primitives[0];
|
||||
primitive.attributes.emplace("POSITION", positionAccessorId);
|
||||
primitive.attributes.emplace("POSITION", accessorId);
|
||||
}
|
||||
|
||||
void addColorsToGltf(PntsContent& parsedContent, CesiumGltf::Model& gltf) {
|
||||
|
|
@ -994,65 +1057,71 @@ void addColorsToGltf(PntsContent& parsedContent, CesiumGltf::Model& gltf) {
|
|||
PntsSemantic& color = parsedContent.color.value();
|
||||
|
||||
const int64_t count = static_cast<int64_t>(parsedContent.pointsLength);
|
||||
int64_t colorsByteStride = 0;
|
||||
int64_t byteStride = 0;
|
||||
int32_t componentType = 0;
|
||||
std::string type;
|
||||
bool isTranslucent = false;
|
||||
bool isNormalized = false;
|
||||
|
||||
if (parsedContent.colorType == PntsColorType::RGBA) {
|
||||
colorsByteStride = static_cast<int64_t>(sizeof(glm::u8vec4));
|
||||
byteStride = static_cast<int64_t>(sizeof(glm::u8vec4));
|
||||
componentType = CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE;
|
||||
type = CesiumGltf::Accessor::Type::VEC4;
|
||||
isTranslucent = true;
|
||||
isNormalized = true;
|
||||
} else if (parsedContent.colorType == PntsColorType::RGB) {
|
||||
colorsByteStride = static_cast<int64_t>(sizeof(glm::u8vec3));
|
||||
byteStride = static_cast<int64_t>(sizeof(glm::u8vec3));
|
||||
componentType = CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE;
|
||||
isNormalized = true;
|
||||
type = CesiumGltf::Accessor::Type::VEC3;
|
||||
} else if (parsedContent.colorType == PntsColorType::RGB565) {
|
||||
colorsByteStride = static_cast<int64_t>(sizeof(glm::vec3));
|
||||
byteStride = static_cast<int64_t>(sizeof(glm::vec3));
|
||||
componentType = CesiumGltf::Accessor::ComponentType::FLOAT;
|
||||
type = CesiumGltf::Accessor::Type::VEC3;
|
||||
}
|
||||
|
||||
const int64_t colorsByteLength =
|
||||
static_cast<int64_t>(colorsByteStride * count);
|
||||
int32_t colorsBufferId = createBufferInGltf(gltf, color.data);
|
||||
int32_t colorsBufferViewId = createBufferViewInGltf(
|
||||
gltf,
|
||||
colorsBufferId,
|
||||
colorsByteLength,
|
||||
colorsByteStride);
|
||||
int32_t colorsAccessorId = createAccessorInGltf(
|
||||
gltf,
|
||||
colorsBufferViewId,
|
||||
componentType,
|
||||
count,
|
||||
type);
|
||||
const int64_t byteLength = static_cast<int64_t>(byteStride * count);
|
||||
int32_t bufferId = createBufferInGltf(gltf, color.data);
|
||||
int32_t bufferViewId =
|
||||
createBufferViewInGltf(gltf, bufferId, byteLength, byteStride);
|
||||
int32_t accessorId =
|
||||
createAccessorInGltf(gltf, bufferViewId, componentType, count, type);
|
||||
|
||||
CesiumGltf::Accessor& accessor =
|
||||
gltf.accessors[static_cast<uint32_t>(colorsAccessorId)];
|
||||
gltf.accessors[static_cast<uint32_t>(accessorId)];
|
||||
accessor.normalized = isNormalized;
|
||||
|
||||
CesiumGltf::MeshPrimitive& primitive = gltf.meshes[0].primitives[0];
|
||||
primitive.attributes.emplace("COLOR_0", colorsAccessorId);
|
||||
primitive.attributes.emplace("COLOR_0", accessorId);
|
||||
|
||||
if (isTranslucent) {
|
||||
CesiumGltf::Material& material = gltf.materials[static_cast<uint32_t>(primitive.material)];
|
||||
CesiumGltf::Material& material =
|
||||
gltf.materials[static_cast<uint32_t>(primitive.material)];
|
||||
material.alphaMode = CesiumGltf::Material::AlphaMode::BLEND;
|
||||
}
|
||||
} else if (parsedContent.constantRgba) {
|
||||
// Map RGBA from [0, 255] to [0, 1]
|
||||
glm::vec4 materialColor(parsedContent.constantRgba.value());
|
||||
materialColor /= 255.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void addNormalsToGltf(PntsContent& parsedContent, CesiumGltf::Model& gltf) {
|
||||
if (parsedContent.normal) {
|
||||
PntsSemantic& normal = parsedContent.normal.value();
|
||||
|
||||
const int64_t count = static_cast<int64_t>(parsedContent.pointsLength);
|
||||
const int64_t byteStride = static_cast<int64_t>(sizeof(glm ::vec3));
|
||||
const int64_t byteLength = static_cast<int64_t>(byteStride * count);
|
||||
|
||||
int32_t bufferId = createBufferInGltf(gltf, normal.data);
|
||||
int32_t bufferViewId =
|
||||
createBufferViewInGltf(gltf, bufferId, byteLength, byteStride);
|
||||
int32_t accessorId = createAccessorInGltf(
|
||||
gltf,
|
||||
bufferViewId,
|
||||
CesiumGltf::Accessor::ComponentType::FLOAT,
|
||||
count,
|
||||
CesiumGltf::Accessor::Type::VEC3);
|
||||
|
||||
CesiumGltf::MeshPrimitive& primitive = gltf.meshes[0].primitives[0];
|
||||
CesiumGltf::Material& material = gltf.materials[static_cast<uint32_t>(primitive.material)];
|
||||
material.pbrMetallicRoughness.value().baseColorFactor =
|
||||
{materialColor.x, materialColor.y, materialColor.z, materialColor.w};
|
||||
material.alphaMode = CesiumGltf::Material::AlphaMode::BLEND;
|
||||
primitive.attributes.emplace("NORMAL", accessorId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1087,7 +1156,26 @@ void createGltfFromParsedContent(
|
|||
primitive.material = static_cast<int32_t>(materialId);
|
||||
|
||||
addPositionsToGltf(parsedContent, gltf);
|
||||
addColorsToGltf(parsedContent, gltf);
|
||||
|
||||
if (parsedContent.color) {
|
||||
addColorsToGltf(parsedContent, gltf);
|
||||
} else if (parsedContent.constantRgba) {
|
||||
// Map RGBA from [0, 255] to [0, 1]
|
||||
glm::vec4 materialColor(parsedContent.constantRgba.value());
|
||||
materialColor /= 255.0f;
|
||||
|
||||
material.pbrMetallicRoughness.value().baseColorFactor =
|
||||
{materialColor.x, materialColor.y, materialColor.z, materialColor.w};
|
||||
material.alphaMode = CesiumGltf::Material::AlphaMode::BLEND;
|
||||
}
|
||||
|
||||
if (parsedContent.normal) {
|
||||
addNormalsToGltf(parsedContent, gltf);
|
||||
} else {
|
||||
// Points without normals should be rendered without lighting, which we
|
||||
// can indicate with the KHR_materials_unlit extension.
|
||||
material.addExtension<CesiumGltf::ExtensionKhrMaterialsUnlit>();
|
||||
}
|
||||
|
||||
if (parsedContent.rtcCenter) {
|
||||
// Add the RTC_CENTER value to the glTF as a CESIUM_RTC extension.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@
|
|||
#include <CesiumAsync/AsyncSystem.h>
|
||||
#include <CesiumAsync/HttpHeaders.h>
|
||||
#include <CesiumGltf/ExtensionCesiumRTC.h>
|
||||
#include <CesiumGltf/ExtensionKhrMaterialsUnlit.h>
|
||||
#include <CesiumGltf/ExtensionMeshPrimitiveExtFeatureMetadata.h>
|
||||
#include <CesiumGltf/ExtensionModelExtFeatureMetadata.h>
|
||||
#include <CesiumGltf/MetadataFeatureTableView.h>
|
||||
#include <CesiumGltf/MetadataPropertyView.h>
|
||||
#include <CesiumUtility/Math.h>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <rapidjson/document.h>
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
|
||||
using namespace CesiumGltf;
|
||||
using namespace Cesium3DTilesSelection;
|
||||
using namespace CesiumUtility;
|
||||
|
||||
template <typename Type>
|
||||
static void checkBufferContents(
|
||||
|
|
@ -32,19 +35,20 @@ static void checkBufferContents(
|
|||
const glm::vec3& value =
|
||||
*reinterpret_cast<const glm::vec3*>(buffer.data() + i * byteStride);
|
||||
const glm::vec3& expectedValue = expected[i];
|
||||
CHECK(value.x == Approx(expectedValue.x));
|
||||
CHECK(value.y == Approx(expectedValue.y));
|
||||
CHECK(value.z == Approx(expectedValue.z));
|
||||
CHECK(Math::equalsEpsilon(
|
||||
static_cast<glm::dvec3>(value),
|
||||
static_cast<glm::dvec3>(expectedValue),
|
||||
Math::Epsilon6));
|
||||
}
|
||||
} else if constexpr (std::is_same_v<Type, glm::vec4>) {
|
||||
for (size_t i = 0; i < expected.size(); ++i) {
|
||||
const glm::vec4& value =
|
||||
*reinterpret_cast<const glm::vec4*>(buffer.data() + i * byteStride);
|
||||
const glm::vec4& expectedValue = expected[i];
|
||||
CHECK(value.x == Approx(expectedValue.x));
|
||||
CHECK(value.y == Approx(expectedValue.y));
|
||||
CHECK(value.z == Approx(expectedValue.z));
|
||||
CHECK(value.w == Approx(expectedValue.w));
|
||||
CHECK(Math::equalsEpsilon(
|
||||
static_cast<glm::dvec4>(value),
|
||||
static_cast<glm::dvec4>(expectedValue),
|
||||
Math::Epsilon6));
|
||||
}
|
||||
} else if constexpr (
|
||||
std::is_same_v<Type, glm::u8vec3> || std::is_same_v<Type, glm::u8vec4>) {
|
||||
|
|
@ -70,8 +74,9 @@ static void checkAttribute(
|
|||
|
||||
const int32_t accessorId = attributes.at(attributeSemantic);
|
||||
REQUIRE(accessorId >= 0);
|
||||
REQUIRE(accessorId < gltf.accessors.size());
|
||||
const Accessor& accessor = gltf.accessors[accessorId];
|
||||
const uint32_t accessorIdUint = static_cast<uint32_t>(accessorId);
|
||||
REQUIRE(accessorIdUint < gltf.accessors.size());
|
||||
const Accessor& accessor = gltf.accessors[accessorIdUint];
|
||||
|
||||
int32_t expectedComponentType = -1;
|
||||
std::string expectedType;
|
||||
|
|
@ -79,6 +84,12 @@ static void checkAttribute(
|
|||
if constexpr (std::is_same_v<Type, glm::vec3>) {
|
||||
expectedComponentType = Accessor::ComponentType::FLOAT;
|
||||
expectedType = Accessor::Type::VEC3;
|
||||
} else if constexpr (std::is_same_v<Type, glm::u8vec3>) {
|
||||
expectedComponentType = Accessor::ComponentType::UNSIGNED_BYTE;
|
||||
expectedType = Accessor::Type::VEC3;
|
||||
} else if constexpr (std::is_same_v<Type, glm::u8vec4>) {
|
||||
expectedComponentType = Accessor::ComponentType::UNSIGNED_BYTE;
|
||||
expectedType = Accessor::Type::VEC4;
|
||||
} else {
|
||||
FAIL("Accessor check has not been implemented for the given type.");
|
||||
}
|
||||
|
|
@ -92,16 +103,18 @@ static void checkAttribute(
|
|||
|
||||
const int32_t bufferViewId = accessor.bufferView;
|
||||
REQUIRE(bufferViewId >= 0);
|
||||
REQUIRE(bufferViewId < gltf.bufferViews.size());
|
||||
const BufferView& bufferView = gltf.bufferViews[bufferViewId];
|
||||
const uint32_t bufferViewIdUint = static_cast<uint32_t>(bufferViewId);
|
||||
REQUIRE(bufferViewIdUint < gltf.bufferViews.size());
|
||||
const BufferView& bufferView = gltf.bufferViews[bufferViewIdUint];
|
||||
CHECK(bufferView.byteLength == expectedByteLength);
|
||||
CHECK(bufferView.byteOffset == 0);
|
||||
|
||||
const int32_t bufferId = bufferView.buffer;
|
||||
REQUIRE(bufferId >= 0);
|
||||
REQUIRE(bufferId < gltf.buffers.size());
|
||||
const uint32_t bufferIdUint = static_cast<uint32_t>(bufferId);
|
||||
REQUIRE(bufferIdUint < gltf.buffers.size());
|
||||
|
||||
const Buffer& buffer = gltf.buffers[bufferId];
|
||||
const Buffer& buffer = gltf.buffers[static_cast<uint32_t>(bufferIdUint)];
|
||||
CHECK(buffer.byteLength == expectedByteLength);
|
||||
CHECK(static_cast<int64_t>(buffer.cesium.data.size()) == buffer.byteLength);
|
||||
}
|
||||
|
|
@ -123,6 +136,24 @@ TEST_CASE("Converts simple point cloud to glTF") {
|
|||
// Check for single mesh node
|
||||
REQUIRE(gltf.nodes.size() == 1);
|
||||
Node& node = gltf.nodes[0];
|
||||
std::vector<double> expectedMatrix = {
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0};
|
||||
CHECK(node.matrix == expectedMatrix);
|
||||
CHECK(node.mesh == 0);
|
||||
|
||||
// Check for single mesh primitive
|
||||
|
|
@ -136,6 +167,7 @@ TEST_CASE("Converts simple point cloud to glTF") {
|
|||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(material.pbrMetallicRoughness);
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == 1);
|
||||
|
|
@ -151,18 +183,12 @@ TEST_CASE("Converts simple point cloud to glTF") {
|
|||
CHECK(accessor.count == pointsLength);
|
||||
CHECK(accessor.type == Accessor::Type::VEC3);
|
||||
|
||||
const glm::vec3 expectedMin(
|
||||
-3.2968313694000244,
|
||||
-4.033046722412109,
|
||||
-3.522307872772217);
|
||||
const glm::vec3 expectedMin(-3.2968313, -4.0330467, -3.5223078);
|
||||
CHECK(accessor.min[0] == Approx(expectedMin.x));
|
||||
CHECK(accessor.min[1] == Approx(expectedMin.y));
|
||||
CHECK(accessor.min[2] == Approx(expectedMin.z));
|
||||
|
||||
const glm::vec3 expectedMax(
|
||||
3.2968313694000244,
|
||||
4.033046722412109,
|
||||
3.522307872772217);
|
||||
const glm::vec3 expectedMax(3.2968313, 4.0330467, 3.5223078);
|
||||
CHECK(accessor.max[0] == Approx(expectedMax.x));
|
||||
CHECK(accessor.max[1] == Approx(expectedMax.y));
|
||||
CHECK(accessor.max[2] == Approx(expectedMax.z));
|
||||
|
|
@ -227,6 +253,7 @@ TEST_CASE("Converts point cloud with RGBA to glTF") {
|
|||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(material.alphaMode == Material::AlphaMode::BLEND);
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
|
|
@ -235,39 +262,20 @@ TEST_CASE("Converts point cloud with RGBA to glTF") {
|
|||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position attribute is present
|
||||
// Check that position and color attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::u8vec4>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
|
||||
// Check color attribute more thoroughly
|
||||
REQUIRE(attributes.find("COLOR_0") != attributes.end());
|
||||
int32_t colorAccessorId = attributes.at("COLOR_0");
|
||||
REQUIRE(colorAccessorId >= 0);
|
||||
REQUIRE(colorAccessorId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorAccessorId = static_cast<uint32_t>(attributes.at("COLOR_0"));
|
||||
Accessor& colorAccessor = gltf.accessors[colorAccessorId];
|
||||
CHECK(colorAccessor.byteOffset == 0);
|
||||
CHECK(colorAccessor.componentType == Accessor::ComponentType::UNSIGNED_BYTE);
|
||||
CHECK(colorAccessor.count == pointsLength);
|
||||
CHECK(colorAccessor.type == Accessor::Type::VEC4);
|
||||
CHECK(colorAccessor.normalized);
|
||||
|
||||
int32_t colorBufferViewId = colorAccessor.bufferView;
|
||||
REQUIRE(colorBufferViewId >= 0);
|
||||
REQUIRE(colorBufferViewId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferViewId = static_cast<uint32_t>(colorAccessor.bufferView);
|
||||
BufferView& colorBufferView = gltf.bufferViews[colorBufferViewId];
|
||||
CHECK(colorBufferView.byteLength == pointsLength * sizeof(glm::u8vec4));
|
||||
CHECK(colorBufferView.byteOffset == 0);
|
||||
|
||||
int32_t colorBufferId = colorBufferView.buffer;
|
||||
REQUIRE(colorBufferId >= 0);
|
||||
REQUIRE(colorBufferId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferId = static_cast<uint32_t>(colorBufferView.buffer);
|
||||
Buffer& colorBuffer = gltf.buffers[colorBufferId];
|
||||
CHECK(colorBuffer.byteLength == pointsLength * sizeof(glm::u8vec4));
|
||||
CHECK(
|
||||
static_cast<int64_t>(colorBuffer.cesium.data.size()) ==
|
||||
colorBuffer.byteLength);
|
||||
|
||||
const std::vector<glm::u8vec4> expectedColors = {
|
||||
glm::u8vec4(139, 151, 182, 108),
|
||||
|
|
@ -304,6 +312,7 @@ TEST_CASE("Converts point cloud with RGB to glTF") {
|
|||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(material.alphaMode == Material::AlphaMode::OPAQUE);
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
|
|
@ -312,39 +321,20 @@ TEST_CASE("Converts point cloud with RGB to glTF") {
|
|||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position attribute is present
|
||||
// Check that position and color attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::u8vec3>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
|
||||
// Check color attribute more thoroughly
|
||||
REQUIRE(attributes.find("COLOR_0") != attributes.end());
|
||||
int32_t colorAccessorId = attributes.at("COLOR_0");
|
||||
REQUIRE(colorAccessorId >= 0);
|
||||
REQUIRE(colorAccessorId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorAccessorId = static_cast<uint32_t>(attributes.at("COLOR_0"));
|
||||
Accessor& colorAccessor = gltf.accessors[colorAccessorId];
|
||||
CHECK(colorAccessor.byteOffset == 0);
|
||||
CHECK(colorAccessor.componentType == Accessor::ComponentType::UNSIGNED_BYTE);
|
||||
CHECK(colorAccessor.count == pointsLength);
|
||||
CHECK(colorAccessor.type == Accessor::Type::VEC3);
|
||||
CHECK(colorAccessor.normalized);
|
||||
|
||||
int32_t colorBufferViewId = colorAccessor.bufferView;
|
||||
REQUIRE(colorBufferViewId >= 0);
|
||||
REQUIRE(colorBufferViewId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferViewId = static_cast<uint32_t>(colorAccessor.bufferView);
|
||||
BufferView& colorBufferView = gltf.bufferViews[colorBufferViewId];
|
||||
CHECK(colorBufferView.byteLength == pointsLength * sizeof(glm::u8vec3));
|
||||
CHECK(colorBufferView.byteOffset == 0);
|
||||
|
||||
int32_t colorBufferId = colorBufferView.buffer;
|
||||
REQUIRE(colorBufferId >= 0);
|
||||
REQUIRE(colorBufferId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferId = static_cast<uint32_t>(colorBufferView.buffer);
|
||||
Buffer& colorBuffer = gltf.buffers[colorBufferId];
|
||||
CHECK(colorBuffer.byteLength == pointsLength * sizeof(glm::u8vec3));
|
||||
CHECK(
|
||||
static_cast<int64_t>(colorBuffer.cesium.data.size()) ==
|
||||
colorBuffer.byteLength);
|
||||
|
||||
const std::vector<glm::u8vec3> expectedColors = {
|
||||
glm::u8vec3(139, 151, 182),
|
||||
|
|
@ -381,6 +371,7 @@ TEST_CASE("Converts point cloud with RGB565 to glTF") {
|
|||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(material.alphaMode == Material::AlphaMode::OPAQUE);
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
|
|
@ -389,39 +380,21 @@ TEST_CASE("Converts point cloud with RGB565 to glTF") {
|
|||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position attribute is present
|
||||
// Check that position and color attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
|
||||
// Check color attribute more thoroughly
|
||||
REQUIRE(attributes.find("COLOR_0") != attributes.end());
|
||||
int32_t colorAccessorId = attributes.at("COLOR_0");
|
||||
REQUIRE(colorAccessorId >= 0);
|
||||
REQUIRE(colorAccessorId < expectedAttributeCount);
|
||||
|
||||
// Check color attribute more thoroughly
|
||||
uint32_t colorAccessorId = static_cast<uint32_t>(attributes.at("COLOR_0"));
|
||||
Accessor& colorAccessor = gltf.accessors[colorAccessorId];
|
||||
CHECK(colorAccessor.byteOffset == 0);
|
||||
CHECK(colorAccessor.componentType == Accessor::ComponentType::FLOAT);
|
||||
CHECK(colorAccessor.count == pointsLength);
|
||||
CHECK(colorAccessor.type == Accessor::Type::VEC3);
|
||||
CHECK(!colorAccessor.normalized);
|
||||
|
||||
int32_t colorBufferViewId = colorAccessor.bufferView;
|
||||
REQUIRE(colorBufferViewId >= 0);
|
||||
REQUIRE(colorBufferViewId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferViewId = static_cast<uint32_t>(colorAccessor.bufferView);
|
||||
BufferView& colorBufferView = gltf.bufferViews[colorBufferViewId];
|
||||
CHECK(colorBufferView.byteLength == pointsLength * sizeof(glm::vec3));
|
||||
CHECK(colorBufferView.byteOffset == 0);
|
||||
|
||||
int32_t colorBufferId = colorBufferView.buffer;
|
||||
REQUIRE(colorBufferId >= 0);
|
||||
REQUIRE(colorBufferId < expectedAttributeCount);
|
||||
|
||||
uint32_t colorBufferId = static_cast<uint32_t>(colorBufferView.buffer);
|
||||
Buffer& colorBuffer = gltf.buffers[colorBufferId];
|
||||
CHECK(colorBuffer.byteLength == pointsLength * sizeof(glm::vec3));
|
||||
CHECK(
|
||||
static_cast<int64_t>(colorBuffer.cesium.data.size()) ==
|
||||
colorBuffer.byteLength);
|
||||
|
||||
const std::vector<glm::vec3> expectedColors = {
|
||||
glm::vec3(0.5483871, 0.5873016, 0.7096773),
|
||||
|
|
@ -476,4 +449,195 @@ TEST_CASE("Converts point cloud with CONSTANT_RGBA") {
|
|||
CHECK(baseColorFactor[3] == Approx(expectedConstantRGBA.w));
|
||||
|
||||
CHECK(material.alphaMode == Material::AlphaMode::BLEND);
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
}
|
||||
|
||||
TEST_CASE("Converts point cloud with normals to glTF") {
|
||||
std::filesystem::path testFilePath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
||||
testFilePath = testFilePath / "PointCloud" / "pointCloudNormals.pnts";
|
||||
const int32_t pointsLength = 8;
|
||||
const int32_t expectedAttributeCount = 3;
|
||||
|
||||
GltfConverterResult result = loadPnts(testFilePath);
|
||||
|
||||
REQUIRE(result.model);
|
||||
Model& gltf = *result.model;
|
||||
|
||||
CHECK(gltf.hasExtension<CesiumGltf::ExtensionCesiumRTC>());
|
||||
CHECK(gltf.nodes.size() == 1);
|
||||
|
||||
REQUIRE(gltf.meshes.size() == 1);
|
||||
Mesh& mesh = gltf.meshes[0];
|
||||
REQUIRE(mesh.primitives.size() == 1);
|
||||
MeshPrimitive& primitive = mesh.primitives[0];
|
||||
|
||||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(!material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.buffers.size() == expectedAttributeCount);
|
||||
|
||||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position, color, and normal attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::u8vec3>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "NORMAL", pointsLength);
|
||||
|
||||
// Check normal attribute more thoroughly
|
||||
uint32_t normalAccessorId = static_cast<uint32_t>(attributes.at("NORMAL"));
|
||||
Accessor& normalAccessor = gltf.accessors[normalAccessorId];
|
||||
|
||||
uint32_t normalBufferViewId =
|
||||
static_cast<uint32_t>(normalAccessor.bufferView);
|
||||
BufferView& normalBufferView = gltf.bufferViews[normalBufferViewId];
|
||||
|
||||
uint32_t normalBufferId = static_cast<uint32_t>(normalBufferView.buffer);
|
||||
Buffer& normalBuffer = gltf.buffers[normalBufferId];
|
||||
|
||||
const std::vector<glm::vec3> expectedNormals = {
|
||||
glm::vec3(-0.9854088, 0.1667507, 0.0341110),
|
||||
glm::vec3(-0.5957704, 0.5378777, 0.5964436),
|
||||
glm::vec3(-0.5666092, -0.7828890, -0.2569800),
|
||||
glm::vec3(-0.5804154, -0.7226123, 0.3754320),
|
||||
glm::vec3(-0.8535281, -0.1291752, -0.5047805),
|
||||
glm::vec3(0.7557975, 0.1243999, 0.6428800),
|
||||
glm::vec3(0.1374090, -0.2333731, -0.9626296),
|
||||
glm::vec3(-0.0633145, 0.9630424, 0.2618022)};
|
||||
|
||||
checkBufferContents<glm::vec3>(normalBuffer.cesium.data, expectedNormals);
|
||||
}
|
||||
|
||||
TEST_CASE("Converts point cloud with oct-encoded normals to glTF") {
|
||||
std::filesystem::path testFilePath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
||||
testFilePath =
|
||||
testFilePath / "PointCloud" / "pointCloudNormalsOctEncoded.pnts";
|
||||
const int32_t pointsLength = 8;
|
||||
const int32_t expectedAttributeCount = 3;
|
||||
|
||||
GltfConverterResult result = loadPnts(testFilePath);
|
||||
|
||||
REQUIRE(result.model);
|
||||
Model& gltf = *result.model;
|
||||
|
||||
CHECK(gltf.hasExtension<CesiumGltf::ExtensionCesiumRTC>());
|
||||
CHECK(gltf.nodes.size() == 1);
|
||||
|
||||
REQUIRE(gltf.meshes.size() == 1);
|
||||
Mesh& mesh = gltf.meshes[0];
|
||||
REQUIRE(mesh.primitives.size() == 1);
|
||||
MeshPrimitive& primitive = mesh.primitives[0];
|
||||
|
||||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(!material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.buffers.size() == expectedAttributeCount);
|
||||
|
||||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position, color, and normal attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::u8vec3>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "NORMAL", pointsLength);
|
||||
|
||||
// Check normal attribute more thoroughly
|
||||
uint32_t normalAccessorId = static_cast<uint32_t>(attributes.at("NORMAL"));
|
||||
Accessor& normalAccessor = gltf.accessors[normalAccessorId];
|
||||
CHECK(!normalAccessor.normalized);
|
||||
|
||||
uint32_t normalBufferViewId =
|
||||
static_cast<uint32_t>(normalAccessor.bufferView);
|
||||
BufferView& normalBufferView = gltf.bufferViews[normalBufferViewId];
|
||||
|
||||
uint32_t normalBufferId = static_cast<uint32_t>(normalBufferView.buffer);
|
||||
Buffer& normalBuffer = gltf.buffers[normalBufferId];
|
||||
|
||||
const std::vector<glm::vec3> expectedNormals = {
|
||||
glm::vec3(-0.9856477, 0.1634960, 0.0420418),
|
||||
glm::vec3(-0.5901730, 0.5359042, 0.6037402),
|
||||
glm::vec3(-0.5674310, -0.7817938, -0.2584963),
|
||||
glm::vec3(-0.5861990, -0.7179291, 0.3754308),
|
||||
glm::vec3(-0.8519385, -0.1283743, -0.5076620),
|
||||
glm::vec3(0.7587127, 0.1254564, 0.6392304),
|
||||
glm::vec3(0.1354662, -0.2292506, -0.9638947),
|
||||
glm::vec3(-0.0656172, 0.9640687, 0.2574214)};
|
||||
|
||||
checkBufferContents<glm::vec3>(normalBuffer.cesium.data, expectedNormals);
|
||||
}
|
||||
|
||||
TEST_CASE("Converts point cloud with quantized positions to glTF") {
|
||||
std::filesystem::path testFilePath = Cesium3DTilesSelection_TEST_DATA_DIR;
|
||||
testFilePath = testFilePath / "PointCloud" / "pointCloudQuantized.pnts";
|
||||
const int32_t pointsLength = 8;
|
||||
const int32_t expectedAttributeCount = 2;
|
||||
|
||||
GltfConverterResult result = loadPnts(testFilePath);
|
||||
|
||||
REQUIRE(result.model);
|
||||
Model& gltf = *result.model;
|
||||
|
||||
CHECK(!gltf.hasExtension<CesiumGltf::ExtensionCesiumRTC>());
|
||||
CHECK(gltf.nodes.size() == 1);
|
||||
|
||||
REQUIRE(gltf.meshes.size() == 1);
|
||||
Mesh& mesh = gltf.meshes[0];
|
||||
REQUIRE(mesh.primitives.size() == 1);
|
||||
MeshPrimitive& primitive = mesh.primitives[0];
|
||||
|
||||
REQUIRE(gltf.materials.size() == 1);
|
||||
Material& material = gltf.materials[0];
|
||||
CHECK(material.hasExtension<ExtensionKhrMaterialsUnlit>());
|
||||
|
||||
REQUIRE(gltf.accessors.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.bufferViews.size() == expectedAttributeCount);
|
||||
REQUIRE(gltf.buffers.size() == expectedAttributeCount);
|
||||
|
||||
auto attributes = primitive.attributes;
|
||||
REQUIRE(attributes.size() == expectedAttributeCount);
|
||||
|
||||
// Check that position and color attributes are present
|
||||
checkAttribute<glm::vec3>(gltf, primitive, "POSITION", pointsLength);
|
||||
checkAttribute<glm::u8vec3>(gltf, primitive, "COLOR_0", pointsLength);
|
||||
|
||||
// Check position attribute more thoroughly
|
||||
uint32_t positionAccessorId =
|
||||
static_cast<uint32_t>(attributes.at("POSITION"));
|
||||
Accessor& positionAccessor = gltf.accessors[positionAccessorId];
|
||||
CHECK(!positionAccessor.normalized);
|
||||
|
||||
const glm::vec3 expectedMin(1215009.59, -4736317.08, 4081601.7);
|
||||
CHECK(positionAccessor.min[0] == Approx(expectedMin.x));
|
||||
CHECK(positionAccessor.min[1] == Approx(expectedMin.y));
|
||||
CHECK(positionAccessor.min[2] == Approx(expectedMin.z));
|
||||
|
||||
const glm::vec3 expectedMax(1215016.18, -4736309.02, 4081608.74);
|
||||
CHECK(positionAccessor.max[0] == Approx(expectedMax.x));
|
||||
CHECK(positionAccessor.max[1] == Approx(expectedMax.y));
|
||||
CHECK(positionAccessor.max[2] == Approx(expectedMax.z));
|
||||
|
||||
uint32_t positionBufferViewId =
|
||||
static_cast<uint32_t>(positionAccessor.bufferView);
|
||||
BufferView& positionBufferView = gltf.bufferViews[positionBufferViewId];
|
||||
|
||||
uint32_t positionBufferId = static_cast<uint32_t>(positionBufferView.buffer);
|
||||
Buffer& positionBuffer = gltf.buffers[positionBufferId];
|
||||
|
||||
const std::vector<glm::vec3> expectedPositions = {
|
||||
glm::vec3(1215010.39, -4736313.38, 4081601.7),
|
||||
glm::vec3(1215015.23, -4736312.13, 4081601.7),
|
||||
glm::vec3(1215009.59, -4736310.26, 4081605.53),
|
||||
glm::vec3(1215014.43, -4736309.02, 4081605.53),
|
||||
glm::vec3(1215011.34, -4736317.08, 4081604.92),
|
||||
glm::vec3(1215016.18, -4736315.84, 4081604.92),
|
||||
glm::vec3(1215010.54, -4736313.97, 4081608.74),
|
||||
glm::vec3(1215015.38, -4736312.73, 4081608.74)};
|
||||
|
||||
checkBufferContents<glm::vec3>(positionBuffer.cesium.data, expectedPositions);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue