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

visgl / luma.gl / 23355601514

20 Mar 2026 05:50PM UTC coverage: 52.166% (-25.8%) from 77.934%
23355601514

push

github

web-flow
chore: Migrate to vitest (#2554)

4188 of 11561 branches covered (36.23%)

Branch coverage included in aggregate %.

613 of 632 new or added lines in 22 files covered. (96.99%)

343 existing lines in 28 files now uncovered.

7757 of 11337 relevant lines covered (68.42%)

394.31 hits per line

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

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

5
import type {TypedArray} from '@math.gl/core';
6
import type {PrimitiveTopology} from '@luma.gl/core';
7
import {uid} from '../utils/uid';
8

9
export type GeometryProps = {
10
  id?: string;
11
  /** Determines how vertices are read from the 'vertex' attributes */
12
  topology: 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip';
13
  /** Auto calculated from attributes if not provided */
14
  vertexCount?: number;
15
  attributes: Record<string, GeometryAttribute | TypedArray>;
16
  indices?: GeometryAttribute | TypedArray;
17
};
18

19
export type GeometryAttributes = {
20
  POSITION: GeometryAttribute;
21
  NORMAL: GeometryAttribute;
22
  TEXCOORD_0: GeometryAttribute;
23
  COLOR_0?: GeometryAttribute;
24
  indices?: GeometryAttribute & {size: 1; value: Uint32Array | Uint16Array};
25
};
26

27
export type GeometryAttribute = {
28
  size?: number;
29
  value: TypedArray;
30
  [key: string]: any;
31
};
32

33
export class Geometry {
34
  readonly id: string;
35
  /** Determines how vertices are read from the 'vertex' attributes */
36
  readonly topology?: PrimitiveTopology;
37
  readonly vertexCount: number;
38
  readonly indices?: GeometryAttribute;
39
  readonly attributes: {
40
    POSITION: GeometryAttribute;
41
    NORMAL: GeometryAttribute;
42
    TEXCOORD_0: GeometryAttribute;
43
    COLOR_0?: GeometryAttribute;
44
    [key: string]: GeometryAttribute | undefined;
45
  };
46

47
  userData: Record<string, unknown> = {};
209✔
48

49
  constructor(props: GeometryProps) {
50
    const {attributes = {}, indices = null, vertexCount = null} = props;
209✔
51

52
    this.id = props.id || uid('geometry');
209!
53
    this.topology = props.topology;
209✔
54

55
    if (indices) {
209!
56
      this.indices = ArrayBuffer.isView(indices) ? {value: indices, size: 1} : indices;
209✔
57
    }
58

59
    // @ts-expect-error
60
    this.attributes = {};
209✔
61

62
    for (const [attributeName, attributeValue] of Object.entries(attributes)) {
209✔
63
      // Wrap "unwrapped" arrays and try to autodetect their type
64
      const attribute: GeometryAttribute = ArrayBuffer.isView(attributeValue)
621!
65
        ? {value: attributeValue}
66
        : attributeValue;
67

68
      if (!ArrayBuffer.isView(attribute.value)) {
621!
69
        throw new Error(
603✔
70
          `${this._print(attributeName)}: must be typed array or object with value as typed array`
71
        );
72
      }
73

74
      if ((attributeName === 'POSITION' || attributeName === 'positions') && !attribute.size) {
621!
75
        attribute.size = 3;
603✔
76
      }
77

78
      // Move indices to separate field
79
      if (attributeName === 'indices') {
621!
80
        if (this.indices) {
603!
UNCOV
81
          throw new Error('Multiple indices detected');
×
82
        }
83
        this.indices = attribute;
603✔
84
      } else {
85
        this.attributes[attributeName] = attribute;
621✔
86
      }
87
    }
88

89
    if (this.indices && this.indices['isIndexed'] !== undefined) {
209!
90
      this.indices = Object.assign({}, this.indices);
203✔
91
      delete this.indices['isIndexed'];
203✔
92
    }
93

94
    this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices);
209✔
95
  }
96

97
  getVertexCount(): number {
UNCOV
98
    return this.vertexCount;
×
99
  }
100

101
  /**
102
   * Return an object with all attributes plus indices added as a field.
103
   * TODO Geometry types are a mess
104
   */
105
  getAttributes(): GeometryAttributes {
106
    // @ts-ignore
107
    return this.indices ? {indices: this.indices, ...this.attributes} : this.attributes;
×
108
  }
109

110
  // PRIVATE
111

112
  _print(attributeName: string): string {
UNCOV
113
    return `Geometry ${this.id} attribute ${attributeName}`;
×
114
  }
115

116
  /**
117
   * GeometryAttribute
118
   * value: typed array
119
   * type: indices, vertices, uvs
120
   * size: elements per vertex
121
   * target: WebGL buffer type (string or constant)
122
   *
123
   * @param attributes
124
   * @param indices
125
   * @returns
126
   */
127
  _setAttributes(attributes: Record<string, GeometryAttribute>, indices: any): this {
128
    return this;
×
129
  }
130

131
  _calculateVertexCount(attributes: GeometryAttributes, indices?: GeometryAttribute): number {
132
    if (indices) {
228!
133
      return indices.value.length;
228✔
134
    }
135
    let vertexCount = Infinity;
6✔
136
    for (const attribute of Object.values(attributes)) {
6✔
137
      const {value, size, constant} = attribute;
18✔
138
      if (!constant && value && size !== undefined && size >= 1) {
18!
139
        vertexCount = Math.min(vertexCount, value.length / size);
18✔
140
      }
141
    }
142

143
    // assert(Number.isFinite(vertexCount));
144
    return vertexCount;
6✔
145
  }
146
}
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