Skip to content

CloudCollection.createDrawCommands() never sets command.boundingVolume — crashes non-OIT (mobile) translucent sort with "undefined is not an object (evaluating 'e.boundingVolume.distanceSquaredTo')" #13714

Description

@VladBosovets

What happened?

CloudCollection.prototype.updatecreateDrawCommands (packages/engine/Source/Scene/CloudCollection.js) pushes a Pass.TRANSLUCENT DrawCommand for every cloud without ever assigning command.boundingVolume:

https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Scene/CloudCollection.js

function createDrawCommands(cloudCollection, frameState) {
  const that = cloudCollection;
  const pass = frameState.passes;
  const uniforms = that._uniforms;
  const commandList = frameState.commandList;
  if (pass.render) {
    const colorList = that._colorCommands;
    const va = that._vaf.va;
    const vaLength = va.length;
    colorList.length = vaLength;
    for (let i = 0; i < vaLength; i++) {
      let command = colorList[i];
      if (!defined(command)) {
        command = colorList[i] = new DrawCommand();
      }
      command.pass = Pass.TRANSLUCENT;
      command.owner = cloudCollection;
      command.uniformMap = uniforms;
      command.count = va[i].indicesCount;
      command.vertexArray = va[i].va;
      command.shaderProgram = that._sp;
      command.renderState = that._rs;
      if (that._instanced) {
        command.count = 6;
        command.instanceCount = that._clouds.length;
      }
      commandList.push(command);
      // <-- command.boundingVolume is never set here
    }
  }
}

Compare to BillboardCollection.update and PointPrimitiveCollection.update, which both explicitly set command.boundingVolume = boundingVolume; in the equivalent loop before pushing to commandList.

Why it's usually invisible: when the scene uses GPU-based Order Independent Translucency (OIT — the default on desktop browsers with WebGL2/float-texture support), translucent commands are drawn without ever being distance-sorted, so command.boundingVolume is never read and the missing assignment has no effect.

On a device/context that falls back to the CPU depth-sort path (no OIT support — in practice this means mobile Safari / Chrome-iOS, since Chrome-iOS is WebKit under the hood), Scene's executeTranslucentCommandsBackToFront/FrontToBack sorts frameState.commandList's translucent commands with a comparator that reads command.boundingVolume.distanceSquaredTo(position) on every command — and throws on the cloud command, since boundingVolume is undefined.

Expected behavior: CloudCollection's translucent commands should be safely sortable like any other translucent primitive's commands, regardless of which translucency path the device uses.

Suggested fix: set command.boundingVolume in createDrawCommands, the same way BillboardCollection/PointPrimitiveCollection already do — presumably derived from each cloud's position + maximumSize/scale (a bounding sphere per cloud, or per-batch if commands are batched).

Reproduction steps

  1. Add a CloudCollection with at least one CumulusCloud to a scene.
  2. View it on a device/browser that doesn't use Cesium's OIT path (e.g. a real iPhone, Safari or Chrome — desktop-Chromium device emulation does not reproduce this, since emulation doesn't change which translucency path Cesium actually picks).
  3. The very first frame the cloud is visible, rendering throws and (with default Viewer settings) the render loop stops permanently:
TypeError: undefined is not an object (evaluating 'e.boundingVolume.distanceSquaredTo')
    at backToFront (Scene.js, inside executeTranslucentCommandsBackToFront)
    at mergeSort (mergeSort.js)
    ...
    at executeTranslucentCommandsBackToFront
    at executeCommandsInViewport
    at updateAndExecuteCommands
    at Scene.prototype.render

Sandcastle example

Not provided — this was found via source-reading rather than an isolated live repro. The missing assignment is visible directly in the linked source, and the failure mode only manifests on non-OIT devices (real mobile hardware), which Sandcastle's usual desktop-browser testing wouldn't catch either.

Environment

Browser: Chrome on iOS (CriOS/WebKit)
CesiumJS Version: 1.144.0 (bug present verbatim on main as of 2026-08-23, see linked source above)
Operating System: iOS (iPhone)

Not reproducible on: desktop Chrome, including with device-metrics/UA emulation of an iPhone (Chromium's OIT path is unaffected by that emulation).

AI acknowledgment

  • I used AI to generate this issue report.
  • (If the above is checked) I have reviewed the AI-generated content before submitting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions