From 2b4c2843c72c45157638dbd35f7fea50f4ff5fa2 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 29 Oct 2025 13:39:03 +1100 Subject: [PATCH] Attempt to work around warning spam from Unity physics engines. "Detected one or more triangles where the distance between any 2 vertices is greater than 500 units. The resulting Triangle Mesh can impact simulation and query stability. It is recommended to tessellate meshes that have large triangles." This commit tries to avoid this warning by scaling down the vertex positions of every tile mesh to fit inside a cube with a diagonal <= 1 unit. And then scale it back up to its proper size via the transformation matrix. It reduces the number of occurrences of this warning, but for some reason it doesn't eliminate them entirely. --- .../src/UnityPrepareRendererResources.cpp | 71 +++++++++++++++++-- .../src/UnityPrepareRendererResources.h | 13 ++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/native~/Runtime/src/UnityPrepareRendererResources.cpp b/native~/Runtime/src/UnityPrepareRendererResources.cpp index dbeff78c..28a78b51 100644 --- a/native~/Runtime/src/UnityPrepareRendererResources.cpp +++ b/native~/Runtime/src/UnityPrepareRendererResources.cpp @@ -586,6 +586,9 @@ void loadPrimitive( } stride += numTexCoords * sizeof(Vector2); + Vector3 minPosition; + Vector3 maxPosition; + if (computeFlatNormals) { ::computeFlatNormals( pWritePos + normalByteOffset, @@ -595,7 +598,19 @@ void loadPrimitive( positionView); for (int64_t i = 0; i < vertexCount; ++i) { TIndex vertexIndex = indices[i]; - *reinterpret_cast(pWritePos) = positionView[vertexIndex]; + Vector3 position = positionView[vertexIndex]; + if (i == 0) { + minPosition = position; + maxPosition = position; + } else { + minPosition.x = glm::min(minPosition.x, position.x); + minPosition.y = glm::min(minPosition.y, position.y); + minPosition.z = glm::min(minPosition.z, position.z); + maxPosition.x = glm::max(maxPosition.x, position.x); + maxPosition.y = glm::max(maxPosition.y, position.y); + maxPosition.z = glm::max(maxPosition.z, position.z); + } + *reinterpret_cast(pWritePos) = position; // skip position and normal pWritePos += 2 * sizeof(Vector3); // Skip the slot allocated for vertex colors, we will fill them in @@ -612,7 +627,19 @@ void loadPrimitive( } } else { for (int64_t i = 0; i < vertexCount; ++i) { - *reinterpret_cast(pWritePos) = positionView[i]; + Vector3 position = positionView[i]; + if (i == 0) { + minPosition = position; + maxPosition = position; + } else { + minPosition.x = glm::min(minPosition.x, position.x); + minPosition.y = glm::min(minPosition.y, position.y); + minPosition.z = glm::min(minPosition.z, position.z); + maxPosition.x = glm::max(maxPosition.x, position.x); + maxPosition.y = glm::max(maxPosition.y, position.y); + maxPosition.z = glm::max(maxPosition.z, position.z); + } + *reinterpret_cast(pWritePos) = position; pWritePos += sizeof(Vector3); if (hasNormals) { @@ -635,6 +662,26 @@ void loadPrimitive( } } + glm::dvec3 widths( + maxPosition.x - minPosition.x, + maxPosition.y - minPosition.y, + maxPosition.z - minPosition.z); + + double nextPowerOfTwo = glm::ceil(glm::log2(glm::length(widths))); + primitiveInfo.vertexScaleFactor = glm::exp2(nextPowerOfTwo); + + float vertexScaleFactor = float(primitiveInfo.vertexScaleFactor); + + pWritePos = pBufferStart; + uint8_t* pEndPos = pBufferStart + nativeVertexBuffer.Length(); + while (pWritePos < pEndPos) { + Vector3& position = *reinterpret_cast(pWritePos); + position.x /= vertexScaleFactor; + position.y /= vertexScaleFactor; + position.z /= vertexScaleFactor; + pWritePos += stride; + } + // Fill in vertex colors separately, if they exist. if (hasVertexColors) { // Color comes after position and normal. @@ -900,6 +947,12 @@ UnityPrepareRendererResources::prepareInLoadThread( return asyncSystem.createResolvedFuture( TileLoadResultAndRenderResources{std::move(tileLoadResult), nullptr}); + const auto urlIt = pModel->extras.find("Cesium3DTiles_TileUrl"); + std::string modelUrl = "Unknown"; + if (urlIt != pModel->extras.end()) { + modelUrl = urlIt->second.getStringOrDefault("Unknown glTF"); + } + int32_t numberOfPrimitives = countPrimitives(*pModel); struct IntermediateLoadThreadResult { @@ -936,7 +989,7 @@ UnityPrepareRendererResources::prepareInLoadThread( std::move(tileLoadResult)}; }) .thenInMainThread( - [asyncSystem, tileset = this->_tilesetGameObject]( + [asyncSystem, tileset = this->_tilesetGameObject, modelUrl]( IntermediateLoadThreadResult&& workerResult) mutable { if (tileset == nullptr) { // Tileset GameObject was deleted while we were loading a tile @@ -970,6 +1023,8 @@ UnityPrepareRendererResources::prepareInLoadThread( for (int32_t i = 0, len = meshes.Length(); i < len; ++i) { UnityEngine::Mesh unityMesh = CesiumForUnity::CesiumObjectPools::MeshPool().Get(); + unityMesh.name( + "Tile URL " + modelUrl + " Primitive " + std::to_string(i)); // Don't let Unity unload this mesh during the time in between // when we create it and when we attach it to a GameObject. if (shouldShowTilesInHierarchy) { @@ -1526,9 +1581,17 @@ void* UnityPrepareRendererResources::prepareInMainThread( UnityEngine::HideFlags::HideInHierarchy); } + double vertexScaleFactor = primitiveInfo.vertexScaleFactor; + primitiveGameObject.transform().parent(pModelGameObject->transform()); primitiveGameObject.layer(tilesetLayer); - glm::dmat4 modelToEcef = tileTransform * transform; + glm::dmat4 modelToEcef = + tileTransform * transform * + glm::dmat4( + glm::dvec4(vertexScaleFactor, 0.0, 0.0, 0.0), + glm::dvec4(0.0, vertexScaleFactor, 0.0, 0.0), + glm::dvec4(0.0, 0.0, vertexScaleFactor, 0.0), + glm::dvec4(0.0, 0.0, 0.0, 1.0)); CesiumForUnity::CesiumGlobeAnchor anchor = primitiveGameObject diff --git a/native~/Runtime/src/UnityPrepareRendererResources.h b/native~/Runtime/src/UnityPrepareRendererResources.h index 51f2528d..213d1b80 100644 --- a/native~/Runtime/src/UnityPrepareRendererResources.h +++ b/native~/Runtime/src/UnityPrepareRendererResources.h @@ -59,6 +59,19 @@ struct CesiumPrimitiveInfo { * the corresponding Unity texture coordinate index. */ std::unordered_map rasterOverlayUvIndexMap{}; + + /** + * @brief The scale factor that was used to divide each vertex position in + * this primitive when creating the Unity mesh. The primitive's transformation + * matrix must scale up (multiply) by this same factor in order to return the + * mesh to its original size. + * + * We do this silly dance because Unity (or perhaps PhysX) complains + * incessantly about meshes with triangles that are more than 500 units. + * + * For best precision, this factor should be a power of two. + */ + double vertexScaleFactor = 1.0f; }; /**