3D Tiles: the next big step for Cesium and 3D geospatial

Hi all,

Over the next 4-5 months, we are going to work with our friends at the University of Pennsylvania to develop open-source tools for debugging, analyzing, and validating 3D Tiles tilesets.

This will be an important part of the 3D Tiles ecosystem to ensure interoperability among exporters, converters, and renderers that implement 3D Tiles, similar to the new glTF Validator from Khronos.

If you want to keep an eye on the progress, watch the new repo:

GitHub - CesiumGS/3d-tiles-validator: Validator for 3D Tiles 🚦

Note that these tools are not for converting other formats to 3D Tiles. These tools are for working with existing 3D Tiles tilesets.

Patrick

Our latest 3D Tiles tutorial from Web3D is now up:

Hi all,

Rob Taglang just completed a major update to the Instanced 3D Model (i3dm) tile format for 3D Tiles that is more efficient and flexible than the initial prototype. Check out the updated spec:

https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Instanced3DModel/README.md

The Cesium pull request will also be merged into the 3d-tiles branch soon:

https://github.com/AnalyticalGraphicsInc/cesium/pull/4101

Patrick

Hi all

The Cesium pull request will also be merged into the 3d-tiles branch soon:

[https://github.com/AnalyticalGraphicsInc/cesium/pull/4101](https://github.com/AnalyticalGraphicsInc/cesium/pull/4101)

This was merged. Let us know your feedback when you are able to test it out.

Thanks,

Patrick

Hi folks,

We started putting together the roadmap for the 3D Tiles validator, which will help ensure an interoperable ecosystem of 3D Tiles converters, tools, and renderers:

3D Tiles Validator Roadmap · Issue #5 · CesiumGS/3d-tiles-validator · GitHub

Please chime in with your input.

This will start in mid-September.

Patrick

Hello everyone,

I've never used Cesium, so maybe my questions are already answered somewhere on the forum. I'd like to use Cesium for visualization of a large city, and I've got a big postgresql database of spatial objects (buildings, plots, etc). As far as I can tell, the best way to get the needed amount of data on the client is to stream it by "chunks" - 3D tiles, that cover only current visible map area. But how do I generate 3D tiles from postgres data? I want some 3D tiles to be pre-generated, and some to be generated on the fly (something similar to 2D tiling solutions like Tilecache, Mapproxy etc.) But regarding to this forum branch, the tools aren't implemented yet. Or are they already present in some unfinished development form?
So how did you make 3D tiles for NYC buildings? What was the initial form of this data and how did you translate it to 3D tiles?
What do you use on the server side to stream tiles to the client?

Hi,

There are two source datasets for the NYC demo. One is the CityGML from NYC’s open data, and the other is OSM from Mapzen’s metro extracts. You can flip between these in the app.

AGI, the company that started Cesium, is building 3D Tiles converters as part of the upcoming service, https://cesiumjs.com/

We are also building some open-source tools that can be used as part of a 3D Tiles pipeline:

Patrick

I would like to learn and apply
“3D tiles” . I read article about “Cesium-dev” and reviewed “3D Tiles.html” But
I don’t know how to apply to my Project.

I am calling building data with
codes that is written below but It takes time increase of buildings. Can you
help me understable and clear way?

var viewer = new Cesium.Viewer(‘cesiumContainer’, {
scene3DOnly : true,
shadows : true
});

var dataSource2 = new Cesium.GeoJsonDataSource();
var promise = dataSource2.load(’…/…/Build/test-1.geojson’);
promise.then(function(dataSource2) {
viewer.dataSources.add(dataSource2);
viewer.zoomTo(dataSource2);

var entities2 = dataSource2.entities.values;

var colorHash = {};
for (var i = 0; i < entities2.length; i++) {

    var entity = entities2[i];
    var name = entity.KAT_ADEDI;
    var color = colorHash[name];
    if (!color) {
        color = Cesium.Color.GREEN;
        colorHash[name] = color;
    }

   .
    entity.polygon.material = color;

    entity.polygon.outline = false;

 
    entity.polygon.extrudedHeight = entity.properties.KAT_ADEDI;
   
}

}).otherwise(function(error){

window.alert(error);
   
   
    });

``

Thank you in advance

Selahattin

Selahattin - I’m not aware of any GeoJSON -> 3D Tiles converters yet, but watch this thread for 3D Tiles news in general. If you need help specifically with GeoJSON in Cesium, please start a new thread.

Patrick

Hi,

Right now I'm using the 3D Tiles branch of Cesium, and had a question regarding the properties of ModelInstanceCollection (https://github.com/AnalyticalGraphicsInc/cesium/blob/3d-tiles/Source/Scene/ModelInstanceCollection.js). The problem I'm running into is the need to add the Heightreference to the models used within this instance. I have tried doing so by just manually adding it within Cesium.Js (Unminified version) however got the error that the Heightreference is not supported without a scene (However, I add it as a primitive collection straight to the Viewer.Scene.Primitives).

Are there any plans to officially support HeightReference as of now or is it more beneficial to sample the terrain and generate a height-variable for my models?

My implementation within Cesium.js was very simple, I just added a this._heightReference variable to the constructor of ModelInstanceCollection and at the definition of the model added this again as a definition (using Cesium.HeightReference.CLAMP_TO_GROUND).

Additionally; I've been trying to instead use sampleTerrain to get the correct height. I'm getting it , but want to wait for the PrimitiveCollection of ModelInstanceCollection to be done before adding it to my primitives. The issue I was running into is the fact that the system seems to be running too fast and skipping over the Model Variable while trying to add, causing it to never actually load the model (the DebugBoundingSphere is visible and in the right place, there just aren't any models and logging it in the console shows the _model variable of ModelInstanceCollection to be undefined).

Right now, I'm trying to call on the ReadyPromise within ModelInstanceCollection. I logged it and can get the promise just fine. But the promise doesn't return properly and never shows it as being ready. Now I'm getting my model, but I can't add it to my primitives because the follow snippet never triggers.

        var readyPrimitives = primitiveCollection.readyPromise;

        Cesium.when(readyPrimitives, function(){
                viewer.scene.primitives.add(primitiveCollection);
                     console.log("placed trees");
            });

Hello,

The HeightReference error is happening because you need to also pass in the scene when creating a Model with a height reference. Right now though HeightReference isn’t supported for ModelInstanceCollection. The collection contains a model for the purpose or rendering, but not positioning, so any modifications to the underlying model’s position, either by height reference or modelMatrix, will end up moving all instances at the same time. Instead it’s definitely better to do the other solution you mentioned which is sample heights ahead of time. I think supporting HeightReference in ModelInstanceCollection could be a very useful feature to have.

For your code snippet, you need to add the collection to the scene first so that it can update and eventually trigger the ready promise. Try:

var readyPrimitives = viewer.scene.primitives.add(new Cesium.ModelInstanceCollection(…));

Cesium.when(readyPrimitives, function(){
console.log(“placed trees”);
});

Hi Sean, thanks for the reply.

The above code snippet also doesn't seem to work for me. Instead using a 5 second timer before adding the collection seems to work consistently, like in the following code:

            primitiveCollection = self.placeTrees(updatedPositions); // This returns the ModelInstanceCollection to add later.
            setTimeout(SpawnTrees, 5000);

            function SpawnTrees(){
                viewer.scene.primitives.add(primitiveCollection);
            }

            console.log(primitiveCollection);

What I tried to use, was your example in the following way:

            primitiveCollection = viewer.scene.primitives.add(self.placeTrees(updatedPositions)); // self.placeTrees still returns the modelinstancecollection;
            Cesium.when (primitiveCollection, function () {
            console.log("trees have been placed");
            });

This code snippet doesn't seem to run, none of the trees I am trying to place exist anymore and _model shows undefined, neither does "trees have been placed" get printed in the console.

I'm mostly getting issues where it simply isn't waiting for the model to exist before moving on and the Promise in _readyPromise of PrimitiveCollection never actually resolving. Waiting, for some reason, fixes this. For now I'll use this solution I have (albeit, that it is a bit of dirty code..)

Thanks for the help,

Heerco

Oh sorry I think I had a mistake in my sample code. Try:

primitiveCollection = viewer.scene.primitives.add(self.placeTrees(updatedPositions));
Cesium.when (primitiveCollection.readyPromise, function () {
console.log(“trees have been placed”);
});

Or you could also do:

Hi Sean, thanks for the reply.
The above code snippet also doesn’t seem to work for me. Instead using a 5 second timer before adding the collection seems to work consistently, like in the following code:

primitiveCollection = self.placeTrees(updatedPositions); // This returns the ModelInstanceCollection to add later.
setTimeout(SpawnTrees, 5000);

function SpawnTrees(){
viewer.scene.primitives.add(primitiveCollection);
}

console.log(primitiveCollection);

What I tried to use, was your example in the following way:

primitiveCollection = viewer.scene.primitives.add(self.placeTrees(updatedPositions)); // self.placeTrees still returns the modelinstancecollection;
Cesium.when (primitiveCollection, function () {
console.log(“trees have been placed”);
});

This code snippet doesn’t seem to run, none of the trees I am trying to place exist anymore and _model shows undefined, neither does “trees have been placed” get printed in the console.

I’m mostly getting issues where it simply isn’t waiting for the model to exist before moving on and the Promise in _readyPromise of PrimitiveCollection never actually resolving. Waiting, for some reason, fixes this. For now I’ll use this solution I have (albeit, that it is a bit of dirty code…)

Thanks for the help,

Heerco

primitiveCollection = viewer.scene.primitives.add(self.placeTrees(updatedPositions));
primitiveCollection.readyPromise.then(function () {
console.log(“trees have been placed”);
});

Hi Sean, thanks again for the reply.

This is unfortunately not the way I want to use this. I want to wait on the ModelInstanceCollection to give me the go-ahead from readyPromise before I add it to the primitives. I am having issues using this in the way you've posted because the _model variable ends up undefined, leaving me with a lack of trees, for some reason waiting those 5 seconds is what helps. I might look further into the readyPromise later.

Thanks again for the help

Usually you need to add it to scene.primitives in order for it to update and become ready. I’m not sure why the readyPromise would be called but the model would be undefined. Would you be able to send me a full code sample so I can see what’s going on?

Sorry for the late reply, unfortunately I wasn't able to attend to my code before now.

Right now, this is the current structure:

            var promise = Cesium.sampleTerrain(self.terrainProvider, 11, locations);
            Cesium.when(promise, function(updatedPositions) {
                primitiveCollection = self.placeTrees(updatedPositions);
                setTimeout(SpawnTrees, 5000);

                function SpawnTrees(){
                    viewer.scene.primitives.add(primitiveCollection);
                }
                console.log(primitiveCollection);
            });

where placeTrees runs this:

var scale = new Cesium.Cartesian3(1, 1, 1);
    var treeInstances = ;
    locations.forEach(function (location) {
        var cartesianPosition = Cesium.Cartesian3.fromRadians(location.latitude, location.longitude, -location.height);
        var modMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(cartesianPosition);
        treeInstances.push({modelMatrix: modMatrix});
    });

    var primitives = new Cesium.ModelInstanceCollection({
        instances: treeInstances,
        url: "lib/pine-alpha.glb",
        allowPicking: false,
        boundingSphere: Cesium.BoundingSphere.fromEllipsoid(Cesium.Ellipsoid.WGS84),
        shadowMode: Cesium.ShadowMode.DISABLED
    });
    return primitives;

(the shadowmode is something I added because the shadows are a MASSIVE resource hog and not needed in our usecase).

There are a few things I noticed. You are calling Cartesian3.fromRadians with latitude before longitude, but it should be the other way around. The bounding sphere could be a more reasonable size, or just don’t pass one in at all, it will be generated automatically based on the instance positions. I removed the timeout and worked directly with promises - I’m adding it to scene.primitives before it’s ready, but this needs to be done to load the collection. shadowMode is not a valid option for ModelInstanceCollection right now. If you want to disable shadows entirely, make sure that viewer.shadows is false.

Here’s a working demo that I created around your sample code. Let me know how it goes.

var viewer = new Cesium.Viewer(‘cesiumContainer’);

var terrainProvider = new Cesium.CesiumTerrainProvider({

url : 'https://assets.agi.com/stk-terrain/world',

requestWaterMask : true,

requestVertexNormals : true

});

viewer.terrainProvider = terrainProvider;

var centerLongitude = -1.31968;

var centerLatitude = 0.698874;

var center = Cesium.Cartesian3.fromRadians(centerLongitude, centerLatitude);

viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(0.0, -0.35, 500));

var locations = new Array(10);

for (var i = 0; i < 10; ++i) {

var longitude = centerLongitude + Math.random() * 0.00001;

var latitude = centerLatitude + Math.random() * 0.00001;

locations[i] = new Cesium.Cartographic(longitude, latitude);

}

Cesium.sampleTerrain(viewer.terrainProvider, 11, locations).then(function(updatedPositions) {

var primitiveCollection = placeTrees(updatedPositions);

viewer.scene.primitives.add(primitiveCollection);

primitiveCollection.readyPromise.then(function() {

    console.log('Ready');

});

});

function placeTrees(locations) {

var scale = new Cesium.Cartesian3(1, 1, 1);

var treeInstances = [];

locations.forEach(function (location) {

    var cartesianPosition = Cesium.Cartesian3.fromRadians(location.longitude, location.latitude, location.height);

    var modMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(cartesianPosition);

    treeInstances.push({modelMatrix: modMatrix});

});



var primitives = new Cesium.ModelInstanceCollection({

    instances: treeInstances,

    url: "../../SampleData/Models/CesiumAir/Cesium_Air.gltf",

    allowPicking: false

});

return primitives;

}

Hi there Sean,

First off; I added the shadowmode under ModelInstanceCollection within Cesium itself as an option so I could edit it. I have also created a pullrequest with this change but this wasn't added to the branch. Nevertheless, it works and turns my framerate from 10 fps to 60 fps. I only need the shadows off on the trees, not on the rest of the objects within our scene. So it's specific usecase (and it's an option innate on the model, why isn't it able to be edited within the ModelInstanceCollection in the first place?).

Aside from that, the adapted example still doesn't function. It simply does not show the model. It refuses to "find" it and the readypromise never gets called, which has been the crux of the problem from the start. It doesn't seem to "ready" itself.

Right now, I'm running into a whole different problem. Namely that I call remove on the old ModelInstanceCollection (viewer.scene.primitives.remove(primitiveCollection)) and it crashes because it still attempts to update. It works fine if I give it time before I attempt to another modelInstanceCollection, but if it happens too quick it instantly crashes the renderer. While, if I console.log both the PrimitiveCollection within the scene (console.log(viewer.scene.primitives) - logs the whole primitive collection of the scene with 6 instances, 5 are used for other things and 1 modelinstancecollection) and the primitiveCollection itself (console.log(primitiveCollection.isDestroyed() - logs False nearly always) both show it's still there.

The Error :

An error occurred while rendering. Rendering has stopped.
DeveloperError: This object was destroyed, i.e., destroy() was called.
Error
    at new DeveloperError (http://localhost:63342/lib/cesium-unminified/Cesium.js:1317:19)
    at ModelInstanceCollection.throwOnDestroyed (http://localhost:63342/lib/cesium-unminified/Cesium.js:25397:19)
    at PrimitiveCollection.update (http://localhost:63342/lib/cesium-unminified/Cesium.js:165306:27)
    at updatePrimitives (http://localhost:63342/lib/cesium-unminified/Cesium.js:173562:27)
    at executeCommandsInViewport (http://localhost:63342/lib/cesium-unminified/Cesium.js:173478:9)
    at updateAndExecuteCommands (http://localhost:63342/lib/cesium-unminified/Cesium.js:173346:17)
    at render (http://localhost:63342/lib/cesium-unminified/Cesium.js:173772:9)
    at Scene.render (http://localhost:63342/lib/cesium-unminified/Cesium.js:173810:13)
    at CesiumWidget.render (http://localhost:63342/lib/cesium-unminified/Cesium.js:182779:25)
    at render (http://localhost:63342/lib/cesium-unminified/Cesium.js:182167:32)
OK

I'm deleting the old ModelInstanceCollection to save on performance and resources, because if I don't the frames dip with 10 for each collection I add. But, it's updating the old one while that should be removed and I create a new one a bit later.

also, regarding the Radians conversion, if I do not do the conversion as is shown in my cript, the trees will not be in the correct place.

fixed the current problem. That's where I did use the readyPromise to request for it's status, works fine now! Thanks for all the help.