• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 14683349798

26 Apr 2025 05:08PM UTC coverage: 74.055% (-0.9%) from 74.913%
14683349798

push

github

web-flow
feat(core): TextureFormat generics (#2377)

2019 of 2652 branches covered (76.13%)

Branch coverage included in aggregate %.

62 of 262 new or added lines in 15 files covered. (23.66%)

196 existing lines in 9 files now uncovered.

26575 of 35960 relevant lines covered (73.9%)

47.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.14
/modules/engine/src/modules/picking/picking-manager.ts
1
// luma.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {Device, Framebuffer} from '@luma.gl/core';
1✔
6
import {ShaderInputs} from '../../shader-inputs';
1✔
7
import {pickingUniforms, INVALID_INDEX} from './picking-uniforms';
1✔
8
// import {picking} from './color-picking';
1✔
9

1✔
10
/** Information about picked object */
1✔
11
export type PickInfo = {
1✔
12
  batchIndex: number | null;
1✔
13
  objectIndex: number | null;
1✔
14
};
1✔
15

1✔
16
export type PickingManagerProps = {
1✔
17
  /** Shader Inputs from models to pick */
1✔
18
  shaderInputs?: ShaderInputs<{picking: typeof pickingUniforms.props}>;
1✔
19
  /** Callback */
1✔
20
  onObjectPicked?: (info: PickInfo) => void;
1✔
21
};
1✔
22

1✔
23
/**
1✔
24
 * Helper class for using the new picking module
1✔
25
 * @todo Port to WebGPU
1✔
26
 * @todo Support multiple models
1✔
27
 * @todo Switching picking module
1✔
28
 */
1✔
29
export class PickingManager {
1!
30
  device: Device;
×
31
  props: Required<PickingManagerProps>;
×
32
  /** Info from latest pick operation */
×
33
  pickInfo: PickInfo = {batchIndex: null, objectIndex: null};
×
34
  /** Framebuffer used for picking */
×
35
  framebuffer: Framebuffer | null = null;
×
36

×
37
  static defaultProps: Required<PickingManagerProps> = {
×
38
    shaderInputs: undefined!,
×
39
    onObjectPicked: () => {}
×
40
  };
×
41

×
42
  constructor(device: Device, props: PickingManagerProps) {
×
43
    this.device = device;
×
44
    this.props = {...PickingManager.defaultProps, ...props};
×
45
  }
×
46

×
47
  destroy() {
×
48
    this.framebuffer?.destroy();
×
49
  }
×
50

×
51
  // TODO - Ask for a cached framebuffer? a Framebuffer factory?
×
52
  getFramebuffer() {
×
53
    if (!this.framebuffer) {
×
54
      this.framebuffer = this.device.createFramebuffer({
×
55
        colorAttachments: ['rgba8unorm', 'rg32sint'],
×
56
        depthStencilAttachment: 'depth24plus'
×
57
      });
×
58
    }
×
59
    return this.framebuffer;
×
60
  }
×
61

×
62
  /** Clear highlighted / picked object */
×
63
  clearPickState() {
×
64
    this.props.shaderInputs.setProps({picking: {highlightedObjectIndex: null}});
×
65
  }
×
66

×
67
  /** Prepare for rendering picking colors */
×
68
  beginRenderPass() {
×
69
    const framebuffer = this.getFramebuffer();
×
70
    framebuffer.resize(this.device.getDefaultCanvasContext().getDevicePixelSize());
×
71

×
72
    this.props.shaderInputs?.setProps({picking: {isActive: true}});
×
73

×
74
    const pickingPass = this.device.beginRenderPass({
×
75
      framebuffer,
×
76
      clearColors: [new Float32Array([0, 0, 0, 0]), new Int32Array([-1, -1, 0, 0])],
×
77
      clearDepth: 1
×
78
    });
×
79

×
80
    return pickingPass;
×
81
  }
×
82

×
NEW
83
  async updatePickInfo(mousePosition: [number, number]): Promise<PickInfo | null> {
×
84
    const framebuffer = this.getFramebuffer();
×
85

×
86
    // use the center pixel location in device pixel range
×
87
    const [pickX, pickY] = this.getPickPosition(mousePosition);
×
88

×
89
    // Read back
×
90
    const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
×
91
      sourceX: pickX,
×
92
      sourceY: pickY,
×
93
      sourceWidth: 1,
×
94
      sourceHeight: 1,
×
95
      sourceAttachment: 1
×
96
    });
×
97
    if (!pixelData) {
×
98
      return null;
×
99
    }
×
100

×
101
    const pickInfo: PickInfo = {
×
102
      objectIndex: pixelData[0] === INVALID_INDEX ? null : pixelData[0],
×
103
      batchIndex: pixelData[1] === INVALID_INDEX ? null : pixelData[1]
×
104
    };
×
105

×
106
    // Call callback if picked object has changed
×
107
    if (
×
108
      pickInfo.objectIndex !== this.pickInfo.objectIndex ||
×
109
      pickInfo.batchIndex !== this.pickInfo.batchIndex
×
110
    ) {
×
111
      this.pickInfo = pickInfo;
×
112
      this.props.onObjectPicked(pickInfo);
×
113
      // console.log(`Object ${pickInfo.objectIndex} in batch ${pickInfo.batchIndex} was picked`)
×
114
    }
×
115

×
116
    this.props.shaderInputs?.setProps({
×
117
      picking: {
×
118
        isActive: false,
×
119
        highlightedBatchIndex: pickInfo.batchIndex,
×
120
        highlightedObjectIndex: pickInfo.objectIndex
×
121
      }
×
122
    });
×
123

×
124
    return this.pickInfo;
×
125
  }
×
126

×
127
  /**
×
128
   * Get pick position in device pixel range
×
129
   * use the center pixel location in device pixel range
×
130
   */
×
131
  getPickPosition(mousePosition: [number, number]): [number, number] {
×
132
    const devicePixels = this.device.getDefaultCanvasContext().cssToDevicePixels(mousePosition);
×
133
    const pickX = devicePixels.x + Math.floor(devicePixels.width / 2);
×
134
    const pickY = devicePixels.y + Math.floor(devicePixels.height / 2);
×
135
    return [pickX, pickY];
×
136
  }
×
137
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc