Skip to content

Conversation

javagl
Copy link
Contributor

@javagl javagl commented Oct 11, 2025

The issue about Time-dynamic 3D Tiles has recently has been addressed on the side of the specification, with draft extension PRs. The subsequent comments in this issue already dived into details of the proposed extensions and approach. This has some overlap to some discussion in one of the specification PRs.

I'll try to summarize some of the points that are relevant for the interplay between the specification and the implementation within CesiumJS, both in terms of the approach that will be taken, as well as questions that still have to be addressed.


Representation

The original proposal suggested to use the 'multiple contents' concept of 3D Tiles to represent time-dynamic tiles. The idea was to associate a time range with each content (using metadata), and select the respective content for rendering depending on the time range.

After some discussion, the intention is now rather to separate the concepts of "multiple contents" and "time-dynamic content". There are many open questions about the concept of "time-dynamicness" (some listed below). Having a clean slate offers more degrees of freedom for what can go into the specification, and reduces the risk of interfering with existing aspects of the implementations.

Two options have been considered for this separation:

  1. The time-dynamic content could be represented as an extension object within the tile.content.
  "content": {
    "uri": "content-2025-09-25-revision0.glb",
    "extensions": {
      "3DTILES_dynamic": {
        "dynamicContents": [
          {
            // Magic...
          },
        ]
      }
    }
  }
  1. The time-dynamic content could be a new content type:
  "content": {
    "uri": "timeDynamicContent.json"
   }

timeDynamicContent.json:

{
  "dynamicContents": [
    {
      // Magic...
    },
  ]
}

The current intention is option 2., to define it as a new content type. A summary of the reasoning:

  • There may be many entries in that "time dynamic content" decription. The tileset JSON could become huge
  • It's easier to update/extend a small timeDynamicContent.json with an additional time stamp, than to re-process and modify the whole tileset JSON
  • Important: This approach allows time-dynamic content to transparently be used in implicit tilesets! Many of the questions about how to represent the time stamps and their different content URIs in an implicit tileset become obsolete. Everything is handled within the content.

For explicit tilesets and the first draft implementations, the different ways of separating the data does not matter so much. The pseudocode difference is that of
const d = tile.getExtension(...).dynamicContent; vs.
const d = tile.content.dynamicContent;

There are maaaany low-level quirks omitted here. But one important point is that the content can be created either with the object that was fetched from the tileset JSON, or the one that was loaded from the content JSON file.

In any case, there will be a top-level extension object in the tileset JSON that describes the structure of the dynamic content in more detail.


Features and capabilities

The efforts (and names) focus on time-dynamic 3D Tiles. That's the primary use-case. The seemingly philosophical question "What is 'time'?" is important in this context:

  • On the level of the specification, it is hard to answer. It could be a unix time stamp, or an ISO8601 string, or a string like "übermorgen". Whatever the representation is, it could turn out to be wrong (or have severe drawbacks and limitations) for certain use-cases.
  • On the level of the implementation, it is easy to answer: It's what determines which content will be displayed. Really, it's just
    const content = getContentFor(time);

Based on some discussion in the linked issues/PRs, the "dynamicness" for 3D Tiles should not be limited to "time" (given that we cannot even say what that is). There could, for example, be different "revisions" of the same tileset, and it should be possible to dynamically change which revision is displayed. At this point, "time" just becomes "one dimension in the space of things that can be displayed".

One important distinction is whether these dimensions are discrete (like 'revisions'), or continuous (like some forms of 'time'). The continuous dimensions would require some form of "binning/discretization", based on "intervals/ranges". There are some options about where and how this discretization could take place.

The current intention is to only have discrete keys for the dynamic contents. An overly suggestive example:

"dynamicContents": [
  {
    "uri": "content-time0-revision0.glb",
    "keys": {
      "exampleTimeStamp": "time0",
      "exampleRevision": "revision0"
    }
  },
  {
    "uri": "content-time1-revision1.glb",
    "keys": {
      "exampleTimeStamp": "time1",
      "exampleRevision": "revision1"
    }
  }
]

There is a representation of dynamic contents, with different revisions. The application will determine which of the contents should be displayed. The "exampleRevision" could be selected with a dropdown menu. The "exampleTimeStamp" could be selected by the application as well. For example, when the "time" is a value in [0,1], then a "time" of 0.2 could be clamped to 0 and cause the selection of the "time0" time stamp.


Implementation

There are several open questions for the implementation, on various levels. Most of the subsequent comments here will likely revolve around the question of "(Oh dear), how are we going to handle that?". An attempt to sumarize the current state, open questions, and considerations:

  • There now is a Dynamic3DTileContent class. This class is-a Cesium3DTileContent. Most functions in this interface are underspecified. Many functions that could make sense are missing. Nearly all implementations of nearly all functions are just returning "dummy" values (just 0 or undefined), because they simply don't make sense for what constitutes "(tile) content". And for nearly all functions, there are deeeep questions about what they should do for "dynamic content". For example: It is not clear whether the Cesium3DTileContent.trianglesLength implementation should return the number of triangles that are currently loaded, or the triangles that are currently displayed. Both options will be wrong in one way or another.
  • There are things like the Cesium3DTileContentState. For whatever reason, this contentState is stored in the tile, and not the content. (Corollary: It should be part of the Cesium3DTileContent interface!). Dynamic content can not go thorough the fixed sequence of states (like LOADING->PROCESSING->READY) that is assumed for other contents. Which state it should be in, at which point in time, has to be decided. (Maybe EXPIRED could be (ab)used to represent state transitions here, but that remains to be investigated).
  • There are the update function and the ready flag. Dynamic contents is either "always ready" or "never ready". It will likely be the former, but ... some update will still have to be called, even when it already counts as ready. More generally: The Cesium3DTile class has about 15 properties that reflect the state of (and should be stored in) its content, including flags hasEmptyContent, hasRenderableContent, hasMultipleContents, hasTilesetContent, and hasImplicitContent, which should not exist to begin with, and which can neither be true nor false for dynamic content.
  • The Dynamic3DTileContent class has to load other contents. And it has to do this dynamically. I tried to take inspiration from Multiple3DTileContent, but this has limitations that should be fixed independently. The Dynamic3DTileContent class has to send out requests, wait for the responses, post-process that content, and possibly unload content that is no longer "needed" (until the user drags the time slider, and it is needed again). I'm currently trying to create sorts of "utility classes" for all that, but this pretty much experimental.

