CesiumGS / 3d-tiles-validator

Validator for 3D Tiles :vertical_traffic_light:
https://cesium.com
Apache License 2.0
421 stars 156 forks source link

How to calculate coordinate with 'transform' and 'boundingVolume'? #158

Open sophia-xll opened 5 years ago

sophia-xll commented 5 years ago

According to 3dtiles-spec: An additional tile transform may be applied to transform a tile's local coordinate system to the parent tile's coordinate system.

I know how to calculate transform: screenshot at 2018-11-05 15 03 54

Howerer, how to calculate coordinate with 'transform' and 'boundingVolume:box'?

tileset.json: {"asset": {"version": "1.0"}, "geometricError": 1319.96566351801, "root": { "boundingVolume": { "box": [ 1.04773789644241e-9, -0.504322739783674, -0.616247923113406, 5641.31597575976, 0, 0, 0, 6311.33777747117, 0, 0, 0, 25.8459681412205] }, "children": [ { "boundingVolume": { "box": [ 2797.89169446776, -3133.07355675142, 12.5403080463512, 2843.42428129305, 0, 0, 0, 3180.84259900837, 0, 0, 0, 15.1854221645246 ]}, "content": {"url": "tileset_1_1_0.json"}, "geometricError": 164.995707939751, "refine": "REPLACE" }] "geometricError": 329.991415879503, "refine": "REPLACE", "transform": [ -0.895897517499185, -0.444260777177997, 0, 0, 0.283927872032897, -0.572569735547873, 0.769122136866122, 0, -0.341690798268945, 0.689054613072027, 0.639101821764334, 0, -2176357.29229105, 4388848.16197401, 4070680.03399528, 1 ] } }

tileset_1_1_0.json: {"asset": {"version": "1.0"}, "root": { "boundingVolume": { "box": [ 2.3283064365387e-10, -0.113443724578246, 3.57330994307995, 2843.42474428168, 0, 0, 0, 3179.80389127671, 0, 0, 0, 15.5025072079152] }, "children": [], "content": {"url": "1/1/0.b3dm"}, "geometricError": 164.995707939751, "refine": "REPLACE", "transform": [ 0.999999837172188, 0.00036471139259886, -0.000438909100338114, 0, -0.000364495423816037, 0.999999812521052, 0.000492037546955482, 0, 0.000439088469750899, -0.000491877486479553, 0.999999782628903, 0, 2796.72426339705, -3132.95792494155, 8.96653714217246, 1] } }

How to calculate children'box of tileset.json according to tileset_1_1_0.json? By the way, I have tried children:box of tileset.json not equal to T0*T1*B1.

javagl commented 1 year ago

By the way, I have tried children:box of tileset.json not equal to T0T1B1.

It is not entirely clear what you compared here.

These are the input files:

tileset.json

{
  "asset": { "version": "1.0" },
  "geometricError": 1319.96566351801,
  "root": {
    "boundingVolume": {
      "box": [
        1.04773789644241e-9, -0.504322739783674, -0.616247923113406,
        5641.31597575976, 0, 0, 
        0, 6311.33777747117, 0, 
        0, 0, 25.8459681412205
      ]
    },
    "children": [
      {
        "boundingVolume": {
          "box": [
            2797.89169446776, -3133.07355675142, 12.5403080463512,
            2843.42428129305, 0, 0, 0, 3180.84259900837, 0, 0, 0,
            15.1854221645246
          ]
        },
        "content": { "url": "tileset_1_1_0.json" },
        "geometricError": 164.995707939751,
        "refine": "REPLACE"
      }
    ],
    "geometricError": 329.991415879503,
    "refine": "REPLACE",
    "transform": [
      -0.895897517499185, -0.444260777177997, 0, 0, 0.283927872032897,
      -0.572569735547873, 0.769122136866122, 0, -0.341690798268945,
      0.689054613072027, 0.639101821764334, 0, -2176357.29229105,
      4388848.16197401, 4070680.03399528, 1
    ]
  }
}

and tileset_1_1_0.json

{
  "asset": { "version": "1.0" },
  "root": {
    "boundingVolume": {
      "box": [
        2.3283064365387e-10, -0.113443724578246, 3.57330994307995, 
        2843.42474428168, 0, 0, 
        0, 3179.80389127671, 0, 
        0, 0, 15.5025072079152
      ]
    },
    "children": [],
    "content": { "url": "1/1/0.b3dm" },
    "geometricError": 164.995707939751,
    "refine": "REPLACE",
    "transform": [
      0.999999837172188, 0.00036471139259886, -0.000438909100338114, 0,
      -0.000364495423816037, 0.999999812521052, 0.000492037546955482, 0,
      0.000439088469750899, -0.000491877486479553, 0.999999782628903, 0,
      2796.72426339705, -3132.95792494155, 8.96653714217246, 1
    ]
  }
}

They can be loaded with the following sandcastle:

