Skip to content

Commit ca73c60

Browse files
authored
Merge pull request #1736 from CesiumGS/gltf-lines
Add support for glTF line primitives
2 parents 18baaa1 + 4660e74 commit ca73c60

File tree

6 files changed

+276
-75
lines changed

6 files changed

+276
-75
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change Log {#changes}
22

3+
### ? - ?
4+
5+
##### Additions :tada:
6+
7+
- Added support for rendering glTFs with line primitives.
8+
39
### v2.19.1 - 2025-09-02
410

511
##### Fixes :wrench:

Source/CesiumRuntime/Private/CesiumGltfComponent.cpp

Lines changed: 86 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "CesiumCommon.h"
66
#include "CesiumEncodedMetadataUtility.h"
77
#include "CesiumFeatureIdSet.h"
8+
#include "CesiumGltfLinesComponent.h"
89
#include "CesiumGltfPointsComponent.h"
910
#include "CesiumGltfPrimitiveComponent.h"
1011
#include "CesiumGltfTextures.h"
@@ -682,14 +683,21 @@ static void setUnlitNormals(
682683
glm::dvec4(
683684
VecMath::createVector3D(FVector(positionBuffer.VertexPosition(i))),
684685
1.0));
685-
glm::dvec3 normal = ellipsoid.geodeticSurfaceNormal(positionFixed);
686+
687+
glm::dvec3 surfaceNormal = ellipsoid.geodeticSurfaceNormal(positionFixed);
688+
689+
if (surfaceNormal == glm::dvec3(0.0)) {
690+
// This can happen for tilesets georeferenced at the center of the earth,
691+
// resulting in NaN normals. Manually assign a normal aligned with Z-up.
692+
surfaceNormal = glm::dvec3(0.0, 0.0, 1.0);
693+
}
686694

687695
normalBuffer.SetVertexTangents(
688696
i,
689697
FVector3f(0.0f),
690698
FVector3f(0.0),
691-
FVector3f(VecMath::createVector(
692-
glm::normalize(ellipsoidFixedToVertex * glm::dvec4(normal, 0.0)))));
699+
FVector3f(VecMath::createVector(glm::normalize(
700+
ellipsoidFixedToVertex * glm::dvec4(surfaceNormal, 0.0)))));
693701
}
694702
}
695703

@@ -1259,36 +1267,6 @@ std::string getPrimitiveName(
12591267
return name;
12601268
}
12611269

