Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log {#changes}

### ? - ?

##### Additions :tada:

- Added support for rendering glTFs with line primitives.

### v2.19.1 - 2025-09-02

##### Fixes :wrench:
Expand Down
161 changes: 86 additions & 75 deletions Source/CesiumRuntime/Private/CesiumGltfComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "CesiumCommon.h"
#include "CesiumEncodedMetadataUtility.h"
#include "CesiumFeatureIdSet.h"
#include "CesiumGltfLinesComponent.h"
#include "CesiumGltfPointsComponent.h"
#include "CesiumGltfPrimitiveComponent.h"
#include "CesiumGltfTextures.h"
Expand Down Expand Up @@ -682,14 +683,21 @@ static void setUnlitNormals(
glm::dvec4(
VecMath::createVector3D(FVector(positionBuffer.VertexPosition(i))),
1.0));
glm::dvec3 normal = ellipsoid.geodeticSurfaceNormal(positionFixed);

glm::dvec3 surfaceNormal = ellipsoid.geodeticSurfaceNormal(positionFixed);

if (surfaceNormal == glm::dvec3(0.0)) {
// This can happen for tilesets georeferenced at the center of the earth,
// resulting in NaN normals. Manually assign a normal aligned with Z-up.
surfaceNormal = glm::dvec3(0.0, 0.0, 1.0);
}

normalBuffer.SetVertexTangents(
i,
FVector3f(0.0f),
FVector3f(0.0),
FVector3f(VecMath::createVector(
glm::normalize(ellipsoidFixedToVertex * glm::dvec4(normal, 0.0)))));
FVector3f(VecMath::createVector(glm::normalize(
ellipsoidFixedToVertex * glm::dvec4(surfaceNormal, 0.0)))));
}
}

Expand Down Expand Up @@ -1259,36 +1267,6 @@ std::string getPrimitiveName(
return name;
}

/// Helper used to log only once per unsupported primitive mode.
struct PrimitiveModeLogger {
std::array<
std::atomic_bool,
(size_t)CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN + 1>
alreadyLogged;

PrimitiveModeLogger()
: alreadyLogged{
{{false}, {false}, {false}, {false}, {false}, {false}, {false}}} {}

inline void OnUnsupportedMode(int32_t primMode) {
bool bPrintLog = false;
if (primMode < 0 || primMode >= (int32_t)alreadyLogged.size()) {
ensureMsgf(false, TEXT("Unknown primitive mode %d!"), primMode);
bPrintLog = true;
} else if (!alreadyLogged[(size_t)primMode].exchange(true)) {
bPrintLog = true;
}
if (bPrintLog) {
UE_LOG(
LogCesium,
Warning,
TEXT("Primitive mode %d is not supported"),
primMode);
}
}
};
static PrimitiveModeLogger UnsupportedPrimitiveLogger;

