diff --git a/CHANGES.md b/CHANGES.md index b1fe4740..fdf047c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Change Log {#changes} +## ? - ? + +##### Additions :tada: + +- Added support for rendering glTFs with line primitives. + ## v1.23.3 - 2026-06-01 ##### Fixes :wrench: diff --git a/native~/extern/cesium-native b/native~/extern/cesium-native index c2ec19ab..50e7b5ee 160000 --- a/native~/extern/cesium-native +++ b/native~/extern/cesium-native @@ -1 +1 @@ -Subproject commit c2ec19abce6a45f59e9556c3252dd2bf8e69e9e0 +Subproject commit 50e7b5ee636f0227686ac69a37ffbf9f4f6b0c8e diff --git a/native~/src/Runtime/Cesium3DTilesetImpl.cpp b/native~/src/Runtime/Cesium3DTilesetImpl.cpp index 7542a697..3cdb30bd 100644 --- a/native~/src/Runtime/Cesium3DTilesetImpl.cpp +++ b/native~/src/Runtime/Cesium3DTilesetImpl.cpp @@ -581,53 +581,9 @@ void Cesium3DTilesetImpl::DestroyTileset( this->_destroyTilesetOnNextUpdate = false; } -void Cesium3DTilesetImpl::LoadTileset( - const DotNet::CesiumForUnity::Cesium3DTileset& tileset) { - TilesetOptions options{}; - options.rendererOptions = std::make_any(tileset); - options.maximumScreenSpaceError = tileset.maximumScreenSpaceError(); - options.preloadAncestors = tileset.preloadAncestors(); - options.preloadSiblings = tileset.preloadSiblings(); - options.forbidHoles = tileset.forbidHoles(); - options.maximumSimultaneousTileLoads = tileset.maximumSimultaneousTileLoads(); - options.maximumCachedBytes = tileset.maximumCachedBytes(); - options.loadingDescendantLimit = tileset.loadingDescendantLimit(); - options.enableFrustumCulling = tileset.enableFrustumCulling(); - options.enableFogCulling = tileset.enableFogCulling(); - options.enforceCulledScreenSpaceError = - tileset.enforceCulledScreenSpaceError(); - options.culledScreenSpaceError = tileset.culledScreenSpaceError(); - // options.enableLodTransitionPeriod = tileset.useLodTransitions(); - // options.lodTransitionLength = tileset.lodTransitionLength(); - options.showCreditsOnScreen = tileset.showCreditsOnScreen(); - options.loadErrorCallback = - [tileset](const TilesetLoadFailureDetails& details) { - int typeValue = (int)details.type; - CesiumForUnity::Cesium3DTilesetLoadFailureDetails unityDetails( - tileset, - CesiumForUnity::Cesium3DTilesetLoadType(typeValue), - details.statusCode, - System::String(details.message)); - - CesiumForUnity::Cesium3DTileset::BroadcastCesium3DTilesetLoadFailure( - unityDetails); - }; - - // Generous per-frame time limits for loading / unloading on main thread. - options.mainThreadLoadingTimeLimit = 5.0; - options.tileCacheUnloadTimeLimit = 5.0; - - DotNet::CesiumForUnity::CesiumGeoreference georeferenceComponent = - tileset.gameObject() - .GetComponentInParent(); - if (georeferenceComponent != nullptr) { - options.ellipsoid = - georeferenceComponent.ellipsoid().NativeImplementation().GetEllipsoid(); - } - - TilesetContentOptions contentOptions{}; - contentOptions.generateMissingNormalsSmooth = tileset.generateSmoothNormals(); - +namespace { +CesiumImage::SupportedGpuCompressedPixelFormats +getSupportedGpuCompressedPixelFormats() { CesiumImage::SupportedGpuCompressedPixelFormats supportedFormats; supportedFormats.ETC2_RGBA = UnityEngine::SystemInfo::IsFormatSupported( DotNet::UnityEngine::Experimental::Rendering::GraphicsFormat:: @@ -682,12 +638,69 @@ void Cesium3DTilesetImpl::LoadTileset( DotNet::UnityEngine::Experimental::Rendering::GraphicsFormat:: RG_EAC_UNorm, DotNet::UnityEngine::Experimental::Rendering::FormatUsage::Sample); + return supportedFormats; +} +} // namespace + +void Cesium3DTilesetImpl::LoadTileset( + const DotNet::CesiumForUnity::Cesium3DTileset& tileset) { + TilesetOptions options{}; + options.rendererOptions = std::make_any(tileset); + options.maximumScreenSpaceError = tileset.maximumScreenSpaceError(); + options.preloadAncestors = tileset.preloadAncestors(); + options.preloadSiblings = tileset.preloadSiblings(); + options.forbidHoles = tileset.forbidHoles(); + options.maximumSimultaneousTileLoads = tileset.maximumSimultaneousTileLoads(); + options.maximumCachedBytes = tileset.maximumCachedBytes(); + options.loadingDescendantLimit = tileset.loadingDescendantLimit(); + options.enableFrustumCulling = tileset.enableFrustumCulling(); + options.enableFogCulling = tileset.enableFogCulling(); + options.enforceCulledScreenSpaceError = + tileset.enforceCulledScreenSpaceError(); + options.culledScreenSpaceError = tileset.culledScreenSpaceError(); + // options.enableLodTransitionPeriod = tileset.useLodTransitions(); + // options.lodTransitionLength = tileset.lodTransitionLength(); + options.showCreditsOnScreen = tileset.showCreditsOnScreen(); + options.loadErrorCallback = + [tileset](const TilesetLoadFailureDetails& details) { + int typeValue = (int)details.type; + CesiumForUnity::Cesium3DTilesetLoadFailureDetails unityDetails( + tileset, + CesiumForUnity::Cesium3DTilesetLoadType(typeValue), + details.statusCode, + System::String(details.message)); + + CesiumForUnity::Cesium3DTileset::BroadcastCesium3DTilesetLoadFailure( + unityDetails); + }; + + // Generous per-frame time limits for loading / unloading on main thread. + options.mainThreadLoadingTimeLimit = 5.0; + options.tileCacheUnloadTimeLimit = 5.0; - contentOptions.ktx2TranscodeTargets = - CesiumImage::Ktx2TranscodeTargets(supportedFormats, false); + DotNet::CesiumForUnity::CesiumGeoreference georeferenceComponent = + tileset.gameObject() + .GetComponentInParent(); + if (georeferenceComponent != nullptr) { + options.ellipsoid = + georeferenceComponent.ellipsoid().NativeImplementation().GetEllipsoid(); + } + TilesetContentOptions contentOptions{}; + contentOptions.generateMissingNormalsSmooth = tileset.generateSmoothNormals(); + contentOptions.ktx2TranscodeTargets = CesiumImage::Ktx2TranscodeTargets( + getSupportedGpuCompressedPixelFormats(), + false); contentOptions.applyTextureTransform = false; + // Although Unity provides MeshTopology.LineStrip, it crashes when given an + // index buffer with a primitive restart constant. Therefore, ensure all + // primitive modes are converted before they are processed by Unity. + contentOptions.primitiveModeOptions.convertLineLoop = true; + contentOptions.primitiveModeOptions.convertLineStrip = true; + contentOptions.primitiveModeOptions.convertTriangleFan = true; + contentOptions.primitiveModeOptions.convertTriangleStrip = true; + options.contentOptions = contentOptions; CesiumForUnity::CesiumCameraManager cameraManager = diff --git a/native~/src/Runtime/UnityPrepareRendererResources.cpp b/native~/src/Runtime/UnityPrepareRendererResources.cpp index 10d95ba9..02dc224a 100644 --- a/native~/src/Runtime/UnityPrepareRendererResources.cpp +++ b/native~/src/Runtime/UnityPrepareRendererResources.cpp @@ -331,22 +331,24 @@ void loadPrimitive( using namespace DotNet::Unity::Collections::LowLevel::Unsafe; CESIUM_TRACE("Cesium::loadPrimitive"); - int32_t indexCount = 0; + int32_t indexCount = static_cast(indicesView.size()); switch (primitive.mode) { - case MeshPrimitive::Mode::TRIANGLES: case MeshPrimitive::Mode::POINTS: - indexCount = static_cast(indicesView.size()); break; - case MeshPrimitive::Mode::TRIANGLE_STRIP: - case MeshPrimitive::Mode::TRIANGLE_FAN: - indexCount = static_cast(3 * (indicesView.size() - 2)); + case MeshPrimitive::Mode::LINES: + if (indexCount < 2) { + return; + } + break; + case MeshPrimitive::Mode::TRIANGLES: + if (indexCount < 3) { + return; + } break; default: - // TODO: add support for other primitive types. - return; - } - - if (indexCount < 3 && primitive.mode != MeshPrimitive::Mode::POINTS) { + // The options set in Cesium3DTilesetImpl::LoadTileset should ensure we only + // receive supported primitive modes. + CESIUM_ASSERT(false); return; } @@ -367,11 +369,12 @@ void loadPrimitive( AccessorView(gltf, normalAccessorIt->second); hasNormals = normalView.status() == AccessorViewStatus::Valid; } else if ( - !primitiveInfo.isUnlit && primitive.mode != MeshPrimitive::Mode::POINTS) { + !primitiveInfo.isUnlit && + primitive.mode == MeshPrimitive::Mode::TRIANGLES) { computeFlatNormals = hasNormals = true; } - // Check if we need to upgrade to a large index type to accommodate the + // Check if we need to upgrade to a large index type to accommodate the // larger number of vertices we need for flat normals. if (computeFlatNormals && indexFormat == IndexFormat::UInt16 && indexCount >= std::numeric_limits::max()) { @@ -397,34 +400,8 @@ void loadPrimitive( Unity::Collections::LowLevel::Unsafe::NativeArrayUnsafeUtility:: GetUnsafeBufferPointerWithoutChecks(dest)); - switch (primitive.mode) { - case MeshPrimitive::Mode::TRIANGLE_STRIP: - for (int64_t 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]; - } - } - break; - case MeshPrimitive::Mode::TRIANGLE_FAN: - for (int64_t i = 0; i < indicesView.size() - 2; ++i) { - indices[3 * i] = indicesView[0]; - indices[3 * i + 1] = indicesView[i + 1]; - indices[3 * i + 2] = indicesView[i + 2]; - } - break; - case MeshPrimitive::Mode::TRIANGLES: - case MeshPrimitive::Mode::POINTS: - default: - for (int64_t i = 0; i < indicesView.size(); ++i) { - indices[i] = indicesView[i]; - } - break; + for (int64_t i = 0; i < indicesView.size(); ++i) { + indices[i] = indicesView[i]; } // Max attribute count supported by Unity, see VertexAttribute. @@ -664,12 +641,19 @@ void loadPrimitive( // for each. SubMeshDescriptor subMeshDescriptor{}; - if (primitive.mode == MeshPrimitive::Mode::POINTS) { + switch (primitive.mode) { + case MeshPrimitive::Mode::POINTS: subMeshDescriptor.topology = MeshTopology::Points; - primitiveInfo.containsPoints = true; - } else { + break; + case MeshPrimitive::Mode::LINES: + subMeshDescriptor.topology = MeshTopology::Lines; + break; + case MeshPrimitive::Mode::TRIANGLES: + default: subMeshDescriptor.topology = MeshTopology::Triangles; + break; } + primitiveInfo.mode = primitive.mode; subMeshDescriptor.indexStart = 0; subMeshDescriptor.indexCount = indexCount; @@ -1010,11 +994,16 @@ UnityPrepareRendererResources::prepareInLoadThread( const std::int32_t len = meshes.Length(); std::vector instanceIDs; for (int32_t i = 0; i < len; ++i) { - // Don't attempt to bake a physics mesh from a point cloud or - // from an invalid triangle mesh. - if (primitiveInfos[i].containsPoints || - isDegenerateTriangleMesh(meshes[i])) { + // Don't attempt to bake a physics mesh from points, lines, or + // an invalid triangle mesh. + switch (primitiveInfos[i].mode) { + case CesiumGltf::MeshPrimitive::Mode::POINTS: + case CesiumGltf::MeshPrimitive::Mode::LINES: continue; + default: + if (isDegenerateTriangleMesh(meshes[i])) { + continue; + } } instanceIDs.push_back(meshes[i].GetInstanceID()); @@ -1588,7 +1577,7 @@ void* UnityPrepareRendererResources::prepareInMainThread( materialProperties); } - if (primitiveInfo.containsPoints) { + if (primitiveInfo.mode == CesiumGltf::MeshPrimitive::Mode::POINTS) { CesiumForUnity::CesiumPointCloudRenderer pointCloudRenderer = primitiveGameObject .AddComponent(); @@ -1619,13 +1608,19 @@ void* UnityPrepareRendererResources::prepareInMainThread( } if (createPhysicsMeshes) { - if (!primitiveInfo.containsPoints && - !isDegenerateTriangleMesh(unityMesh)) { - // This should not trigger mesh baking for physics, because the - // meshes were already baked in the worker thread. - UnityEngine::MeshCollider meshCollider = - primitiveGameObject.AddComponent(); - meshCollider.sharedMesh(unityMesh); + switch (primitiveInfo.mode) { + case CesiumGltf::MeshPrimitive::Mode::POINTS: + case CesiumGltf::MeshPrimitive::Mode::LINES: + break; + default: + if (!isDegenerateTriangleMesh(unityMesh)) { + // This should not trigger mesh baking for physics, because the + // meshes were already baked in the worker thread. + UnityEngine::MeshCollider meshCollider = + primitiveGameObject.AddComponent(); + meshCollider.sharedMesh(unityMesh); + } + break; } } diff --git a/native~/src/Runtime/UnityPrepareRendererResources.h b/native~/src/Runtime/UnityPrepareRendererResources.h index 9a5b8edd..676e10a4 100644 --- a/native~/src/Runtime/UnityPrepareRendererResources.h +++ b/native~/src/Runtime/UnityPrepareRendererResources.h @@ -28,10 +28,9 @@ struct CreateModelOptions { */ struct CesiumPrimitiveInfo { /** - * @brief Whether or not the primitive's mode is set to POINTS. - * This affects whether or not it can be baked into a physics mesh. + * @brief The mode of the glTF primitive. */ - bool containsPoints = false; + int32_t mode = CesiumGltf::MeshPrimitive::Mode::TRIANGLES; /** * @brief Whether or not the primitive contains translucent vertex