CesiumGlobeAnchor and 3D Tiles Oriented Bounding Box

As part of a personal game, just to learn and test more or less, I’m trying to read the geometric error of each 3D tile in the scene through the Google Photorealistic 3D Tiles API.

I calculate the cartesian position of the CesiumGlobeAnchor through the longitudeLatitudeHeight property, (I know that CesiumGlobeAnchor has the positionGlobeFixed property). The calculated vector lies inside the OBB of higher geometric error value 3D tiles. Still, as I travel down the tileset tree and the geometric error value gets lower, at some point, the vector goes outside the OBB, specifically above the OBB. It’s not a precision issue as I use the Vector<double> and Matrix<double> from the Math.NET Numerics package.

Ex, in a unit test, with a 3D tile whose longitudeLatitudeHeight value is {18.6328125, 44.6484374999999, 12489.4559707589}, the calculated cartesian vector is {4315320.91557922, 1455017.67623684, 4468415.1838329} (Z is up), but as you can see in the image, the vector lies above, specifically 8655 meters above the OBB of the 3D Tiles:
ID:UlRPVEYubm9kZWRhdGEucGxhbmV0b2lkPWVhcnRoLG5vZGVfZGF0YV9lcG9jaD05NzAscGF0aD0zMDQyNzM2MyxjYWNoZV92ZXJzaW9uPTYsaW1hZ2VyeV9lcG9jaD05OTUsYWxpZ25tZW50X3ZlcnNpb249Uk9DS1RSRUVfOTgyX0dPT0dMRV9EQVRVTV8yMDI0MDQwMlQwNzUzWl9nZW5lcmF0ZWRfYXRfMjAyNDA3MDFUMTIwN1o

At first, I thought that just using the EGM2008 model, as I did in a past Java project, through the GeographicLib.NET package, would correct the elevation, and it does lower the elevation derived from the longitudeLatitudeHeight.height prop, but not enough.
Then I thought it might be an issue with the transform matrix, but as I moved down the tileset tree, it stayed as an identity one.

Yes, I know I could just check if the vector goes through the OBB, but that would defeat my purpose of learning/testing. Or I could load the 3D tiles through the API, to the scene myself, which I might do if the current approach turns out to run too slow, although that itself may introduce performance issues if I’m not careful enough.

HI @DavixDevelop, welcome to the community!

The detailed write-up is appreciated! Still, I’m not sure what’s happening from a glance.

Could you provide some context for why you’re trying to compute the geometric error this way? And, are you able to share your computations / code in more detail? That would help us be able to give more informed feedback. Thank you!

The reason why I’m trying to compute the geometric error this way is because that’s the only way that comes to my mind, as this is my first time doing game development itself, rather than just mods or models for games. If there’s another way to find it, I would gladly appreciate the advice.

Regarding the code itself, here are the two most relevant methods. The first one navigates through the tileset tree/populates it (parsed tiles from the API are stored in a local database), while the second one checks if the origin point is within the OBB of the 3D Tile. Note, for FindMeshTile method parameters, tile is the starting tile, ergo the Global Root Tile (when FindMeshTile is called the first time), meshTileID is a composite primary key ( made from the tile UUID, dataset index, and tile type), meshTileUrl contains the data to construct the URL of the tile (the key, session, name (UUID), dataset name, tile type (.json or .glb)), geoCoords contains the properties from CesiumGlobeAnchor.longitudeLatitudeHeight, origin is the calculated cartesian double vector from said geoCoords, and lastly meshTransform is the calculated beforehand (before FindMeshTile is called) transform matrix from the Global Root Tile.

public static Model.Tile FindMeshTile(Tile tile, string meshTileID, ParsedUrl meshTileUrl, Vector<double> origin, GeoCoords geoCoords, ref Matrix<double> meshTransform) {    
    if (tile.ID == meshTileID) {
        return tile;
    }

    //If tile is type of JSON, and It's not processed yet (It's TileSet not yet in the database)
    if(tile.GetTileType()  == TileType.JSON && !tile.Processed) {
        tile = RequestTileset(tile, meshTileUrl.Key, meshTileUrl.Session);
        Tile tile1 = sceneDatabase.GetTile(meshTileID);
        if(tile1 != null) {
            meshTransform *= VectorUtility.MatrixFromArray(tile1.GetTransform());
            return tile1;
        }
    }
    
    //Check if requested tile is inside of the child tiles of the tile
    List<Tile> childTiles = sceneDatabase.GetChildTilesFromParentTile(tile.ID);
    List<int> overlappingTilesIndexes = new List<int>();
    for (int x = 0; x < childTiles.Count; x++) {
        Tile childTile = childTiles[x];   
        Matrix<double> childTransform = meshTransform * VectorUtility.MatrixFromArray(childTile.GetTransform());

        //Check if Cartesian origin point is inside the BBO
        if (childTile.GetVolumeType() == BoundingVolume.VolumeType.BOX && VectorUtility.IsPointInsideOBB(origin, childTile.GetBounds(), childTransform))
            overlappingTilesIndexes.Add(x);
        else if (childTile.GetVolumeType() == BoundingVolume.VolumeType.SPHERE && VectorUtility.IsPointInsideBoundingSphere(origin, childTile.GetBounds(), childTransform))
            overlappingTilesIndexes.Add(x);
        else if (childTile.GetVolumeType() == BoundingVolume.VolumeType.REGION && GeoUtility.IsGeoCoordInsideBoundingRegion(geoCoords, childTile.GetBounds()))
            overlappingTilesIndexes.Add(x);
    }

    foreach(int overlapingIndex in overlappingTilesIndexes) {
        Tile childTile = childTiles[overlapingIndex];
        Matrix<double> childTransform = meshTransform * VectorUtility.MatrixFromArray(childTile.GetTransform());
        Tile tile1 = FindMeshTile(childTile, meshTileID, meshTileUrl, origin, geoCoords, ref childTransform);
        if(tile1 != null) {
            meshTransform *= VectorUtility.MatrixFromArray(tile1.GetTransform());
            return tile1;
        }
    }


    return null;
}

public static bool IsPointInsideOBB(Vector<double> point, double[] bbo, Matrix<double> transform) {
    //Get volume bounds of child tile
    //Center of the BBO
    Vector<double> center = Vector3(bbo[0], bbo[1], bbo[2]);
    //X axis direction and half length of BBO
    Vector<double> xAxis = Vector3(bbo[3], bbo[4], bbo[5]);
    //Y axis direction and half length of BBO
    Vector<double> yAxis = Vector3(bbo[6], bbo[7], bbo[8]);
    //Z axis direction and half length of BBO
    Vector<double> zAxis = Vector3(bbo[9], bbo[10], bbo[11]);

    //Transform BBO by transform matrix
    center = transform.MultiplyPoint3x4(center);
    xAxis = transform.MultiplyVector(xAxis);
    yAxis = transform.MultiplyVector(yAxis);
    zAxis = transform.MultiplyVector(zAxis);

    //Vector from the center of the OBB to the point
    Vector<double> dir = point - center;

    //Project dir onto each of the OBB local axis
    double X = dir.DotProduct(xAxis.Normalize(2));
    double Y = dir.DotProduct(yAxis.Normalize(2));
    double Z = dir.DotProduct(zAxis.Normalize(2));

    double xMagnitude = xAxis.L2Norm();
    double yMagnitude = yAxis.L2Norm();
    double zMagnitude = zAxis.L2Norm();

    bool insideX = Math.Abs(X) <= xMagnitude;
    bool insideY = Math.Abs(Y) <= yMagnitude;
    bool insideZ = Math.Abs(Z) <= zMagnitude;

    //Check if the point lies inside the extent of the OBB foreach axis
    return insideX && insideY && insideZ;
}