Click or drag to resize

Marker Batch Primitive

Markers are 2D images that always face the camera and generally remain a constant pixel size. Markers are sometimes called sprites or billboards. They have many uses, including: representing points of interest, visualizing a large amount of real-time data, replacing models to improve performance, and for rendering image-based effects such as clouds, smoke, fire, and vapor trails.

The marker batch, MarkerBatchPrimitive, renders a group of markers; each marker is defined by a position and optional properties, such as a texture, color, or rotation. Using the marker batch is similar to using other batch primitives, such as the text batch and point batch.

In addition to the sections below, see the Favorite Features: The Marker Batch Primitive blog post for more information on the marker batch.

Note Note

The marker batch requires a video card that supports OpenGL 2.0 or 1.5 with the proper extensions. See the System Requirements topic and the supported method for more information.

Basic Example

Using the marker batch can be as simple as creating an instance of MarkerBatchPrimitive, providing the instance with a texture and collection of positions, and then adding the instance to the primitive manager. This is shown in the following example:

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 3000.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 3000.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-90.25), Trig.degreesToRadians(29.98), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-121.92), Trig.degreesToRadians(37.37), 0.0));

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setTexture(SceneManager.getTextures().fromUri(texturePath));
markerBatch.setCartographic(earth, positions);

SceneManager.getPrimitives().add(markerBatch);
Marker Batch Basic Example

Each marker is rendered with the same texture. Since the marker batch's SizeSource (get) is MarkerBatchSizeSource.FROM_TEXTURE by default, the size of each marker is the size of the texture in pixels.

Basic Properties

The Texture (get / set), Color (get / set), Size (get / set), and Rotation (get / set) properties define the appearance of markers. When Texture (get / set) is null, markers are rendered as a solid color, as shown below.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setCartographic(earth, positions);
markerBatch.setColor(Color.YELLOW);

SceneManager.getPrimitives().add(markerBatch);
Marker Batch No Texture

If instead of setting Color (get / set) in the above example, we instead set the marker batch's Texture (get / set), the result would be:

Java
markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png")));
Marker Batch One Texture

A marker batch's color and texture can be used together. The Color (get / set) property is multiplied with the texture color at each pixel. By default, Color (get / set) is white, so just the texture color is shown. Setting Color (get / set) to yellow in the above example produces the following:

Marker Batch Texture And Color

By default, when a texture is assigned to a marker batch, the size of each marker is the same size as the texture in pixels. This is because the marker batch's SizeSource (get) defaults to MarkerBatchSizeSource.FROM_TEXTURE. To use a custom marker size, the marker batch can be initialized with a SizeSource (get) of MarkerBatchSizeSource.USER_DEFINED and then the Size (get / set) property can be set. For example:

Java
MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED);
markerBatch.setSize(new DimensionF(128.0f, 64.0f));

The marker batch's Rotation (get / set) property is used to apply a screen-space rotation. The following example rotates each marker 30 degrees counter-clockwise.

Java
markerBatch.setRotation(Trig.degreesToRadians(30.0));
Marker Batch Per Batch Rotation
Per-Marker Properties

Properties, such as Texture (get / set) and Color (get / set), apply to every marker in a marker batch. In many cases, it is useful to assign different properties to each marker in a marker batch. This allows one marker batch to represent several markers, which is drastically more efficient than creating one marker batch per marker. See the Batching topic for additional performance tips.

To assign per-marker properties to a marker batch, create an instance of MarkerBatchPrimitiveOptionalParameters, provide it with collections for per-marker properties, then pass it to a marker batch's set method, along with the positions for the markers. The following example creates a marker batch with three markers, each with a unique texture.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

ArrayList<Texture2D> textures = new ArrayList<Texture2D>();
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/agiLogo.png")));

MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters();
parameters.setTextures(textures);

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setCartographic(earth, positions, parameters);

SceneManager.getPrimitives().add(markerBatch);
Marker Batch Per Marker Texture

When per-marker properties are used, the corresponding per-batch property is ignored. In the above example, the marker batch's Texture (get / set) property is ignored, since per-batch textures were provided. You can mix and match per-batch and per-marker properties. For example, the above code can be modified to also include per-marker rotations and a per-batch color.

