I’m pretty sure we are missing something fundamental here, as this seems to be such an ordinary use case even without using CesiumIon.
Well, yes, but actually, no. Coordinate systems can be complicated, and therefore, the disclaimer: Everything that I say here should be taken with a grain of salt. But I’ll try…
You have the given cartographic coordinates of the campus:
const cartographic = Cesium.Cartographic.fromDegrees(
6.928297092437934,
50.92807545113617,
0
);
These are the latitude/longitude/height (given in degrees here) that describe the position on the globe.
Then you can compute the cartesian coordinates from them:
const cartesian = Cesium.Cartographic.toCartesian(cartographic);
(or using Cartesian3.fromDegrees
like you did in the Sandcastle)
These are the coordinates relative to the center of the earth (Earth-Centered Earth-Fixed (ECEF) coordinates)
From that, you can compute the “East-North-Up -to- Fixed Frame” matrix, with
const matrix = Cesium.Transforms.eastNorthUpToFixedFrame(cartesian);
This matrix can roughly be imagined as the matrix that transforms an object from the center of the earth to the respective position, including the proper rotation for that position (some details omitted … and computing that is actually somewhat tricky…)
In your case, this matrix is
-0.12062712460954786,0.9926978879842713,0,0,
-0.7706863170115904,-0.09364951363581787,0.6302954619596158,0,
0.6256929738933811,0.07603072923063509,0.7763553507467535,0,
3998831.079016911,485915.70577487565,4928505.253901741,1
(in column-major form here).
You can see that this matrix does involve a rotation. And this matrix is set as the modelMatrix
of the tileset in your case. Without that, the tileset itself is located at “the center of the earth”, but this matrix causes it to end up where the Universität Köln actually is.
Now, you want to place the Geographie-Südbau-Building 303 relative to that, based on the cartographic position of that building.
It looks like you tried to accomplish this by
- computing the difference (
campus - building
) of the cartesian coordinates
- using that as the translation of the tile transform (i.e. as the last column, with the remaining matrix being the identity matrix)
And you mentioned the ‘trial and error’, which apparently consisted of swizzling and adjusting these entries…
The problem:
This transform is applied on top of the transform of the tileset itself. So whatever you’re inserting there as the transform
of the tile, it will be post-multiplied to the modelMatrix
that you set for the tileset.
(The relevant section in the specification is 3d-tiles/specification at main · CesiumGS/3d-tiles · GitHub )
But how can we do this accurately using calculations?
I think (with the usual disclaimers) that a generic and pragmatic way could be:
- Compute the ENU matrix of the campus center,
campusMatrix
- Compute the ENU matrix of the building,
buildingMatrix
- Compute the node transform as
transform = inverse(campusMatrix) * buildingMatrix
This is the matrix that has to be put into the transform
of the tile that contains the building.
One advantage here is that this will take the curvature of earth and the orientation of the center node into account. If you only used the difference in cartesian coordinates and used this as a plain translation, then it would not account for the orientation (rotation) that is implied by the “root” transform (i.e. the campus/modelMatrix
). An attempt to draw something was made…:
Well, roughly like that…
The curvature may not be sooo important, but the orientation definitely is. That’s why you had to “swizzle” the entries of the translation component to make it “roughly correct”.
However, here’s a sandcastle that combines a few of these things. It contains a few utility functions, e.g. for computing the tile.transform
, and prints it to the console for the specific building that you gave in the example:
(Adjust the tileset URL accordingly)
The output will be
buildingNodeTransform
0.9999999873427992,0.00012352201856777323,-0.0001002831598706233,0,
-0.0001235201691789367,0.9999999922012257,0.000018447653096775873,0,
0.0001002854377798873,-0.000018435265870364592,0.9999999948014859,0,
640.9285668455414,-117.50585927954307,-0.03322102688252926,1.0000000000000002
which can just be copy-and-pasted into the transform
of the respective node of the tileset JSON, yielding this:
tileset-main.json
:
{
"asset": {
"version": "1.1"
},
"root": {
"children": [
{
"contents": [
{
"uri": "tileset-303.json"
}
],
"boundingVolume": {
"box": [
0.032474517822265625,
0.10713672637939453,
0,
2500.923995971679688,
0,
0,
0,
2500.70527458190918,
0,
0,
0,
200
]
},
"refine": "ADD",
"transform": [
0.9999999873427992,0.00012352201856777323,-0.0001002831598706233,0,
-0.0001235201691789367,0.9999999922012257,0.000018447653096775873,0,
0.0001002854377798873,-0.000018435265870364592,0.9999999948014859,0,
640.9285668455414,-117.50585927954307,-0.03322102688252926,1.0000000000000002
]
},
{
"contents": [
{
"uri": "tileset-test.json"
}
],
"boundingVolume": {
"box": [
0.032474517822265625,
0.10713672637939453,
0,
2500.923995971679688,
0,
0,
0,
2500.70527458190918,
0,
0,
0,
200
]
},
"refine": "ADD"
}
],
"boundingVolume": {
"box": [
0.032474517822265625,
0.10713672637939453,
0,
2500.923995971679688,
0,
0,
0,
2500.70527458190918,
0,
0,
0,
200
]
},
"geometricError": 2000
}
}
Now… this appears to be a pretty “manual” process. And it is, in your case. How much “manual” work is involved here also depends on which tool you are using to create the main tileset JSON to begin with.
For Cesium ion, there is the “Location Editor”, described at Set Location for Data Uploaded to Cesium ion – Cesium . But when you’re not using ion, then all these computations will have to be done “somewhere else”.
Some convenience functionality might be added in the 3d-tiles-tools
. For example, I just opened Document and extend the functionality of `createTilesetJson` · Issue #104 · CesiumGS/3d-tiles-tools · GitHub to keep track of a possible functionality to place given glTF assets at a certain position.
But in your case, when you have many glTF assets, and different cartographic positions for each of them, then you’ll probably have to do some computations manually.
Maybe the snippets from the Sandcastle can help, and maybe this (somewhat elaborate) answer helps sorting out the non-trivial cases where a certain tile.transform
does not only have to take into account the root transform, but also the transforms of all its parent tiles…