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

visgl / luma.gl / 23098510270

14 Mar 2026 11:16PM UTC coverage: 75.881% (+0.2%) from 75.702%
23098510270

Pull #2391

github

web-flow
Merge 371a8361c into 8ae983f4a
Pull Request #2391: Refactor conversion of glTF nodes to luma.gl nodes, add skin capability

2462 of 3163 branches covered (77.84%)

Branch coverage included in aggregate %.

315 of 382 new or added lines in 14 files covered. (82.46%)

3 existing lines in 2 files now uncovered.

29411 of 38841 relevant lines covered (75.72%)

90.99 hits per line

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

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

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

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

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

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

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

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

2✔
36
    this.traverse((node, {worldMatrix}) => {
2✔
37
      const bounds = node.getBounds();
4✔
38
      if (!bounds) {
4✔
39
        return;
2✔
40
      }
2✔
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

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

16✔
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
        }
48✔
57
      }
16✔
58
    });
2✔
59
    if (!Number.isFinite(result[0][0])) {
2✔
60
      return null;
1✔
61
    }
1✔
62
    return result;
1✔
63
  }
2✔
64

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

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

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

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

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

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

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

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