// Create a viewer, add the tileset, and zoom to the tileset
const viewer = new Cesium.Viewer("cesiumContainer");
const tileset = viewer.scene.primitives.add(
  new Cesium.Cesium3DTileset({
    url: "http://localhost:8003/tileset.json",
    debugShowBoundingVolume: true,
    maximumScreenSpaceError: 1.0,
  })
);
const offset = new Cesium.HeadingPitchRange(
  0,
  Cesium.Math.toRadians(-67.5),
  40000.0
);
viewer.zoomTo(tileset, offset);

/**
 * Creates an OrientedBoundingBox from an array of numbers, as
 * it is given in the tileset JSON input
 *
 * @param {Number[]} box The array
 * @returns The OrientedBoundingBox
 */
function createOrientedBoundingBox(box) {
  const center = Cesium.Cartesian3.fromElements(
    box[0],
    box[1],
    box[2],
    new Cesium.Cartesian3()
  );
  const halfAxes = Cesium.Matrix3.fromArray(box, 3, new Cesium.Matrix3());
  return new Cesium.OrientedBoundingBox(center, halfAxes);
}

/**
 * Transforms the given oriented bounding box with the given
 * transform, and returns the result
 *
 * @param {OrientedBoundingBox} obb The oriented bounding box
 * @param {Matrix4} transform The transform
 * @returns The transformed oriented bounding box
 */
function transformOrientedBoundingBox(obb, transform) {
  const center = Cesium.Matrix4.multiplyByPoint(
    transform,
    obb.center,
    new Cesium.Cartesian3()
  );
  const rotationScale = Cesium.Matrix4.getMatrix3(
    transform,
    new Cesium.Matrix3()
  );
  const halfAxes = Cesium.Matrix3.multiply(
    rotationScale,
    obb.halfAxes,
    new Cesium.Matrix3()
  );
  return new Cesium.OrientedBoundingBox(center, halfAxes);
}

// After all tiles have been loaded, print some bounding volume information:
tileset.allTilesLoaded.addEventListener(function () {
  // NOTE: The following lines make MANY assumptions about the
  // structure of the tileset, and are tailored SPECIFICALLY
  // for this example:
  const transform0 = tileset.root.transform;
  const externalRoot = tileset.root.children[0].content.tile.children[0];
  const transform1 = externalRoot.transform;
  const externalRootBox = externalRoot.boundingVolume.boundingVolume;

  // Compute the product of the root transform, and of the
  // root transform of the external tileset
  const T0 = Cesium.Matrix4.fromArray(transform0, 0, new Cesium.Matrix4());
  const T1 = Cesium.Matrix4.fromArray(transform1, 0, new Cesium.Matrix4());
  const P = Cesium.Matrix4.multiply(T0, T1, new Cesium.Matrix4());

  // Create an oriented bounding box from the original input
  // in the (external) tileset JSON file
  const boxFromInputJson = [
    2.3283064365387e-10, -0.113443724578246, 3.57330994307995, 2843.42474428168,
    0, 0, 0, 3179.80389127671, 0, 0, 0, 15.5025072079152,
  ];
  const originalChildBox = createOrientedBoundingBox(boxFromInputJson);

  // Print a comparison of
  // - the original bounding box of the external tileset
  // - the result of transforming this bounding box with T0*t1
  // - the bounding box of the external tileset root,
  //   as it was loaded with CesiumJS
  console.log("originalChildBox:");
  console.log("    center " + originalChildBox.center);
  console.log("    halfAxes " + originalChildBox.halfAxes);

  const transformed = transformOrientedBoundingBox(originalChildBox, P);
  console.log("transformed:");
  console.log("    center " + transformed.center);
  console.log("    halfAxes " + transformed.halfAxes);

  console.log("externalRootBox:");
  console.log("    center " + externalRootBox.center);
  console.log("    halfAxes " + externalRootBox.halfAxes);

  //debugger;
});

The output will be

originalChildBox:
    center (2.3283064365387e-10, -0.113443724578246, 3.57330994307995)
    halfAxes (2843.42474428168, 0, 0)
(0, 3179.80389127671, 0)
(0, 0, 15.5025072079152)
transformed:
    center (-2179756.7235751543, 4389408.229815634, 4068278.332300388)
    halfAxes (-2546.6958814336103, 903.3385461820669, -5.305326306015831)
(-1264.6755963022426, -1819.0661398295, 10.683413748708821)
(5.919066787294536e-14, 2446.657032950468, 9.901813631454507)
externalRootBox:
    center (-2179756.7235751543, 4389408.229815634, 4068278.332300388)
    halfAxes (-2546.6958814336103, 903.3385461820669, -5.305326306015831)
(-1264.6755963022426, -1819.0661398295, 10.683413748708821)
(5.919066787294536e-14, 2446.657032950468, 9.901813631454507)

The originalChildBox is the bounding box that was given in the tileset_1_1_0.json file. This bounding box is transformed with T0 * T1 (which are the transforms from tileset.json and tileset_1_1_0.json, respectively). The result is equal to the bounding box that is obtained directly from the external tileset root, after it was loaded with CesiumJS.

So usually, the bounding box of a single child eventually should be T0*T1*B1.

(It becomes trickier whe this does not only involve a bounding box, but also regions or spheres (or just multiple bounding boxes). But the overall idea of spatial consistency can be maintained there as well)