Java
ArrayList<Double> rotations = new ArrayList<Double>();
rotations.add(Trig.degreesToRadians(25.0));
rotations.add(Trig.degreesToRadians(50.0));
rotations.add(Trig.degreesToRadians(75.0));
parameters.setRotations(rotations);

markerBatch.setCartographic(earth, positions, parameters);
Color lightGreen = new Color(0x90EE90);
markerBatch.setColor(lightGreen);
Marker Batch Per Marker Texture And Rotation

Besides enabling a lot of flexibility, per-batch and per-marker properties allow the marker batch to automatically optimize for the specified mix of per-batch and per-marker properties. The marker batch generates source code for an optimized shader program that runs on the video card based on the specified properties. No extra memory is used nor is any extra computation performed for properties that are not used.

Size Units

The size of markers can be defined in either pixels or meters. By default, the size is in pixels. No matter how far away the camera is from a marker, it will take up the same number of pixels on the screen as shown below.

Marker Batch Units in Pixels

To set the size to meters, set the marker batch's SizeUnit (get / set) property to MarkerBatchUnit.METERS. When the units are meters, a marker's size depends on the camera's position, similar to the Model Primitive, as shown below.

Marker Batch Units in Meters

Sizing markers in meters has several uses including a quick and dirty way to implement imposters to improve performance, and image-based rendering effects such as clouds, smoke, fire, and vapor trails.

Translucency

Most textures used for markers have texels that are either completely opaque or completely transparent, like all of the markers shown thus far. Some textures have translucent texels, that is, texels that are neither completely opaque nor completely transparent. An example of three such textures is shown below:

Translucent Markers

The marker batch's RenderPass (get / set) and SortOrder (get) properties are used to support translucent textures. By default, these properties are initialized to efficiently support opaque textures, which includes textures with completely transparent texels.

If the above translucent textures were assigned to a default initialized marker batch, the translucency will not be shown, and the result would be similar to the image on the left below.

Translucent Markers Default
Opaque render pass, default sort order
Translucent Markers with Render Pass
Translucent render pass, default sort order
Translucent Markers with Sort Order
Translucent render pass, back-to-front sort order

In order to show the texture's translucency, the marker batch's RenderPass (get / set) must be set to MarkerBatchRenderPass.TRANSLUCENT. This will result in the middle image above. In most cases, this is all that is necessary to support translucent textures.

When translucent markers overlap each other, initialize the marker batch with a SortOrder (get) of MarkerBatchSortOrder.BACK_TO_FRONT. This ensures the correct blending of translucent markers, which makes it clear which marker is in front of which marker. Compare the above middle and right images. In the middle image, it looks like the triangle is behind the purple marker. On the right, when MarkerBatchSortOrder.BACK_TO_FRONT is used, it is clear that the triangle is really in front. Example code to create the image on the right is shown below:

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

ArrayList<Texture2D> textures = new ArrayList<Texture2D>();
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentCircle64.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentHoles64.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentTriangle64.png")));

MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters();
parameters.setTextures(textures);

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.FROM_TEXTURE, MarkerBatchSortOrder.BACK_TO_FRONT);
markerBatch.setRenderPass(MarkerBatchRenderPass.TRANSLUCENT);
markerBatch.setCartographic(earth, positions, parameters);

SceneManager.getPrimitives().add(markerBatch);

Using MarkerBatchSortOrder.BACK_TO_FRONT is not as efficient as the default, MarkerBatchSortOrder.BY_TEXTURE. MarkerBatchSortOrder.BACK_TO_FRONT requires the CPU to sort the markers before rendering and can increase the number of costly textures changes (unless a texture atlas is used). Therefore, it is recommended to only use MarkerBatchSortOrder.BACK_TO_FRONT when it is visually noticeable.

Tip Tip

An opaque marker can be made translucent by setting the marker batch's Translucency (get / set) property and its RenderPass (get / set) to MarkerBatchRenderPass.BASED_ON_TRANSLUCENCY, which is the default.

Horizontal and Vertical Origins