1262-
/// Helper used to log only once per unsupported primitive mode.
1263-
struct PrimitiveModeLogger {
1264-
std::array<
1265-
std::atomic_bool,
1266-
(size_t)CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN + 1>
1267-
alreadyLogged;
1268-
1269-
PrimitiveModeLogger()
1270-
: alreadyLogged{
1271-
{{false}, {false}, {false}, {false}, {false}, {false}, {false}}} {}
1272-
1273-
inline void OnUnsupportedMode(int32_t primMode) {
1274-
bool bPrintLog = false;
1275-
if (primMode < 0 || primMode >= (int32_t)alreadyLogged.size()) {
1276-
ensureMsgf(false, TEXT("Unknown primitive mode %d!"), primMode);
1277-
bPrintLog = true;
1278-
} else if (!alreadyLogged[(size_t)primMode].exchange(true)) {
1279-
bPrintLog = true;
1280-
}
1281-
if (bPrintLog) {
1282-
UE_LOG(
1283-
LogCesium,
1284-
Warning,
1285-
TEXT("Primitive mode %d is not supported"),
1286-
primMode);
1287-
}
1288-
}
1289-
};
1290-
static PrimitiveModeLogger UnsupportedPrimitiveLogger;
1291-
12921270
template <class TIndexAccessor>
12931271
TArray<uint32>
12941272
getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
@@ -1315,9 +1293,8 @@ getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
13151293
}
13161294
break;
13171295
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
1318-
// The TRIANGLE_FAN primitive mode cannot be enabled without creating a
1319-
// custom render proxy, so the geometry must be emulated through separate
1320-
// triangles.
1296+
// The TRIANGLE_FAN primitive mode is not supported in Unreal, so geometry
1297+
// must be emulated through separate triangles.
13211298
indices.SetNum(
13221299
static_cast<TArray<uint32>::SizeType>(3 * (indicesView.size() - 2)));
13231300
for (int32 i = 2, j = 0; i < indicesView.size(); ++i, j += 3) {
@@ -1326,7 +1303,31 @@ getIndices(const TIndexAccessor& indicesView, int32 primitiveMode) {
13261303
indices[j + 2] = indicesView[i];
13271304
}
13281305
break;
1306+
case CesiumGltf::MeshPrimitive::Mode::LINE_LOOP:
1307+
// The LINE_LOOP primitive mode is not supported in Unreal, so geometry must
1308+
// be emulated through separate lines.
1309+
indices.SetNum(
1310+
static_cast<TArray<uint32>::SizeType>(2 * indicesView.size()));
1311+
for (int32 i = 0, j = 0; i < indicesView.size(); ++i, j += 2) {
1312+
// Loop to the first index once we reach the last line segment.
1313+
size_t nextIndex = (i < indicesView.size() - 1) ? i + 1 : 0;
1314+
1315+
indices[j] = indicesView[i];
1316+
indices[j + 1] = indicesView[nextIndex];
1317+
}
1318+
break;
1319+
case CesiumGltf::MeshPrimitive::Mode::LINE_STRIP:
1320+
// The LINE_STRIP primitive mode is not supported in Unreal, so geometry
1321+
// must be emulated through separate lines.
1322+
indices.SetNum(
1323+
static_cast<TArray<uint32>::SizeType>(2 * (indicesView.size() - 1)));
1324+
for (int32 i = 0, j = 0; i < indicesView.size() - 1; ++i, j += 2) {
1325+
indices[j] = indicesView[i];
1326+
indices[j + 1] = indicesView[i + 1];
1327+
}
1328+
break;
13291329
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
1330+
case CesiumGltf::MeshPrimitive::Mode::LINES:
13301331
case CesiumGltf::MeshPrimitive::Mode::POINTS:
13311332
default:
13321333
indices.SetNum(static_cast<TArray<uint32>::SizeType>(indicesView.size()));
@@ -1358,18 +1359,6 @@ static void loadPrimitive(
13581359
CesiumGltf::MeshPrimitive& primitive =
13591360
mesh.primitives[options.primitiveIndex];
13601361

1361-
switch (primitive.mode) {
1362-
case CesiumGltf::MeshPrimitive::Mode::POINTS:
1363-
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
1364-
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
1365-
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
1366-
break;
1367-
default:
1368-
// TODO: add support for other primitive types.
1369-
UnsupportedPrimitiveLogger.OnUnsupportedMode(primitive.mode);
1370-
return;
1371-
}
1372-
13731362
const std::string name = getPrimitiveName(model, mesh, primitive);
13741363
primitiveResult.name = name;
13751364

@@ -1426,14 +1415,14 @@ static void loadPrimitive(
14261415
!options.pMeshOptions->pNodeOptions->pModelOptions
14271416
->ignoreKhrMaterialsUnlit;
14281417

1429-
// We can't calculate flat normals for points or lines, so we have to force
1430-
// them to be unlit if no normals are specified. Otherwise this causes a
1431-
// crash when attempting to calculate flat normals.
14321418
bool isTriangles =
14331419
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES ||
14341420
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN ||
14351421
primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP;
14361422

1423+
// We can't calculate flat normals for points or lines, so we have to force
1424+
// them to be unlit if no normals are specified. Otherwise this causes a
1425+
// crash when attempting to calculate flat normals.
14371426
if (!isTriangles && !hasNormals) {
14381427
primitiveResult.isUnlit = true;
14391428
}
@@ -1536,12 +1525,23 @@ static void loadPrimitive(
15361525
// vertices shared by multiple triangles. If we don't have tangents, but
15371526
// need them, we need to use a tangent space generation algorithm which
15381527
// requires duplicated vertices.
1539-
bool normalsAreRequired = !primitiveResult.isUnlit;
1528+
bool normalsAreRequired = !primitiveResult.isUnlit && isTriangles;
15401529
bool needToGenerateFlatNormals = normalsAreRequired && !hasNormals;
15411530
bool needToGenerateTangents = needsTangents && !hasTangents;
15421531
bool duplicateVertices = needToGenerateFlatNormals || needToGenerateTangents;
1543-
duplicateVertices = duplicateVertices &&
1544-
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
1532+
1533+
// Some primitive modes may require duplication of vertices anyways due to
1534+
// the lack of support in Unreal.
1535+
switch (primitive.mode) {
1536+
case CesiumGltf::MeshPrimitive::Mode::LINE_LOOP:
1537+
case CesiumGltf::MeshPrimitive::Mode::LINE_STRIP:
1538+
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
1539+
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
1540+
duplicateVertices = true;
1541+
break;
1542+
default:
1543+
break;
1544+
}
15451545

15461546
uint32 numVertices =
15471547
duplicateVertices ? uint32(indices.Num()) : uint32(positionView.size());
@@ -1707,16 +1707,14 @@ static void loadPrimitive(
17071707
FVector3f(normal.X, -normal.Y, normal.Z));
17081708
}
17091709
}
1710+
} else if (primitiveResult.isUnlit || !isTriangles) {
1711+
setUnlitNormals(
1712+
LODResources.VertexBuffers,
1713+
ellipsoid,
1714+
transform * yInvertMatrix * scaleMatrix);
17101715
} else {
1711-
if (primitiveResult.isUnlit) {
1712-
setUnlitNormals(
1713-
LODResources.VertexBuffers,
1714-
ellipsoid,
1715-
transform * yInvertMatrix * scaleMatrix);
1716-
} else {
1717-
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals)
1718-
computeFlatNormals(LODResources.VertexBuffers);
1719-
}
1716+
TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::ComputeFlatNormals)
1717+
computeFlatNormals(LODResources.VertexBuffers);
17201718
}
17211719

