Skip to content

Chapter 8: Debug and Validation Tools#

Welcome back to the loaders.gl-showcases tutorial! In the previous chapter, we explored how the application integrates with ArcGIS to securely access private content. Now that we've covered how to load and display various 3D datasets, it's time to look at the tools the application provides to understand what's inside that data and check for potential issues. This is where the Debug and Validation Tools come in.

What are Debug and Validation Tools?#

Imagine you're looking at a complex 3D model on the map, but something doesn't look quite right. Maybe a texture seems stretched, some geometry appears distorted, or you suspect the data isn't structured optimally. Without the right tools, it's hard to figure out why.

As the concept description explains:

This system provides special features, mainly on the Debug page, to inspect the detailed properties of individual 3D tiles (like geometry, textures, and metadata) and check for potential issues. It's like a mechanic's toolkit and diagnostic scanner for the 3D models, allowing users to look under the hood and identify problems within the data itself.

These tools are designed to give developers and advanced users insights into the technical details of the 3D tiles being rendered and flag potential data quality issues.

The Central Use Case: Diagnosing a Visual Problem#

Let's say a building model loaded as a 3D tileset has a weird visual artifact on one specific part of its surface. You suspect it might be related to the geometry, the texture coordinates (UVs), or perhaps a problem with how that particular tile is defined (like its bounding volume or level of detail settings).

The central use case for Debug and Validation tools is to help you investigate this specific tile: 1. Identify the problematic tile. 2. Inspect its properties (vertex count, texture presence, etc.). 3. Visualize its internal structure (like seeing the wireframe or normals). 4. Run automated checks for common data issues on that tile.

By using these tools, you can gather clues to understand the root cause of the visual problem.

Key Concepts#

The Debug and Validation tools offer several key functionalities:

  • Tile Inspection: The ability to click on an individual tile on the map and view detailed information about it.
  • Property Display: Showing technical metadata for a selected tile, such as its ID, depth in the tileset tree, number of vertices, refinement strategy, distance from camera, and whether it has textures or materials.
  • Visual Debugging Overlays: Rendering additional visual elements directly on the 3D tiles to help diagnose issues, such as:
    • Wireframe: Showing the triangle mesh of the geometry.
    • Bounding Volumes: Displaying the boxes or spheres that define the spatial extent of tiles, which helps understand the tileset's spatial partitioning.
    • Normals: Visualizing the surface normals of the geometry, important for lighting and shading.
    • Texture UVs: Overlaying a special texture to check how textures are mapped onto the geometry.
    • Tile Coloring: Coloring tiles based on different properties (like depth, distance from camera, or a custom color) to understand the tileset structure and behavior.
  • Automated Validation Checks: Running specific tests on a selected tile to identify common data quality issues, such as geometry extending outside its defined bounding volume, problematic LOD (Level of Detail) settings, or inconsistencies between geometry and texture coordinates.

How to Use#

These tools are primarily accessed through the Debug Panel and the Tile Details Panel.

1. Accessing the Debug Panel#

The main gateway to many debugging visualizations is the Debug Panel.

  • In the application's main interface (specifically on the Debug page/mode), find the main tools panel (either on the right side on desktop or bottom on mobile, handled by MainToolsPanel or MobileToolsPanel components in DebugApp.tsx).
  • Click the "Debug" button (often has a bug icon).

This will open the DebugPanel component (src/components/debug-panel/debug-panel.tsx). This panel contains various toggle switches and radio buttons to enable or disable visual debugging overlays and change tile coloring modes.

// Simplified snippet from src/components/debug-panel/debug-panel.tsx
export const DebugPanel = ({ onClose }: DebugPanelProps) => {
  const dispatch = useAppDispatch();
  const debugOptions = useAppSelector(selectDebugOptions); // Get debug settings from state

  return (
    <PanelContainer> {/* ... panel structure ... */}
      <PanelHeader> {/* ... header ... */}</PanelHeader>
      <CloseButton onClick={onClose} />
      <PanelHorizontalLine />
      <ToggleOptionsContainer>
        <ItemContainer>
          <Title>Wireframe mode</Title>
          <ToggleSwitch // Example Toggle Switch
            checked={debugOptions.wireframe} // Reads state
            onChange={() => // Dispatches action on change
              dispatch(setDebugOptions({ wireframe: !debugOptions.wireframe }))
            }
          />
        </ItemContainer>
        {/* ... other toggle switches ... */}

        <Title>Color</Title>
        <RadioButtonWrapper>
          {Object.keys(TileColoredBy).map((value) => {
            const selected = debugOptions.tileColorMode === TileColoredBy[value];
            return (
              <ListItem // Example Radio Button (ListItem with type=Radio)
                key={value}
                title={TileColoredBy[value]}
                type={ListItemType.Radio}
                selected={selected} // Reads state
                onChange={() => // Dispatches action on change
                  dispatch(setDebugOptions({ tileColorMode: TileColoredBy[value] }))
                }
              />
            );
          })}
        </RadioButtonWrapper>
        {/* ... other radio button groups and options ... */}
      </ToggleOptionsContainer>
      {/* ... upload texture panel ... */}
    </PanelContainer>
  );
};

This snippet shows how the DebugPanel component uses ToggleSwitch and ListItem components to display and control various debug options. Each control reads its current state (checked or selected) from the debugOptions in the Redux store (Chapter 4: Global State Management) using useAppSelector(selectDebugOptions). When a control is toggled or selected, its onChange handler dispatches an action (setDebugOptions) to update that specific option in the global state. The Map Visualization component (DeckGlWrapper) reads these debugOptions from the state and applies the corresponding visual overlays or coloring.

2. Toggling Visual Overlays and Tile Coloring#

In the Debug Panel:

  • Flip the toggle switches next to options like "Wireframe mode", "Bounding Volumes", "Texture UVs", or "Show normals".
  • Under the "Color" section, select a radio button for options like "By Depth", "By LOD", or "By distance to camera". If "Custom" is selected, a color picker will appear in the Tile Details Panel (see below).

As soon as you change these settings, the Map Visualization will update to show the selected overlays or coloring.

3. Accessing the Tile Details Panel#

To inspect a specific tile:

  • Ensure "Enable picking" is toggled on in the Debug Panel.
  • Click directly on a tile visible on the map.

If picking is enabled and you click a tile, a Tile Details Panel (src/components/tile-details-panel/tile-details-panel.tsx) will appear, typically near the clicked tile or in a fixed location. This panel shows detailed information about the selected tile.

// Simplified snippet from src/pages/debug-app/debug-app.tsx's handleClick function
const handleClick = (info: PickingInfo) => {
  // info.object contains the Tile3D object that was clicked
  if (!info.object) {
    // If nothing was clicked (or picking is off/failed)
    handleCloseTilePanel(); // Hide the panel
    return;
  }
  // If a tile was clicked, set it as the selected tile in the component's state
  setSelectedTile(info.object);
  // Also reset any previously generated normals debug data for a different tile
  setNormalsDebugData(null);
};

This handleClick function, part of the DebugApp page component, is called by the MapWrapper component (DeckGlWrapper) whenever the user clicks on the map and picking is enabled. If the click hit a tile (info.object is a Tile3D object), it updates the selectedTile state variable in DebugApp. This state change causes the DebugApp component to re-render, and if selectedTile is not null, it renders the TileDetailsPanel component, passing the selectedTile object to it.

4. Inspecting Tile Properties#

When the Tile Details Panel is open, you'll see various properties listed, such as:

  • Tile ID
  • Type (e.g., 'mesh')
  • Children Count and IDs
  • Vertex Count
  • Distance to Camera
  • Refinement Type (Add/Replace)
  • Has Texture / Has Material
  • Bounding Type (Sphere, Box, Region)
  • LOD Metric Type/Value
  • Screen Space Error
  • Depth in the tree

This information is displayed using the TileMetadata component within TileDetailsPanel.tsx, which simply formats and presents the properties from the selected Tile3D object.

