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

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

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

0.0
/src/engine/Graphics/GraphicsGroup.ts
1
import { Vector } from '../Math/vector';
2
import { Graphic, GraphicOptions } from './Graphic';
3
import { Animation, HasTick } from './Animation';
4
import { ExcaliburGraphicsContext } from './Context/ExcaliburGraphicsContext';
5
import { BoundingBox } from '../Collision/Index';
6
import { Logger } from '../Util/Log';
7

8
export interface GraphicsGroupingOptions {
9
  members: (GraphicsGrouping | Graphic)[];
10
  /**
11
   * Default true, GraphicsGroup will use the anchor to position all the graphics based on their combined bounds
12
   *
13
   * Setting to false will ignore anchoring from parent components and position the top left of all graphics at the actor's position,
14
   * positioning graphics in the group is done with the `offset` property.
15
   */
16
  useAnchor?: boolean;
17
}
18

19
export interface GraphicsGrouping {
20
  offset: Vector;
21
  graphic: Graphic;
22
  /**
23
   * Optionally disable this graphics bounds as part of group calculation, default true
24
   * if unspecified
25
   *
26
   * You may want disable this if you're using text because their bounds will affect
27
   * the centering of the whole group.
28
   *
29
   * **WARNING** having inaccurate bounds can cause offscreen culling issues.
30
   */
31
  useBounds?: boolean;
32
}
33

34
export class GraphicsGroup extends Graphic implements HasTick {
UNCOV
35
  private _logger = Logger.getInstance();
×
UNCOV
36
  public useAnchor: boolean = true;
×
UNCOV
37
  public members: (GraphicsGrouping | Graphic)[] = [];
×
38

39
  constructor(options: GraphicsGroupingOptions & GraphicOptions) {
UNCOV
40
    super(options);
×
UNCOV
41
    this.members = options.members;
×
UNCOV
42
    this.useAnchor = options.useAnchor ?? this.useAnchor;
×
UNCOV
43
    this._updateDimensions();
×
44
  }
45

46
  public clone(): GraphicsGroup {
UNCOV
47
    return new GraphicsGroup({
×
48
      members: [...this.members],
49
      ...this.cloneGraphicOptions()
50
    });
51
  }
52

53
  private _updateDimensions(): BoundingBox {
UNCOV
54
    const bb = this.localBounds;
×
UNCOV
55
    this.width = bb.width;
×
UNCOV
56
    this.height = bb.height;
×
UNCOV
57
    return bb;
×
58
  }
59

60
  public get localBounds(): BoundingBox {
UNCOV
61
    const bb = new BoundingBox();
×
UNCOV
62
    for (const member of this.members) {
×
UNCOV
63
      if (member instanceof Graphic) {
×
64
        member.localBounds.combine(bb, bb);
×
65
      } else {
UNCOV
66
        const { graphic, offset: pos, useBounds } = member;
×
UNCOV
67
        const shouldUseBounds = useBounds === undefined ? true : useBounds;
×
UNCOV
68
        if (graphic) {
×
UNCOV
69
          if (shouldUseBounds) {
×
UNCOV
70
            graphic.localBounds.translate(pos).combine(bb, bb);
×
71
          }
72
        } else {
73
          this._logger.warnOnce(`Graphics group member has an null or undefined graphic, member definition: ${JSON.stringify(member)}.`);
×
74
        }
75
      }
76
    }
UNCOV
77
    return bb;
×
78
  }
79

80
  private _isAnimationOrGroup(graphic: Graphic): graphic is Animation | GraphicsGroup {
UNCOV
81
    return graphic instanceof Animation || graphic instanceof GraphicsGroup;
×
82
  }
83

84
  public tick(elapsed: number, idempotencyToken?: number) {
UNCOV
85
    for (const member of this.members) {
×
86
      let graphic: Graphic;
UNCOV
87
      if (member instanceof Graphic) {
×
88
        graphic = member;
×
89
      } else {
UNCOV
90
        graphic = member.graphic;
×
91
      }
UNCOV
92
      if (this._isAnimationOrGroup(graphic)) {
×
UNCOV
93
        graphic.tick(elapsed, idempotencyToken);
×
94
      }
95
    }
96
  }
97

98
  public reset() {
UNCOV
99
    for (const member of this.members) {
×
100
      let graphic: Graphic;
UNCOV
101
      if (member instanceof Graphic) {
×
102
        graphic = member;
×
103
      } else {
UNCOV
104
        graphic = member.graphic;
×
105
      }
UNCOV
106
      if (this._isAnimationOrGroup(graphic)) {
×
UNCOV
107
        graphic.reset();
×
108
      }
109
    }
110
  }
111

112
  protected _preDraw(ex: ExcaliburGraphicsContext, x: number, y: number) {
UNCOV
113
    this._updateDimensions();
×
UNCOV
114
    super._preDraw(ex, this.useAnchor ? x : 0, this.useAnchor ? y : 0);
×
115
  }
116

117
  protected _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number) {
UNCOV
118
    const pos = Vector.Zero;
×
UNCOV
119
    for (const member of this.members) {
×
120
      let graphic: Graphic;
UNCOV
121
      if (member instanceof Graphic) {
×
122
        graphic = member;
×
123
      } else {
UNCOV
124
        graphic = member.graphic;
×
UNCOV
125
        member.offset.clone(pos);
×
126
      }
UNCOV
127
      if (!graphic) {
×
128
        continue;
×
129
      }
UNCOV
130
      ex.save();
×
UNCOV
131
      ex.translate(x, y);
×
UNCOV
132
      graphic.draw(ex, pos.x, pos.y);
×
UNCOV
133
      if (this.showDebug) {
×
134
        /* istanbul ignore next */
135
        ex.debug.drawRect(0, 0, this.width, this.height);
136
      }
UNCOV
137
      ex.restore();
×
138
    }
139
  }
140
}
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