17221720
if (hasTangents) {
@@ -1753,13 +1751,12 @@ static void loadPrimitive(
17531751

17541752
FStaticMeshSectionArray& Sections = LODResources.Sections;
17551753
FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
1756-
// This will be ignored if the primitive contains points.
1754+
// This will be ignored if the primitive contains lines or points.
17571755
section.NumTriangles = indices.Num() / 3;
17581756
section.FirstIndex = 0;
17591757
section.MinVertexIndex = 0;
17601758
section.MaxVertexIndex = numVertices - 1;
1761-
section.bEnableCollision =
1762-
primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
1759+
section.bEnableCollision = isTriangles;
17631760
section.bCastShadow = true;
17641761
section.MaterialIndex = 0;
17651762

@@ -1784,9 +1781,7 @@ static void loadPrimitive(
17841781
LODResources.bHasReversedDepthOnlyIndices = false;
17851782

17861783
#if ENGINE_VERSION_5_5_OR_HIGHER
1787-
// UE 5.5 requires that we do this in order to avoid a crash when ray
1788-
// tracing is enabled.
1789-
if (primitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS) {
1784+
if (isTriangles) {
17901785
// UE 5.5 requires that we do this in order to avoid a crash when ray
17911786
// tracing is enabled.
17921787
RenderData->InitializeRayTracingRepresentationFromRenderingLODs();
@@ -1800,7 +1795,7 @@ static void loadPrimitive(
18001795

18011796
primitiveResult.transform = transform * yInvertMatrix * scaleMatrix;
18021797

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

30413036
UStaticMeshComponent* pMesh = nullptr;
30423037
ICesiumPrimitive* pCesiumPrimitive = nullptr;
3038+
30433039
if (meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) {
30443040
UCesiumGltfPointsComponent* pPointMesh =
30453041
NewObject<UCesiumGltfPointsComponent>(pGltf, componentName);
@@ -3049,6 +3045,14 @@ static void loadPrimitiveGameThreadPart(
30493045
pPointMesh->Dimensions = loadResult.dimensions;
30503046
pMesh = pPointMesh;
30513047
pCesiumPrimitive = pPointMesh;
3048+
} else if (
3049+
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINES ||
3050+
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINE_LOOP ||
3051+
meshPrimitive.mode == CesiumGltf::MeshPrimitive::Mode::LINE_STRIP) {
3052+
UCesiumGltfLinesComponent* pLineMesh =
3053+
NewObject<UCesiumGltfLinesComponent>(pGltf, componentName);
3054+
pMesh = pLineMesh;
3055+
pCesiumPrimitive = pLineMesh;
30523056
} else if (!instanceTransforms.empty()) {
30533057
auto* pInstancedComponent =
30543058
NewObject<UCesiumGltfInstancedComponent>(pGltf, componentName);
@@ -3110,11 +3114,18 @@ static void loadPrimitiveGameThreadPart(
31103114
primData.pTilesetActor->GetTranslucencySortPriority();
31113115

31123116
pStaticMesh = NewObject<UStaticMesh>(pMesh, componentName);
3113-
// Not only does the concept of ray tracing a point cloud not make much
3114-
// sense, but if Unreal will crash trying to generate ray tracing
3115-
// information for a static mesh without triangles.
3116-
pStaticMesh->bSupportRayTracing =
3117-
meshPrimitive.mode != CesiumGltf::MeshPrimitive::Mode::POINTS;
3117+
// Unreal will crash trying to generate ray tracing information for a static
3118+
// mesh without triangles (and it doesn't make sense anyways!)
3119+
switch (meshPrimitive.mode) {
3120+
case CesiumGltf::MeshPrimitive::Mode::TRIANGLES:
3121+
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_FAN:
3122+
case CesiumGltf::MeshPrimitive::Mode::TRIANGLE_STRIP:
3123+
pStaticMesh->bSupportRayTracing = true;
3124+
break;
3125+
default:
3126+
pStaticMesh->bSupportRayTracing = false;
3127+
break;
3128+
}
31183129
pMesh->SetStaticMesh(pStaticMesh);
31193130

31203131
pStaticMesh->SetFlags(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
2+
3+
#include "CesiumGltfLinesComponent.h"
4+
#include "CesiumGltfLinesSceneProxy.h"
5+
#include "SceneInterface.h"
6+
7+
// Sets default values for this component's properties
8+
UCesiumGltfLinesComponent::UCesiumGltfLinesComponent() {}
9+
10+
UCesiumGltfLinesComponent::~UCesiumGltfLinesComponent() {}
11+
12+
FPrimitiveSceneProxy* UCesiumGltfLinesComponent::CreateSceneProxy() {
13+
if (!IsValid(this)) {
14+
return nullptr;
15+
}
16+
17+
return new FCesiumGltfLinesSceneProxy(this, GetScene()->GetFeatureLevel());
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
2+
3+
#pragma once
4+
5+
#include "CesiumGltfPrimitiveComponent.h"
6+
#include "CesiumGltfLinesComponent.generated.h"
7+
8+
/**
9+
* A component that represents and renders a glTF lines primitive.
10+
*/
11+
UCLASS()
12+
class UCesiumGltfLinesComponent : public UCesiumGltfPrimitiveComponent {
13+
GENERATED_BODY()
14+
15+
public:
16+
// Sets default values for this component's properties
17+
UCesiumGltfLinesComponent();
18+
virtual ~UCesiumGltfLinesComponent();
19+
20+
// Override UPrimitiveComponent interface.
21+
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
22+
};

0 commit comments

Comments
 (0)