template <class TIndexAccessor>
TArray<uint32>
getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
Expand All @@ -1315,9 +1293,8 @@ getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
}
break;
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
// The TRIANGLE_FAN primitive mode cannot be enabled without creating a
// custom render proxy, so the geometry must be emulated through separate
// triangles.
// The TRIANGLE_FAN primitive mode is not supported in Unreal, so geometry
// must be emulated through separate triangles.
indices.SetNum(
static_cast<TArray<uint32>::SizeType>(3 * (indicesView.size() - 2)));
for (int32 i = 2, j = 0; i < indicesView.size(); ++i, j += 3) {
Expand All @@ -1326,7 +1303,31 @@ getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
indices[j + 2] = indicesView[i];
}
break;
case CesiumGltf::MeshPrimitive::Mode::LINE_LOOP:
// The LINE_LOOP primitive mode is not supported in Unreal, so geometry must
// be emulated through separate lines.
indices.SetNum(
static_cast<TArray<uint32>::SizeType>(2 * indicesView.size()));
for (int32 i = 0, j = 0; i < indicesView.size(); ++i, j += 2) {
// Loop to the first index once we reach the last line segment.
size_t nextIndex = (i < indicesView.size() - 1) ? i + 1 : 0;

indices[j] = indicesView[i];
indices[j + 1] = indicesView[nextIndex];
}
break;
case CesiumGltf::MeshPrimitive::Mode::LINE_STRIP:
// The LINE_STRIP primitive mode is not supported in Unreal, so geometry
// must be emulated through separate lines.
indices.SetNum(
static_cast<TArray<uint32>::SizeType>(2 * (indicesView.size() - 1)));
for (int32 i = 0, j = 0; i < indicesView.size() - 1; ++i, j += 2) {
indices[j] = indicesView[i];
indices[j + 1] = indicesView[i + 1];
}
break;
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
case CesiumGltf::MeshPrimitive::Mode::LINES:
case CesiumGltf::MeshPrimitive::Mode::POINTS:
default:
indices.SetNum(static_cast<TArray<uint32>::SizeType>(indicesView.size()));
Expand Down Expand Up @@ -1358,18 +1359,6 @@ static void loadPrimitive(
CesiumGltf::MeshPrimitive& primitive =
mesh.primitives[options.primitiveIndex];

switch (primitive.mode) {
case CesiumGltf::MeshPrimitive::Mode::POINTS:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
break;
default:
// TODO: add support for other primitive types.
UnsupportedPrimitiveLogger.OnUnsupportedMode(primitive.mode);
return;
}

const std::string name = getPrimitiveName(model, mesh, primitive);
primitiveResult.name = name;

Expand Down Expand Up @@ -1426,14 +1415,14 @@ static void loadPrimitive(
!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;

// 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.
if (!isTriangles && !hasNormals) {
primitiveResult.isUnlit = true;
}
Expand Down Expand Up @@ -1536,12 +1525,23 @@ static void loadPrimitive(
// 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 normalsAreRequired = !primitiveResult.isUnlit && isTriangles;
bool needToGenerateFlatNormals = normalsAreRequired && !hasNormals;
bool needToGenerateTangents = needsTangents && !hasTangents;
bool duplicateVertices = needToGenerateFlatNormals || needToGenerateTangents;
duplicateVertices = duplicateVertices &&
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;

// Some primitive modes may require duplication of vertices anyways due to
// the lack of support in Unreal.
switch (primitive.mode) {
case CesiumGltf::MeshPrimitive::Mode::LINE_LOOP:
case CesiumGltf::MeshPrimitive::Mode::LINE_STRIP:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
duplicateVertices = true;
break;
default:
break;
}

uint32 numVertices =
duplicateVertices ? uint32(indices.Num()) : uint32(positionView.size());
Expand Down Expand Up @@ -1707,16 +1707,14 @@ static void loadPrimitive(
FVector3f(normal.X, -normal.Y, normal.Z));
}
}
} else if (primitiveResult.isUnlit || !isTriangles) {
setUnlitNormals(
LODResources.VertexBuffers,
ellipsoid,
transform * yInvertMatrix * scaleMatrix);
} else {
if (primitiveResult.isUnlit) {
setUnlitNormals(
LODResources.VertexBuffers,
ellipsoid,
transform * yInvertMatrix * scaleMatrix);
} else {
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals)
computeFlatNormals(LODResources.VertexBuffers);
}
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals)
computeFlatNormals(LODResources.VertexBuffers);
}

