3680 lines
129 KiB
C++
3680 lines
129 KiB
C++
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
|
|
|
#include "CesiumGltfComponent.h"
|
|
#include "Async/Async.h"
|
|
#include "CesiumCommon.h"
|
|
#include "CesiumEncodedFeaturesMetadata.h"
|
|
#include "CesiumEncodedMetadataUtility.h"
|
|
#include "CesiumFeatureIdSet.h"
|
|
#include "CesiumGltfPointsComponent.h"
|
|
#include "CesiumGltfPrimitiveComponent.h"
|
|
#include "CesiumGltfTextures.h"
|
|
#include "CesiumMaterialUserData.h"
|
|
#include "CesiumRasterOverlays.h"
|
|
#include "CesiumRuntime.h"
|
|
#include "CesiumTextureUtility.h"
|
|
#include "CesiumTransforms.h"
|
|
#include "Chaos/AABBTree.h"
|
|
#include "Chaos/CollisionConvexMesh.h"
|
|
#include "Chaos/TriangleMeshImplicitObject.h"
|
|
#include "CreateGltfOptions.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "HttpModule.h"
|
|
#include "Interfaces/IHttpResponse.h"
|
|
#include "LoadGltfResult.h"
|
|
#include "Materials/Material.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "MeshTypes.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "PhysicsEngine/PhysicsSettings.h"
|
|
#include "PixelFormat.h"
|
|
#include "Runtime/Launch/Resources/Version.h"
|
|
#include "StaticMeshOperations.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "UObject/ConstructorHelpers.h"
|
|
#include "VecMath.h"
|
|
#include "mikktspace.h"
|
|
|
|
#include <CesiumGeometry/Axis.h>
|
|
#include <CesiumGeometry/Rectangle.h>
|
|
#include <CesiumGeometry/Transforms.h>
|
|
#include <CesiumGltf/AccessorUtility.h>
|
|
#include <CesiumGltf/AccessorView.h>
|
|
#include <CesiumGltf/ExtensionExtInstanceFeatures.h>
|
|
#include <CesiumGltf/ExtensionExtMeshFeatures.h>
|
|
#include <CesiumGltf/ExtensionExtMeshGpuInstancing.h>
|
|
#include <CesiumGltf/ExtensionKhrMaterialsUnlit.h>
|
|
#include <CesiumGltf/ExtensionKhrTextureTransform.h>
|
|
#include <CesiumGltf/ExtensionMeshPrimitiveExtStructuralMetadata.h>
|
|
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
|
|
#include <CesiumGltf/KhrTextureTransform.h>
|
|
#include <CesiumGltf/PropertyType.h>
|
|
#include <CesiumGltf/TextureInfo.h>
|
|
#include <CesiumGltfContent/GltfUtilities.h>
|
|
#include <CesiumRasterOverlays/RasterOverlay.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayTile.h>
|
|
#include <CesiumUtility/Tracing.h>
|
|
#include <CesiumUtility/joinToString.h>
|
|
#include <cstddef>
|
|
#include <glm/ext/matrix_transform.hpp>
|
|
#include <glm/gtc/matrix_inverse.hpp>
|
|
#include <glm/gtc/quaternion.hpp>
|
|
#include <glm/mat3x3.hpp>
|
|
#include <iostream>
|
|
#include <type_traits>
|
|
|
|
#if WITH_EDITOR
|
|
#include "ScopedTransaction.h"
|
|
#endif
|
|
|
|
using namespace CesiumTextureUtility;
|
|
using namespace CreateGltfOptions;
|
|
using namespace LoadGltfResult;
|
|
|
|
// To debug which urls correspond to which gltf components you see in the view,
|
|
// - Set this define to 1
|
|
// - Click on a piece of terrain in the editor viewport to select it
|
|
// - Press delete to try to delete it
|
|
// Note that the console gives an error, but also tells you the url associated
|
|
// with it
|
|
#define DEBUG_GLTF_ASSET_NAMES 0
|
|
|
|
namespace {
|
|
using TMeshVector2 = FVector2f;
|
|
using TMeshVector3 = FVector3f;
|
|
using TMeshVector4 = FVector4f;
|
|
} // namespace
|
|
|
|
static uint32_t nextMaterialId = 0;
|
|
|
|
namespace {
|
|
class HalfConstructedReal : public UCesiumGltfComponent::HalfConstructed {
|
|
public:
|
|
LoadedModelResult loadModelResult{};
|
|
};
|
|
} // namespace
|
|
|
|
template <class... T> struct IsAccessorView;
|
|
|
|
template <class T> struct IsAccessorView<T> : std::false_type {};
|
|
|
|
template <class T>
|
|
struct IsAccessorView<CesiumGltf::AccessorView<T>> : std::true_type {};
|
|
|
|
template <class T>
|
|
static uint32_t updateTextureCoordinates(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
bool duplicateVertices,
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const TArray<uint32>& indices,
|
|
const std::optional<T>& texture,
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap) {
|
|
if (!texture) {
|
|
return 0;
|
|
}
|
|
|
|
return updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
"TEXCOORD_" + std::to_string(texture.value().texCoord),
|
|
gltfToUnrealTexCoordMap);
|
|
}
|
|
|
|
uint32_t updateTextureCoordinates(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
bool duplicateVertices,
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const TArray<uint32>& indices,
|
|
const std::string& attributeName,
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap) {
|
|
auto uvAccessorIt = primitive.attributes.find(attributeName);
|
|
if (uvAccessorIt == primitive.attributes.end()) {
|
|
// Texture not used, texture coordinates don't matter.
|
|
return 0;
|
|
}
|
|
|
|
int32_t uvAccessorID = uvAccessorIt->second;
|
|
auto mapIt = gltfToUnrealTexCoordMap.find(uvAccessorID);
|
|
if (mapIt != gltfToUnrealTexCoordMap.end()) {
|
|
// Texture coordinates for this accessor are already populated.
|
|
return mapIt->second;
|
|
}
|
|
|
|
size_t textureCoordinateIndex = gltfToUnrealTexCoordMap.size();
|
|
gltfToUnrealTexCoordMap[uvAccessorID] = textureCoordinateIndex;
|
|
|
|
CesiumGltf::AccessorView<TMeshVector2> uvAccessor(model, uvAccessorID);
|
|
if (uvAccessor.status() != CesiumGltf::AccessorViewStatus::Valid) {
|
|
return 0;
|
|
}
|
|
|
|
if (duplicateVertices) {
|
|
for (int i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
if (vertexIndex >= 0 && vertexIndex < uvAccessor.size()) {
|
|
vertex.UVs[textureCoordinateIndex] = uvAccessor[vertexIndex];
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = 0; i < vertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
if (i >= 0 && i < uvAccessor.size()) {
|
|
vertex.UVs[textureCoordinateIndex] = uvAccessor[i];
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
return textureCoordinateIndex;
|
|
}
|
|
|
|
static int mikkGetNumFaces(const SMikkTSpaceContext* Context) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
return vertices.Num() / 3;
|
|
}
|
|
|
|
static int
|
|
mikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
return FaceIdx < (vertices.Num() / 3) ? 3 : 0;
|
|
}
|
|
|
|
static void mikkGetPosition(
|
|
const SMikkTSpaceContext* Context,
|
|
float Position[3],
|
|
const int FaceIdx,
|
|
const int VertIdx) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
const TMeshVector3& position = vertices[FaceIdx * 3 + VertIdx].Position;
|
|
Position[0] = position.X;
|
|
Position[1] = -position.Y;
|
|
Position[2] = position.Z;
|
|
}
|
|
|
|
static void mikkGetNormal(
|
|
const SMikkTSpaceContext* Context,
|
|
float Normal[3],
|
|
const int FaceIdx,
|
|
const int VertIdx) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
const TMeshVector3& normal = vertices[FaceIdx * 3 + VertIdx].TangentZ;
|
|
Normal[0] = normal.X;
|
|
Normal[1] = -normal.Y;
|
|
Normal[2] = normal.Z;
|
|
}
|
|
|
|
static void mikkGetTexCoord(
|
|
const SMikkTSpaceContext* Context,
|
|
float UV[2],
|
|
const int FaceIdx,
|
|
const int VertIdx) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
const TMeshVector2& uv = vertices[FaceIdx * 3 + VertIdx].UVs[0];
|
|
UV[0] = uv.X;
|
|
UV[1] = uv.Y;
|
|
}
|
|
|
|
static void mikkSetTSpaceBasic(
|
|
const SMikkTSpaceContext* Context,
|
|
const float Tangent[3],
|
|
const float BitangentSign,
|
|
const int FaceIdx,
|
|
const int VertIdx) {
|
|
TArray<FStaticMeshBuildVertex>& vertices =
|
|
*reinterpret_cast<TArray<FStaticMeshBuildVertex>*>(Context->m_pUserData);
|
|
FStaticMeshBuildVertex& vertex = vertices[FaceIdx * 3 + VertIdx];
|
|
|
|
FVector3f TangentZ = vertex.TangentZ;
|
|
TangentZ.Y = -TangentZ.Y;
|
|
|
|
FVector3f TangentX = TMeshVector3(Tangent[0], Tangent[1], Tangent[2]);
|
|
FVector3f TangentY =
|
|
BitangentSign * TMeshVector3::CrossProduct(TangentZ, TangentX);
|
|
|
|
TangentX.Y = -TangentX.Y;
|
|
TangentY.Y = -TangentY.Y;
|
|
|
|
vertex.TangentX = TangentX;
|
|
vertex.TangentY = TangentY;
|
|
}
|
|
|
|
static void computeTangentSpace(TArray<FStaticMeshBuildVertex>& vertices) {
|
|
SMikkTSpaceInterface MikkTInterface{};
|
|
MikkTInterface.m_getNormal = mikkGetNormal;
|
|
MikkTInterface.m_getNumFaces = mikkGetNumFaces;
|
|
MikkTInterface.m_getNumVerticesOfFace = mikkGetNumVertsOfFace;
|
|
MikkTInterface.m_getPosition = mikkGetPosition;
|
|
MikkTInterface.m_getTexCoord = mikkGetTexCoord;
|
|
MikkTInterface.m_setTSpaceBasic = mikkSetTSpaceBasic;
|
|
MikkTInterface.m_setTSpace = nullptr;
|
|
|
|
SMikkTSpaceContext MikkTContext{};
|
|
MikkTContext.m_pInterface = &MikkTInterface;
|
|
MikkTContext.m_pUserData = (void*)(&vertices);
|
|
// MikkTContext.m_bIgnoreDegenerates = false;
|
|
genTangSpaceDefault(&MikkTContext);
|
|
}
|
|
|
|
static void setUnlitNormals(
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid,
|
|
const glm::dmat4& vertexToEllipsoidFixed) {
|
|
glm::dmat4 ellipsoidFixedToVertex =
|
|
glm::affineInverse(vertexToEllipsoidFixed);
|
|
|
|
for (int i = 0; i < vertices.Num(); i++) {
|
|
FStaticMeshBuildVertex& v = vertices[i];
|
|
v.TangentX = v.TangentY = TMeshVector3(0.0f);
|
|
|
|
glm::dvec3 positionFixed = glm::dvec3(
|
|
vertexToEllipsoidFixed *
|
|
glm::dvec4(VecMath::createVector3D(FVector(v.Position)), 1.0));
|
|
glm::dvec3 normal = ellipsoid.geodeticSurfaceNormal(positionFixed);
|
|
v.TangentZ = FVector3f(VecMath::createVector(
|
|
glm::normalize(ellipsoidFixedToVertex * glm::dvec4(normal, 0.0))));
|
|
}
|
|
}
|
|
|
|
static void computeFlatNormals(TArray<FStaticMeshBuildVertex>& vertices) {
|
|
// Compute flat normals
|
|
for (int i = 0; i < vertices.Num(); i += 3) {
|
|
FStaticMeshBuildVertex& v0 = vertices[i];
|
|
FStaticMeshBuildVertex& v1 = vertices[i + 1];
|
|
FStaticMeshBuildVertex& v2 = vertices[i + 2];
|
|
|
|
// The Y axis has previously been inverted, so undo that before
|
|
// computing the normal direction. Then invert the Y coordinate of the
|
|
// normal, too.
|
|
|
|
TMeshVector3 v01 = v1.Position - v0.Position;
|
|
v01.Y = -v01.Y;
|
|
TMeshVector3 v02 = v2.Position - v0.Position;
|
|
v02.Y = -v02.Y;
|
|
TMeshVector3 normal = TMeshVector3::CrossProduct(v01, v02);
|
|
|
|
normal.Y = -normal.Y;
|
|
|
|
v0.TangentX = v1.TangentX = v2.TangentX = TMeshVector3(0.0f);
|
|
v0.TangentY = v1.TangentY = v2.TangentY = TMeshVector3(0.0f);
|
|
v0.TangentZ = v1.TangentZ = v2.TangentZ = normal.GetSafeNormal();
|
|
}
|
|
}
|
|
|
|
template <typename TIndex>
|
|
#if ENGINE_VERSION_5_4_OR_HIGHER
|
|
static Chaos::FTriangleMeshImplicitObjectPtr
|
|
#else
|
|
static TSharedPtr<Chaos::FTriangleMeshImplicitObject, ESPMode::ThreadSafe>
|
|
#endif
|
|
BuildChaosTriangleMeshes(
|
|
const TArray<FStaticMeshBuildVertex>& vertexData,
|
|
const TArray<uint32>& indices);
|
|
|
|
static const CesiumGltf::Material defaultMaterial;
|
|
static const CesiumGltf::MaterialPBRMetallicRoughness
|
|
defaultPbrMetallicRoughness;
|
|
|
|
struct ColorVisitor {
|
|
bool duplicateVertices;
|
|
TArray<FStaticMeshBuildVertex>& StaticMeshBuildVertices;
|
|
const TArray<uint32>& indices;
|
|
|
|
bool operator()(CesiumGltf::AccessorView<nullptr_t>&& invalidView) {
|
|
return false;
|
|
}
|
|
|
|
template <typename TColorView> bool operator()(TColorView&& colorView) {
|
|
if (colorView.status() != CesiumGltf::AccessorViewStatus::Valid) {
|
|
return false;
|
|
}
|
|
|
|
bool success = true;
|
|
if (duplicateVertices) {
|
|
for (int i = 0; success && i < this->indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = this->StaticMeshBuildVertices[i];
|
|
uint32 vertexIndex = this->indices[i];
|
|
if (vertexIndex >= colorView.size()) {
|
|
success = false;
|
|
} else {
|
|
success =
|
|
ColorVisitor::convertColor(colorView[vertexIndex], vertex.Color);
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = 0; success && i < this->StaticMeshBuildVertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = this->StaticMeshBuildVertices[i];
|
|
if (i >= colorView.size()) {
|
|
success = false;
|
|
} else {
|
|
success = ColorVisitor::convertColor(colorView[i], vertex.Color);
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
template <typename TElement>
|
|
static bool convertColor(
|
|
const CesiumGltf::AccessorTypes::VEC3<TElement>& color,
|
|
FColor& out) {
|
|
out.A = 255;
|
|
return convertElement(color.value[0], out.R) &&
|
|
convertElement(color.value[1], out.G) &&
|
|
convertElement(color.value[2], out.B);
|
|
}
|
|
|
|
template <typename TElement>
|
|
static bool convertColor(
|
|
const CesiumGltf::AccessorTypes::VEC4<TElement>& color,
|
|
FColor& out) {
|
|
return convertElement(color.value[0], out.R) &&
|
|
convertElement(color.value[1], out.G) &&
|
|
convertElement(color.value[2], out.B) &&
|
|
convertElement(color.value[3], out.A);
|
|
}
|
|
|
|
static bool convertElement(float value, uint8_t& out) {
|
|
out = uint8_t(value * 255.0f);
|
|
return true;
|
|
}
|
|
|
|
static bool convertElement(uint8_t value, uint8_t& out) {
|
|
out = value;
|
|
return true;
|
|
}
|
|
|
|
static bool convertElement(uint16_t value, uint8_t& out) {
|
|
out = uint8_t(value / 256);
|
|
return true;
|
|
}
|
|
|
|
template <typename T> static bool convertColor(const T& color, FColor& out) {
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
static bool convertElement(const T& color, uint8_t& out) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
static TUniquePtr<CesiumTextureUtility::LoadedTextureResult> loadTexture(
|
|
CesiumGltf::Model& model,
|
|
const std::optional<T>& gltfTextureInfo,
|
|
bool sRGB) {
|
|
if (!gltfTextureInfo || gltfTextureInfo.value().index < 0 ||
|
|
gltfTextureInfo.value().index >= model.textures.size()) {
|
|
if (gltfTextureInfo && gltfTextureInfo.value().index >= 0) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("Texture index must be less than %d, but is %d"),
|
|
model.textures.size(),
|
|
gltfTextureInfo.value().index);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t textureIndex = gltfTextureInfo.value().index;
|
|
CesiumGltf::Texture& texture = model.textures[textureIndex];
|
|
return loadTextureFromModelAnyThreadPart(model, texture, sRGB);
|
|
}
|
|
|
|
static void applyWaterMask(
|
|
CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
LoadedPrimitiveResult& primitiveResult) {
|
|
// Initialize water mask if needed.
|
|
auto onlyWaterIt = primitive.extras.find("OnlyWater");
|
|
auto onlyLandIt = primitive.extras.find("OnlyLand");
|
|
if (onlyWaterIt != primitive.extras.end() && onlyWaterIt->second.isBool() &&
|
|
onlyLandIt != primitive.extras.end() && onlyLandIt->second.isBool()) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ApplyWaterMask)
|
|
bool onlyWater = onlyWaterIt->second.getBoolOrDefault(false);
|
|
bool onlyLand = onlyLandIt->second.getBoolOrDefault(true);
|
|
primitiveResult.onlyWater = onlyWater;
|
|
primitiveResult.onlyLand = onlyLand;
|
|
if (!onlyWater && !onlyLand) {
|
|
// We have to use the water mask
|
|
auto waterMaskTextureIdIt = primitive.extras.find("WaterMaskTex");
|
|
if (waterMaskTextureIdIt != primitive.extras.end() &&
|
|
waterMaskTextureIdIt->second.isInt64()) {
|
|
int32_t waterMaskTextureId = static_cast<int32_t>(
|
|
waterMaskTextureIdIt->second.getInt64OrDefault(-1));
|
|
CesiumGltf::TextureInfo waterMaskInfo;
|
|
waterMaskInfo.index = waterMaskTextureId;
|
|
if (waterMaskTextureId >= 0 &&
|
|
waterMaskTextureId < model.textures.size()) {
|
|
primitiveResult.waterMaskTexture =
|
|
loadTexture(model, std::make_optional(waterMaskInfo), false);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
primitiveResult.onlyWater = false;
|
|
primitiveResult.onlyLand = true;
|
|
}
|
|
|
|
auto waterMaskTranslationXIt = primitive.extras.find("WaterMaskTranslationX");
|
|
auto waterMaskTranslationYIt = primitive.extras.find("WaterMaskTranslationY");
|
|
auto waterMaskScaleIt = primitive.extras.find("WaterMaskScale");
|
|
|
|
if (waterMaskTranslationXIt != primitive.extras.end() &&
|
|
waterMaskTranslationXIt->second.isDouble() &&
|
|
waterMaskTranslationYIt != primitive.extras.end() &&
|
|
waterMaskTranslationYIt->second.isDouble() &&
|
|
waterMaskScaleIt != primitive.extras.end() &&
|
|
waterMaskScaleIt->second.isDouble()) {
|
|
primitiveResult.waterMaskTranslationX =
|
|
waterMaskTranslationXIt->second.getDoubleOrDefault(0.0);
|
|
primitiveResult.waterMaskTranslationY =
|
|
waterMaskTranslationYIt->second.getDoubleOrDefault(0.0);
|
|
primitiveResult.waterMaskScale =
|
|
waterMaskScaleIt->second.getDoubleOrDefault(1.0);
|
|
}
|
|
}
|
|
|
|
#pragma region Features Metadata helper functions(load thread)
|
|
|
|
static bool textureUsesSpecifiedImage(
|
|
const CesiumGltf::Model& model,
|
|
int32_t textureIndex,
|
|
int32_t imageIndex) {
|
|
if (textureIndex < 0 || textureIndex >= model.textures.size()) {
|
|
return false;
|
|
}
|
|
|
|
const CesiumGltf::Texture& texture = model.textures[textureIndex];
|
|
return texture.source == imageIndex;
|
|
}
|
|
|
|
static bool hasMaterialTextureConflicts(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::Material& material,
|
|
int32_t imageIndex) {
|
|
if (material.pbrMetallicRoughness) {
|
|
const std::optional<CesiumGltf::TextureInfo>& maybeBaseColorTexture =
|
|
material.pbrMetallicRoughness->baseColorTexture;
|
|
if (maybeBaseColorTexture && textureUsesSpecifiedImage(
|
|
model,
|
|
maybeBaseColorTexture->index,
|
|
imageIndex)) {
|
|
return true;
|
|
}
|
|
|
|
const std::optional<CesiumGltf::TextureInfo>&
|
|
maybeMetallicRoughnessTexture =
|
|
material.pbrMetallicRoughness->metallicRoughnessTexture;
|
|
if (maybeMetallicRoughnessTexture &&
|
|
textureUsesSpecifiedImage(
|
|
model,
|
|
maybeMetallicRoughnessTexture->index,
|
|
imageIndex)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (material.normalTexture && textureUsesSpecifiedImage(
|
|
model,
|
|
material.normalTexture->index,
|
|
imageIndex)) {
|
|
return true;
|
|
}
|
|
|
|
if (material.emissiveTexture && textureUsesSpecifiedImage(
|
|
model,
|
|
material.emissiveTexture->index,
|
|
imageIndex)) {
|
|
return true;
|
|
}
|
|
|
|
if (material.occlusionTexture && textureUsesSpecifiedImage(
|
|
model,
|
|
material.occlusionTexture->index,
|
|
imageIndex)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Creates texture coordinate accessors for the feature ID sets and metadata in
|
|
* the primitive. This enables feature ID texture / property texture picking
|
|
* without requiring UVs in the physics bodies.
|
|
*/
|
|
static void createTexCoordAccessorsForFeaturesMetadata(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
const FCesiumPrimitiveFeatures& primitiveFeatures,
|
|
const FCesiumPrimitiveMetadata& primitiveMetadata,
|
|
const FCesiumModelMetadata& modelMetadata,
|
|
std::unordered_map<int32_t, CesiumGltf::TexCoordAccessorType>&
|
|
texCoordAccessorsMap) {
|
|
auto featureIdTextures =
|
|
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSetsOfType(
|
|
primitiveFeatures,
|
|
ECesiumFeatureIdSetType::Texture);
|
|
|
|
for (const FCesiumFeatureIdSet& featureIdSet : featureIdTextures) {
|
|
FCesiumFeatureIdTexture featureIdTexture =
|
|
UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDTexture(
|
|
featureIdSet);
|
|
|
|
int64 gltfTexCoordSetIndex = UCesiumFeatureIdTextureBlueprintLibrary::
|
|
GetGltfTextureCoordinateSetIndex(featureIdTexture);
|
|
if (gltfTexCoordSetIndex < 0 ||
|
|
texCoordAccessorsMap.find(gltfTexCoordSetIndex) !=
|
|
texCoordAccessorsMap.end()) {
|
|
// Skip if the index is invalid or if it has already been accounted for.
|
|
continue;
|
|
}
|
|
texCoordAccessorsMap.emplace(
|
|
gltfTexCoordSetIndex,
|
|
CesiumGltf::getTexCoordAccessorView(
|
|
model,
|
|
primitive,
|
|
gltfTexCoordSetIndex));
|
|
}
|
|
|
|
auto propertyTextureIndices =
|
|
UCesiumPrimitiveMetadataBlueprintLibrary::GetPropertyTextureIndices(
|
|
primitiveMetadata);
|
|
auto propertyTextures =
|
|
UCesiumModelMetadataBlueprintLibrary::GetPropertyTexturesAtIndices(
|
|
modelMetadata,
|
|
propertyTextureIndices);
|
|
|
|
for (const FCesiumPropertyTexture& propertyTexture : propertyTextures) {
|
|
auto properties =
|
|
UCesiumPropertyTextureBlueprintLibrary::GetProperties(propertyTexture);
|
|
|
|
for (const auto& propertyIt : properties) {
|
|
int64 gltfTexCoordSetIndex =
|
|
UCesiumPropertyTexturePropertyBlueprintLibrary::
|
|
GetGltfTextureCoordinateSetIndex(propertyIt.Value);
|
|
if (gltfTexCoordSetIndex < 0 ||
|
|
texCoordAccessorsMap.find(gltfTexCoordSetIndex) !=
|
|
texCoordAccessorsMap.end()) {
|
|
// Skip if the index is invalid or if it has already been accounted for.
|
|
continue;
|
|
}
|
|
texCoordAccessorsMap.emplace(
|
|
gltfTexCoordSetIndex,
|
|
CesiumGltf::getTexCoordAccessorView(
|
|
model,
|
|
primitive,
|
|
gltfTexCoordSetIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the primitive's information for the texture coordinates required for
|
|
* features and metadata styling. This processes existing texture coordinate
|
|
* sets for feature ID textures and property textures, and generates new texture
|
|
* coordinates for attribute and implicit feature ID sets.
|
|
*/
|
|
static void updateTextureCoordinatesForFeaturesMetadata(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
bool duplicateVertices,
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const TArray<uint32>& indices,
|
|
const FCesiumPrimitiveFeatures& primitiveFeatures,
|
|
const CesiumEncodedFeaturesMetadata::EncodedPrimitiveFeatures&
|
|
encodedPrimitiveFeatures,
|
|
const CesiumEncodedFeaturesMetadata::EncodedPrimitiveMetadata&
|
|
encodedPrimitiveMetadata,
|
|
const CesiumEncodedFeaturesMetadata::EncodedModelMetadata&
|
|
encodedModelMetadata,
|
|
TMap<FString, uint32_t>& featuresMetadataTexcoordParameters,
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap) {
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(
|
|
Cesium::UpdateTextureCoordinatesForFeaturesMetadata)
|
|
|
|
for (const int64 propertyTextureIndex :
|
|
encodedPrimitiveMetadata.propertyTextureIndices) {
|
|
// Property textures can be made accessible in Unreal materials without
|
|
// requiring a texture coordinate set on the primitive. If it is not present
|
|
// in primitive metadata, then do not set the parameter.
|
|
const CesiumEncodedFeaturesMetadata::EncodedPropertyTexture&
|
|
encodedPropertyTexture =
|
|
encodedModelMetadata.propertyTextures[propertyTextureIndex];
|
|
|
|
for (const CesiumEncodedFeaturesMetadata::EncodedPropertyTextureProperty&
|
|
encodedProperty : encodedPropertyTexture.properties) {
|
|
|
|
FString fullPropertyName = CesiumEncodedFeaturesMetadata::
|
|
getMaterialNameForPropertyTextureProperty(
|
|
encodedPropertyTexture.name,
|
|
encodedProperty.name);
|
|
|
|
featuresMetadataTexcoordParameters.Emplace(
|
|
fullPropertyName +
|
|
CesiumEncodedFeaturesMetadata::MaterialTexCoordIndexSuffix,
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
"TEXCOORD_" +
|
|
std::to_string(encodedProperty.textureCoordinateSetIndex),
|
|
gltfToUnrealTexCoordMap));
|
|
}
|
|
}
|
|
|
|
// These are necessary for retrieving feature ID attributes, since we'll be
|
|
// taking feature IDs from the attribute itself and putting them into
|
|
// texcoords. We could technically just make an AccessorView on the attribute,
|
|
// but there are multiple feature ID component types, and
|
|
// FCesiumFeatureIdAttribute already creates the accessor view for us.
|
|
const TArray<FCesiumFeatureIdSet>& featureIDSets =
|
|
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
|
primitiveFeatures);
|
|
|
|
for (const CesiumEncodedFeaturesMetadata::EncodedFeatureIdSet&
|
|
encodedFeatureIDSet : encodedPrimitiveFeatures.featureIdSets) {
|
|
FString SafeName = CesiumEncodedFeaturesMetadata::createHlslSafeName(
|
|
encodedFeatureIDSet.name);
|
|
if (encodedFeatureIDSet.attribute) {
|
|
int32_t attribute = *encodedFeatureIDSet.attribute;
|
|
std::string attributeName = "_FEATURE_ID_" + std::to_string(attribute);
|
|
if (primitive.attributes.find(attributeName) ==
|
|
primitive.attributes.end()) {
|
|
continue;
|
|
}
|
|
|
|
// This was already validated when creating the EncodedFeatureIdSet.
|
|
int32_t accessor = primitive.attributes.at(attributeName);
|
|
|
|
uint32_t textureCoordinateIndex = gltfToUnrealTexCoordMap.size();
|
|
gltfToUnrealTexCoordMap[accessor] = textureCoordinateIndex;
|
|
featuresMetadataTexcoordParameters.Emplace(
|
|
SafeName,
|
|
textureCoordinateIndex);
|
|
|
|
const FCesiumFeatureIdSet& featureIDSet =
|
|
featureIDSets[encodedFeatureIDSet.index];
|
|
const FCesiumFeatureIdAttribute& featureIDAttribute =
|
|
UCesiumFeatureIdSetBlueprintLibrary::GetAsFeatureIDAttribute(
|
|
featureIDSet);
|
|
|
|
// Each feature ID corresponds to a vertex, so the vertex count is just
|
|
// the length of the attribute.
|
|
int64 vertexCount = UCesiumFeatureIdAttributeBlueprintLibrary::GetCount(
|
|
featureIDAttribute);
|
|
|
|
// We encode unsigned integer feature ids as floats in the u-channel of
|
|
// a texture coordinate slot.
|
|
if (duplicateVertices) {
|
|
for (int64_t i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
if (vertexIndex >= 0 && vertexIndex < vertexCount) {
|
|
float featureId = static_cast<float>(
|
|
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
|
featureIDAttribute,
|
|
vertexIndex));
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(featureId, 0.0f);
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
} else {
|
|
for (int64_t i = 0; i < vertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
if (i < vertexCount) {
|
|
float featureId = static_cast<float>(
|
|
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
|
featureIDAttribute,
|
|
i));
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(featureId, 0.0f);
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
} else if (encodedFeatureIDSet.texture) {
|
|
const CesiumEncodedFeaturesMetadata::EncodedFeatureIdTexture&
|
|
encodedFeatureIDTexture = *encodedFeatureIDSet.texture;
|
|
featuresMetadataTexcoordParameters.Emplace(
|
|
SafeName + CesiumEncodedFeaturesMetadata::MaterialTexCoordIndexSuffix,
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
"TEXCOORD_" +
|
|
std::to_string(
|
|
encodedFeatureIDTexture.textureCoordinateSetIndex),
|
|
gltfToUnrealTexCoordMap));
|
|
} else {
|
|
// Similar to feature ID attributes, we encode the unsigned integer vertex
|
|
// ids as floats in the u-channel of a texture coordinate slot. If it ever
|
|
// becomes possible to access the vertex ID through an Unreal material
|
|
// node, this can be removed.
|
|
uint32_t textureCoordinateIndex = gltfToUnrealTexCoordMap.size();
|
|
gltfToUnrealTexCoordMap[-1] = textureCoordinateIndex;
|
|
featuresMetadataTexcoordParameters.Emplace(
|
|
SafeName,
|
|
textureCoordinateIndex);
|
|
if (duplicateVertices) {
|
|
for (int64_t i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
vertex.UVs[textureCoordinateIndex] =
|
|
TMeshVector2(static_cast<float>(vertexIndex), 0.0f);
|
|
}
|
|
} else {
|
|
for (int64_t i = 0; i < vertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
vertex.UVs[textureCoordinateIndex] =
|
|
TMeshVector2(static_cast<float>(i), 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
static void updateTextureCoordinatesForMetadata_DEPRECATED(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::MeshPrimitive& primitive,
|
|
bool duplicateVertices,
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const TArray<uint32>& indices,
|
|
const CesiumEncodedMetadataUtility::EncodedMetadata& encodedMetadata,
|
|
const CesiumEncodedMetadataUtility::EncodedMetadataPrimitive&
|
|
encodedPrimitiveMetadata,
|
|
const TArray<FCesiumFeatureIdAttribute>& featureIdAttributes,
|
|
TMap<FString, uint32_t>& metadataTextureCoordinateParameters,
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap) {
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinatesForMetadata)
|
|
|
|
for (const CesiumEncodedMetadataUtility::EncodedFeatureIdTexture&
|
|
encodedFeatureIdTexture :
|
|
encodedPrimitiveMetadata.encodedFeatureIdTextures) {
|
|
metadataTextureCoordinateParameters.Emplace(
|
|
encodedFeatureIdTexture.baseName + "UV",
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
"TEXCOORD_" +
|
|
std::to_string(
|
|
encodedFeatureIdTexture.textureCoordinateAttributeId),
|
|
gltfToUnrealTexCoordMap));
|
|
}
|
|
|
|
for (const FString& featureTextureName :
|
|
encodedPrimitiveMetadata.featureTextureNames) {
|
|
const CesiumEncodedMetadataUtility::EncodedFeatureTexture*
|
|
pEncodedFeatureTexture =
|
|
encodedMetadata.encodedFeatureTextures.Find(featureTextureName);
|
|
if (pEncodedFeatureTexture) {
|
|
for (const CesiumEncodedMetadataUtility::EncodedFeatureTextureProperty&
|
|
encodedProperty : pEncodedFeatureTexture->properties) {
|
|
metadataTextureCoordinateParameters.Emplace(
|
|
encodedProperty.baseName + "UV",
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
"TEXCOORD_" + std::to_string(
|
|
encodedProperty.textureCoordinateAttributeId),
|
|
gltfToUnrealTexCoordMap));
|
|
}
|
|
}
|
|
}
|
|
|
|
const CesiumGltf::ExtensionExtMeshFeatures* pFeatures =
|
|
primitive.getExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
|
|
|
if (pFeatures) {
|
|
for (const CesiumEncodedMetadataUtility::EncodedFeatureIdAttribute&
|
|
encodedFeatureIdAttribute :
|
|
encodedPrimitiveMetadata.encodedFeatureIdAttributes) {
|
|
const FCesiumFeatureIdAttribute& featureIdAttribute =
|
|
featureIdAttributes[encodedFeatureIdAttribute.index];
|
|
|
|
int32_t attribute = featureIdAttribute.getAttributeIndex();
|
|
std::string attributeName = "_FEATURE_ID_" + std::to_string(attribute);
|
|
if (primitive.attributes.find(attributeName) ==
|
|
primitive.attributes.end()) {
|
|
continue;
|
|
}
|
|
|
|
// This was already validated when creating the EncodedFeatureIdSet.
|
|
int32_t accessor = primitive.attributes.at(attributeName);
|
|
|
|
uint32_t textureCoordinateIndex = gltfToUnrealTexCoordMap.size();
|
|
gltfToUnrealTexCoordMap[accessor] = textureCoordinateIndex;
|
|
metadataTextureCoordinateParameters.Emplace(
|
|
encodedFeatureIdAttribute.name,
|
|
textureCoordinateIndex);
|
|
|
|
// Each feature ID corresponds to a vertex, so the vertex count is just
|
|
// the length of the attribute.
|
|
int64 vertexCount = UCesiumFeatureIdAttributeBlueprintLibrary::GetCount(
|
|
featureIdAttribute);
|
|
|
|
// We encode unsigned integer feature ids as floats in the u-channel of
|
|
// a texture coordinate slot.
|
|
if (duplicateVertices) {
|
|
for (int64_t i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
if (vertexIndex >= 0 && vertexIndex < vertexCount) {
|
|
float featureId = static_cast<float>(
|
|
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
|
featureIdAttribute,
|
|
vertexIndex));
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(featureId, 0.0f);
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
} else {
|
|
for (int64_t i = 0; i < vertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = vertices[i];
|
|
if (i < vertexCount) {
|
|
float featureId = static_cast<float>(
|
|
UCesiumFeatureIdAttributeBlueprintLibrary::GetFeatureID(
|
|
featureIdAttribute,
|
|
i));
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(featureId, 0.0f);
|
|
} else {
|
|
vertex.UVs[textureCoordinateIndex] = TMeshVector2(0.0f, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
static void loadPrimitiveFeaturesMetadata(
|
|
LoadedPrimitiveResult& primitiveResult,
|
|
const CreatePrimitiveOptions& options,
|
|
CesiumGltf::Model& model,
|
|
CesiumGltf::MeshPrimitive& primitive,
|
|
bool duplicateVertices,
|
|
TArray<FStaticMeshBuildVertex>& vertices,
|
|
const TArray<uint32>& indices) {
|
|
|
|
CesiumGltf::ExtensionExtMeshFeatures* pFeatures =
|
|
primitive.getExtension<CesiumGltf::ExtensionExtMeshFeatures>();
|
|
|
|
if (pFeatures) {
|
|
int32_t materialIndex = primitive.material;
|
|
if (materialIndex >= 0 && materialIndex < model.materials.size()) {
|
|
const CesiumGltf::Material& material =
|
|
model.materials[primitive.material];
|
|
|
|
for (CesiumGltf::FeatureId& featureId : pFeatures->featureIds) {
|
|
if (!featureId.texture) {
|
|
continue;
|
|
}
|
|
|
|
if (featureId.texture->extras.find("makeImageCopy") !=
|
|
featureId.texture->extras.end()) {
|
|
continue;
|
|
}
|
|
|
|
int32_t textureIndex = featureId.texture->index;
|
|
if (textureIndex < 0 || textureIndex >= model.textures.size()) {
|
|
continue;
|
|
}
|
|
|
|
const CesiumGltf::Texture& texture = model.textures[textureIndex];
|
|
if (texture.source < 0 || texture.source >= model.images.size()) {
|
|
continue;
|
|
}
|
|
|
|
int32_t imageIndex = model.textures[textureIndex].source;
|
|
if (hasMaterialTextureConflicts(model, material, imageIndex)) {
|
|
// Add a flag in the extras to indicate a copy should be made.
|
|
// This is checked for in FCesiumFeatureIdTexture.
|
|
featureId.texture->extras.insert({"makeImageCopy", true});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata* pMetadata =
|
|
primitive.getExtension<
|
|
CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata>();
|
|
|
|
const CreateGltfOptions::CreateModelOptions* pModelOptions =
|
|
options.pMeshOptions->pNodeOptions->pModelOptions;
|
|
const LoadGltfResult::LoadedModelResult* pModelResult =
|
|
options.pMeshOptions->pNodeOptions->pHalfConstructedModelResult;
|
|
|
|
primitiveResult.Features =
|
|
pFeatures ? FCesiumPrimitiveFeatures(model, primitive, *pFeatures)
|
|
: FCesiumPrimitiveFeatures();
|
|
primitiveResult.Metadata =
|
|
pMetadata ? FCesiumPrimitiveMetadata(primitive, *pMetadata)
|
|
: FCesiumPrimitiveMetadata();
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
primitiveResult.Metadata_DEPRECATED = FCesiumMetadataPrimitive{
|
|
primitiveResult.Features,
|
|
primitiveResult.Metadata,
|
|
pModelResult->Metadata};
|
|
|
|
createTexCoordAccessorsForFeaturesMetadata(
|
|
model,
|
|
primitive,
|
|
primitiveResult.Features,
|
|
primitiveResult.Metadata,
|
|
pModelResult->Metadata,
|
|
primitiveResult.TexCoordAccessorMap);
|
|
|
|
const FCesiumFeaturesMetadataDescription* pFeaturesMetadataDescription =
|
|
pModelOptions->pFeaturesMetadataDescription;
|
|
|
|
// Check for deprecated metadata description
|
|
const FMetadataDescription* pMetadataDescription_DEPRECATED =
|
|
pModelOptions->pEncodedMetadataDescription_DEPRECATED;
|
|
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap =
|
|
primitiveResult.GltfToUnrealTexCoordMap;
|
|
|
|
if (pFeaturesMetadataDescription) {
|
|
primitiveResult.EncodedFeatures =
|
|
CesiumEncodedFeaturesMetadata::encodePrimitiveFeaturesAnyThreadPart(
|
|
pFeaturesMetadataDescription->Features,
|
|
primitiveResult.Features);
|
|
|
|
primitiveResult.EncodedMetadata =
|
|
CesiumEncodedFeaturesMetadata::encodePrimitiveMetadataAnyThreadPart(
|
|
pFeaturesMetadataDescription->PrimitiveMetadata,
|
|
primitiveResult.Metadata,
|
|
pModelResult->Metadata);
|
|
|
|
updateTextureCoordinatesForFeaturesMetadata(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
primitiveResult.Features,
|
|
primitiveResult.EncodedFeatures,
|
|
primitiveResult.EncodedMetadata,
|
|
pModelResult->EncodedMetadata,
|
|
primitiveResult.FeaturesMetadataTexCoordParameters,
|
|
gltfToUnrealTexCoordMap);
|
|
} else if (pMetadataDescription_DEPRECATED) {
|
|
primitiveResult.EncodedMetadata_DEPRECATED =
|
|
CesiumEncodedMetadataUtility::encodeMetadataPrimitiveAnyThreadPart(
|
|
*pMetadataDescription_DEPRECATED,
|
|
primitiveResult.Metadata_DEPRECATED);
|
|
|
|
updateTextureCoordinatesForMetadata_DEPRECATED(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
vertices,
|
|
indices,
|
|
*pModelResult->EncodedMetadata_DEPRECATED,
|
|
*primitiveResult.EncodedMetadata_DEPRECATED,
|
|
UCesiumMetadataPrimitiveBlueprintLibrary::GetFeatureIdAttributes(
|
|
primitiveResult.Metadata_DEPRECATED),
|
|
primitiveResult.FeaturesMetadataTexCoordParameters,
|
|
gltfToUnrealTexCoordMap);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
#pragma endregion
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* @brief Constrain the length of the given string.
|
|
*
|
|
* If the string is shorter than the maximum length, it is returned.
|
|
* If it is not longer than 3 characters, the first maxLength
|
|
* characters will be returned.
|
|
* Otherwise, the result will be of the form `prefix + "..." + suffix`,
|
|
* with the prefix and suffix chosen so that the length of the result
|
|
* is maxLength
|
|
*
|
|
* @param s The input string
|
|
* @param maxLength The maximum length.
|
|
* @return The constrained string
|
|
*/
|
|
std::string constrainLength(const std::string& s, const size_t maxLength) {
|
|
if (s.length() <= maxLength) {
|
|
return s;
|
|
}
|
|
if (maxLength <= 3) {
|
|
return s.substr(0, maxLength);
|
|
}
|
|
const std::string ellipsis("...");
|
|
const size_t prefixLength = ((maxLength - ellipsis.length()) + 1) / 2;
|
|
const size_t suffixLength = (maxLength - ellipsis.length()) / 2;
|
|
const std::string prefix = s.substr(0, prefixLength);
|
|
const std::string suffix = s.substr(s.length() - suffixLength, suffixLength);
|
|
return prefix + ellipsis + suffix;
|
|
}
|
|
|
|
/**
|
|
* @brief Create an FName from the given strings.
|
|
*
|
|
* This will combine the prefix and the suffix and create an FName.
|
|
* If the string would be longer than the given length, then
|
|
* the prefix will be shortened (in an unspecified way), to
|
|
* constrain the result to a length of maxLength.
|
|
*
|
|
* The default maximum length is 256, because Unreal may in turn
|
|
* add a prefix like the `/Internal/Path/Name` to this name.
|
|
*
|
|
* @param prefix The prefix input string
|
|
* @param suffix The suffix input string
|
|
* @param maxLength The maximum length
|
|
* @return The FName
|
|
*/
|
|
FName createSafeName(
|
|
const std::string& prefix,
|
|
const std::string& suffix,
|
|
const size_t maxLength = 256) {
|
|
std::string constrainedPrefix =
|
|
constrainLength(prefix, maxLength - suffix.length());
|
|
std::string combined = constrainedPrefix + suffix;
|
|
return FName(combined.c_str());
|
|
}
|
|
|
|
// This matrix converts from right-handed Z-up to Unreal
|
|
// left-handed Z-up by flipping the Y axis. It effectively undoes the Y-axis
|
|
// flipping that we did when creating the mesh in the first place. This is
|
|
// necessary to work around a problem in UE 5.1 where negatively-scaled meshes
|
|
// don't work correctly for collision.
|
|
// See https://github.com/CesiumGS/cesium-unreal/pull/1126
|
|
// Note that this matrix is its own inverse.
|
|
|
|
constexpr glm::dmat4 yInvertMatrix = {
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
-1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0};
|
|
} // namespace
|
|
|
|
template <class TIndexAccessor>
|
|
static void loadPrimitive(
|
|
LoadedPrimitiveResult& primitiveResult,
|
|
const glm::dmat4x4& transform,
|
|
const CreatePrimitiveOptions& options,
|
|
const CesiumGltf::Accessor& positionAccessor,
|
|
const CesiumGltf::AccessorView<TMeshVector3>& positionView,
|
|
const TIndexAccessor& indicesView,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive<T>)
|
|
|
|
CesiumGltf::Model& model =
|
|
*options.pMeshOptions->pNodeOptions->pModelOptions->pModel;
|
|
CesiumGltf::Mesh& mesh = model.meshes[options.pMeshOptions->meshIndex];
|
|
CesiumGltf::MeshPrimitive& primitive =
|
|
mesh.primitives[options.primitiveIndex];
|
|
|
|
if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::TRIANGLES &&
|
|
primitive.mode != CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP &&
|
|
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS) {
|
|
// TODO: add support for other primitive types.
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("Primitive mode %d is not supported"),
|
|
primitive.mode);
|
|
return;
|
|
}
|
|
|
|
std::string name = "glTF";
|
|
|
|
auto urlIt = model.extras.find("Cesium3DTiles_TileUrl");
|
|
if (urlIt != model.extras.end()) {
|
|
name = urlIt->second.getStringOrDefault("glTF");
|
|
name = constrainLength(name, 256);
|
|
}
|
|
|
|
auto meshIt = std::find_if(
|
|
model.meshes.begin(),
|
|
model.meshes.end(),
|
|
[&mesh](const CesiumGltf::Mesh& candidate) {
|
|
return &candidate == &mesh;
|
|
});
|
|
if (meshIt != model.meshes.end()) {
|
|
int64_t meshIndex = meshIt - model.meshes.begin();
|
|
name += " mesh " + std::to_string(meshIndex);
|
|
}
|
|
|
|
auto primitiveIt = std::find_if(
|
|
mesh.primitives.begin(),
|
|
mesh.primitives.end(),
|
|
[&primitive](const CesiumGltf::MeshPrimitive& candidate) {
|
|
return &candidate == &primitive;
|
|
});
|
|
if (primitiveIt != mesh.primitives.end()) {
|
|
int64_t primitiveIndex = primitiveIt - mesh.primitives.begin();
|
|
name += " primitive " + std::to_string(primitiveIndex);
|
|
}
|
|
|
|
primitiveResult.name = name;
|
|
|
|
if (positionView.status() != CesiumGltf::AccessorViewStatus::Valid) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("%s: Invalid position buffer"),
|
|
UTF8_TO_TCHAR(name.c_str()));
|
|
return;
|
|
}
|
|
|
|
if constexpr (IsAccessorView<TIndexAccessor>::value) {
|
|
if (indicesView.status() != CesiumGltf::AccessorViewStatus::Valid) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("%s: Invalid indices buffer"),
|
|
UTF8_TO_TCHAR(name.c_str()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto normalAccessorIt = primitive.attributes.find("NORMAL");
|
|
CesiumGltf::AccessorView<TMeshVector3> normalAccessor;
|
|
bool hasNormals = false;
|
|
if (normalAccessorIt != primitive.attributes.end()) {
|
|
int normalAccessorID = normalAccessorIt->second;
|
|
normalAccessor =
|
|
CesiumGltf::AccessorView<TMeshVector3>(model, normalAccessorID);
|
|
hasNormals =
|
|
normalAccessor.status() == CesiumGltf::AccessorViewStatus::Valid;
|
|
if (!hasNormals) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT(
|
|
"%s: Invalid normal buffer. Flat normals will be auto-generated instead."),
|
|
UTF8_TO_TCHAR(name.c_str()));
|
|
}
|
|
}
|
|
|
|
int materialID = primitive.material;
|
|
const CesiumGltf::Material& material =
|
|
materialID >= 0 && materialID < model.materials.size()
|
|
? model.materials[materialID]
|
|
: defaultMaterial;
|
|
|
|
primitiveResult.materialIndex = materialID;
|
|
|
|
primitiveResult.isUnlit =
|
|
material.hasExtension<CesiumGltf::ExtensionKhrMaterialsUnlit>() &&
|
|
!options.pMeshOptions->pNodeOptions->pModelOptions
|
|
->ignoreKhrMaterialsUnlit;
|
|
|
|
// We can't calculate flat normals for points or lines, so we have to force
|
|
// them to be unlit if no normals are specified. Otherwise this causes a
|
|
// crash when attempting to calculate flat normals.
|
|
bool isTriangles =
|
|
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES ||
|
|
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN ||
|
|
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP;
|
|
|
|
if (!isTriangles && !hasNormals) {
|
|
primitiveResult.isUnlit = true;
|
|
}
|
|
|
|
const CesiumGltf::MaterialPBRMetallicRoughness& pbrMetallicRoughness =
|
|
material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value()
|
|
: defaultPbrMetallicRoughness;
|
|
|
|
bool hasNormalMap = material.normalTexture.has_value();
|
|
if (hasNormalMap) {
|
|
const CesiumGltf::Texture* pTexture = CesiumGltf::Model::getSafe(
|
|
&model.textures,
|
|
material.normalTexture->index);
|
|
hasNormalMap =
|
|
pTexture != nullptr &&
|
|
CesiumGltf::Model::getSafe(&model.images, pTexture->source) != nullptr;
|
|
}
|
|
|
|
bool needsTangents =
|
|
hasNormalMap ||
|
|
options.pMeshOptions->pNodeOptions->pModelOptions->alwaysIncludeTangents;
|
|
|
|
bool hasTangents = false;
|
|
auto tangentAccessorIt = primitive.attributes.find("TANGENT");
|
|
CesiumGltf::AccessorView<TMeshVector4> tangentAccessor;
|
|
if (tangentAccessorIt != primitive.attributes.end()) {
|
|
int tangentAccessorID = tangentAccessorIt->second;
|
|
tangentAccessor =
|
|
CesiumGltf::AccessorView<TMeshVector4>(model, tangentAccessorID);
|
|
hasTangents =
|
|
tangentAccessor.status() == CesiumGltf::AccessorViewStatus::Valid;
|
|
if (!hasTangents) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("%s: Invalid tangent buffer."),
|
|
UTF8_TO_TCHAR(name.c_str()));
|
|
}
|
|
}
|
|
|
|
applyWaterMask(model, primitive, primitiveResult);
|
|
|
|
// The water effect works by animating the normal, and the normal is
|
|
// expressed in tangent space. So if we have water, we need tangents.
|
|
if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture) {
|
|
needsTangents = true;
|
|
}
|
|
|
|
TUniquePtr<FStaticMeshRenderData> RenderData =
|
|
MakeUnique<FStaticMeshRenderData>();
|
|
RenderData->AllocateLODResources(1);
|
|
|
|
FStaticMeshLODResources& LODResources = RenderData->LODResources[0];
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeAABB)
|
|
|
|
const std::vector<double>& min = positionAccessor.min;
|
|
const std::vector<double>& max = positionAccessor.max;
|
|
glm::dvec3 minPosition{std::numeric_limits<double>::max()};
|
|
glm::dvec3 maxPosition{std::numeric_limits<double>::lowest()};
|
|
if (min.size() != 3 || max.size() != 3) {
|
|
for (int64_t i = 0; i < positionView.size(); ++i) {
|
|
minPosition.x = glm::min<double>(minPosition.x, positionView[i].X);
|
|
minPosition.y = glm::min<double>(minPosition.y, positionView[i].Y);
|
|
minPosition.z = glm::min<double>(minPosition.z, positionView[i].Z);
|
|
|
|
maxPosition.x = glm::max<double>(maxPosition.x, positionView[i].X);
|
|
maxPosition.y = glm::max<double>(maxPosition.y, positionView[i].Y);
|
|
maxPosition.z = glm::max<double>(maxPosition.z, positionView[i].Z);
|
|
}
|
|
} else {
|
|
minPosition = glm::dvec3(min[0], min[1], min[2]);
|
|
maxPosition = glm::dvec3(max[0], max[1], max[2]);
|
|
}
|
|
|
|
minPosition *= CesiumPrimitiveData::positionScaleFactor;
|
|
maxPosition *= CesiumPrimitiveData::positionScaleFactor;
|
|
|
|
primitiveResult.dimensions =
|
|
glm::vec3(transform * glm::dvec4(maxPosition - minPosition, 0));
|
|
|
|
FBox aaBox(
|
|
FVector3d(minPosition.x, -minPosition.y, minPosition.z),
|
|
FVector3d(maxPosition.x, -maxPosition.y, maxPosition.z));
|
|
|
|
aaBox.GetCenterAndExtents(
|
|
RenderData->Bounds.Origin,
|
|
RenderData->Bounds.BoxExtent);
|
|
RenderData->Bounds.SphereRadius = 0.0f;
|
|
}
|
|
|
|
TArray<uint32> indices;
|
|
if (primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES ||
|
|
primitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices)
|
|
indices.SetNum(static_cast<TArray<uint32>::SizeType>(indicesView.size()));
|
|
|
|
for (int32 i = 0; i < indicesView.size(); ++i) {
|
|
indices[i] = indicesView[i];
|
|
}
|
|
} else {
|
|
// assume TRIANGLE_STRIP because all others are rejected earlier.
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyIndices)
|
|
indices.SetNum(
|
|
static_cast<TArray<uint32>::SizeType>(3 * (indicesView.size() - 2)));
|
|
for (int32 i = 0; i < indicesView.size() - 2; ++i) {
|
|
if (i % 2) {
|
|
indices[3 * i] = indicesView[i];
|
|
indices[3 * i + 1] = indicesView[i + 2];
|
|
indices[3 * i + 2] = indicesView[i + 1];
|
|
} else {
|
|
indices[3 * i] = indicesView[i];
|
|
indices[3 * i + 1] = indicesView[i + 1];
|
|
indices[3 * i + 2] = indicesView[i + 2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we don't have normals, the gltf spec prescribes that the client
|
|
// implementation must generate flat normals, which requires duplicating
|
|
// vertices shared by multiple triangles. If we don't have tangents, but
|
|
// need them, we need to use a tangent space generation algorithm which
|
|
// requires duplicated vertices.
|
|
bool normalsAreRequired = !primitiveResult.isUnlit;
|
|
bool needToGenerateFlatNormals = normalsAreRequired && !hasNormals;
|
|
bool needToGenerateTangents = needsTangents && !hasTangents;
|
|
bool duplicateVertices = needToGenerateFlatNormals || needToGenerateTangents;
|
|
duplicateVertices = duplicateVertices &&
|
|
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
|
|
|
|
TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
|
|
StaticMeshBuildVertices.SetNum(
|
|
duplicateVertices ? indices.Num()
|
|
: static_cast<int>(positionView.size()));
|
|
|
|
{
|
|
if (duplicateVertices) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyDuplicatedPositions)
|
|
for (int i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
const TMeshVector3& pos = positionView[vertexIndex];
|
|
vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.UVs[0] = TMeshVector2(0.0f, 0.0f);
|
|
vertex.UVs[2] = TMeshVector2(0.0f, 0.0f);
|
|
RenderData->Bounds.SphereRadius = FMath::Max(
|
|
(FVector(vertex.Position) - RenderData->Bounds.Origin).Size(),
|
|
RenderData->Bounds.SphereRadius);
|
|
}
|
|
} else {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyPositions)
|
|
for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
const TMeshVector3& pos = positionView[i];
|
|
vertex.Position.X = pos.X * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.Position.Y = -pos.Y * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.Position.Z = pos.Z * CesiumPrimitiveData::positionScaleFactor;
|
|
vertex.UVs[0] = TMeshVector2(0.0f, 0.0f);
|
|
vertex.UVs[2] = TMeshVector2(0.0f, 0.0f);
|
|
RenderData->Bounds.SphereRadius = FMath::Max(
|
|
(FVector(vertex.Position) - RenderData->Bounds.Origin).Size(),
|
|
RenderData->Bounds.SphereRadius);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool hasVertexColors = false;
|
|
|
|
auto colorAccessorIt = primitive.attributes.find("COLOR_0");
|
|
if (colorAccessorIt != primitive.attributes.end()) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyVertexColors)
|
|
int colorAccessorID = colorAccessorIt->second;
|
|
hasVertexColors = createAccessorView(
|
|
model,
|
|
colorAccessorID,
|
|
ColorVisitor{duplicateVertices, StaticMeshBuildVertices, indices});
|
|
}
|
|
|
|
LODResources.bHasColorVertexData = hasVertexColors;
|
|
|
|
// We need to copy the texture coordinates associated with each texture (if
|
|
// any) into the the appropriate UVs slot in FStaticMeshBuildVertex.
|
|
|
|
std::unordered_map<int32_t, uint32_t>& gltfToUnrealTexCoordMap =
|
|
primitiveResult.GltfToUnrealTexCoordMap;
|
|
|
|
// This must be done before material textures are loaded, in case any of the
|
|
// material textures are also used for features + metadata.
|
|
loadPrimitiveFeaturesMetadata(
|
|
primitiveResult,
|
|
options,
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices);
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadTextures)
|
|
primitiveResult.baseColorTexture =
|
|
loadTexture(model, pbrMetallicRoughness.baseColorTexture, true);
|
|
primitiveResult.metallicRoughnessTexture = loadTexture(
|
|
model,
|
|
pbrMetallicRoughness.metallicRoughnessTexture,
|
|
false);
|
|
primitiveResult.normalTexture =
|
|
loadTexture(model, material.normalTexture, false);
|
|
primitiveResult.occlusionTexture =
|
|
loadTexture(model, material.occlusionTexture, false);
|
|
primitiveResult.emissiveTexture =
|
|
loadTexture(model, material.emissiveTexture, true);
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTextureCoordinates)
|
|
|
|
primitiveResult
|
|
.textureCoordinateParameters["baseColorTextureCoordinateIndex"] =
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
pbrMetallicRoughness.baseColorTexture,
|
|
gltfToUnrealTexCoordMap);
|
|
primitiveResult.textureCoordinateParameters
|
|
["metallicRoughnessTextureCoordinateIndex"] = updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
pbrMetallicRoughness.metallicRoughnessTexture,
|
|
gltfToUnrealTexCoordMap);
|
|
primitiveResult
|
|
.textureCoordinateParameters["normalTextureCoordinateIndex"] =
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
material.normalTexture,
|
|
gltfToUnrealTexCoordMap);
|
|
primitiveResult
|
|
.textureCoordinateParameters["occlusionTextureCoordinateIndex"] =
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
material.occlusionTexture,
|
|
gltfToUnrealTexCoordMap);
|
|
primitiveResult
|
|
.textureCoordinateParameters["emissiveTextureCoordinateIndex"] =
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
material.emissiveTexture,
|
|
gltfToUnrealTexCoordMap);
|
|
|
|
for (size_t i = 0;
|
|
i < primitiveResult.overlayTextureCoordinateIDToUVIndex.size();
|
|
++i) {
|
|
std::string attributeName = "_CESIUMOVERLAY_" + std::to_string(i);
|
|
auto overlayIt = primitive.attributes.find(attributeName);
|
|
if (overlayIt != primitive.attributes.end()) {
|
|
primitiveResult.overlayTextureCoordinateIDToUVIndex[i] =
|
|
updateTextureCoordinates(
|
|
model,
|
|
primitive,
|
|
duplicateVertices,
|
|
StaticMeshBuildVertices,
|
|
indices,
|
|
attributeName,
|
|
gltfToUnrealTexCoordMap);
|
|
} else {
|
|
primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
double scale = 1.0 / CesiumPrimitiveData::positionScaleFactor;
|
|
glm::dmat4 scaleMatrix = glm::dmat4(
|
|
glm::dvec4(scale, 0.0, 0.0, 0.0),
|
|
glm::dvec4(0.0, scale, 0.0, 0.0),
|
|
glm::dvec4(0.0, 0.0, scale, 0.0),
|
|
glm::dvec4(0.0, 0.0, 0.0, 1.0));
|
|
|
|
// TangentX: Tangent
|
|
// TangentY: Bi-tangent
|
|
// TangentZ: Normal
|
|
|
|
if (hasNormals) {
|
|
if (duplicateVertices) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormalsForDuplicatedVertices)
|
|
for (int i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f);
|
|
vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f);
|
|
const TMeshVector3& normal = normalAccessor[vertexIndex];
|
|
vertex.TangentZ.X = normal.X;
|
|
vertex.TangentZ.Y = -normal.Y;
|
|
vertex.TangentZ.Z = normal.Z;
|
|
}
|
|
} else {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyNormals)
|
|
for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
vertex.TangentX = TMeshVector3(0.0f, 0.0f, 0.0f);
|
|
vertex.TangentY = TMeshVector3(0.0f, 0.0f, 0.0f);
|
|
const TMeshVector3& normal = normalAccessor[i];
|
|
vertex.TangentZ.X = normal.X;
|
|
vertex.TangentZ.Y = -normal.Y;
|
|
vertex.TangentZ.Z = normal.Z;
|
|
}
|
|
}
|
|
} else {
|
|
if (primitiveResult.isUnlit) {
|
|
setUnlitNormals(
|
|
StaticMeshBuildVertices,
|
|
ellipsoid,
|
|
transform * yInvertMatrix * scaleMatrix);
|
|
} else {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals)
|
|
computeFlatNormals(StaticMeshBuildVertices);
|
|
}
|
|
}
|
|
|
|
if (hasTangents) {
|
|
if (duplicateVertices) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangentsForDuplicatedVertices)
|
|
for (int i = 0; i < indices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
uint32 vertexIndex = indices[i];
|
|
const TMeshVector4& tangent = tangentAccessor[vertexIndex];
|
|
vertex.TangentX.X = tangent.X;
|
|
vertex.TangentX.Y = -tangent.Y;
|
|
vertex.TangentX.Z = tangent.Z;
|
|
vertex.TangentY =
|
|
TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) *
|
|
tangent.W;
|
|
}
|
|
} else {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CopyTangents)
|
|
for (int i = 0; i < StaticMeshBuildVertices.Num(); ++i) {
|
|
FStaticMeshBuildVertex& vertex = StaticMeshBuildVertices[i];
|
|
const TMeshVector4& tangent = tangentAccessor[i];
|
|
vertex.TangentX = tangent;
|
|
vertex.TangentX.X = tangent.X;
|
|
vertex.TangentX.Y = -tangent.Y;
|
|
vertex.TangentX.Z = tangent.Z;
|
|
vertex.TangentY =
|
|
TMeshVector3::CrossProduct(vertex.TangentZ, vertex.TangentX) *
|
|
tangent.W;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsTangents && !hasTangents) {
|
|
// Use mikktspace to calculate the tangents.
|
|
// Note that this assumes normals and UVs are already populated.
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeTangents)
|
|
computeTangentSpace(StaticMeshBuildVertices);
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitBuffers)
|
|
|
|
// Set to full precision (32-bit) UVs. This is especially important for
|
|
// metadata because integer feature IDs can and will lose meaningful
|
|
// precision when using 16-bit floats.
|
|
LODResources.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(
|
|
true);
|
|
|
|
LODResources.VertexBuffers.PositionVertexBuffer.Init(
|
|
StaticMeshBuildVertices,
|
|
false);
|
|
|
|
FColorVertexBuffer& ColorVertexBuffer =
|
|
LODResources.VertexBuffers.ColorVertexBuffer;
|
|
if (hasVertexColors) {
|
|
ColorVertexBuffer.Init(StaticMeshBuildVertices, false);
|
|
}
|
|
|
|
uint32 numberOfTextureCoordinates =
|
|
gltfToUnrealTexCoordMap.size() == 0
|
|
? 1
|
|
: uint32(gltfToUnrealTexCoordMap.size());
|
|
|
|
FStaticMeshVertexBuffer& vertexBuffer =
|
|
LODResources.VertexBuffers.StaticMeshVertexBuffer;
|
|
vertexBuffer.Init(
|
|
StaticMeshBuildVertices.Num(),
|
|
numberOfTextureCoordinates,
|
|
false);
|
|
|
|
// Manually copy the vertices into the buffer. We do this because UE 5.3
|
|
// and 5.4 have a bug where the overload of `FStaticMeshVertexBuffer::Init`
|
|
// taking an array of `FStaticMeshBuildVertex` will create a mesh with all 8
|
|
// sets of texture coordinates, even when we usually only need one or two.
|
|
// See https://github.com/CesiumGS/cesium-unreal/issues/1513
|
|
for (uint32 vertexIndex = 0;
|
|
vertexIndex < uint32(StaticMeshBuildVertices.Num());
|
|
++vertexIndex) {
|
|
const FStaticMeshBuildVertex& source =
|
|
StaticMeshBuildVertices[vertexIndex];
|
|
|
|
vertexBuffer.SetVertexTangents(
|
|
vertexIndex,
|
|
source.TangentX,
|
|
source.TangentY,
|
|
source.TangentZ);
|
|
for (uint32 uvIndex = 0; uvIndex < numberOfTextureCoordinates;
|
|
uvIndex++) {
|
|
vertexBuffer
|
|
.SetVertexUV(vertexIndex, uvIndex, source.UVs[uvIndex], false);
|
|
}
|
|
}
|
|
}
|
|
|
|
FStaticMeshSectionArray& Sections = LODResources.Sections;
|
|
FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
|
|
// This will be ignored if the primitive contains points.
|
|
section.NumTriangles = indices.Num() / 3;
|
|
section.FirstIndex = 0;
|
|
section.MinVertexIndex = 0;
|
|
section.MaxVertexIndex = StaticMeshBuildVertices.Num() - 1;
|
|
section.bEnableCollision =
|
|
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
|
|
section.bCastShadow = true;
|
|
section.MaterialIndex = 0;
|
|
|
|
if (duplicateVertices) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ReverseWindingOrder)
|
|
for (int32 i = 0; i < indices.Num(); i++) {
|
|
indices[i] = i;
|
|
}
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetIndices)
|
|
LODResources.IndexBuffer.SetIndices(
|
|
indices,
|
|
StaticMeshBuildVertices.Num() >= std::numeric_limits<uint16>::max()
|
|
? EIndexBufferStride::Type::Force32Bit
|
|
: EIndexBufferStride::Type::Force16Bit);
|
|
}
|
|
|
|
LODResources.bHasDepthOnlyIndices = false;
|
|
LODResources.bHasReversedIndices = false;
|
|
LODResources.bHasReversedDepthOnlyIndices = false;
|
|
|
|
#if ENGINE_VERSION_5_5_OR_HIGHER
|
|
// UE 5.5 requires that we do this in order to avoid a crash when ray tracing
|
|
// is enabled.
|
|
RenderData->InitializeRayTracingRepresentationFromRenderingLODs();
|
|
#endif
|
|
|
|
primitiveResult.meshIndex = options.pMeshOptions->meshIndex;
|
|
primitiveResult.primitiveIndex = options.primitiveIndex;
|
|
primitiveResult.RenderData = std::move(RenderData);
|
|
primitiveResult.pCollisionMesh = nullptr;
|
|
|
|
primitiveResult.transform = transform * yInvertMatrix * scaleMatrix;
|
|
|
|
if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS &&
|
|
options.pMeshOptions->pNodeOptions->pModelOptions->createPhysicsMeshes) {
|
|
if (StaticMeshBuildVertices.Num() != 0 && indices.Num() != 0) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook)
|
|
primitiveResult.pCollisionMesh =
|
|
StaticMeshBuildVertices.Num() < TNumericLimits<uint16>::Max()
|
|
? BuildChaosTriangleMeshes<uint16>(
|
|
StaticMeshBuildVertices,
|
|
indices)
|
|
: BuildChaosTriangleMeshes<int32>(
|
|
StaticMeshBuildVertices,
|
|
indices);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void loadIndexedPrimitive(
|
|
LoadedPrimitiveResult& primitiveResult,
|
|
const glm::dmat4x4& transform,
|
|
const CreatePrimitiveOptions& options,
|
|
const CesiumGltf::Accessor& positionAccessor,
|
|
const CesiumGltf::AccessorView<TMeshVector3>& positionView,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
const CesiumGltf::Model& model =
|
|
*options.pMeshOptions->pNodeOptions->pModelOptions->pModel;
|
|
const CesiumGltf::MeshPrimitive& primitive =
|
|
model.meshes[options.pMeshOptions->meshIndex]
|
|
.primitives[options.primitiveIndex];
|
|
|
|
const CesiumGltf::Accessor& indexAccessorGltf =
|
|
model.accessors[primitive.indices];
|
|
if (indexAccessorGltf.componentType ==
|
|
CesiumGltf::Accessor::ComponentType::UNSIGNED_BYTE) {
|
|
CesiumGltf::AccessorView<uint8_t> indexAccessor(model, primitive.indices);
|
|
loadPrimitive(
|
|
primitiveResult,
|
|
transform,
|
|
options,
|
|
positionAccessor,
|
|
positionView,
|
|
indexAccessor,
|
|
ellipsoid);
|
|
primitiveResult.IndexAccessor = indexAccessor;
|
|
} else if (
|
|
indexAccessorGltf.componentType ==
|
|
CesiumGltf::Accessor::ComponentType::UNSIGNED_SHORT) {
|
|
CesiumGltf::AccessorView<uint16_t> indexAccessor(model, primitive.indices);
|
|
loadPrimitive(
|
|
primitiveResult,
|
|
transform,
|
|
options,
|
|
positionAccessor,
|
|
positionView,
|
|
indexAccessor,
|
|
ellipsoid);
|
|
primitiveResult.IndexAccessor = indexAccessor;
|
|
} else if (
|
|
indexAccessorGltf.componentType ==
|
|
CesiumGltf::Accessor::ComponentType::UNSIGNED_INT) {
|
|
CesiumGltf::AccessorView<uint32_t> indexAccessor(model, primitive.indices);
|
|
loadPrimitive(
|
|
primitiveResult,
|
|
transform,
|
|
options,
|
|
positionAccessor,
|
|
positionView,
|
|
indexAccessor,
|
|
ellipsoid);
|
|
primitiveResult.IndexAccessor = indexAccessor;
|
|
} else {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT(
|
|
"Ignoring a glTF primitive because the componentType (%d) of its indices is not supported."),
|
|
indexAccessorGltf.componentType);
|
|
}
|
|
}
|
|
|
|
static void loadPrimitive(
|
|
LoadedPrimitiveResult& result,
|
|
const glm::dmat4x4& transform,
|
|
const CreatePrimitiveOptions& options,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadPrimitive)
|
|
|
|
const CesiumGltf::Model& model =
|
|
*options.pMeshOptions->pNodeOptions->pModelOptions->pModel;
|
|
const CesiumGltf::MeshPrimitive& primitive =
|
|
model.meshes[options.pMeshOptions->meshIndex]
|
|
.primitives[options.primitiveIndex];
|
|
|
|
auto positionAccessorIt = primitive.attributes.find("POSITION");
|
|
if (positionAccessorIt == primitive.attributes.end()) {
|
|
// This primitive doesn't have a POSITION semantic, ignore it.
|
|
return;
|
|
}
|
|
|
|
int positionAccessorID = positionAccessorIt->second;
|
|
const CesiumGltf::Accessor* pPositionAccessor =
|
|
CesiumGltf::Model::getSafe(&model.accessors, positionAccessorID);
|
|
if (!pPositionAccessor) {
|
|
// Position accessor does not exist, so ignore this primitive.
|
|
return;
|
|
}
|
|
|
|
CesiumGltf::AccessorView<TMeshVector3> positionView(
|
|
model,
|
|
*pPositionAccessor);
|
|
|
|
if (primitive.indices < 0 || primitive.indices >= model.accessors.size()) {
|
|
std::vector<uint32_t> syntheticIndexBuffer(positionView.size());
|
|
syntheticIndexBuffer.resize(positionView.size());
|
|
for (uint32_t i = 0; i < positionView.size(); ++i) {
|
|
syntheticIndexBuffer[i] = i;
|
|
}
|
|
loadPrimitive(
|
|
result,
|
|
transform,
|
|
options,
|
|
*pPositionAccessor,
|
|
positionView,
|
|
syntheticIndexBuffer,
|
|
ellipsoid);
|
|
} else {
|
|
loadIndexedPrimitive(
|
|
result,
|
|
transform,
|
|
options,
|
|
*pPositionAccessor,
|
|
positionView,
|
|
ellipsoid);
|
|
}
|
|
result.PositionAccessor = std::move(positionView);
|
|
}
|
|
|
|
static void loadMesh(
|
|
std::optional<LoadedMeshResult>& result,
|
|
const glm::dmat4x4& transform,
|
|
CreateMeshOptions& options,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadMesh)
|
|
|
|
CesiumGltf::Model& model = *options.pNodeOptions->pModelOptions->pModel;
|
|
CesiumGltf::Mesh& mesh = model.meshes[options.meshIndex];
|
|
|
|
result = LoadedMeshResult();
|
|
result->primitiveResults.reserve(mesh.primitives.size());
|
|
for (size_t i = 0; i < mesh.primitives.size(); i++) {
|
|
CreatePrimitiveOptions primitiveOptions = {&options, &*result, i};
|
|
auto& primitiveResult = result->primitiveResults.emplace_back();
|
|
loadPrimitive(primitiveResult, transform, primitiveOptions, ellipsoid);
|
|
|
|
// if it doesn't have render data, then it can't be loaded
|
|
if (!primitiveResult.RenderData) {
|
|
result->primitiveResults.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helpers for different instancing rotation formats
|
|
|
|
namespace {
|
|
template <typename T> struct is_float_quat : std::false_type {};
|
|
|
|
template <>
|
|
struct is_float_quat<CesiumGltf::AccessorTypes::VEC4<float>> : std::true_type {
|
|
};
|
|
|
|
template <typename T> struct is_int_quat : std::false_type {};
|
|
|
|
template <typename T>
|
|
struct is_int_quat<CesiumGltf::AccessorTypes::VEC4<T>>
|
|
: std::conjunction<std::is_integral<T>, std::is_signed<T>> {};
|
|
|
|
template <typename T>
|
|
inline constexpr bool is_float_quat_v = is_float_quat<T>::value;
|
|
|
|
template <typename T>
|
|
inline constexpr bool is_int_quat_v = is_int_quat<T>::value;
|
|
} // namespace
|
|
|
|
static void loadInstancingData(
|
|
const CesiumGltf::Model& model,
|
|
const CesiumGltf::Node& node,
|
|
LoadedNodeResult& result,
|
|
const CesiumGltf::ExtensionExtMeshGpuInstancing* pGpuInstancing,
|
|
const CesiumGltf::ExtensionExtInstanceFeatures* pInstanceFeatures) {
|
|
auto getInstanceAccessor =
|
|
[&](const char* name) -> const CesiumGltf::Accessor* {
|
|
if (auto accessorItr = pGpuInstancing->attributes.find(name);
|
|
accessorItr != pGpuInstancing->attributes.end()) {
|
|
return CesiumGltf::Model::getSafe(&model.accessors, accessorItr->second);
|
|
}
|
|
return nullptr;
|
|
};
|
|
const CesiumGltf::Accessor* translations = getInstanceAccessor("TRANSLATION");
|
|
const CesiumGltf::Accessor* rotations = getInstanceAccessor("ROTATION");
|
|
const CesiumGltf::Accessor* scales = getInstanceAccessor("SCALE");
|
|
|
|
int64_t count = 0;
|
|
if (translations) {
|
|
count = translations->count;
|
|
}
|
|
if (rotations) {
|
|
if (count == 0) {
|
|
count = rotations->count;
|
|
} else if (count != rotations->count) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("instance rotation count %d not consistent with %d"),
|
|
rotations->count,
|
|
count);
|
|
return;
|
|
}
|
|
}
|
|
if (scales) {
|
|
if (count == 0) {
|
|
count = scales->count;
|
|
} else if (count != scales->count) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("instance scale count %d not consistent with %d"),
|
|
scales->count,
|
|
count);
|
|
return;
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
UE_LOG(LogCesium, Warning, TEXT("No valid instance data"));
|
|
return;
|
|
}
|
|
// The glTF instance transforms need to be transformed into the local
|
|
// coordinate system of the Unreal static mesh i.e., Unreals' left-handed
|
|
// system. Another way to think about it is that the geometry, which is stored
|
|
// in the Unreal system, must be transformed to glTF, have the instance
|
|
// transform applied, and then be transformed back to Unreal. It's tempting to
|
|
// do this by trying some manipulation of the individual glTF instance
|
|
// operations, but that general approach has always ended in tears for me.
|
|
// Better to formally multiply out the matrices and be assured that the
|
|
// operation is correct.
|
|
std::vector<glm::dmat4> instanceTransforms(count, glm::dmat4(1.0));
|
|
|
|
// Note: the glm functions translate() and scale() post-multiply the matrix
|
|
// argument by the new transform. E.g., translate() does *not* translate the
|
|
// matrix.
|
|
if (translations) {
|
|
CesiumGltf::AccessorView<glm::fvec3> translationAccessor(
|
|
model,
|
|
*translations);
|
|
if (translationAccessor.status() == CesiumGltf::AccessorViewStatus::Valid) {
|
|
for (int64_t i = 0; i < count; ++i) {
|
|
glm::dvec3 translation(translationAccessor[i]);
|
|
instanceTransforms[i] = glm::translate(
|
|
instanceTransforms[i],
|
|
translation * CesiumPrimitiveData::positionScaleFactor);
|
|
}
|
|
}
|
|
} else {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("Invalid accessor for instance translations"));
|
|
}
|
|
if (rotations) {
|
|
createAccessorView(model, *rotations, [&](auto&& quatView) -> void {
|
|
using QuatType = decltype(quatView[0]);
|
|
using ValueType = std::decay_t<QuatType>;
|
|
if constexpr (is_float_quat_v<ValueType>) {
|
|
for (int i = 0; i < count; ++i) {
|
|
glm::dquat quat(
|
|
quatView[i].value[3],
|
|
quatView[i].value[0],
|
|
quatView[i].value[1],
|
|
quatView[i].value[2]);
|
|
instanceTransforms[i] = instanceTransforms[i] * glm::mat4_cast(quat);
|
|
}
|
|
} else if constexpr (is_int_quat_v<ValueType>) {
|
|
for (int64_t i = 0; i < count; ++i) {
|
|
float val[4];
|
|
for (int j = 0; j < 4; ++j) {
|
|
val[j] = GltfNormalized(quatView[i].value[j]);
|
|
}
|
|
glm::dquat quat(val[3], val[0], val[1], val[2]);
|
|
instanceTransforms[i] = instanceTransforms[i] * glm::mat4_cast(quat);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (scales) {
|
|
CesiumGltf::AccessorView<glm::fvec3> scaleAccessor(model, *scales);
|
|
for (int64_t i = 0; i < count; ++i) {
|
|
glm::dvec3 scaleFactors(scaleAccessor[i]);
|
|
instanceTransforms[i] = glm::scale(instanceTransforms[i], scaleFactors);
|
|
}
|
|
} else {
|
|
UE_LOG(LogCesium, Warning, TEXT("Invalid accessor for instance scales"));
|
|
}
|
|
result.InstanceTransforms.resize(count);
|
|
for (int64_t i = 0; i < count; ++i) {
|
|
glm::dmat4 unrealMat =
|
|
yInvertMatrix * instanceTransforms[i] * yInvertMatrix;
|
|
result.InstanceTransforms[i] = VecMath::createTransform(unrealMat);
|
|
}
|
|
if (pInstanceFeatures) {
|
|
result.pInstanceFeatures =
|
|
MakeShared<FCesiumPrimitiveFeatures>(model, node, *pInstanceFeatures);
|
|
}
|
|
}
|
|
|
|
static void loadNode(
|
|
std::vector<LoadedNodeResult>& loadNodeResults,
|
|
const glm::dmat4x4& transform,
|
|
CreateNodeOptions& options,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadNode)
|
|
|
|
static constexpr std::array<double, 16> identityMatrix = {
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0};
|
|
|
|
CesiumGltf::Model& model = *options.pModelOptions->pModel;
|
|
const CesiumGltf::Node& node = *options.pNode;
|
|
|
|
LoadedNodeResult& result = loadNodeResults.emplace_back();
|
|
|
|
glm::dmat4x4 nodeTransform = transform;
|
|
|
|
const std::vector<double>& matrix = node.matrix;
|
|
bool isIdentityMatrix = false;
|
|
if (matrix.size() == 16) {
|
|
isIdentityMatrix =
|
|
std::equal(matrix.begin(), matrix.end(), identityMatrix.begin());
|
|
}
|
|
|
|
if (matrix.size() == 16 && !isIdentityMatrix) {
|
|
glm::dmat4x4 nodeTransformGltf(
|
|
glm::dvec4(matrix[0], matrix[1], matrix[2], matrix[3]),
|
|
glm::dvec4(matrix[4], matrix[5], matrix[6], matrix[7]),
|
|
glm::dvec4(matrix[8], matrix[9], matrix[10], matrix[11]),
|
|
glm::dvec4(matrix[12], matrix[13], matrix[14], matrix[15]));
|
|
|
|
nodeTransform = nodeTransform * nodeTransformGltf;
|
|
} else {
|
|
glm::dmat4 translation(1.0);
|
|
if (node.translation.size() == 3) {
|
|
translation[3] = glm::dvec4(
|
|
node.translation[0],
|
|
node.translation[1],
|
|
node.translation[2],
|
|
1.0);
|
|
}
|
|
|
|
glm::dquat rotationQuat(1.0, 0.0, 0.0, 0.0);
|
|
if (node.rotation.size() == 4) {
|
|
rotationQuat[0] = node.rotation[0];
|
|
rotationQuat[1] = node.rotation[1];
|
|
rotationQuat[2] = node.rotation[2];
|
|
rotationQuat[3] = node.rotation[3];
|
|
}
|
|
|
|
glm::dmat4 scale(1.0);
|
|
if (node.scale.size() == 3) {
|
|
scale[0].x = node.scale[0];
|
|
scale[1].y = node.scale[1];
|
|
scale[2].z = node.scale[2];
|
|
}
|
|
|
|
nodeTransform =
|
|
nodeTransform * translation * glm::dmat4(rotationQuat) * scale;
|
|
}
|
|
|
|
int meshId = node.mesh;
|
|
if (meshId >= 0 && meshId < model.meshes.size()) {
|
|
if (const auto* pGpuInstancingExtension =
|
|
node.getExtension<CesiumGltf::ExtensionExtMeshGpuInstancing>()) {
|
|
loadInstancingData(
|
|
model,
|
|
node,
|
|
result,
|
|
pGpuInstancingExtension,
|
|
node.getExtension<CesiumGltf::ExtensionExtInstanceFeatures>());
|
|
}
|
|
CreateMeshOptions meshOptions = {&options, &result, meshId};
|
|
loadMesh(result.meshResult, nodeTransform, meshOptions, ellipsoid);
|
|
}
|
|
|
|
for (int childNodeId : node.children) {
|
|
if (childNodeId >= 0 && childNodeId < model.nodes.size()) {
|
|
CreateNodeOptions childNodeOptions = {
|
|
options.pModelOptions,
|
|
options.pHalfConstructedModelResult,
|
|
&model.nodes[childNodeId]};
|
|
loadNode(loadNodeResults, nodeTransform, childNodeOptions, ellipsoid);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/**
|
|
* @brief Apply the transform so that the up-axis of the given model is the
|
|
* Z-axis.
|
|
*
|
|
* By default, the up-axis of a glTF model will the the Y-axis.
|
|
*
|
|
* If the tileset that contained the model had the `asset.gltfUpAxis` string
|
|
* property, then the information about the up-axis has been stored in as a
|
|
* number property called `gltfUpAxis` in the `extras` of the given model.
|
|
*
|
|
* Depending on whether this value is CesiumGeometry::Axis::X, Y, or Z,
|
|
* the given matrix will be multiplied with a matrix that converts the
|
|
* respective axis to be the Z-axis, as required by the 3D Tiles standard.
|
|
*
|
|
* @param model The glTF model
|
|
* @param rootTransform The matrix that will be multiplied with the transform
|
|
*/
|
|
void applyGltfUpAxisTransform(
|
|
const CesiumGltf::Model& model,
|
|
glm::dmat4x4& rootTransform) {
|
|
|
|
auto gltfUpAxisIt = model.extras.find("gltfUpAxis");
|
|
if (gltfUpAxisIt == model.extras.end()) {
|
|
// The default up-axis of glTF is the Y-axis, and no other
|
|
// up-axis was specified. Transform the Y-axis to the Z-axis,
|
|
// to match the 3D Tiles specification
|
|
rootTransform *= CesiumGeometry::Transforms::Y_UP_TO_Z_UP;
|
|
return;
|
|
}
|
|
const CesiumUtility::JsonValue& gltfUpAxis = gltfUpAxisIt->second;
|
|
int gltfUpAxisValue = static_cast<int>(gltfUpAxis.getSafeNumberOrDefault(1));
|
|
if (gltfUpAxisValue == static_cast<int>(CesiumGeometry::Axis::X)) {
|
|
rootTransform *= CesiumGeometry::Transforms::X_UP_TO_Z_UP;
|
|
} else if (gltfUpAxisValue == static_cast<int>(CesiumGeometry::Axis::Y)) {
|
|
rootTransform *= CesiumGeometry::Transforms::Y_UP_TO_Z_UP;
|
|
} else if (gltfUpAxisValue == static_cast<int>(CesiumGeometry::Axis::Z)) {
|
|
// No transform required
|
|
} else {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT("Ignoring unknown gltfUpAxis value: {}"),
|
|
gltfUpAxisValue);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static void loadModelMetadata(
|
|
LoadedModelResult& result,
|
|
const CreateModelOptions& options) {
|
|
CesiumGltf::Model& model = *options.pModel;
|
|
|
|
CesiumGltf::ExtensionModelExtStructuralMetadata* pModelMetadata =
|
|
model.getExtension<CesiumGltf::ExtensionModelExtStructuralMetadata>();
|
|
if (!pModelMetadata) {
|
|
return;
|
|
}
|
|
|
|
model.forEachPrimitiveInScene(
|
|
model.scene,
|
|
[pModelMetadata](
|
|
CesiumGltf::Model& gltf,
|
|
CesiumGltf::Node& /*node*/,
|
|
CesiumGltf::Mesh& /*mesh*/,
|
|
CesiumGltf::MeshPrimitive& primitive,
|
|
const glm::dmat4& /*nodeTransform*/) {
|
|
const CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata*
|
|
pPrimitiveMetadata = primitive.getExtension<
|
|
CesiumGltf::ExtensionMeshPrimitiveExtStructuralMetadata>();
|
|
if (!pPrimitiveMetadata) {
|
|
return;
|
|
}
|
|
|
|
int32_t materialIndex = primitive.material;
|
|
if (materialIndex < 0 || materialIndex >= gltf.materials.size()) {
|
|
return;
|
|
}
|
|
|
|
const CesiumGltf::Material& material =
|
|
gltf.materials[primitive.material];
|
|
|
|
for (const auto& propertyTextureIndex :
|
|
pPrimitiveMetadata->propertyTextures) {
|
|
if (propertyTextureIndex < 0 ||
|
|
static_cast<size_t>(propertyTextureIndex) >=
|
|
pModelMetadata->propertyTextures.size()) {
|
|
continue;
|
|
}
|
|
|
|
CesiumGltf::PropertyTexture& propertyTexture =
|
|
pModelMetadata->propertyTextures[propertyTextureIndex];
|
|
|
|
for (auto& propertyIt : propertyTexture.properties) {
|
|
if (propertyIt.second.extras.find("makeImageCopy") !=
|
|
propertyIt.second.extras.end()) {
|
|
continue;
|
|
}
|
|
|
|
int32_t textureIndex = propertyIt.second.index;
|
|
if (textureIndex < 0 || textureIndex > gltf.textures.size()) {
|
|
continue;
|
|
}
|
|
|
|
const CesiumGltf::Texture& texture = gltf.textures[textureIndex];
|
|
if (texture.source < 0 || texture.source >= gltf.images.size()) {
|
|
continue;
|
|
}
|
|
|
|
if (hasMaterialTextureConflicts(gltf, material, texture.source)) {
|
|
// Add a flag in the extras to indicate a copy should be made.
|
|
// This is checked for in FCesiumPropertyTexture.
|
|
propertyIt.second.extras.insert({"makeImageCopy", true});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
result.Metadata = FCesiumModelMetadata(model, *pModelMetadata);
|
|
|
|
const FCesiumFeaturesMetadataDescription* pFeaturesMetadataDescription =
|
|
options.pFeaturesMetadataDescription;
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
const FMetadataDescription* pMetadataDescription_DEPRECATED =
|
|
options.pEncodedMetadataDescription_DEPRECATED;
|
|
if (pFeaturesMetadataDescription) {
|
|
result.EncodedMetadata =
|
|
CesiumEncodedFeaturesMetadata::encodeModelMetadataAnyThreadPart(
|
|
pFeaturesMetadataDescription->ModelMetadata,
|
|
result.Metadata);
|
|
} else if (pMetadataDescription_DEPRECATED) {
|
|
result.EncodedMetadata_DEPRECATED =
|
|
CesiumEncodedMetadataUtility::encodeMetadataAnyThreadPart(
|
|
*pMetadataDescription_DEPRECATED,
|
|
result.Metadata);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
static CesiumAsync::Future<UCesiumGltfComponent::CreateOffGameThreadResult>
|
|
loadModelAnyThreadPart(
|
|
const CesiumAsync::AsyncSystem& asyncSystem,
|
|
const glm::dmat4x4& transform,
|
|
CreateModelOptions&& options,
|
|
const CesiumGeospatial::Ellipsoid& ellipsoid) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::loadModelAnyThreadPart)
|
|
|
|
return CesiumGltfTextures::createInWorkerThread(asyncSystem, *options.pModel)
|
|
.thenInWorkerThread(
|
|
[transform, ellipsoid, options = std::move(options)]() mutable
|
|
-> UCesiumGltfComponent::CreateOffGameThreadResult {
|
|
auto pHalf = MakeUnique<HalfConstructedReal>();
|
|
|
|
loadModelMetadata(pHalf->loadModelResult, options);
|
|
|
|
glm::dmat4x4 rootTransform = transform;
|
|
|
|
CesiumGltf::Model& model = *options.pModel;
|
|
|
|
{
|
|
rootTransform = CesiumGltfContent::GltfUtilities::applyRtcCenter(
|
|
model,
|
|
rootTransform);
|
|
applyGltfUpAxisTransform(model, rootTransform);
|
|
}
|
|
|
|
if (model.scene >= 0 && model.scene < model.scenes.size()) {
|
|
// Show the default scene
|
|
const CesiumGltf::Scene& defaultScene = model.scenes[model.scene];
|
|
for (int nodeId : defaultScene.nodes) {
|
|
CreateNodeOptions nodeOptions = {
|
|
&options,
|
|
&pHalf->loadModelResult,
|
|
&model.nodes[nodeId]};
|
|
loadNode(
|
|
pHalf->loadModelResult.nodeResults,
|
|
rootTransform,
|
|
nodeOptions,
|
|
ellipsoid);
|
|
}
|
|
} else if (model.scenes.size() > 0) {
|
|
// There's no default, so show the first scene
|
|
const CesiumGltf::Scene& defaultScene = model.scenes[0];
|
|
for (int nodeId : defaultScene.nodes) {
|
|
CreateNodeOptions nodeOptions = {
|
|
&options,
|
|
&pHalf->loadModelResult,
|
|
&model.nodes[nodeId]};
|
|
loadNode(
|
|
pHalf->loadModelResult.nodeResults,
|
|
rootTransform,
|
|
nodeOptions,
|
|
ellipsoid);
|
|
}
|
|
} else if (model.nodes.size() > 0) {
|
|
// No scenes at all, use the first node as the root node.
|
|
CreateNodeOptions nodeOptions = {
|
|
&options,
|
|
&pHalf->loadModelResult,
|
|
&model.nodes[0]};
|
|
loadNode(
|
|
pHalf->loadModelResult.nodeResults,
|
|
rootTransform,
|
|
nodeOptions,
|
|
ellipsoid);
|
|
} else if (model.meshes.size() > 0) {
|
|
// No nodes either, show all the meshes.
|
|
for (size_t i = 0; i < model.meshes.size(); i++) {
|
|
CreateNodeOptions dummyNodeOptions = {
|
|
&options,
|
|
&pHalf->loadModelResult,
|
|
nullptr};
|
|
LoadedNodeResult& dummyNodeResult =
|
|
pHalf->loadModelResult.nodeResults.emplace_back();
|
|
CreateMeshOptions meshOptions = {
|
|
&dummyNodeOptions,
|
|
&dummyNodeResult,
|
|
i};
|
|
loadMesh(
|
|
dummyNodeResult.meshResult,
|
|
rootTransform,
|
|
meshOptions,
|
|
ellipsoid);
|
|
}
|
|
}
|
|
|
|
UCesiumGltfComponent::CreateOffGameThreadResult result;
|
|
result.HalfConstructed = std::move(pHalf);
|
|
result.TileLoadResult = std::move(options.tileLoadResult);
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
bool applyTexture(
|
|
CesiumGltf::Model& model,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
const FMaterialParameterInfo& info,
|
|
CesiumTextureUtility::LoadedTextureResult* pLoadedTexture) {
|
|
CesiumUtility::IntrusivePointer<
|
|
CesiumTextureUtility::ReferenceCountedUnrealTexture>
|
|
pTexture = CesiumTextureUtility::loadTextureGameThreadPart(
|
|
model,
|
|
pLoadedTexture);
|
|
if (!pTexture) {
|
|
return false;
|
|
}
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(info, pTexture->getUnrealTexture());
|
|
|
|
return true;
|
|
}
|
|
|
|
#pragma region Material Parameter setters
|
|
|
|
static void SetGltfParameterValues(
|
|
CesiumGltf::Model& model,
|
|
LoadedPrimitiveResult& loadResult,
|
|
const CesiumGltf::Material& material,
|
|
const CesiumGltf::MaterialPBRMetallicRoughness& pbr,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
EMaterialParameterAssociation association,
|
|
int32 index) {
|
|
for (auto& textureCoordinateSet : loadResult.textureCoordinateParameters) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
UTF8_TO_TCHAR(textureCoordinateSet.first.c_str()),
|
|
association,
|
|
index),
|
|
static_cast<float>(textureCoordinateSet.second));
|
|
}
|
|
|
|
if (pbr.baseColorFactor.size() > 3) {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("baseColorFactor", association, index),
|
|
FLinearColor(
|
|
pbr.baseColorFactor[0],
|
|
pbr.baseColorFactor[1],
|
|
pbr.baseColorFactor[2],
|
|
pbr.baseColorFactor[3]));
|
|
} else if (pbr.baseColorFactor.size() == 3) {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("baseColorFactor", association, index),
|
|
FLinearColor(
|
|
pbr.baseColorFactor[0],
|
|
pbr.baseColorFactor[1],
|
|
pbr.baseColorFactor[2],
|
|
1.));
|
|
} else {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("baseColorFactor", association, index),
|
|
FLinearColor(1., 1., 1., 1.));
|
|
}
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo("metallicFactor", association, index),
|
|
static_cast<float>(loadResult.isUnlit ? 0.0f : pbr.metallicFactor));
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo("roughnessFactor", association, index),
|
|
static_cast<float>(loadResult.isUnlit ? 1.0f : pbr.roughnessFactor));
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo("opacityMask", association, index),
|
|
1.0f);
|
|
|
|
applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("baseColorTexture", association, index),
|
|
loadResult.baseColorTexture.Get());
|
|
applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("metallicRoughnessTexture", association, index),
|
|
loadResult.metallicRoughnessTexture.Get());
|
|
applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("normalTexture", association, index),
|
|
loadResult.normalTexture.Get());
|
|
bool hasEmissiveTexture = applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("emissiveTexture", association, index),
|
|
loadResult.emissiveTexture.Get());
|
|
applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("occlusionTexture", association, index),
|
|
loadResult.occlusionTexture.Get());
|
|
|
|
CesiumGltf::KhrTextureTransform textureTransform;
|
|
FLinearColor baseColorMetallicRoughnessRotation(0.0f, 1.0f, 0.0f, 1.0f);
|
|
const CesiumGltf::ExtensionKhrTextureTransform* pBaseColorTextureTransform =
|
|
pbr.baseColorTexture
|
|
? pbr.baseColorTexture
|
|
->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
|
|
: nullptr;
|
|
|
|
if (pBaseColorTextureTransform) {
|
|
textureTransform =
|
|
CesiumGltf::KhrTextureTransform(*pBaseColorTextureTransform);
|
|
if (textureTransform.status() ==
|
|
CesiumGltf::KhrTextureTransformStatus::Valid) {
|
|
const glm::dvec2& scale = textureTransform.scale();
|
|
const glm::dvec2& offset = textureTransform.offset();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("baseColorScaleOffset", association, index),
|
|
FLinearColor(scale[0], scale[1], offset[0], offset[1]));
|
|
|
|
const glm::dvec2& rotationSineCosine =
|
|
textureTransform.rotationSineCosine();
|
|
baseColorMetallicRoughnessRotation.R = rotationSineCosine[0];
|
|
baseColorMetallicRoughnessRotation.G = rotationSineCosine[1];
|
|
}
|
|
}
|
|
|
|
const CesiumGltf::ExtensionKhrTextureTransform*
|
|
pMetallicRoughnessTextureTransform =
|
|
pbr.metallicRoughnessTexture
|
|
? pbr.metallicRoughnessTexture
|
|
->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
|
|
: nullptr;
|
|
|
|
if (pMetallicRoughnessTextureTransform) {
|
|
textureTransform =
|
|
CesiumGltf::KhrTextureTransform(*pMetallicRoughnessTextureTransform);
|
|
if (textureTransform.status() ==
|
|
CesiumGltf::KhrTextureTransformStatus::Valid) {
|
|
const glm::dvec2& scale = textureTransform.scale();
|
|
const glm::dvec2& offset = textureTransform.offset();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"metallicRoughnessScaleOffset",
|
|
association,
|
|
index),
|
|
FLinearColor(scale[0], scale[1], offset[0], offset[1]));
|
|
|
|
const glm::dvec2& rotationSineCosine =
|
|
textureTransform.rotationSineCosine();
|
|
baseColorMetallicRoughnessRotation.B = rotationSineCosine[0];
|
|
baseColorMetallicRoughnessRotation.A = rotationSineCosine[1];
|
|
}
|
|
}
|
|
|
|
if (pBaseColorTextureTransform || pMetallicRoughnessTextureTransform) {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"baseColorMetallicRoughnessRotation",
|
|
association,
|
|
index),
|
|
baseColorMetallicRoughnessRotation);
|
|
}
|
|
|
|
FLinearColor emissiveNormalRotation(0.0f, 1.0f, 0.0f, 1.0f);
|
|
|
|
const CesiumGltf::ExtensionKhrTextureTransform* pEmissiveTextureTransform =
|
|
material.emissiveTexture
|
|
? material.emissiveTexture
|
|
->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
|
|
: nullptr;
|
|
|
|
if (pEmissiveTextureTransform) {
|
|
textureTransform =
|
|
CesiumGltf::KhrTextureTransform(*pEmissiveTextureTransform);
|
|
const glm::dvec2& scale = textureTransform.scale();
|
|
const glm::dvec2& offset = textureTransform.offset();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("emissiveScaleOffset", association, index),
|
|
FLinearColor(scale[0], scale[1], offset[0], offset[1]));
|
|
|
|
const glm::dvec2& rotationSineCosine =
|
|
textureTransform.rotationSineCosine();
|
|
emissiveNormalRotation.R = rotationSineCosine[0];
|
|
emissiveNormalRotation.G = rotationSineCosine[1];
|
|
}
|
|
|
|
const CesiumGltf::ExtensionKhrTextureTransform* pNormalTextureTransform =
|
|
material.normalTexture
|
|
? material.normalTexture
|
|
->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
|
|
: nullptr;
|
|
|
|
if (pNormalTextureTransform) {
|
|
textureTransform =
|
|
CesiumGltf::KhrTextureTransform(*pNormalTextureTransform);
|
|
const glm::dvec2& scale = textureTransform.scale();
|
|
const glm::dvec2& offset = textureTransform.offset();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("normalScaleOffset", association, index),
|
|
FLinearColor(scale[0], scale[1], offset[0], offset[1]));
|
|
const glm::dvec2& rotationSineCosine =
|
|
textureTransform.rotationSineCosine();
|
|
emissiveNormalRotation.B = rotationSineCosine[0];
|
|
emissiveNormalRotation.A = rotationSineCosine[1];
|
|
}
|
|
|
|
if (pEmissiveTextureTransform || pNormalTextureTransform) {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("emissiveNormalRotation", association, index),
|
|
emissiveNormalRotation);
|
|
}
|
|
|
|
const CesiumGltf::ExtensionKhrTextureTransform* pOcclusionTransform =
|
|
material.occlusionTexture
|
|
? material.occlusionTexture
|
|
->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
|
|
: nullptr;
|
|
|
|
if (pOcclusionTransform) {
|
|
textureTransform = CesiumGltf::KhrTextureTransform(*pOcclusionTransform);
|
|
const glm::dvec2& scale = textureTransform.scale();
|
|
const glm::dvec2& offset = textureTransform.offset();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("occlusionScaleOffset", association, index),
|
|
FLinearColor(scale[0], scale[1], offset[0], offset[1]));
|
|
|
|
const glm::dvec2& rotationSineCosine =
|
|
textureTransform.rotationSineCosine();
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("occlusionRotation", association, index),
|
|
FLinearColor(
|
|
float(rotationSineCosine[0]),
|
|
float(rotationSineCosine[1]),
|
|
0.0f,
|
|
1.0f));
|
|
}
|
|
|
|
if (material.emissiveFactor.size() >= 3) {
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("emissiveFactor", association, index),
|
|
FVector(
|
|
material.emissiveFactor[0],
|
|
material.emissiveFactor[1],
|
|
material.emissiveFactor[2]));
|
|
} else if (hasEmissiveTexture) {
|
|
// When we have an emissive texture but not a factor, we need to use a
|
|
// factor of vec3(1.0). The default, vec3(0.0), would disable the emission
|
|
// from the texture.
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("emissiveFactor", association, index),
|
|
FVector(1.0f, 1.0f, 1.0f));
|
|
}
|
|
}
|
|
|
|
void SetWaterParameterValues(
|
|
CesiumGltf::Model& model,
|
|
LoadedPrimitiveResult& loadResult,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
EMaterialParameterAssociation association,
|
|
int32 index) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo("OnlyLand", association, index),
|
|
static_cast<float>(loadResult.onlyLand));
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo("OnlyWater", association, index),
|
|
static_cast<float>(loadResult.onlyWater));
|
|
|
|
if (!loadResult.onlyLand && !loadResult.onlyWater) {
|
|
applyTexture(
|
|
model,
|
|
pMaterial,
|
|
FMaterialParameterInfo("WaterMask", association, index),
|
|
loadResult.waterMaskTexture.Get());
|
|
}
|
|
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo("WaterMaskTranslationScale", association, index),
|
|
FVector(
|
|
loadResult.waterMaskTranslationX,
|
|
loadResult.waterMaskTranslationY,
|
|
loadResult.waterMaskScale));
|
|
}
|
|
|
|
static void SetFeaturesMetadataParameterValues(
|
|
const CesiumGltf::Model& model,
|
|
UCesiumGltfComponent& gltfComponent,
|
|
LoadedPrimitiveResult& loadResult,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
EMaterialParameterAssociation association,
|
|
int32 index) {
|
|
// This handles texture coordinate indices for both attribute feature ID
|
|
// sets and property textures.
|
|
for (const auto& textureCoordinateSet :
|
|
loadResult.FeaturesMetadataTexCoordParameters) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(textureCoordinateSet.Key),
|
|
association,
|
|
index),
|
|
textureCoordinateSet.Value);
|
|
}
|
|
|
|
if (encodePrimitiveFeaturesGameThreadPart(loadResult.EncodedFeatures)) {
|
|
for (CesiumEncodedFeaturesMetadata::EncodedFeatureIdSet&
|
|
encodedFeatureIdSet : loadResult.EncodedFeatures.featureIdSets) {
|
|
FString SafeName = CesiumEncodedFeaturesMetadata::createHlslSafeName(
|
|
encodedFeatureIdSet.name);
|
|
if (encodedFeatureIdSet.nullFeatureId) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(
|
|
SafeName +
|
|
CesiumEncodedFeaturesMetadata::MaterialNullFeatureIdSuffix),
|
|
association,
|
|
index),
|
|
static_cast<float>(*encodedFeatureIdSet.nullFeatureId));
|
|
}
|
|
|
|
if (encodedFeatureIdSet.texture) {
|
|
CesiumEncodedFeaturesMetadata::SetFeatureIdTextureParameterValues(
|
|
pMaterial,
|
|
association,
|
|
index,
|
|
SafeName,
|
|
*encodedFeatureIdSet.texture);
|
|
}
|
|
}
|
|
|
|
for (const CesiumEncodedFeaturesMetadata::EncodedPropertyTexture&
|
|
propertyTexture : gltfComponent.EncodedMetadata.propertyTextures) {
|
|
CesiumEncodedFeaturesMetadata::SetPropertyTextureParameterValues(
|
|
pMaterial,
|
|
association,
|
|
index,
|
|
propertyTexture);
|
|
}
|
|
|
|
for (const CesiumEncodedFeaturesMetadata::EncodedPropertyTable&
|
|
propertyTable : gltfComponent.EncodedMetadata.propertyTables) {
|
|
CesiumEncodedFeaturesMetadata::SetPropertyTableParameterValues(
|
|
pMaterial,
|
|
association,
|
|
index,
|
|
propertyTable);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SetMetadataFeatureTableParameterValues_DEPRECATED(
|
|
const CesiumEncodedMetadataUtility::EncodedMetadataFeatureTable&
|
|
encodedFeatureTable,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
EMaterialParameterAssociation association,
|
|
int32 index) {
|
|
for (const CesiumEncodedMetadataUtility::EncodedMetadataProperty&
|
|
encodedProperty : encodedFeatureTable.encodedProperties) {
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(
|
|
FMaterialParameterInfo(FName(encodedProperty.name), association, index),
|
|
encodedProperty.pTexture->pTexture->getUnrealTexture());
|
|
}
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
static void SetMetadataParameterValues_DEPRECATED(
|
|
const CesiumGltf::Model& model,
|
|
UCesiumGltfComponent& gltfComponent,
|
|
LoadedPrimitiveResult& loadResult,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
EMaterialParameterAssociation association,
|
|
int32 index) {
|
|
/**
|
|
* The following is the naming convention for deprecated encoded metadata:
|
|
*
|
|
* Feature Id Textures:
|
|
* - Base: "FIT_<feature table name>_"...
|
|
* - Texture: ..."TX"
|
|
* - Texture Coordinate Index: ..."UV"
|
|
* - Channel Mask: ..."CM"
|
|
*
|
|
* Feature Id Attributes:
|
|
* - Texture Coordinate Index (feature ids are encoded into UVs):
|
|
* "FA_<feature table name>"
|
|
*
|
|
* Feature Texture Properties:
|
|
* - Base: "FTX_<feature texture name>_<property name>_"...
|
|
* - Texture: ..."TX"
|
|
* - Texture Coordinate Index: ..."UV"
|
|
* - Swizzle: ..."SW"
|
|
*
|
|
* Encoded Feature Table Properties:
|
|
* - Encoded Property Table:
|
|
* "FTB_<feature table name>_<property name>"
|
|
*/
|
|
|
|
if (!loadResult.EncodedMetadata_DEPRECATED ||
|
|
!encodeMetadataPrimitiveGameThreadPart(
|
|
*loadResult.EncodedMetadata_DEPRECATED)) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& textureCoordinateSet :
|
|
loadResult.FeaturesMetadataTexCoordParameters) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(textureCoordinateSet.Key),
|
|
association,
|
|
index),
|
|
textureCoordinateSet.Value);
|
|
}
|
|
|
|
for (const FString& featureTextureName :
|
|
loadResult.EncodedMetadata_DEPRECATED->featureTextureNames) {
|
|
CesiumEncodedMetadataUtility::EncodedFeatureTexture*
|
|
pEncodedFeatureTexture =
|
|
gltfComponent.EncodedMetadata_DEPRECATED->encodedFeatureTextures
|
|
.Find(featureTextureName);
|
|
|
|
if (pEncodedFeatureTexture) {
|
|
for (CesiumEncodedMetadataUtility::EncodedFeatureTextureProperty&
|
|
encodedProperty : pEncodedFeatureTexture->properties) {
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(encodedProperty.baseName + "TX"),
|
|
association,
|
|
index),
|
|
encodedProperty.pTexture->pTexture->getUnrealTexture());
|
|
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(encodedProperty.baseName + "SW"),
|
|
association,
|
|
index),
|
|
FLinearColor(
|
|
encodedProperty.channelOffsets[0],
|
|
encodedProperty.channelOffsets[1],
|
|
encodedProperty.channelOffsets[2],
|
|
encodedProperty.channelOffsets[3]));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (CesiumEncodedMetadataUtility::EncodedFeatureIdTexture&
|
|
encodedFeatureIdTexture :
|
|
loadResult.EncodedMetadata_DEPRECATED->encodedFeatureIdTextures) {
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(encodedFeatureIdTexture.baseName + "TX"),
|
|
association,
|
|
index),
|
|
encodedFeatureIdTexture.pTexture->pTexture->getUnrealTexture());
|
|
|
|
FLinearColor channelMask;
|
|
switch (encodedFeatureIdTexture.channel) {
|
|
case 1:
|
|
channelMask = FLinearColor::Green;
|
|
break;
|
|
case 2:
|
|
channelMask = FLinearColor::Blue;
|
|
break;
|
|
default:
|
|
channelMask = FLinearColor::Red;
|
|
}
|
|
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
FName(encodedFeatureIdTexture.baseName + "CM"),
|
|
association,
|
|
index),
|
|
channelMask);
|
|
|
|
const CesiumEncodedMetadataUtility::EncodedMetadataFeatureTable*
|
|
pEncodedFeatureTable =
|
|
gltfComponent.EncodedMetadata_DEPRECATED->encodedFeatureTables.Find(
|
|
encodedFeatureIdTexture.featureTableName);
|
|
|
|
if (pEncodedFeatureTable) {
|
|
SetMetadataFeatureTableParameterValues_DEPRECATED(
|
|
*pEncodedFeatureTable,
|
|
pMaterial,
|
|
association,
|
|
index);
|
|
}
|
|
}
|
|
|
|
for (const CesiumEncodedMetadataUtility::EncodedFeatureIdAttribute&
|
|
encodedFeatureIdAttribute :
|
|
loadResult.EncodedMetadata_DEPRECATED->encodedFeatureIdAttributes) {
|
|
const CesiumEncodedMetadataUtility::EncodedMetadataFeatureTable*
|
|
pEncodedFeatureTable =
|
|
gltfComponent.EncodedMetadata_DEPRECATED->encodedFeatureTables.Find(
|
|
encodedFeatureIdAttribute.featureTableName);
|
|
|
|
if (pEncodedFeatureTable) {
|
|
SetMetadataFeatureTableParameterValues_DEPRECATED(
|
|
*pEncodedFeatureTable,
|
|
pMaterial,
|
|
association,
|
|
index);
|
|
}
|
|
}
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#pragma endregion
|
|
|
|
namespace {
|
|
void addInstanceFeatureIds(
|
|
UCesiumGltfInstancedComponent* pInstancedComponent,
|
|
const FCesiumFeaturesMetadataDescription& featuresDescription) {
|
|
const TSharedPtr<FCesiumPrimitiveFeatures>& pInstanceFeatures =
|
|
pInstancedComponent->pInstanceFeatures;
|
|
if (!pInstanceFeatures) {
|
|
return;
|
|
}
|
|
const TArray<FCesiumFeatureIdSet>& allFeatureIdSets =
|
|
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDSets(
|
|
*pInstanceFeatures);
|
|
|
|
const TArray<FCesiumFeatureIdSetDescription>& featureIDSetDescriptions =
|
|
featuresDescription.Features.FeatureIdSets;
|
|
|
|
int32_t featureIdTextureCounter = 0;
|
|
|
|
TArray<int32> activeFeatureIdSets;
|
|
|
|
for (int32 i = 0; i < allFeatureIdSets.Num(); ++i) {
|
|
FString name = CesiumEncodedFeaturesMetadata::getNameForFeatureIDSet(
|
|
allFeatureIdSets[i],
|
|
featureIdTextureCounter);
|
|
|
|
const FCesiumFeatureIdSetDescription* pDescription =
|
|
featureIDSetDescriptions.FindByPredicate(
|
|
[&name](
|
|
const FCesiumFeatureIdSetDescription& existingFeatureIDSet) {
|
|
return existingFeatureIDSet.Name == name;
|
|
});
|
|
|
|
if (pDescription) {
|
|
activeFeatureIdSets.Emplace(i);
|
|
}
|
|
}
|
|
|
|
int32 featureSetCount = activeFeatureIdSets.Num();
|
|
if (featureSetCount == 0) {
|
|
return;
|
|
}
|
|
pInstancedComponent->SetNumCustomDataFloats(featureSetCount);
|
|
int32 numInstances = pInstancedComponent->GetInstanceCount();
|
|
pInstancedComponent->PerInstanceSMCustomData.SetNum(
|
|
featureSetCount * numInstances);
|
|
for (int32 j = 0; j < featureSetCount; ++j) {
|
|
int64_t setIndex = activeFeatureIdSets[j];
|
|
|
|
for (int32 i = 0; i < numInstances; ++i) {
|
|
int64 featureId =
|
|
UCesiumPrimitiveFeaturesBlueprintLibrary::GetFeatureIDFromInstance(
|
|
*pInstanceFeatures,
|
|
i,
|
|
setIndex);
|
|
pInstancedComponent
|
|
->SetCustomDataValue(i, j, static_cast<float>(featureId), true);
|
|
}
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
static void loadPrimitiveGameThreadPart(
|
|
CesiumGltf::Model& model,
|
|
UCesiumGltfComponent* pGltf,
|
|
LoadedPrimitiveResult& loadResult,
|
|
const glm::dmat4x4& cesiumToUnrealTransform,
|
|
const Cesium3DTilesSelection::Tile& tile,
|
|
bool createNavCollision,
|
|
ACesium3DTileset* pTilesetActor,
|
|
const std::vector<FTransform>& instanceTransforms,
|
|
const TSharedPtr<FCesiumPrimitiveFeatures>& pInstanceFeatures) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadPrimitive)
|
|
|
|
#if DEBUG_GLTF_ASSET_NAMES
|
|
FName componentName = createSafeName(loadResult.name, "");
|
|
#else
|
|
FName componentName = "";
|
|
#endif
|
|
|
|
const Cesium3DTilesSelection::BoundingVolume& boundingVolume =
|
|
tile.getContentBoundingVolume().value_or(tile.getBoundingVolume());
|
|
|
|
CesiumGltf::MeshPrimitive& meshPrimitive =
|
|
model.meshes[loadResult.meshIndex].primitives[loadResult.primitiveIndex];
|
|
|
|
UStaticMeshComponent* pMesh = nullptr;
|
|
ICesiumPrimitive* pCesiumPrimitive = nullptr;
|
|
if (meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) {
|
|
UCesiumGltfPointsComponent* pPointMesh =
|
|
NewObject<UCesiumGltfPointsComponent>(pGltf, componentName);
|
|
pPointMesh->UsesAdditiveRefinement =
|
|
tile.getRefine() == Cesium3DTilesSelection::TileRefine::Add;
|
|
pPointMesh->GeometricError = static_cast<float>(tile.getGeometricError());
|
|
pPointMesh->Dimensions = loadResult.dimensions;
|
|
pMesh = pPointMesh;
|
|
pCesiumPrimitive = pPointMesh;
|
|
} else if (!instanceTransforms.empty()) {
|
|
auto* pInstancedComponent =
|
|
NewObject<UCesiumGltfInstancedComponent>(pGltf, componentName);
|
|
pMesh = pInstancedComponent;
|
|
for (const FTransform& transform : instanceTransforms) {
|
|
pInstancedComponent->AddInstance(transform, false);
|
|
}
|
|
pInstancedComponent->pInstanceFeatures = pInstanceFeatures;
|
|
|
|
const std::optional<FCesiumFeaturesMetadataDescription>&
|
|
maybeFeaturesDescription =
|
|
pTilesetActor->getFeaturesMetadataDescription();
|
|
if (maybeFeaturesDescription) {
|
|
addInstanceFeatureIds(pInstancedComponent, *maybeFeaturesDescription);
|
|
}
|
|
|
|
pCesiumPrimitive = pInstancedComponent;
|
|
} else {
|
|
auto* pComponent =
|
|
NewObject<UCesiumGltfPrimitiveComponent>(pGltf, componentName);
|
|
pMesh = pComponent;
|
|
pCesiumPrimitive = pComponent;
|
|
}
|
|
CesiumPrimitiveData& primData = pCesiumPrimitive->getPrimitiveData();
|
|
|
|
UStaticMesh* pStaticMesh;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetupMesh)
|
|
primData.pTilesetActor = pTilesetActor;
|
|
primData.overlayTextureCoordinateIDToUVIndex =
|
|
loadResult.overlayTextureCoordinateIDToUVIndex;
|
|
primData.GltfToUnrealTexCoordMap =
|
|
std::move(loadResult.GltfToUnrealTexCoordMap);
|
|
primData.TexCoordAccessorMap = std::move(loadResult.TexCoordAccessorMap);
|
|
primData.PositionAccessor = std::move(loadResult.PositionAccessor);
|
|
primData.IndexAccessor = std::move(loadResult.IndexAccessor);
|
|
primData.HighPrecisionNodeTransform = loadResult.transform;
|
|
pCesiumPrimitive->UpdateTransformFromCesium(cesiumToUnrealTransform);
|
|
pMesh->bUseDefaultCollision = false;
|
|
pMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
|
|
pMesh->SetFlags(
|
|
RF_Transient | RF_DuplicateTransient | RF_TextExportTransient);
|
|
primData.pModel = &model;
|
|
primData.pMeshPrimitive = &meshPrimitive;
|
|
primData.boundingVolume = boundingVolume;
|
|
pMesh->SetRenderCustomDepth(pGltf->CustomDepthParameters.RenderCustomDepth);
|
|
pMesh->SetCustomDepthStencilWriteMask(
|
|
pGltf->CustomDepthParameters.CustomDepthStencilWriteMask);
|
|
pMesh->SetCustomDepthStencilValue(
|
|
pGltf->CustomDepthParameters.CustomDepthStencilValue);
|
|
if (loadResult.isUnlit) {
|
|
pMesh->bCastDynamicShadow = false;
|
|
}
|
|
|
|
pStaticMesh = NewObject<UStaticMesh>(pMesh, componentName);
|
|
pMesh->SetStaticMesh(pStaticMesh);
|
|
|
|
pStaticMesh->SetFlags(
|
|
RF_Transient | RF_DuplicateTransient | RF_TextExportTransient);
|
|
pStaticMesh->NeverStream = true;
|
|
|
|
pStaticMesh->SetRenderData(std::move(loadResult.RenderData));
|
|
}
|
|
|
|
const CesiumGltf::Material& material =
|
|
loadResult.materialIndex != -1 ? model.materials[loadResult.materialIndex]
|
|
: defaultMaterial;
|
|
|
|
const CesiumGltf::MaterialPBRMetallicRoughness& pbr =
|
|
material.pbrMetallicRoughness ? material.pbrMetallicRoughness.value()
|
|
: defaultPbrMetallicRoughness;
|
|
|
|
const FName ImportedSlotName(
|
|
*(TEXT("CesiumMaterial") + FString::FromInt(nextMaterialId++)));
|
|
|
|
const auto is_in_blend_mode = [&model](auto& result) {
|
|
return result.materialIndex != -1 &&
|
|
model.materials[result.materialIndex].alphaMode ==
|
|
CesiumGltf::Material::AlphaMode::BLEND;
|
|
};
|
|
|
|
#if PLATFORM_MAC
|
|
// TODO: figure out why water material crashes mac
|
|
UMaterialInterface* pBaseMaterial =
|
|
(is_in_blend_mode(loadResult) && pbr.baseColorFactor.size() > 3)
|
|
? pGltf->BaseMaterialWithTranslucency
|
|
: pGltf->BaseMaterial;
|
|
#else
|
|
UMaterialInterface* pBaseMaterial;
|
|
if (loadResult.onlyWater || !loadResult.onlyLand) {
|
|
pBaseMaterial = pGltf->BaseMaterialWithWater;
|
|
} else {
|
|
pBaseMaterial =
|
|
(is_in_blend_mode(loadResult) && pbr.baseColorFactor.size() > 3)
|
|
? pGltf->BaseMaterialWithTranslucency
|
|
: pGltf->BaseMaterial;
|
|
}
|
|
#endif
|
|
|
|
UMaterialInstanceDynamic* pMaterial;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::SetupMaterial)
|
|
|
|
pMaterial = UMaterialInstanceDynamic::Create(
|
|
pBaseMaterial,
|
|
nullptr,
|
|
ImportedSlotName);
|
|
|
|
pMaterial->SetFlags(
|
|
RF_Transient | RF_DuplicateTransient | RF_TextExportTransient);
|
|
SetGltfParameterValues(
|
|
model,
|
|
loadResult,
|
|
material,
|
|
pbr,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::GlobalParameter,
|
|
INDEX_NONE);
|
|
SetWaterParameterValues(
|
|
model,
|
|
loadResult,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::GlobalParameter,
|
|
INDEX_NONE);
|
|
|
|
UMaterialInstance* pBaseAsMaterialInstance =
|
|
Cast<UMaterialInstance>(pBaseMaterial);
|
|
UCesiumMaterialUserData* pCesiumData =
|
|
pBaseAsMaterialInstance
|
|
? pBaseAsMaterialInstance
|
|
->GetAssetUserData<UCesiumMaterialUserData>()
|
|
: nullptr;
|
|
|
|
// If possible and necessary, attach the CesiumMaterialUserData now.
|
|
#if WITH_EDITORONLY_DATA
|
|
if (pBaseAsMaterialInstance && !pCesiumData) {
|
|
const FStaticParameterSet& parameters =
|
|
pBaseAsMaterialInstance->GetStaticParameters();
|
|
|
|
bool hasLayers = parameters.bHasMaterialLayers;
|
|
if (hasLayers) {
|
|
#if WITH_EDITOR
|
|
FScopedTransaction transaction(
|
|
FText::FromString("Add Cesium User Data to Material"));
|
|
pBaseAsMaterialInstance->Modify();
|
|
#endif
|
|
pCesiumData = NewObject<UCesiumMaterialUserData>(
|
|
pBaseAsMaterialInstance,
|
|
NAME_None,
|
|
RF_Transactional);
|
|
pBaseAsMaterialInstance->AddAssetUserData(pCesiumData);
|
|
pCesiumData->PostEditChangeOwner();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (pCesiumData) {
|
|
SetGltfParameterValues(
|
|
model,
|
|
loadResult,
|
|
material,
|
|
pbr,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
0);
|
|
|
|
// Initialize fade uniform to fully visible, in case LOD transitions
|
|
// are off.
|
|
int fadeLayerIndex = pCesiumData->LayerNames.Find("DitherFade");
|
|
if (fadeLayerIndex >= 0) {
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"FadePercentage",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
fadeLayerIndex),
|
|
1.0f);
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"FadingType",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
fadeLayerIndex),
|
|
0.0f);
|
|
}
|
|
|
|
// If there's a "Water" layer, set its parameters
|
|
int32 waterIndex = pCesiumData->LayerNames.Find("Water");
|
|
if (waterIndex >= 0) {
|
|
SetWaterParameterValues(
|
|
model,
|
|
loadResult,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
waterIndex);
|
|
}
|
|
|
|
int32 featuresMetadataIndex =
|
|
pCesiumData->LayerNames.Find("FeaturesMetadata");
|
|
int32 metadataIndex = pCesiumData->LayerNames.Find("Metadata");
|
|
if (featuresMetadataIndex >= 0) {
|
|
SetFeaturesMetadataParameterValues(
|
|
model,
|
|
*pGltf,
|
|
loadResult,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
featuresMetadataIndex);
|
|
} else if (metadataIndex >= 0) {
|
|
// Set parameters for materials generated by the old implementation
|
|
SetMetadataParameterValues_DEPRECATED(
|
|
model,
|
|
*pGltf,
|
|
loadResult,
|
|
pMaterial,
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
metadataIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
primData.Features = std::move(loadResult.Features);
|
|
primData.Metadata = std::move(loadResult.Metadata);
|
|
|
|
primData.EncodedFeatures = std::move(loadResult.EncodedFeatures);
|
|
primData.EncodedMetadata = std::move(loadResult.EncodedMetadata);
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
// Doing the above std::move operations invalidates the pointers in the
|
|
// FCesiumMetadataPrimitive constructed on the loadResult. It's a bit
|
|
// awkward, but we have to reconstruct the metadata primitive here.
|
|
primData.Metadata_DEPRECATED = FCesiumMetadataPrimitive{
|
|
primData.Features,
|
|
primData.Metadata,
|
|
pGltf->Metadata};
|
|
|
|
if (loadResult.EncodedMetadata_DEPRECATED) {
|
|
primData.EncodedMetadata_DEPRECATED =
|
|
std::move(loadResult.EncodedMetadata_DEPRECATED);
|
|
}
|
|
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
pMaterial->TwoSided = true;
|
|
|
|
pStaticMesh->AddMaterial(pMaterial);
|
|
|
|
pStaticMesh->SetLightingGuid();
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::InitResources)
|
|
pStaticMesh->InitResources();
|
|
}
|
|
|
|
// Set up RenderData bounds and LOD data
|
|
pStaticMesh->CalculateExtendedBounds();
|
|
pStaticMesh->GetRenderData()->ScreenSize[0].Default = 1.0f;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::BodySetup)
|
|
|
|
pStaticMesh->CreateBodySetup();
|
|
|
|
UBodySetup* pBodySetup = pMesh->GetBodySetup();
|
|
|
|
// pMesh->UpdateCollisionFromStaticMesh();
|
|
pBodySetup->CollisionTraceFlag =
|
|
ECollisionTraceFlag::CTF_UseComplexAsSimple;
|
|
|
|
if (loadResult.pCollisionMesh) {
|
|
#if ENGINE_VERSION_5_4_OR_HIGHER
|
|
pBodySetup->TriMeshGeometries.Add(loadResult.pCollisionMesh);
|
|
#else
|
|
pBodySetup->ChaosTriMeshes.Add(loadResult.pCollisionMesh);
|
|
#endif
|
|
}
|
|
|
|
// Mark physics meshes created, no matter if we actually have a collision
|
|
// mesh or not. We don't want the editor creating collision meshes itself in
|
|
// the game thread, because that would be slow.
|
|
pBodySetup->bCreatedPhysicsMeshes = true;
|
|
pBodySetup->bSupportUVsAndFaceRemap =
|
|
UPhysicsSettings::Get()->bSupportUVFromHitResults;
|
|
}
|
|
|
|
if (createNavCollision) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::CreateNavCollision)
|
|
pStaticMesh->CreateNavCollision(true);
|
|
}
|
|
|
|
pMesh->SetMobility(pGltf->Mobility);
|
|
|
|
pMesh->SetupAttachment(pGltf);
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::RegisterComponent)
|
|
pMesh->RegisterComponent();
|
|
}
|
|
}
|
|
|
|
/*static*/ CesiumAsync::Future<UCesiumGltfComponent::CreateOffGameThreadResult>
|
|
UCesiumGltfComponent::CreateOffGameThread(
|
|
const CesiumAsync::AsyncSystem& AsyncSystem,
|
|
const glm::dmat4x4& Transform,
|
|
CreateModelOptions&& Options,
|
|
const CesiumGeospatial::Ellipsoid& Ellipsoid) {
|
|
return loadModelAnyThreadPart(
|
|
AsyncSystem,
|
|
Transform,
|
|
std::move(Options),
|
|
Ellipsoid);
|
|
}
|
|
|
|
/*static*/ UCesiumGltfComponent* UCesiumGltfComponent::CreateOnGameThread(
|
|
CesiumGltf::Model& model,
|
|
ACesium3DTileset* pTilesetActor,
|
|
TUniquePtr<HalfConstructed> pHalfConstructed,
|
|
const glm::dmat4x4& cesiumToUnrealTransform,
|
|
UMaterialInterface* pBaseMaterial,
|
|
UMaterialInterface* pBaseTranslucentMaterial,
|
|
UMaterialInterface* pBaseWaterMaterial,
|
|
FCustomDepthParameters CustomDepthParameters,
|
|
const Cesium3DTilesSelection::Tile& tile,
|
|
bool createNavCollision) {
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::LoadModel)
|
|
|
|
HalfConstructedReal* pReal =
|
|
static_cast<HalfConstructedReal*>(pHalfConstructed.Get());
|
|
|
|
// TODO: was this a common case before?
|
|
// (This code checked if there were no loaded primitives in the model)
|
|
// if (result.size() == 0) {
|
|
// return nullptr;
|
|
// }
|
|
|
|
UCesiumGltfComponent* Gltf = NewObject<UCesiumGltfComponent>(pTilesetActor);
|
|
Gltf->SetMobility(pTilesetActor->GetRootComponent()->Mobility);
|
|
Gltf->SetFlags(RF_Transient | RF_DuplicateTransient | RF_TextExportTransient);
|
|
|
|
Gltf->Metadata = std::move(pReal->loadModelResult.Metadata);
|
|
Gltf->EncodedMetadata = std::move(pReal->loadModelResult.EncodedMetadata);
|
|
Gltf->EncodedMetadata_DEPRECATED =
|
|
std::move(pReal->loadModelResult.EncodedMetadata_DEPRECATED);
|
|
|
|
if (pBaseMaterial) {
|
|
Gltf->BaseMaterial = pBaseMaterial;
|
|
}
|
|
|
|
if (pBaseTranslucentMaterial) {
|
|
Gltf->BaseMaterialWithTranslucency = pBaseTranslucentMaterial;
|
|
}
|
|
|
|
if (pBaseWaterMaterial) {
|
|
Gltf->BaseMaterialWithWater = pBaseWaterMaterial;
|
|
}
|
|
|
|
Gltf->CustomDepthParameters = CustomDepthParameters;
|
|
|
|
encodeModelMetadataGameThreadPart(Gltf->EncodedMetadata);
|
|
|
|
if (Gltf->EncodedMetadata_DEPRECATED) {
|
|
encodeMetadataGameThreadPart(*Gltf->EncodedMetadata_DEPRECATED);
|
|
}
|
|
|
|
for (LoadedNodeResult& node : pReal->loadModelResult.nodeResults) {
|
|
if (node.meshResult) {
|
|
for (LoadedPrimitiveResult& primitive :
|
|
node.meshResult->primitiveResults) {
|
|
loadPrimitiveGameThreadPart(
|
|
model,
|
|
Gltf,
|
|
primitive,
|
|
cesiumToUnrealTransform,
|
|
tile,
|
|
createNavCollision,
|
|
pTilesetActor,
|
|
node.InstanceTransforms,
|
|
node.pInstanceFeatures);
|
|
}
|
|
}
|
|
}
|
|
|
|
Gltf->SetVisibility(false, true);
|
|
Gltf->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
return Gltf;
|
|
}
|
|
|
|
UCesiumGltfComponent::UCesiumGltfComponent() : USceneComponent() {
|
|
// Structure to hold one-time initialization
|
|
struct FConstructorStatics {
|
|
ConstructorHelpers::FObjectFinder<UMaterialInstance> BaseMaterial;
|
|
ConstructorHelpers::FObjectFinder<UMaterialInstance>
|
|
BaseMaterialWithTranslucency;
|
|
ConstructorHelpers::FObjectFinder<UMaterialInstance> BaseMaterialWithWater;
|
|
ConstructorHelpers::FObjectFinder<UTexture2D> Transparent1x1;
|
|
FConstructorStatics()
|
|
: BaseMaterial(TEXT(
|
|
"/CesiumForUnreal/Materials/Instances/MI_CesiumThreeOverlaysAndClipping.MI_CesiumThreeOverlaysAndClipping")),
|
|
BaseMaterialWithTranslucency(TEXT(
|
|
"/CesiumForUnreal/Materials/Instances/MI_CesiumThreeOverlaysAndClippingTranslucent.MI_CesiumThreeOverlaysAndClippingTranslucent")),
|
|
BaseMaterialWithWater(TEXT(
|
|
"/CesiumForUnreal/Materials/Instances/MI_CesiumThreeOverlaysAndClippingAndWater.MI_CesiumThreeOverlaysAndClippingAndWater")),
|
|
Transparent1x1(
|
|
TEXT("/CesiumForUnreal/Textures/transparent1x1.transparent1x1")) {
|
|
}
|
|
};
|
|
static FConstructorStatics ConstructorStatics;
|
|
|
|
this->BaseMaterial = ConstructorStatics.BaseMaterial.Object;
|
|
this->BaseMaterialWithTranslucency =
|
|
ConstructorStatics.BaseMaterialWithTranslucency.Object;
|
|
this->BaseMaterialWithWater = ConstructorStatics.BaseMaterialWithWater.Object;
|
|
this->Transparent1x1 = ConstructorStatics.Transparent1x1.Object;
|
|
|
|
PrimaryComponentTick.bCanEverTick = false;
|
|
}
|
|
|
|
void UCesiumGltfComponent::UpdateTransformFromCesium(
|
|
const glm::dmat4& cesiumToUnrealTransform) {
|
|
for (USceneComponent* pSceneComponent : this->GetAttachChildren()) {
|
|
if (auto* pCesiumPrimitive = Cast<ICesiumPrimitive>(pSceneComponent)) {
|
|
pCesiumPrimitive->UpdateTransformFromCesium(cesiumToUnrealTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
template <typename Func>
|
|
void forEachPrimitiveComponent(UCesiumGltfComponent* pGltf, Func&& f) {
|
|
for (USceneComponent* pSceneComponent : pGltf->GetAttachChildren()) {
|
|
UCesiumGltfPrimitiveComponent* pPrimitive =
|
|
Cast<UCesiumGltfPrimitiveComponent>(pSceneComponent);
|
|
if (pPrimitive) {
|
|
UMaterialInstanceDynamic* pMaterial =
|
|
Cast<UMaterialInstanceDynamic>(pPrimitive->GetMaterial(0));
|
|
|
|
if (!IsValid(pMaterial) || pMaterial->IsUnreachable()) {
|
|
// Don't try to update the material while it's in the process of
|
|
// being destroyed. This can lead to the render thread freaking out
|
|
// when it's asked to update a parameter for a material that has
|
|
// been marked for garbage collection.
|
|
continue;
|
|
}
|
|
|
|
UMaterialInterface* pBaseMaterial = pMaterial->Parent;
|
|
UMaterialInstance* pBaseAsMaterialInstance =
|
|
Cast<UMaterialInstance>(pBaseMaterial);
|
|
UCesiumMaterialUserData* pCesiumData =
|
|
pBaseAsMaterialInstance
|
|
? pBaseAsMaterialInstance
|
|
->GetAssetUserData<UCesiumMaterialUserData>()
|
|
: nullptr;
|
|
|
|
f(pPrimitive, pMaterial, pCesiumData);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
} // namespace
|
|
|
|
void UCesiumGltfComponent::AttachRasterTile(
|
|
const Cesium3DTilesSelection::Tile& tile,
|
|
const CesiumRasterOverlays::RasterOverlayTile& rasterTile,
|
|
UTexture2D* pTexture,
|
|
const glm::dvec2& translation,
|
|
const glm::dvec2& scale,
|
|
int32 textureCoordinateID) {
|
|
FVector4 translationAndScale(translation.x, translation.y, scale.x, scale.y);
|
|
|
|
forEachPrimitiveComponent(
|
|
this,
|
|
[&rasterTile, pTexture, &translationAndScale, textureCoordinateID](
|
|
UCesiumGltfPrimitiveComponent* pPrimitive,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
UCesiumMaterialUserData* pCesiumData) {
|
|
CesiumPrimitiveData& primData = pPrimitive->getPrimitiveData();
|
|
// If this material uses material layers and has the Cesium user data,
|
|
// set the parameters on each material layer that maps to this overlay
|
|
// tile.
|
|
if (pCesiumData) {
|
|
FString name(
|
|
UTF8_TO_TCHAR(rasterTile.getOverlay().getName().c_str()));
|
|
|
|
for (int32 i = 0; i < pCesiumData->LayerNames.Num(); ++i) {
|
|
if (pCesiumData->LayerNames[i] != name) {
|
|
continue;
|
|
}
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"Texture",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
i),
|
|
pTexture);
|
|
pMaterial->SetVectorParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"TranslationScale",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
i),
|
|
translationAndScale);
|
|
check(
|
|
textureCoordinateID >= 0 &&
|
|
textureCoordinateID <
|
|
primData.overlayTextureCoordinateIDToUVIndex.size());
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"TextureCoordinateIndex",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
i),
|
|
static_cast<float>(primData.overlayTextureCoordinateIDToUVIndex
|
|
[textureCoordinateID]));
|
|
}
|
|
} else {
|
|
pMaterial->SetTextureParameterValue(
|
|
createSafeName(rasterTile.getOverlay().getName(), "_Texture"),
|
|
pTexture);
|
|
pMaterial->SetVectorParameterValue(
|
|
createSafeName(
|
|
rasterTile.getOverlay().getName(),
|
|
"_TranslationScale"),
|
|
translationAndScale);
|
|
pMaterial->SetScalarParameterValue(
|
|
createSafeName(
|
|
rasterTile.getOverlay().getName(),
|
|
"_TextureCoordinateIndex"),
|
|
static_cast<float>(primData.overlayTextureCoordinateIDToUVIndex
|
|
[textureCoordinateID]));
|
|
}
|
|
});
|
|
}
|
|
|
|
void UCesiumGltfComponent::DetachRasterTile(
|
|
const Cesium3DTilesSelection::Tile& tile,
|
|
const CesiumRasterOverlays::RasterOverlayTile& rasterTile,
|
|
UTexture2D* pTexture) {
|
|
forEachPrimitiveComponent(
|
|
this,
|
|
[this, &rasterTile, pTexture](
|
|
UCesiumGltfPrimitiveComponent* pPrimitive,
|
|
UMaterialInstanceDynamic* pMaterial,
|
|
UCesiumMaterialUserData* pCesiumData) {
|
|
// If this material uses material layers and has the Cesium user data,
|
|
// clear the parameters on each material layer that maps to this
|
|
// overlay tile.
|
|
if (pCesiumData) {
|
|
FString name(
|
|
UTF8_TO_TCHAR(rasterTile.getOverlay().getName().c_str()));
|
|
for (int32 i = 0; i < pCesiumData->LayerNames.Num(); ++i) {
|
|
if (pCesiumData->LayerNames[i] != name) {
|
|
continue;
|
|
}
|
|
|
|
pMaterial->SetTextureParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"Texture",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
i),
|
|
this->Transparent1x1);
|
|
}
|
|
} else {
|
|
pMaterial->SetTextureParameterValue(
|
|
createSafeName(rasterTile.getOverlay().getName(), "_Texture"),
|
|
this->Transparent1x1);
|
|
}
|
|
});
|
|
}
|
|
|
|
void UCesiumGltfComponent::SetCollisionEnabled(
|
|
ECollisionEnabled::Type NewType) {
|
|
for (USceneComponent* pSceneComponent : this->GetAttachChildren()) {
|
|
UCesiumGltfPrimitiveComponent* pPrimitive =
|
|
Cast<UCesiumGltfPrimitiveComponent>(pSceneComponent);
|
|
if (pPrimitive) {
|
|
pPrimitive->SetCollisionEnabled(NewType);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCesiumGltfComponent::BeginDestroy() {
|
|
// Clear everything we can in order to reduce memory usage, because this
|
|
// UObject might not actually get deleted by the garbage collector until
|
|
// much later.
|
|
this->Metadata = FCesiumModelMetadata();
|
|
this->EncodedMetadata = CesiumEncodedFeaturesMetadata::EncodedModelMetadata();
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
this->EncodedMetadata_DEPRECATED.reset();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
Super::BeginDestroy();
|
|
}
|
|
|
|
void UCesiumGltfComponent::UpdateFade(float fadePercentage, bool fadingIn) {
|
|
if (!this->IsVisible()) {
|
|
return;
|
|
}
|
|
|
|
UCesiumMaterialUserData* pCesiumData =
|
|
BaseMaterial->GetAssetUserData<UCesiumMaterialUserData>();
|
|
|
|
if (!pCesiumData) {
|
|
return;
|
|
}
|
|
|
|
int fadeLayerIndex = pCesiumData->LayerNames.Find("DitherFade");
|
|
if (fadeLayerIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
fadePercentage = glm::clamp(fadePercentage, 0.0f, 1.0f);
|
|
|
|
for (USceneComponent* pChild : this->GetAttachChildren()) {
|
|
UCesiumGltfPrimitiveComponent* pPrimitive =
|
|
Cast<UCesiumGltfPrimitiveComponent>(pChild);
|
|
if (!pPrimitive || pPrimitive->GetMaterials().IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
UMaterialInstanceDynamic* pMaterial =
|
|
Cast<UMaterialInstanceDynamic>(pPrimitive->GetMaterials()[0]);
|
|
if (!pMaterial) {
|
|
continue;
|
|
}
|
|
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"FadePercentage",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
fadeLayerIndex),
|
|
fadePercentage);
|
|
pMaterial->SetScalarParameterValueByInfo(
|
|
FMaterialParameterInfo(
|
|
"FadingType",
|
|
EMaterialParameterAssociation::LayerParameter,
|
|
fadeLayerIndex),
|
|
fadingIn ? 0.0f : 1.0f);
|
|
}
|
|
}
|
|
|
|
template <typename TIndex>
|
|
#if ENGINE_VERSION_5_4_OR_HIGHER
|
|
static Chaos::FTriangleMeshImplicitObjectPtr
|
|
#else
|
|
static TSharedPtr<Chaos::FTriangleMeshImplicitObject, ESPMode::ThreadSafe>
|
|
#endif
|
|
BuildChaosTriangleMeshes(
|
|
const TArray<FStaticMeshBuildVertex>& vertexData,
|
|
const TArray<uint32>& indices) {
|
|
int32 vertexCount = vertexData.Num();
|
|
Chaos::TParticles<Chaos::FRealSingle, 3> vertices;
|
|
vertices.AddParticles(vertexCount);
|
|
for (int32 i = 0; i < vertexCount; ++i) {
|
|
vertices.X(i) = vertexData[i].Position;
|
|
}
|
|
|
|
int32 triangleCount = indices.Num() / 3;
|
|
TArray<Chaos::TVector<TIndex, 3>> triangles;
|
|
triangles.Reserve(triangleCount);
|
|
TArray<int32> faceRemap;
|
|
faceRemap.Reserve(triangleCount);
|
|
|
|
for (int32 i = 0; i < triangleCount; ++i) {
|
|
const int32 index0 = 3 * i;
|
|
int32 vIndex0 = indices[index0 + 1];
|
|
int32 vIndex1 = indices[index0];
|
|
int32 vIndex2 = indices[index0 + 2];
|
|
|
|
triangles.Add(Chaos::TVector<int32, 3>(vIndex0, vIndex1, vIndex2));
|
|
faceRemap.Add(i);
|
|
}
|
|
|
|
TUniquePtr<TArray<int32>> pFaceRemap = MakeUnique<TArray<int32>>(faceRemap);
|
|
TArray<uint16> materials;
|
|
materials.SetNum(triangles.Num());
|
|
|
|
#if ENGINE_VERSION_5_4_OR_HIGHER
|
|
return new Chaos::FTriangleMeshImplicitObject(
|
|
MoveTemp(vertices),
|
|
MoveTemp(triangles),
|
|
MoveTemp(materials),
|
|
MoveTemp(pFaceRemap),
|
|
nullptr,
|
|
false);
|
|
#else
|
|
return MakeShared<Chaos::FTriangleMeshImplicitObject, ESPMode::ThreadSafe>(
|
|
MoveTemp(vertices),
|
|
MoveTemp(triangles),
|
|
MoveTemp(materials),
|
|
MoveTemp(pFaceRemap),
|
|
nullptr,
|
|
false);
|
|
#endif
|
|
}
|