Similar to the Text Batch Primitive, the marker batch supports per-batch or per-marker horizontal and vertical origins. Origins are commonly used when two primitives are at the same position and need to be separated. By default, a marker's position defines where its center is. Use the Origin (get / set) property or the setOrigins method to change the origin.

The following example creates a point batch with three points and a marker batch with three markers. Per-marker origins are defined so the markers and points are not rendered on top of each other.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

ArrayList<Texture2D> textures = new ArrayList<Texture2D>();
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png")));
textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/agiLogo.png")));

ArrayList<Origin> origins = new ArrayList<Origin>();
origins.add(Origin.TOP_CENTER);
origins.add(Origin.BOTTOM_LEFT);
origins.add(Origin.BOTTOM_CENTER);

MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters();
parameters.setTextures(textures);
parameters.setOrigins(origins);

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setCartographic(earth, positions, parameters);
SceneManager.getPrimitives().add(markerBatch);

PointBatchPrimitive pointBatch = new PointBatchPrimitive();
pointBatch.setColor(Color.RED);
pointBatch.setPixelSize(5.0f);
pointBatch.setCartographic(earth, positions);
SceneManager.getPrimitives().add(pointBatch);
Marker Batch Per Marker Origins
Pixel and Eye Offsets

Similar to the Text Batch Primitive, the marker batch supports per-batch or per-marker pixel and eye offsets.

Pixel offsets are set using the PixelOffset (get / set) property or the setPixelOffsets method. The pixel offset defines a translation to apply from the marker's origin. In pixel coordinates, increasing X moves from the left toward the right side of the window, and increasing Y moves from the bottom to the top. The following example applies a pixel offset of 80 pixels in the X direction and 40 in the Y direction.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png")));
markerBatch.setOrigin(Origin.BOTTOM_LEFT);
markerBatch.setPixelOffset(new PointF(80.0f, 40.0f));
markerBatch.setCartographic(earth, positions);
SceneManager.getPrimitives().add(markerBatch);

PointBatchPrimitive pointBatch = new PointBatchPrimitive();
pointBatch.setColor(Color.RED);
pointBatch.setPixelSize(5.0f);
pointBatch.setCartographic(earth, positions);
SceneManager.getPrimitives().add(pointBatch);
Marker Batch Pixel Offset

In addition to pixel offsets, an eye space offset can be applied per-batch or per-marker using the EyeOffset (get / set) property or the setEyeOffsets method. Each unit in eye space is 1 meter. Positive X points towards the camera's right, positive Y points up, and negative Z points along the view direction. For example, a positive Y offset may be applied to translate a marker so it is always "on top" of a model primitive.

The following example demonstrates how applying an eye offset of (0, 100000, 0) affects the position of a marker from different view points. Note that an eye offset is different than a pixel offset or a world translation (e.g. offset the altitude by 10 meters).

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive();
markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png")));
markerBatch.setEyeOffset(new Cartesian(0.0, 100000.0, 0.0));
markerBatch.setCartographic(earth, positions);
SceneManager.getPrimitives().add(markerBatch);
PointBatchPrimitive pointBatch = new PointBatchPrimitive();

pointBatch.setColor(Color.RED);
pointBatch.setPixelSize(5.0f);
pointBatch.setCartographic(earth, positions);
SceneManager.getPrimitives().add(pointBatch);
Marker Batch Eye Offset
From this view, the eye offset moves the markers approximately north.
Marker Batch Eye Offset
From a different view, the offset moves the markers approximately along their surface normals.
Alignment Options

The marker batch supports per batch alignment options. The alignToNorth method will orient each marker so that its up vector is pointing towards a central body's north axis. Similarly, the alignToAxis method will orient each marker so that its up vector is pointing towards an arbitrary axis. The default behavior for a marker batch is to align each marker's up vector to the up vector of the camera, which can be set with the alignToScreen method. The following image shows markers aligned to the Earth's north axis:

Marker Batch World Aligned
Dynamic Updates

As explained in the Dynamic Updates topic, the marker batch provides set and setPartial methods for efficient dynamic updates of positions and per-marker properties such as color or rotation. Use the set method to completely redefine the marker batch. If this is done frequently (e.g. every few frames), initialize the marker batch with SetHint.FREQUENT.

Use the setPartial method to update a subset of positions and/or per-marker properties in the marker batch. Make sure to initialize the marker batch with SetHint.SetPartial. The following example creates a marker batch with three markers with per-marker sizes. setPartialCartographic is used to modify the position and size of two markers.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

ArrayList<DimensionF> sizes = new ArrayList<DimensionF>();
sizes.add(new DimensionF(32.0f, 32.0f));
sizes.add(new DimensionF(64.0f, 64.0f));
sizes.add(new DimensionF(128.0f, 128.0f));

MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters();
parameters.setSizes(sizes);

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED, MarkerBatchSortOrder.BY_TEXTURE, SetHint.PARTIAL);
markerBatch.setCartographic(earth, positions, parameters);
SceneManager.getPrimitives().add(markerBatch);

// Modify 2 markers in the batch
ArrayList<Cartographic> newPositions = new ArrayList<Cartographic>();
newPositions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 100000.0));
newPositions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 100000.0));

ArrayList<DimensionF> newSizes = new ArrayList<DimensionF>();
newSizes.add(new DimensionF(256.0f, 256.0f));
newSizes.add(new DimensionF(512.0f, 512.0f));

MarkerBatchPrimitiveOptionalParameters newParameters = new MarkerBatchPrimitiveOptionalParameters();
newParameters.setSizes(newSizes);

markerBatch.setPartialCartographic(earth, newPositions, newParameters, Arrays.asList(0, 2));
Advanced: Texture Atlas

When a marker batch uses a significant number of textures, changing textures during rendering can become a performance problem even when using the default SortOrder (get), which is MarkerBatchSortOrder.BY_TEXTURE. The problem can become worse when using MarkerBatchSortOrder.BACK_TO_FRONT is used, as described in the Translucency section above.

A texture atlas is a technique that improves rendering performance by combining many small textures into one large texture; texture coordinates are used to index to each small texture in the large texture. If your marker batch uses many small textures, you should consider combining them. For example, text is commonly rendered with a texture atlas that contains all of the font's characters. However, note that in Insight3D, you should use the Text Batch Primitive to render text.

Marker Batch Text Texture Atlas

In the per-marker properties section above, a marker batch with three markers and three textures was created:

Marker Batch Per Marker Texture

The same results can be achieved with a single texture atlas. First, combine the three textures into a single texture:

Graphics Marker Batch Logo Texture Atlas
Tip Tip

You may find NVIDIA's Texture Atlas Tools helpful for creating texture atlases.

Instead of assigning per-marker textures, a per-batch texture is used along with per-marker texture coordinates and sizes. In this case, each sub-texture is the same height so the t and q texture coordinates are 0 and 1. The s and p texture coordinates are based on the x position and width of the sub-texture in the texture.

Java
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
ArrayList<Cartographic> positions = new ArrayList<Cartographic>();
positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0));
positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0));

ArrayList<TextureCoordinate4DF> textureCoordinates = new ArrayList<TextureCoordinate4DF>();
textureCoordinates.add(new TextureCoordinate4DF(0.0f, 0.0f, 93.0f / 235.0f, 1.0f));
textureCoordinates.add(new TextureCoordinate4DF(95.0f / 235.0f, 0.0f, 155.0f / 235.0f, 1.0f));
textureCoordinates.add(new TextureCoordinate4DF(157.0f / 235.0f, 0.0f, 1.0f, 1.0f));

ArrayList<DimensionF> sizes = new ArrayList<DimensionF>();
sizes.add(new DimensionF(93.0f, 64.0f));
sizes.add(new DimensionF(60.0f, 64.0f));
sizes.add(new DimensionF(78.0f, 64.0f));

MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters();
parameters.setTextureCoordinates(textureCoordinates);
parameters.setSizes(sizes);

MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED);
markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logoTextureAtlas.png")));
markerBatch.setCartographic(earth, positions, parameters);

SceneManager.getPrimitives().add(markerBatch);