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

visgl / deck.gl / 23689953389

28 Mar 2026 05:00PM UTC coverage: 79.878% (-11.0%) from 90.907%
23689953389

push

github

web-flow
chore(test): vitest migration (#9969)

3050 of 3709 branches covered (82.23%)

Branch coverage included in aggregate %.

131 of 169 new or added lines in 11 files covered. (77.51%)

171 existing lines in 60 files now uncovered.

13988 of 17621 relevant lines covered (79.38%)

26917.67 hits per line

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

95.83
/modules/layers/src/point-cloud-layer/point-cloud-layer.ts
1
// deck.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {
6
  Layer,
7
  color,
8
  project32,
9
  picking,
10
  UNIT,
11
  LayerProps,
12
  LayerDataSource,
13
  UpdateParameters,
14
  Unit,
15
  AccessorFunction,
16
  Position,
17
  Accessor,
18
  Color,
19
  Material,
20
  DefaultProps
21
} from '@deck.gl/core';
22
import {Parameters} from '@luma.gl/core';
23
import {Model, Geometry} from '@luma.gl/engine';
24
import {gouraudMaterial} from '@luma.gl/shadertools';
25

26
import {pointCloudUniforms, PointCloudProps} from './point-cloud-layer-uniforms';
27
import vs from './point-cloud-layer-vertex.glsl';
28
import fs from './point-cloud-layer-fragment.glsl';
29
import source from './point-cloud-layer.wgsl';
30

31
const DEFAULT_COLOR = [0, 0, 0, 255] as const;
4✔
32
const DEFAULT_NORMAL = [0, 0, 1] as const;
4✔
33

34
const defaultProps: DefaultProps<PointCloudLayerProps> = {
4✔
35
  sizeUnits: 'pixels',
36
  pointSize: {type: 'number', min: 0, value: 10}, //  point radius in pixels
37

UNCOV
38
  getPosition: {type: 'accessor', value: (x: any) => x.position},
×
39
  getNormal: {type: 'accessor', value: DEFAULT_NORMAL},
40
  getColor: {type: 'accessor', value: DEFAULT_COLOR},
41

42
  material: true,
43

44
  // Depreated
45
  radiusPixels: {deprecatedFor: 'pointSize'}
46
};
47

48
// support loaders.gl point cloud format
49
function normalizeData(data) {
50
  const {header, attributes} = data;
11✔
51
  if (!header || !attributes) {
11✔
52
    return;
10✔
53
  }
54

55
  data.length = header.vertexCount;
1✔
56

57
  if (attributes.POSITION) {
1✔
58
    attributes.instancePositions = attributes.POSITION;
1✔
59
  }
60
  if (attributes.NORMAL) {
1✔
61
    attributes.instanceNormals = attributes.NORMAL;
1✔
62
  }
63
  if (attributes.COLOR_0) {
1✔
64
    const {size, value} = attributes.COLOR_0;
1✔
65
    attributes.instanceColors = {size, type: 'unorm8', value};
1✔
66
  }
67
}
68

69
/** All properties supported by PointCloudLayer. */
70
export type PointCloudLayerProps<DataT = unknown> = _PointCloudLayerProps<DataT> & LayerProps;
71

72
/** Properties added by PointCloudLayer. */
73
type _PointCloudLayerProps<DataT> = {
74
  data: LayerDataSource<DataT>;
75
  /**
76
   * The units of the point size, one of `'meters'`, `'common'`, and `'pixels'`.
77
   * @default 'pixels'
78
   */
79
  sizeUnits?: Unit;
80

81
  /**
82
   * Global radius of all points, in units specified by `sizeUnits`
83
   * @default 10
84
   */
85
  pointSize?: number;
86

87
  /**
88
   * @deprecated Use `pointSize` instead
89
   */
90
  radiusPixels?: number;
91

92
  /**
93
   * Material settings for lighting effect.
94
   *
95
   * @default true
96
   * @see https://deck.gl/docs/developer-guide/using-lighting
97
   */
98
  material?: Material;
99

100
  /**
101
   * Method called to retrieve the position of each object.
102
   * @default object => object.position
103
   */
104
  getPosition?: AccessorFunction<DataT, Position>;
105

106
  /**
107
   * The normal of each object, in `[nx, ny, nz]`.
108
   * @default [0, 0, 1]
109
   */
110
  getNormal?: Accessor<DataT, Readonly<[number, number, number]>>;
111

112
  /**
113
   * The rgba color is in the format of `[r, g, b, [a]]`
114
   * @default [0, 0, 0, 255]
115
   */
116
  getColor?: Accessor<DataT, Color>;
117
};
118