// Simplified snippet showing how tile properties are accessed in TileDetailsPanel.tsx
export const TileDetailsPanel = ({ tile, /* ... other props ... */ }) => {
  // Access properties directly from the Tile3D object passed as a prop
  const {
    id,
    type,
    header: { children: tileChildren }, // Nested access
    distanceToCamera,
    content: { vertexCount, texture, material }, // Access content properties
    refine,
    lodMetricType,
    lodMetricValue,
    screenSpaceError,
    depth,
  } = tile; // Destructure properties from the tile object

  // Prepare an array of objects for display in the TileMetadata component
  const TILE_INFO: TileInfo[] = [
    { title: "Tile Id", value: formatStringValue(id) || NO_DATA },
    { title: "Type", value: formatStringValue(type) || NO_DATA },
    // ... other properties mapped similarly ...
    { title: "Vertex count", value: formatIntValue(vertexCount) || NO_DATA },
    { title: "Has Texture", value: Boolean(texture).toString() },
    // ... etc. ...
  ];

  return (
    <Container> {/* ... */}
      {/* ... Header, Back Button, Close Button ... */}
      <ContentWrapper>
        {/* ... Validate Button ... */}
        <TileMetadata tileInfo={TILE_INFO} /> {/* Display the properties */}
        {/* ... TextureSection, Normals ... */}
      </ContentWrapper>
    </Container>
  );
};

This demonstrates how the TileDetailsPanel receives the tile object as a prop and directly accesses its properties (id, type, content.vertexCount, etc.) to build the list of information displayed by the TileMetadata component. Helper functions (formatStringValue, formatIntValue) are used to format the values nicely for display.

5. Visualizing Normals and Texture UVs (Specific Overlays)#

The Normals section in the Tile Details Panel allows fine-grained control over visualizing normals for the selected tile:

  • Toggle "Show normals". This will draw lines representing normals on the tile geometry.
  • Use the input fields to change the "Percent of triangles with normals" (useful for dense meshes) and "Normals length".

This is handled by the Normals component (src/components/tile-details-panel/normals.tsx), which takes handlers (onShowNormals, onChangeTrianglesPercentage, onChangeNormalsLength) as props. These handlers update the normalsDebugData, trianglesPercentage, and normalsLength state variables in the parent DebugApp component. The MapWrapper component in DebugApp receives these updated values as props and uses them to render the normal lines via a dedicated layer (often a LineLayer). The actual data generation for the normal lines is done by the generateBinaryNormalsDebugData utility function (src/utils/debug/normals-utils.ts) based on the tile's geometry and normal attributes.

Similarly, toggling "Texture UVs" in the main Debug Panel (src/components/debug-panel/debug-panel.tsx) enables an overlay using a specific debug texture. This is controlled by the showUVDebugTexture debug option in the Redux state. When this option is true, the DeckGlWrapper uses a special shader and texture provided via the loadDebugTextureImage prop to visualize the UV mapping. The Debug Panel also allows uploading custom UV debug textures via a file upload modal (UploadPanel).

6. Custom Tile Coloring#

If you select "Custom" under the "Color" options in the Debug Panel, the Tile Details Panel for the selected tile will show a color picker (TileColorSection).

  • Use the color picker to choose a specific color. The selected tile will change to this color on the map.
  • Use the "Reset Color" button to remove the custom color for that tile.

This functionality allows you to highlight specific tiles of interest with a custom color. The selected color for each tile ID is stored in the coloredTilesMap state variable within DebugApp.tsx. This map is passed down to the MapWrapper component via the coloredTilesMap prop, which uses it to apply the custom colors instead of the default coloring mode (like coloring by depth).