(The pull request template is omitted for now. It will be inserted before this is converted to 'Ready For Review')

@github-actions
Copy link

Thank you for the pull request, @javagl!

✅ We can confirm we have a CLA on file for you.

@javagl
Copy link
Contributor Author

javagl commented Oct 15, 2025

The PR has been updated with a first draft of an implementation. This does not fully reflect the latest state of all decisions. There are several open questions. But can serve as a preview.

Data side

The dynamic content definition contains information like the following:

"dynamicContents": [
  {
    "uri": "content-2025-09-25-revision0.glb",
    "keys": {
      "exampleTimeStamp": "2025-09-25",
      "exampleRevision": "revision0"
    }
  },
  {
    "uri": "content-2025-09-25-revision1.glb",
    "keys": {
      "exampleTimeStamp": "2025-09-25",
      "exampleRevision": "revision1"
    }
  }
]

Each entry indicates which content (URI) should be shown for the respective configuration of exampleTimeStamp and exampleRevision. (Right now, this is still stored as an extension object of the content. It will be in a standalone JSON file in the future).

Application side

In the application, the selection of the dynamic content is governed by a structure like the following:

const dynamicContentsPropertyProvider = () => {
  return {
    exampleTimeStamp: "2025-09-25",
    exampleRevision: "revision1"
  };
};
tileset.setDynamicContentPropertyProvider(dynamicContentsPropertyProvider);

Yes, this returns a fixed dummy value here. The point is: There is that "dynamic contents property provider" that provides the properties that should be used for selecting the active content at any point in time. (See the Sandcastle in the example for details).

Connecting the two

The main work for wiring this together is currently in a class called Dynamic3DTileContent. It is a Cesium3DTileContent implementation. Most of the questions about how the functions of that interface should be implemented are still open.

The Dynamic3DTileContent contains a _dynamicContentUriLookup. This is an N-dimensional map: The "dynamic contents property provider" provides the properties for the active content. This map is used for looking up all content URIs that should be active.

The Dynamic3DTileContent also contains something that is referred to as "content handles". This is supposed to wrap away the complexity that comes with Request/Resource/RequestScheduler and their throttling/severKeys/priorityFunction and all the promises that are associated with that. The goal was to have something that can be used like this:

if (contentHandle.failed) {
  console.log("Error!");
  return;
}
const content = contentHandle.tryGetContent();
if (!defined(content)) {
   console.log("Still waiting for content");
   return;
}
console.log("Got content: ", content);

without having to care about what that tryGetContent is actually doing. (It's complicated...)

Combining these functionalities, the Dynamic3DTileContent can offer two things:

  • an _allContents getter that returns the contents that are currently in memory
  • an _activeContents getter that returns the contents that are currently "active"

Based on that, during its update call, it can essentially set all contents "invisible", and only set the "active" contents visible.

The class maintains the _loadedContentHandles which stores the contents which are currently in memory. This is implemented as an LRU cache. Some details about how this cache is filled and how contents are unloaded are omitted here. But it's important to be able to not always keep all contents in memory, and still try to not reload the content during each rendering pass or so.


Example

The following is an example tileset and a sandcastle.

PR0001 example.zip

The tileset contains a single dynamic content. It defines 64 time stamps and 3 "revisions", meaning that it has 192 actual contents.

The follwing screencap summarizes a few aspects that can be tested with the sandcastle:

  • Scrolling through the time selects one of the 64 time stamps
  • The dropdown menu can be used for selecting the revision
  • The elaborate debug log show that it's possible to switch through "recently used" configurations without triggering reloads
  • Styling can be applied to the tileset, and survives content changes
  • Picking only refers to the content that is currently active

Cesium Dynamic PR0001

@javagl
Copy link
Contributor Author

javagl commented Oct 16, 2025

Until now, the dynamic contents information was fetched from an extension in the content object. Now the dynamic content is an actual new content type.

A strong reason for treating it as a new content type is that this should simplify using dynamic contents within implicit tilesets. So I created an example for that as well. Here's a screencap of that example:

Cesium Dynamic PR0002

The previous test data (explicit) and the new data set (implicit) both with using that new content type are attached here, together with the Sandcastle:

PR0002 example.zip

(Note: I have a bunch of infrastructure for ~"creating test data like that". This was developed in my spare time, off-the-clock. Tweaking it for cases like this sometimes requires a bit of fiddling and copy-and paste and hacks and manual steps, and now there's a public class _CesiumImplicitDynamicTest here. This specific fiddling and test data generation is at least partially Cesium time. Maybe, one day, I can allocate a (large enough) chunk of spare time to clean this up and make it more usable...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant