Efficient Animation (moving from Google Earth to Cesium)

Gents,

I need to animate multiple small circles. At most there could be few hundred.

Here is an example

In GE I generated and processed KML each frame with acceptable performance.

In Cesium I combine all lines in one primitive and all dots into another and replace primitives each frame.

This performs trice worse, which is unacceptable and could be a show-stopper.

Actually, there is no need to re-create the whole thing each frame. Some parts only need to be updated once per second. But the whole “dashboard” should move along the model smoothly.

Now, I’m about to try drawing it on canvas and then giving it to Cesium as a ground overlay (each frame).

And my questions:

  1. What are possible ways to improve performance of current implementation?

  2. How can I move multiple geometries at once? Will this improve performance comparing to re-creating them? (move each frame, but re-create rarely)

  3. I found myself implementing something like Entity API on top of geometries/appearances just to be able to combine multiple geometries into one primitive. Can I control this in Entity API or CZML?

  4. Do you think that “canvas + ground overlay” could work?

  5. Can you propose another, possibly better, solution?

Thank you.

var appearance = new C.PerInstanceColorAppearance({ flat: true })

var instances = _.map(o.circle, function(circle) {

var color = circle.fill || circle.stroke || C.Color.WHITE

return new C.GeometryInstance({

geometry: new (circle.fill ? C.CircleGeometry : C.CircleOutlineGeometry)({

center: C.c3(circle.center, circle.alt)

, radius: circle.radius

, vertexFormat: appearance.vertexFormat

})

, attributes: {

color: C.ColorGeometryInstanceAttribute.fromColor(color)

}

})

})

o.primitive = new C.Primitive({

geometryInstances: instances

, appearance: appearance

, asynchronous: o.asynchronous

})

f.primitives.remove(prev)

f.primitives.add(o.primitive)

Just tried “canvas + ground overlay”.

Works, but flickers badly with white.

Why? Can anything be done?

var appearance = new C.MaterialAppearance({

material: C.Material.fromType(‘Image’, {

image: o.rectangle.material // canvas.toDataURL()

})

})

instances = new C.GeometryInstance({

geometry: new C.RectangleGeometry({

rectangle: o.rectangle.coordinates

, vertexFormat: appearance.vertexFormat

})

})

asynchronous: false

“All dots in one primitive replaced each frame” implementation spends a lot of time in EllipseGeometry.createGeometry and GeometryPipeline.combineInstances.
Looks like it is time to try to reuse the same geometry in multiple instances and NOT combine instances.

Please, give me some hints as to what to explore.

Hi,

For a batching strategy, you could probably put all static circles in one Primitive, and then have one Primitive per circle for moving ones.

These performance tips for creating circles will also be useful: https://groups.google.com/forum/#!searchin/cesium-dev/granularity/cesium-dev/CzkZXqX6axQ/p5vtAzTlQQsJ

Patrick

Thanks!

So, now I try to move primitives using primitive.modelMatrix (to avoid re-creation):

  • create “unit” geometry

  • use it in multiple primitives

  • scale and place using primitive.modelMatrix

  • paint via primitive.appearance

It works with EllipsoidGeometry but I do not understand how to do the same for CircleGeometry.

Please, advise.

var C = Cesium

var p = C.Cartesian3.fromDegrees(144.767305, -38.339623)

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

var geometry = new C.EllipsoidGeometry()

var alt = 1e-6

/*

// trying to do the same with circle fails:

// - exceptions with zero center or w/o it

// - nothing visible with near-zero center

var geometry = new C.CircleGeometry({

radius: 1

, center: new C.Cartesian3(0, 0, alt) // C.Cartesian3.ZERO

})

*/

var primitive = viewer.scene.primitives.add(new C.Primitive({

geometryInstances: new C.GeometryInstance({ geometry: geometry })

, appearance: new C.MaterialAppearance()

, modelMatrix: C.Transforms.eastNorthUpToFixedFrame§

// , debugShowBoundingVolume: true

}))

// scale, move and paint

setTimeout(function() {

p.x += 5

var m = C.Transforms.eastNorthUpToFixedFrame§

C.Matrix4.multiplyByUniformScale(m, 2, m)

primitive.modelMatrix = m

primitive.appearance.material = C.Material.fromType(‘Color’, { color: C.Color.BLUE })

}, 3000)

viewer.camera.flyTo({

destination: p

, duration: 0

})

A circle geometry is different than an ellipsoid. The the circle depends on the curvature of Earth’s ellipsoid so it needs to be recomputed for each point for it to be correct; a simple affine transformation in a 4x4 matrix is not enough.

With that said, if the circle radius is reasonably small, you could get away with it, but I suspect you’ll need to write a custom geometry that creates the circle in, for example, the xy plane (would not be too hard).

Patrick

Thank you again.

I now experiment with degenerate cylinders and think they will do the trick.

Ah, yes. Good idea. That should do it.

Patrick

Can you comment on remaining questions?

These are not of major importance ATM for me, but still interesting.

Thank you.

Hi,

  1. How can I move multiple geometries at once?

Batching makes this hard. I would put each in a separate Primitive and then move each primitive.

  1. I found myself implementing something like Entity API on top of geometries/appearances just to be able to combine multiple geometries into one primitive. Can I control this in Entity API or CZML?

The Entity API abstracts away the batching detail and tries to do a good job for the most common cases since a typical user should not have to determine how to batch geometry. I don’t believe we currently expose anything to influence the batching at this layer.

Patrick

Alexander, I know you had some questions in other threads that I haven’t gotten around to answering yet, but hopefully will soon. I’ve had my head down on KML and related things that need to be done for 1.7. One thing I will mention briefly is that when working with entities, it will batch dynamic objects separately and automatically as long as you give it a property that changes over time. For a more concrete example, assume you wanted to set the height of a polygon, the simple way to do this is:

entity.polygon.height = 3;

What you are really doing (under the hood) is creating a ConstantProperty with a value of 3. It’s shorthand for actually writing.

entity.polygon.height = new ConstantProperty(3);

If you plan on height changing, then simply re-assigning height is not the best way to do it. Instead you would want to use a time-dynamic property. Which property you use really depends on the nature of the animation and change. For example, SampledProperty is useful if you want data to be interpolated and change every frame (like a position or a circle growing and shrinking). Assuming you had three times (t1, t2, r3)

var property = new Cesium.SampledProperty(Number);

property.addSample(t1, 30);

property.addSample(t2, 15);

property.addSample(t3, 45);

entity.polygon.height = property;

So by default, height is undefined until t1, at which case it’s 30, it will linearly decrease until t2, when it will be 15, and then increase to 45 by t3. Since height is not a per-instance attribute, having a polygon with a dynamic height will cause the entity system to automatically batch it separately from the static data.

The “catch-all” dynamic property is the CallbackProperty

var height = 0;

entity.polygon.height = new Cesium.CallbackProperty(function(time, result){

return height;

}, false);

CallbackProperty let’s you specify your own function to calculate the property value. The second parameter is a boolean indicating if the value is constant (so passing false makes the property dynamic). So in the above example, we can change height at our leisure and the entity system will automatically pick it up. It will also notice that the property is dynamic and so it will be batches separately.

There are quite a few other properties as well (TimeIntervalCollectionProperty is another useful one) but I won’t try and go into them here. I plan on writing a new tutorial that follows up Visualizing Spatial Data and concentrates on time-dynamic stuff as soon as 1.7 is out. In the mean time, I hope this sheds some light on the subject for you (even though I’m sure I glossed over a lot of the details).

Hope that helps,

Matt

So, I just need to be explicit on what’s dynamic and Entity API will apply batching and other optimizations as appropriate.

Thanks!

Yes, exactly. The benefit of this approach is that when we further improve batching or optimize support for dynamic geometry; anyone using the Entity API gets those optimizations without having to change anything. I actually expect to implement a bunch of performance improvement for dynamic entity geometry for the 1.8 release (April 1st)