• 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

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

5
import {Matrix4, Vector3} from '@math.gl/core';
6
import {log} from '@luma.gl/core';
7
import {ScenegraphNode, ScenegraphNodeProps} from './scenegraph-node';
8

9
export type GroupNodeProps = ScenegraphNodeProps & {
10
  children?: ScenegraphNode[];
11
};
12

13
export class GroupNode extends ScenegraphNode {
14
  children: ScenegraphNode[];
15

16
  constructor(children: ScenegraphNode[]);
17
  constructor(props?: GroupNodeProps);
18

19
  constructor(props: ScenegraphNode[] | GroupNodeProps = {}) {
37!
20
    props = Array.isArray(props) ? {children: props} : props;
37!
21
    const {children = []} = props;
37✔
22
    log.assert(
37✔
23
      children.every(child => child instanceof ScenegraphNode),
22✔
24
      'every child must an instance of ScenegraphNode'
25
    );
26
    super(props);
37✔
27
    this.children = children;
37✔
28
  }
29

30
  override getBounds(): [number[], number[]] | null {
31
    const result: [number[], number[]] = [
2✔
32
      [Infinity, Infinity, Infinity],
33
      [-Infinity, -Infinity, -Infinity]
34
    ];
35

36
    this.traverse((node, {worldMatrix}) => {
2✔
37
      const bounds = node.getBounds();
4✔
38
      if (!bounds) {
4!
39
        return;
4✔
40
      }
41
      const [min, max] = bounds;
2✔
42
      const center = new Vector3(min).add(max).divide([2, 2, 2]);
2✔
43
      worldMatrix.transformAsPoint(center, center);
2✔
44
      const halfSize = new Vector3(max).subtract(min).divide([2, 2, 2]);
2✔
45
      worldMatrix.transformAsVector(halfSize, halfSize);
2✔
46

47
      for (let v = 0; v < 8; v++) {
2✔
48
        // Test all 8 corners of the box
49
        const position = new Vector3(v & 0b001 ? -1 : 1, v & 0b010 ? -1 : 1, v & 0b100 ? -1 : 1)
16!
50
          .multiply(halfSize)
51
          .add(center);
52

53
        for (let i = 0; i < 3; i++) {
16✔
54
          result[0][i] = Math.min(result[0][i], position[i]);
48✔
55
          result[1][i] = Math.max(result[1][i], position[i]);
48✔
56
        }
57
      }
58
    });
59
    if (!Number.isFinite(result[0][0])) {
2!
60
      return null;
2✔
61
    }
62
    return result;
1✔
63
  }
64

65
  override destroy(): void {
66
    this.children.forEach(child => child.destroy());
4✔
67
    this.removeAll();
4✔
68
    super.destroy();
4✔
69
  }
70

71
  // Unpacks arrays and nested arrays of children
72
  add(...children: (ScenegraphNode | ScenegraphNode[])[]): this {
73
    for (const child of children) {
22✔
74
      if (Array.isArray(child)) {
24!
75
        this.add(...child);
24✔
76
      } else {
77
        this.children.push(child);
24✔
78
      }
79
    }
80
    return this;
22✔
81
  }
82

83
  remove(child: ScenegraphNode): this {
84
    const children = this.children;
2✔
85
    const indexOf = children.indexOf(child);
2✔
86
    if (indexOf > -1) {
2!
87
      children.splice(indexOf, 1);
2✔
88
    }
89
    return this;
2✔
90
  }
91

92
  removeAll(): this {
93
    this.children = [];
5✔
94
    return this;
5✔
95
  }
96

97
  traverse(
98
    visitor: (node: ScenegraphNode, context: {worldMatrix: Matrix4}) => void,
99
    {worldMatrix = new Matrix4()} = {}
12!
100
  ) {
101
    const modelMatrix = new Matrix4(worldMatrix).multiplyRight(this.matrix);
6✔
102

103
    for (const child of this.children) {
6✔
104
      if (child instanceof GroupNode) {
9!
105
        child.traverse(visitor, {worldMatrix: modelMatrix});
9✔
106
      } else {
107
        visitor(child, {worldMatrix: modelMatrix});
9✔
108
      }
109
    }
110
  }
111

112
  preorderTraversal(
113
    visitor: (node: ScenegraphNode, context: {worldMatrix: Matrix4}) => void,
114
    {worldMatrix = new Matrix4()} = {}
×
115
  ) {
UNCOV
116
    const modelMatrix = new Matrix4(worldMatrix).multiplyRight(this.matrix);
×
UNCOV
117
    visitor(this, {worldMatrix: modelMatrix});
×
118

UNCOV
119
    for (const child of this.children) {
×
UNCOV
120
      if (child instanceof GroupNode) {
×
UNCOV
121
        child.preorderTraversal(visitor, {worldMatrix: modelMatrix});
×
122
      } else {
123
        visitor(child, {worldMatrix: modelMatrix});
×
124
      }
125
    }
126
  }
127
}
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