if (hasTangents) {
Expand Down Expand Up @@ -1753,13 +1751,12 @@ static void loadPrimitive(

FStaticMeshSectionArray& Sections = LODResources.Sections;
FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
// This will be ignored if the primitive contains points.
// This will be ignored if the primitive contains lines or points.
section.NumTriangles = indices.Num() / 3;
section.FirstIndex = 0;
section.MinVertexIndex = 0;
section.MaxVertexIndex = numVertices - 1;
section.bEnableCollision =
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
section.bEnableCollision = isTriangles;
section.bCastShadow = true;
section.MaterialIndex = 0;

Expand All @@ -1784,9 +1781,7 @@ static void loadPrimitive(
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.
if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS) {
if (isTriangles) {
// UE 5.5 requires that we do this in order to avoid a crash when ray
// tracing is enabled.
RenderData->InitializeRayTracingRepresentationFromRenderingLODs();
Expand All @@ -1800,7 +1795,7 @@ static void loadPrimitive(

primitiveResult.transform = transform * yInvertMatrix * scaleMatrix;

if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS &&
if (isTriangles &&
options.pMeshOptions->pNodeOptions->pModelOptions->createPhysicsMeshes) {
if (numVertices != 0 && indices.Num() != 0) {
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ChaosCook)
Expand Down Expand Up @@ -3040,6 +3035,7 @@ static void loadPrimitiveGameThreadPart(

UStaticMeshComponent* pMesh = nullptr;
ICesiumPrimitive* pCesiumPrimitive = nullptr;

if (meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) {
UCesiumGltfPointsComponent* pPointMesh =
NewObject<UCesiumGltfPointsComponent>(pGltf, componentName);
Expand All @@ -3049,6 +3045,14 @@ static void loadPrimitiveGameThreadPart(
pPointMesh->Dimensions = loadResult.dimensions;
pMesh = pPointMesh;
pCesiumPrimitive = pPointMesh;
} else if (
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINES ||
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINE_LOOP ||
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINE_STRIP) {
UCesiumGltfLinesComponent* pLineMesh =
NewObject<UCesiumGltfLinesComponent>(pGltf, componentName);
pMesh = pLineMesh;
pCesiumPrimitive = pLineMesh;
} else if (!instanceTransforms.empty()) {
auto* pInstancedComponent =
NewObject<UCesiumGltfInstancedComponent>(pGltf, componentName);
Expand Down Expand Up @@ -3110,11 +3114,18 @@ static void loadPrimitiveGameThreadPart(
primData.pTilesetActor->GetTranslucencySortPriority();

pStaticMesh = NewObject<UStaticMesh>(pMesh, componentName);
// Not only does the concept of ray tracing a point cloud not make much
// sense, but if Unreal will crash trying to generate ray tracing
// information for a static mesh without triangles.
pStaticMesh->bSupportRayTracing =
meshPrimitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
// Unreal will crash trying to generate ray tracing information for a static
// mesh without triangles (and it doesn't make sense anyways!)
switch (meshPrimitive.mode) {
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
pStaticMesh->bSupportRayTracing = true;
break;
default:
pStaticMesh->bSupportRayTracing = false;
break;
}
pMesh->SetStaticMesh(pStaticMesh);

pStaticMesh->SetFlags(
Expand Down
18 changes: 18 additions & 0 deletions Source/CesiumRuntime/Private/CesiumGltfLinesComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2020-2024 CesiumGS, Inc. and Contributors

#include "CesiumGltfLinesComponent.h"
#include "CesiumGltfLinesSceneProxy.h"
#include "SceneInterface.h"

// Sets default values for this component's properties
UCesiumGltfLinesComponent::UCesiumGltfLinesComponent() {}

UCesiumGltfLinesComponent::~UCesiumGltfLinesComponent() {}

FPrimitiveSceneProxy* UCesiumGltfLinesComponent::CreateSceneProxy() {
if (!IsValid(this)) {
return nullptr;
}

return new FCesiumGltfLinesSceneProxy(this, GetScene()->GetFeatureLevel());
}
22 changes: 22 additions & 0 deletions Source/CesiumRuntime/Private/CesiumGltfLinesComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2020-2024 CesiumGS, Inc. and Contributors

#pragma once

#include "CesiumGltfPrimitiveComponent.h"
#include "CesiumGltfLinesComponent.generated.h"

/**
* A component that represents and renders a glTF lines primitive.
*/
UCLASS()
class UCesiumGltfLinesComponent : public UCesiumGltfPrimitiveComponent {
GENERATED_BODY()

public:
// Sets default values for this component's properties
UCesiumGltfLinesComponent();
virtual ~UCesiumGltfLinesComponent();

// Override UPrimitiveComponent interface.
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
};
Loading