Frontend Development 22 min read

Design and Refactoring of the xGis 3D Map Event System and Picking Engine

This article details the background, problems, and comprehensive refactoring plan for the xGis web‑based 3D map library, covering event classification, API design, layer interaction proxy, CPU/GPU picking implementations, performance trade‑offs, and future optimization directions.

ByteFE
ByteFE
ByteFE
Design and Refactoring of the xGis 3D Map Event System and Picking Engine

xGis is a data‑driven, easy‑to‑use web 3D map library that focuses on geographic data visualization with high performance and rendering quality. It supports flexible custom layer management, allowing developers to add, remove, or modify dozens of layer types.

Background

The library is used in digital twin scenarios where a full 3‑D model of physical objects is combined with mathematical models and intelligent algorithms for simulation and analysis.

Problems

Inconsistent interaction field configuration across different layers.

Built‑in event logic does not meet specific scene requirements (e.g., double‑click vs. single‑click, conditional hover styles).

Duplicate development of interaction modules for each new layer, increasing development cost.

Performance degradation due to redundant event‑listener logic.

Refactoring Approach

The solution starts by classifying events based on their trigger source (container, context, components, controls, window, layer, custom) and then designing a unified API that mirrors JavaScript's core event methods (addListener, emit, removeListener).

Event Classification

Each event source is grouped into categories such as container (DOM container resize), context (WebGL context loss), components (HUD controls like layerControl, zoomControl), controls (map pan/zoom/rotate), window (orientation change), layer (mouse interaction and lifecycle), and custom (library lifecycle events).

Syntax Design

Typical usage mirrors native DOM event handling:

const button = document.querySelector('button');
button.addEventListener('click', (event) => {
  // do something else
});

Custom events are emitted via an internal EventEmitter (e.g., eventemitter3) allowing publish/subscribe patterns for non‑DOM events.

Layer Interaction Proxy

Layer events are bound to a single canvas DOM element. When an event occurs, the system retrieves the set of layers registered for that event, performs hit‑testing (using a picking engine), and emits the appropriate event to each layer.

public on(eventType: CustomUIEventType | LifeCycleEventType | string, handle: (...args: any[]) => void, context?: any) {
  if (!isNotCustomUIEventType(eventType)) {
    this.eventManager.bindEvent(this.id, eventType as CustomUIEventType);
  }
  this.ee.on(eventType, handle, context);
}

Picking Engine Implementation

Two strategies are provided:

CPU raycasting – traverses objects, checks bounding spheres/boxes, then performs triangle‑level intersection tests.

GPU color‑based picking – renders each object with a unique color to an off‑screen buffer and reads the pixel under the cursor.

CPU approach is the default because it handles dynamic scene changes more gracefully, while GPU can be switched on for static, massive datasets.

// CPU raycasting example
raycast(raycaster, intersects) {
  if (geometry.boundingSphere === null) geometry.computeBoundingSphere();
  if (raycaster.ray.intersectsSphere(_sphere) === false) return;
  if (geometry.boundingBox !== null && _ray.intersectsBox(geometry.boundingBox) === false) return;
  const intersection = ray.intersectTriangle();
  if (intersection) intersects.push(intersection);
}
// GPU picking initialization
private __initGPUPick() {
  this.pickingScene = new Scene();
  this.pickingTexture = new WebGLRenderTarget(containerDom.clientWidth, containerDom.clientHeight);
  const pickingMaterial = new ShaderMaterial({
    vertexShader: pickVshader,
    fragmentShader: pickFshader,
    transparent: false,
    side: DoubleSide,
  });
  this.pickingObjMap = new Map();
  // ... layer load handling omitted for brevity
}

Benefits and Reflections

After refactoring, interaction fields are unified, event binding is flexible, developers can extend base layer interaction with minimal code, and overall event handling performance improves by about 20%.

Future work includes exploring octree acceleration for CPU raycasting and handling merged geometry for more efficient GPU picking.

About the Team

The article is authored by the ByteDance Data Platform team, which provides data‑driven products internally and under the Volcano Engine brand to various industries.

JavaScriptCPUGPUWebGLEvent Handling3D mappingPicking Engine
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.