119
/** Render a point cloud with 3D positions, normals and colors. */
120
export default class PointCloudLayer<DataT = any, ExtraPropsT extends {} = {}> extends Layer<
121
  ExtraPropsT & Required<_PointCloudLayerProps<DataT>>
122
> {
123
  static layerName = 'PointCloudLayer';
4✔
124
  static defaultProps = defaultProps;
4✔
125

126
  state!: {
127
    model?: Model;
128
  };
129

130
  getShaders() {
131
    return super.getShaders({
5✔
132
      vs,
133
      fs,
134
      source,
135
      modules: [project32, color, gouraudMaterial, picking, pointCloudUniforms]
136
    });
137
  }
138

139
  initializeState() {
140
    this.getAttributeManager()!.addInstanced({
5✔
141
      instancePositions: {
142
        size: 3,
143
        type: 'float64',
144
        fp64: this.use64bitPositions(),
145
        transition: true,
146
        accessor: 'getPosition'
147
      },
148
      instanceNormals: {
149
        size: 3,
150
        transition: true,
151
        accessor: 'getNormal',
152
        defaultValue: DEFAULT_NORMAL
153
      },
154
      instanceColors: {
155
        size: this.props.colorFormat.length,
156
        type: 'unorm8',
157
        transition: true,
158
        accessor: 'getColor',
159
        defaultValue: DEFAULT_COLOR
160
      }
161
    });
162
  }
163

164
  updateState(params: UpdateParameters<this>): void {
165
    const {changeFlags, props} = params;
18✔
166
    super.updateState(params);
18✔
167
    if (changeFlags.extensionsChanged) {
18✔
168
      this.state.model?.destroy();
5✔
169
      this.state.model = this._getModel();
5✔
170
      this.getAttributeManager()!.invalidateAll();
5✔
171
    }
172
    if (changeFlags.dataChanged) {
18✔
173
      normalizeData(props.data);
11✔
174
    }
175
  }
176

177
  draw({uniforms}) {
178
    const {pointSize, sizeUnits} = this.props;
22✔
179
    const model = this.state.model!;
22✔
180
    const pointCloudProps: PointCloudProps = {
22✔
181
      sizeUnits: UNIT[sizeUnits],
182
      radiusPixels: pointSize
183
    };
184
    model.shaderInputs.setProps({pointCloud: pointCloudProps});
22✔
185
    if (this.context.device.type === 'webgpu') {
22✔
186
      // @ts-expect-error TODO - this line was needed during WebGPU port
187
      model.instanceCount = this.props.data.length;
×
188
    }
189
    model.draw(this.context.renderPass);
22✔
190
  }
191

192
  protected _getModel(): Model {
193
    // TODO(ibgreen): WebGPU complication: Matching attachment state of the renderpass requires including a depth buffer
194
    const parameters =
195
      this.context.device.type === 'webgpu'
5✔
196
        ? ({
197
            depthWriteEnabled: true,
198
            depthCompare: 'less-equal'
199
          } satisfies Parameters)
200
        : undefined;
201

202
    // a triangle that minimally cover the unit circle
203
    const positions: number[] = [];
5✔
204
    for (let i = 0; i < 3; i++) {
5✔
205
      const angle = (i / 3) * Math.PI * 2;
15✔
206
      positions.push(Math.cos(angle) * 2, Math.sin(angle) * 2, 0);
15✔
207
    }
208

209
    return new Model(this.context.device, {
5✔
210
      ...this.getShaders(),
211
      id: this.props.id,
212
      bufferLayout: this.getAttributeManager()!.getBufferLayouts(),
213
      geometry: new Geometry({
214
        topology: 'triangle-list',
215
        attributes: {
216
          positions: new Float32Array(positions)
217
        }
218
      }),
219
      parameters,
220
      isInstanced: true
221
    });
222
  }
223
}
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