// Simplified snippet showing TileColorSection in TileDetailsPanel.tsx
export const TileDetailsPanel = ({ tile, /* ... other props ... */ }) => {
  // ...
  return (
    <Container> {/* ... */}
      {/* ... other sections ... */}
      {/* Conditionally show color picker if custom tile coloring is enabled */}
      {isShowColorPicker && (
        <TileColorSection // Component for the color picker and reset button
          tileId={tileId}
          tileSelectedColor={tileSelectedColor} // Current color for this tile
          isResetButtonDisabled={isResetButtonDisabled} // Is Reset button enabled?
          handleResetColor={handleResetColor} // Handler for Reset button
          handleSelectTileColor={handleSelectTileColor} // Handler when a color is picked
        />
      )}
      {/* ... Normals section ... */}
    </Container>
  );
};

The TileColorSection component receives the tileId and the current color for that tile from its parent (TileDetailsPanel, which gets it from DebugApp's coloredTilesMap). When the user interacts with the color picker or reset button, the provided handlers (handleSelectTileColor, handleResetColor) are called, which update the coloredTilesMap state in DebugApp, triggering a re-render of the map with the new custom tile color.

7. Using the Validator Panel#

The Tile Details Panel also provides access to the automated validation checks:

  • In the Tile Details Panel, click the "Validate Tile" button.

This action triggers a series of validation checks on the selected tile. The Tile Details Panel then switches views to display the Validator Panel (src/components/semantic-validator/semantic-validator.tsx), showing a list of issues found (Warnings) and checks that passed (OK).

// Simplified snippet showing the Validate Button and panel switching in TileDetailsPanel.tsx
export const TileDetailsPanel = ({ tile, /* ... other props ... */ }) => {
  // State to manage which panel is active (Details or Validator)
  const [activeTileInfoPanel, setActiveTileInfoPanel] =
    useState<ActiveTileInfoPanel>(ActiveTileInfoPanel.TileDetailsPanel);
  // State to store validation results
  const [validateTileWarnings, setValidateTileWarnings] = useState([]);
  const [validateTileOk, setValidateTileOk] = useState([]);

  // Function called when "Validate Tile" button is clicked
  const onValidateTile = (tile: Tile3D) => {
    // Call validation utility functions
    validateGeometryInsideBoundingVolume(tile);
    validateGeometryVsTexture(tile);
    compareGeometryBoundingVolumeVsTileBoundingVolume(tile);
    // Switch to the Validator Panel view
    setActiveTileInfoPanel(ActiveTileInfoPanel.ValidateTilePanel);
  };

  // ... useEffect to reset state when tile changes ...

  const isDetailsPanel = activeTileInfoPanel === ActiveTileInfoPanel.TileDetailsPanel;

  return (
    <Container> {/* ... */}
      {/* ... Header with Back/Close buttons ... */}
      <ContentWrapper>
        {/* Show Validate Button only in Details view */}
        {isDetailsPanel && (
          <ValidateButton
            onClick={() => { onValidateTile(tile); }} // Calls onValidateTile handler
          >
            <ValidatorTitle>{VALIDATE_TILE}</ValidatorTitle>
            <ArrowContainer> <ChevronIcon /> </ArrowContainer>
          </ValidateButton>
          /* ... TileMetadata, Texture, Normals sections ... */
        )}
        {/* Show Validator Panel if not in Details view */}
        {!isDetailsPanel && (
          <ValidateTilePanel // Pass validation results as props
            validatedTileWarnings={validateTileWarnings}
            validatedTileOk={validateTileOk}
          />
        )}
      </ContentWrapper>
    </Container>
  );
};

When you click "Validate Tile", the onValidateTile function is called. This function internally calls utility functions (like validateGeometryInsideBoundingVolume, which uses isTileGeometryInsideBoundingVolume from src/utils/debug/tile-debug.ts) that perform the actual checks on the tile object. These utility functions (or helpers they call) return boolean results or error messages, which the onValidateTile function then uses to update the validateTileWarnings and validateTileOk state variables. Finally, it updates the activeTileInfoPanel state, which causes the TileDetailsPanel component to hide the tile details sections and render the ValidateTilePanel instead, passing it the collected warnings and successes.

The SemanticValidator component (src/components/semantic-validator/semantic-validator.tsx) simply receives the warnings and validatedTileOk arrays as props and renders them, usually in a table format, displaying the type and description of each issue or success. It also provides a "Clear" button to empty the list.

// Simplified snippet from src/components/semantic-validator/semantic-validator.tsx
export const SemanticValidator = ({ warnings = [], clearWarnings, onClose }: SemanticValidatorProps) => {
  return (
    <PanelContainer> {/* ... */}
      <PanelHeader> {/* ... header with Close button ... */}</PanelHeader>
      <PanelHorizontalLine />
      <PanelContent>
        {/* Conditional rendering based on whether there are warnings */}
        {warnings && Boolean(warnings.length)
          ? (
              <Table>
                <thead> {/* ... table header ... */}
                  <TableRow>
                    {/* ... header columns ... */}
                    <TableHeader>Warning <TableButton onClick={clearWarnings}>Clear</TableButton></TableHeader>
                  </TableRow>
                </thead>
                <tbody>
                  {/* Map through warnings and render a table row for each */}
                  {warnings.map((warning, index) => (
                    <tr key={`${warning.title}-${index}`}>
                      <td>{index + 1}</td> {/* Row number */}
                      <td>{WARNING_TYPES[warning.type]}</td> {/* Warning Type */}
                      <td>{warning.title}</td> {/* Warning description */}
                    </tr>
                  ))}
                </tbody>
              </Table>
            )
          : (
          <NoIssuesItem>{NO_ISSUES}</NoIssuesItem> // Message if no warnings
            )}
      </PanelContent>
    </PanelContainer>
  );
};

This shows how SemanticValidator receives the warnings array. If the array is not empty, it renders a table header and then maps over the warnings array, creating a table row (<tr>) for each warning and displaying its details. If the warnings array is empty, it displays the "No Issues" message. The clearWarnings prop is called when the "Clear" button is clicked, typically resetting the warnings state in the parent component (TileDetailsPanel which gets it from DebugApp).

8. Performance and Memory Statistics#

While not strictly "validation" of data content, the application also provides panels to view runtime performance metrics, which are valuable debugging tools.

  • In the main tools panel, click the "Memory" button (often shows memory usage).

This opens the MemoryUsagePanel (src/components/memory-usage-panel/memory-usage-panel.tsx). This panel displays statistics like WebGL memory usage (obtained via lumaStats), tileset loading times, and loaded tile counts. These statistics help identify performance bottlenecks or excessive memory consumption. The MemoryUsagePanel reads these statistics from the tilesetsStats and memoryStats state variables managed in the DebugApp component.

Under the Hood: The Debugging Workflow#

Here's a simplified flow of how interacting with the Debug and Validation tools works:

sequenceDiagram
    Participant User
    Participant MainToolsPanel
    Participant DebugPanelUI
    Participant ReduxState
    Participant MapWrapper
    Participant TileDetailsPanelUI
    Participant SemanticValidatorUI
    Participant DebugUtils

    User->>MainToolsPanel: Clicks "Debug" button
    MainToolsPanel->>DebugPanelUI: Opens Debug Panel
    User->>DebugPanelUI: Toggles "Wireframe" switch
    DebugPanelUI->>ReduxState: Dispatches setDebugOptions({wireframe: true})
    ReduxState-->>MapWrapper: State change notifies map
    MapWrapper->>User: Map renders with wireframe overlay

    User->>User: Pans/Zooms map
    User->>MapWrapper: Clicks on a tile
    MapWrapper->>DebugApp: Calls handleClick with Tile3D object
    DebugApp->>DebugApp: Sets selectedTile state
    DebugApp-->>TileDetailsPanelUI: Renders Tile Details Panel for selected tile
    TileDetailsPanelUI->>User: Shows tile properties

    User->>TileDetailsPanelUI: Clicks "Validate Tile" button
    TileDetailsPanelUI->>DebugUtils: Calls validation utilities (e.g., validateGeometryInsideBoundingVolume)
    DebugUtils->>DebugUtils: Checks tile data properties
    DebugUtils-->>TileDetailsPanelUI: Returns validation results (warnings/ok)
    TileDetailsPanelUI->>TileDetailsPanelUI: Updates state with validation results, switches view
    TileDetailsPanelUI-->>SemanticValidatorUI: Renders Semantic Validator Panel with results
    SemanticValidatorUI->>User: Shows list of warnings/successes
  1. Accessing Tools: User clicks buttons in the MainToolsPanel to open the DebugPanel, BookmarksPanel, MemoryUsagePanel, etc. The MainToolsPanel updates the activeButton state in DebugApp, which conditionally renders the appropriate panel component (DebugPanel, SemanticValidator, MemoryUsagePanel).
  2. Debug Options: Interacting with controls in DebugPanel dispatches actions (setDebugOptions) to update the debugOptions state in Redux (debug-options-slice.ts). The MapWrapper component reads these options from Redux via useAppSelector(selectDebugOptions) and applies the visual changes (wireframe, coloring, etc.). Custom UV textures are handled via an upload flow in DebugPanel that results in a texture being available to the MapWrapper.
  3. Tile Picking: Clicking the map when picking is enabled triggers the onClick handler in DeckGlWrapper or ArcgisWrapper. This handler identifies the clicked object (if it's a tile) and calls the handleClick function provided by the parent DebugApp. handleClick sets the selectedTile state, causing DebugApp to render the TileDetailsPanel, passing the selected Tile3D object.
  4. Tile Details: TileDetailsPanel accesses properties directly from the tile prop to display metadata (TileMetadata), texture info (TextureSection), and normals controls (Normals). Interacting with Normals or the custom color picker (TileColorSection) triggers handlers that update state variables in DebugApp (normalsDebugData, coloredTilesMap), which are then passed as props to the MapWrapper to update the visualization.
  5. Validation: Clicking "Validate Tile" in TileDetailsPanel calls utility functions (validateGeometryInsideBoundingVolume, etc.) defined in files like src/utils/debug/tile-debug.ts and src/utils/debug/validation-utils. These functions analyze the tile data and return results. The TileDetailsPanel updates its internal state with these results and switches to rendering the SemanticValidator component, passing it the results to display.
  6. Memory Stats: The MemoryUsagePanel component reads tilesetsStats (compiled by DebugApp using sumTilesetsStats and onAfterRender hook from MapWrapper) and memoryStats (obtained via lumaStats on onWebGLInitialized) from the state managed in DebugApp and displays them.

This interconnected system allows users to go from a high-level visual issue on the map to inspecting specific tile properties, enabling detailed visual overlays, and running automated checks to pinpoint data quality problems.

Conclusion#

In this chapter, we explored the Debug and Validation Tools available in loaders.gl-showcases. We learned how the Debug Panel provides control over global visual overlays like wireframe and tile coloring, interacting with the Redux state to update the Map Visualization. We saw how clicking on a tile opens the Tile Details Panel to display its properties and provide tools for specific tile debugging, such as visualizing normals or applying custom colors. Finally, we looked at the Validator Panel, accessible from the Tile Details Panel, which runs automated checks on tile data quality based on utility functions, helping users identify common issues.

These tools empower users and developers to look "under the hood" of the 3D data, diagnose visual problems, understand tileset structure and performance, and assess data quality – essential capabilities when working with complex 3D geospatial content.

This concludes our tutorial on loaders.gl-showcases. We've journeyed from understanding the basic User Interface Components and the Map Visualization stage to managing Layers, utilizing Global State Management, and exploring powerful features like Bookmarks, Comparison Mode, ArcGIS Integration, and the Debug and Validation Tools covered in this chapter. We hope this tutorial has provided you with a solid foundation for understanding and using the application.


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/debug-panel/debug-panel.tsx), 2(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/semantic-validator/semantic-validator.tsx), 3(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/tile-details-panel/normals.tsx), 4(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/tile-details-panel/tile-details-panel.tsx), 5(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/pages/debug-app/debug-app.tsx), 6(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/debug/normals-utils.ts), 7(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/debug/tile-debug.ts)