Display a massive point cloud by aggregating dozens of datasets.

Giro3D version
THREE.js version
OpenLayers version
CRS
Memory usage (CPU)
Memory usage (GPU)
Frames
Clear color
Clear alpha
Status
Local clipping enabled
Capabilities
WebGL 2
Max texture units
Max texture size
Precision
Max fragment shader uniforms
Logarithmic depth buffer
Max shader attributes
Check shader errors
EXT_clip_control
EXT_color_buffer_float
EXT_color_buffer_half_float
EXT_conservative_depth
EXT_depth_clamp
EXT_float_blend
EXT_polygon_offset_clamp
EXT_texture_compression_bptc
EXT_texture_compression_rgtc
EXT_texture_filter_anisotropic
EXT_texture_mirror_clamp_to_edge
EXT_texture_norm16
NV_shader_noperspective_interpolation
OES_draw_buffers_indexed
OES_sample_variables
OES_shader_multisample_interpolation
OES_texture_float_linear
OVR_multiview2
WEBGL_clip_cull_distance
WEBGL_compressed_texture_astc
WEBGL_compressed_texture_etc
WEBGL_compressed_texture_etc1
WEBGL_compressed_texture_s3tc
WEBGL_compressed_texture_s3tc_srgb
WEBGL_debug_renderer_info
WEBGL_debug_shaders
WEBGL_lose_context
WEBGL_multi_draw
WEBGL_polygon_mode
WEBGL_stencil_texturing
MSAA
EDL
EDL Radius
EDL Strength
Inpainting
Inpainting steps
Inpainting depth contrib.
Point cloud occlusion
Type
FOV
Automatic plane computation
Far plane
Near plane
Max far plane
Min near plane
Width (pixels)
Height (pixels)
x
y
z
color
Enable cache
Default TTL (seconds)
Capacity (MB)
Capacity (entries)
Entries
Memory usage (approx)
Pending requests
Memory tracker
Identifier
Memory usage (CPU)
Memory usage (GPU)
Status
Render order
Enable
Plane normal X
Plane normal Y
Plane normal Z
Distance
Helper size
Negate plane
Visible
Freeze updates
Opacity
Show helpers
Show hidden objects
Name filter
Hierarchy
Properties
isObject3D
uuid
name
type
matrixAutoUpdate
matrixWorldAutoUpdate
matrixWorldNeedsUpdate
visible
castShadow
receiveShadow
frustumCulled
renderOrder
x
y
z
x
y
z
Parameters
Loading metadata...
100% © IGN
index.js
import { Vector3 } from "three";
import { MapControls } from "three/examples/jsm/controls/MapControls.js";

import Instance from "@giro3d/giro3d/core/Instance.js";
import PointCloud from "@giro3d/giro3d/entities/PointCloud.js";
import Inspector from "@giro3d/giro3d/gui/Inspector.js";
import AggregatePointCloudSource from "@giro3d/giro3d/sources/AggregatePointCloudSource";
import COPCSource from "@giro3d/giro3d/sources/COPCSource.js";
import { setLazPerfPath } from "@giro3d/giro3d/sources/las/config.js";

import ColorMap from "@giro3d/giro3d/core/ColorMap.js";

function bindDropDown(id, onChange) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLSelectElement)) {
    throw new Error(
      "invalid binding element: expected HTMLSelectElement, got: " +
        element.constructor.name,
    );
  }

  element.onchange = () => {
    onChange(element.value);
  };

  const callback = (v) => {
    element.value = v;
    onChange(element.value);
  };

  const setOptions = (options) => {
    const items = options.map(
      (opt) =>
        `<option value=${opt.id} ${opt.selected ? "selected" : ""}>${opt.name}</option>`,
    );
    element.innerHTML = items.join("\n");
  };

  return [callback, element.value, element, setOptions];
}

function bindNumberInput(id, onChange) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLInputElement)) {
    throw new Error(
      "invalid binding element: expected HTMLInputElement, got: " +
        element.constructor.name,
    );
  }

  element.onchange = () => {
    onChange(element.valueAsNumber);
  };

  const callback = (v) => {
    element.value = v.toString();
    onChange(element.valueAsNumber);
  };

  return [callback, element.valueAsNumber, element];
}

function bindProgress(id) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLDivElement)) {
    throw new Error(
      "invalid binding element: expected HTMLDivElement, got: " +
        element.constructor.name,
    );
  }

  const setProgress = (normalized, text) => {
    element.style.width = `${Math.round(normalized * 100)}%`;
    if (text) {
      element.innerText = text;
    }
  };

  return [setProgress, element.parentElement];
}

function bindToggle(id, onChange) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLInputElement)) {
    throw new Error(
      "invalid binding element: expected HTMLButtonElement, got: " +
        element.constructor.name,
    );
  }

  element.oninput = function oninput() {
    onChange(element.checked);
  };

  const callback = (v) => {
    element.checked = v;
    onChange(element.checked);
  };

  return [callback, element.checked, element];
}

function formatPointCount(count, numberFormat = undefined) {
  let displayedPointCount = count;
  let suffix = "";

  if (count > 1_000_000) {
    displayedPointCount /= 1_000_000;
    suffix = "M";
  } else if (count > 1_000_000_000) {
    displayedPointCount /= 1_000_000_000;
    suffix = "B";
  }

  if (numberFormat == null) {
    numberFormat = new Intl.NumberFormat(undefined, {
      maximumFractionDigits: 2,
    });
  }

  return numberFormat.format(displayedPointCount) + suffix;
}

function makeColorRamp(
  preset,
  discrete = false,
  invert = false,
  mirror = false,
) {
  let nshades = discrete ? 10 : 256;

  const values = colormap({ colormap: preset, nshades });

  const colors = values.map((v) => new Color(v));

  if (invert) {
    colors.reverse();
  }

  if (mirror) {
    const mirrored = [...colors, ...colors.reverse()];
    return mirrored;
  }

  return colors;
}

// LAS processing requires the WebAssembly laz-perf library
// This path is specific to your project, and must be set accordingly.
setLazPerfPath("/assets/wasm");

Instance.registerCRS(
  "EPSG:2154",
  "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs",
);

const instance = new Instance({
  target: "view",
  crs: "EPSG:2154",
  backgroundColor: "black",
  renderer: {
    logarithmicDepthBuffer: true,
  },
});

instance.renderingOptions.enableEDL = true;
instance.renderingOptions.EDLStrength = 5;

const colormaps = {
  Intensity: new ColorMap({ colors: makeColorRamp("jet"), min: 0, max: 100 }),
  Z: new ColorMap({ colors: makeColorRamp("portland"), min: 0, max: 100 }),
};

const datasets = [
  "LHD_FXX_0657_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0657_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0651_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0650_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0653_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0655_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0652_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0656_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0654_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0649_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0648_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0647_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0646_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0644_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0645_6859_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6868_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6864_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6865_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6866_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6867_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6860_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6861_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6862_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6863_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6857_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6858_PTS_O_LAMB93_IGN69.copc.laz",
  "LHD_FXX_0658_6859_PTS_O_LAMB93_IGN69.copc.laz",
];

const server = "https://3d.oslandia.com/giro3d/pointclouds/lidarhd/paris/";

const source = new AggregatePointCloudSource({
  sources: datasets.map((dataset) => new COPCSource({ url: server + dataset })),
});

const pointCloud = new PointCloud({ source });

const [setProgress, progressElement] = bindProgress("progress");

source.addEventListener("progress", () => setProgress(source.progress));

pointCloud.showVolume = true;

async function onInitialized(entity) {
  progressElement.style.display = "none";
  document.getElementById("options").style.display = "block";

  entity.colorMap = colormaps.Z;
  entity.setActiveAttribute("Z");

  document.getElementById("point-count").innerText = formatPointCount(
    entity.pointCount,
  );

  document.getElementById("file-count").innerText =
    source.sources.length.toString();

  const volume = entity.getBoundingBox();
  const center = volume.getCenter(new Vector3());

  const camera = instance.view.camera;
  const lookAt = new Vector3(center.x, center.y, volume.min.z);

  camera.position.set(center.x, center.y - 1, volume.max.z * 10);

  camera.lookAt(lookAt);

  const controls = new MapControls(camera, instance.domElement);
  controls.target.copy(lookAt);
  controls.enableDamping = true;
  controls.dampingFactor = 0.25;
  instance.view.setControls(controls);

  instance.notifyChange(camera);

  const metadata = await source.getMetadata();

  // Update the Z colormap with the min/max height of the datasets.
  colormaps.Z.min = volume.min.z * 1.1;
  colormaps.Z.max = volume.max.z * 0.6;

  colormaps.Intensity.min = 0;
  colormaps.Intensity.max = 5000;

  const [, , , setAvailableAttributes] = bindDropDown(
    "attribute",
    (attribute) => {
      entity.setActiveAttribute(attribute);
      entity.colorMap = colormaps[attribute];
    },
  );

  setAvailableAttributes(
    metadata.attributes.map((att, index) => ({
      id: att.name,
      name: att.name,
      selected: index === 0,
    })),
  );

  bindToggle("show-volume", (show) => (entity.showVolume = show));
  bindToggle("show-tile-volumes", (show) => (entity.showNodeVolumes = show));
  bindToggle("edl", (edl) => {
    instance.renderingOptions.enableEDL = edl;
    instance.notifyChange();
  });
  bindNumberInput("point-budget", (v) => {
    if (v <= 0) {
      entity.pointBudget = null;
    } else {
      entity.pointBudget = v;
    }
  });

  instance.addEventListener("update-end", () => {
    document.getElementById("displayed-point-count").innerText =
      formatPointCount(entity.displayedPointCount);
  });
}

instance.add(pointCloud).then(onInitialized).catch(console.error);

Inspector.attach("inspector", instance);
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Massive point cloud</title>
    <meta charset="UTF-8" />
    <meta name="name" content="aggregate_pointcloud" />
    <meta
      name="description"
      content="Display a massive point cloud by aggregating dozens of datasets."
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
    <link
      href="https://giro3d.org/assets/bootstrap-custom.css"
      rel="stylesheet"
    />
    <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
    <link
      rel="stylesheet"
      type="text/css"
      href="https://giro3d.org/next/examples/css/example.css"
    />
  </head>

  <body>
    <div id="view" class="m-0 p-0 w-100 h-100"></div>
    <div
      id="inspector"
      class="position-absolute top-0 start-0 mh-100 overflow-auto"
    ></div>

    <div class="side-pane-with-status-bar" style="width: 25rem">
      <!--Parameters -->
      <div class="card">
        <div class="card-header">Parameters</div>

        <div class="card-body">
          <div class="progress" role="progressbar">
            <div
              class="progress-bar bg-info progress-bar-striped progress-bar-animated text-dark"
              id="progress"
              style="width: 0%"
            >
              Loading metadata...
            </div>
          </div>

          <div id="options" style="display: none">
            <ul class="list-group mb-3" id="table">
              <li class="list-group-item">
                Total points
                <b id="point-count" class="d-float float-end"></b>
              </li>
              <li
                class="list-group-item"
                title="The number of points currently displayed"
              >
                Displayed points
                <b id="displayed-point-count" class="d-float float-end"></b>
              </li>
              <li class="list-group-item">
                Files: <b id="file-count" class="d-float float-end"></b>
              </li>
            </ul>

            <!-- Active attribute selector -->
            <div class="input-group" id="attribute-group">
              <label class="input-group-text col-5" for="attribute"
                >Dimension</label
              >
              <select
                class="form-select"
                id="attribute"
                autocomplete="off"
                title="Sets the active attribute of the point cloud"
              ></select>
            </div>

            <!-- Point budget -->
            <div class="input-group mt-1" id="attribute-group">
              <label class="input-group-text col-5" for="attribute"
                >Point budget</label
              >
              <input
                type="number"
                min="-1"
                max="99999999999"
                value="-1"
                step="1"
                class="form-control"
                id="point-budget"
                autocomplete="off"
              />
            </div>

            <!-- Show volume -->
            <div class="form-check form-switch mt-2">
              <input
                class="form-check-input"
                type="checkbox"
                role="switch"
                checked
                id="show-volume"
                autocomplete="off"
              />
              <label
                title="Show the volume of the octree"
                class="form-check-label"
                for="show-volume"
                >Show dataset volume</label
              >
            </div>

            <!-- Show octree volumes -->
            <div class="form-check form-switch">
              <input
                class="form-check-input"
                type="checkbox"
                role="switch"
                id="show-tile-volumes"
                autocomplete="off"
              />
              <label
                title="Show the volume of the octree"
                class="form-check-label"
                for="show-tile-volumes"
                >Show octrees</label
              >
            </div>

            <!-- Eye Dome Lighting -->
            <div class="form-check form-switch">
              <input
                class="form-check-input"
                type="checkbox"
                role="switch"
                checked
                id="edl"
                autocomplete="off"
              />
              <label
                title="Toggles Eye Dome Lighting post-processing effect"
                class="form-check-label"
                for="edl"
                >Eye Dome Lighting</label
              >
            </div>
          </div>
        </div>
      </div>
    </div>

    <script type="module" src="index.js"></script>
    <script>
      /* activate popovers */
      const popoverTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="popover"]'),
      );
      popoverTriggerList.map(
        // bootstrap is used as script in the template, disable warning about undef
        // eslint-disable-next-line no-undef
        (popoverTriggerEl) =>
          new bootstrap.Popover(popoverTriggerEl, {
            trigger: "hover",
            placement: "left",
            content: document.getElementById(
              popoverTriggerEl.getAttribute("data-bs-content"),
            ).innerHTML,
            html: true,
          }),
      );
    </script>
  </body>
</html>
package.json
{
    "name": "aggregate_pointcloud",
    "dependencies": {
        "@giro3d/giro3d": "git+https://gitlab.com/giro3d/giro3d.git"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}