# Convert a b3dm 3D Tile (1.0) to a glTF with coordinates expressed in any projected CRS (EPSG:code)

**URL:** <https://community.cesium.com/t/convert-a-b3dm-3d-tile-1-0-to-a-gltf-with-coordinates-expressed-in-any-projected-crs-epsg-code/36964>\
**Category:** 3D Tiles\
**Created:** [November 28, 2024, 4:53pm UTC](https://community.cesium.com/t/convert-a-b3dm-3d-tile-1-0-to-a-gltf-with-coordinates-expressed-in-any-projected-crs-epsg-code/36964 "2024-11-28T16:53:18Z")\
**Posts on this page:** 1\
**Showing post:** 4

<div class="post-metadata">

**Author:** ![Marco13](https://sea2.discourse-cdn.com/cesium/user_avatar/community.cesium.com/marco13/32/2757_2.png) [@Marco13](https://community.cesium.com/u/Marco13)\
**Post date:** [November 28, 2024, 7:23pm UTC](https://community.cesium.com/t/convert-a-b3dm-3d-tile-1-0-to-a-gltf-with-coordinates-expressed-in-any-projected-crs-epsg-code/36964/4 "2024-11-28T19:23:26Z")

</div>

Starting with some context: There are different options for how “something (i.e. the geometry data in a glTF) can be placed on the globe within a tileset”. Some of them do not make much sense _in practice_, but in _theory_, the options are…

- Using a `tile.transform` in the tileset JSON that refers to the GLB
- Using a `node.matrix/translation/rotation/scale` within the glTF
- Directly storing the global coordinates in the `POSITION` attributes of the glTF ( **not** recommended)

(I think that using the `tile.transform` is the most versatile, as I tried to [illustrate in another thread](https://community.cesium.com/t/from-i3dm-to-ext-mesh-gpu-instancing/19731/11). And it has exactly the advantage that you referred to: In other tools (like Blender), the glTF data is then shown in its “canonical” form, using the “standard glTF coordinates”)

There’s an additional caveat, namely the [`RTC_CENTER`](https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel#tileformats-batched3dmodel-coordinate-system). In the B3DM that you linked to, this is indeed given as

```auto
  "RTC_CENTER" : [
    4373246.00084388,
    596545.5803726078,
    4588852.468037587
  ]

```

There is no `tile.transform` in the tileset JSON, so I assume that the question is really mainly about the _orientation_ (rotation) of the model.

* * *

There are some options for “post-processing” such files. The basic workflow would be:

- Extract the GLB data from the B3DM
- Modify the GLB data as desired
- Create a new B3DM from the modified GLB data

The [`3d-tiles-tools`](https://github.com/CesiumGS/3d-tiles-tools) do offer some of this functionality (e.g. `b3dmToGlb` to extract the GLB from a B3DM). But when doing each step manually at the command line, then the Batch- and Feature table information would be lost in that process…

One option to _retain_ this information would be a custom script, based on the `3d-tiles-tools`. With a disclaimer: **The 3D Tiles Tools do not officially provide a public, stable API for that**. They are only intended as a command-line application. But based on the current `main` state of the repository ([this commit](https://github.com/CesiumGS/3d-tiles-tools/tree/3c2b9a4ba7f8280422826d5c7923a1d66dfea2e9)), one could use the following snippet:

```typescript
import fs from "fs";

import { Document } from "@gltf-transform/core";
import { mat4 } from "@gltf-transform/core";

import { TileFormats } from "./src/tilesets";
import { GltfTransform } from "./src/tools";

// Perform a "dummy" modification on the given glTF-Transform
// document: Just change the matrix of the root nodes of all
// scenes in the document
function changeRootTransform(document: Document) {
  const documentRoot = document.getRoot();
  const scenes = documentRoot.listScenes();
  for (const scene of scenes) {
    const nodes = scene.listChildren();
    for (const node of nodes) {
      const oldMatrix = node.getMatrix();

      // Dummy modification: Change the translation of the matrix
      const newMatrix = oldMatrix.slice() as mat4;
      newMatrix[12] = 10.0;
      newMatrix[13] = 20.0;
      newMatrix[14] = 30.0;

      console.log("oldMatrix ", oldMatrix);
      console.log("newMatrix ", newMatrix);
      node.setMatrix(newMatrix);
    }
  }
}

async function postProcessB3dmData(inputB3dmData: Buffer) {
  // Extract the GLB data from the given B3DM data,
  const inputTileData = TileFormats.readTileData(inputB3dmData);
  const inputGlbData = TileFormats.extractGlbPayload(inputTileData);

  // Create a glTF-Transform 'Document' for the GLB
  const io = await GltfTransform.getIO();
  const document = await io.readBinary(inputGlbData);

  // Modify the document
  await document.transform(changeRootTransform);

  // Create the GLB data for the modified document
  const outputGlbData = await io.writeBinary(document);

  // Create a new B3DM data with the modified GLB data,
  // but KEEP the original feature- and batch table data!
  const outputTileData = TileFormats.createB3dmTileDataFromGlb(
    Buffer.from(outputGlbData),
    inputTileData.featureTable.json,
    inputTileData.featureTable.binary,
    inputTileData.batchTable.json,
    inputTileData.batchTable.binary
  );
  const outputB3dmData = TileFormats.createTileDataBuffer(outputTileData);
  return outputB3dmData;
}

async function postProcessB3dm(inputFileName: string, outputFileName: string) {
  const inputB3dmData = fs.readFileSync(inputFileName);
  const outputB3dmData = await postProcessB3dmData(inputB3dmData);
  fs.writeFileSync(outputFileName, outputB3dmData);
}

postProcessB3dm("21.b3dm", "21-modified.b3dm");

```

So the process would be to check out the repository, run `npm install`/`npm run build`, store the given snippet e.g. as `PostProcessB3dm.ts` in the root directory of the project, and then run it with  
`npx ts-node ./PostProcessB3dm.ts`  
to modify the B3DM file (named at the bottom).

There are some inlined comments that describe the basic steps. The main change will be at the place that is currently described as “Dummy modification”: Instead of just arbitrarily changing the matrix, it would be necessary to change that matrix so that it describes the intended change of the coordinate system.

I’m hazy about what that entails. I could imagine that it could be possible to derive the matrix that is necessary to “undo the rotation” from the `region` that is stored in the tileset - probably the inverse of the ENU matrix at its center. But for questions that are specific to the projections _themself_, someone else might have to chime in…

---

_[View the full topic](https://community.cesium.com/t/convert-a-b3dm-3d-tile-1-0-to-a-gltf-with-coordinates-expressed-in-any-projected-crs-epsg-code/36964)._
