• 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

1.27
/src/engine/Input/PointerSystem.ts
1
import { Engine } from '../Engine';
2
import { System, TransformComponent, SystemType, Entity, World, Query, SystemPriority } from '../EntityComponentSystem';
3
import { GraphicsComponent } from '../Graphics/GraphicsComponent';
4
import { Scene } from '../Scene';
5
import { PointerComponent } from './PointerComponent';
6
import { PointerEventReceiver } from './PointerEventReceiver';
7
import { CoordPlane } from '../Math/coord-plane';
8
import { SparseHashGrid } from '../Collision/Detection/SparseHashGrid';
9
import { PointerEventsToObjectDispatcher } from './PointerEventsToObjectDispatcher';
10

11
/**
12
 * The PointerSystem is responsible for dispatching pointer events to entities
13
 * that need them.
14
 *
15
 * The PointerSystem can be optionally configured by the {@apilink PointerComponent}, by default Entities use
16
 * the {@apilink Collider}'s shape for pointer events.
17
 */
18
export class PointerSystem extends System {
19
  static priority = SystemPriority.Higher;
1✔
20

UNCOV
21
  public readonly systemType = SystemType.Update;
×
22

23
  private _engine: Engine;
24
  private _receivers: PointerEventReceiver[];
25
  private _engineReceiver: PointerEventReceiver;
UNCOV
26
  private _graphicsHashGrid = new SparseHashGrid<GraphicsComponent>({ size: 100 });
×
UNCOV
27
  private _graphics: GraphicsComponent[] = [];
×
UNCOV
28
  private _entityToPointer = new Map<Entity, PointerComponent>();
×
UNCOV
29
  private _pointerEventDispatcher = new PointerEventsToObjectDispatcher();
×
30

31
  query: Query<typeof TransformComponent | typeof PointerComponent>;
32

UNCOV
33
  constructor(public world: World) {
×
UNCOV
34
    super();
×
UNCOV
35
    this.query = this.world.query([TransformComponent, PointerComponent]);
×
36

UNCOV
37
    this.query.entityAdded$.subscribe((e) => {
×
UNCOV
38
      const tx = e.get(TransformComponent);
×
UNCOV
39
      const pointer = e.get(PointerComponent);
×
UNCOV
40
      this._pointerEventDispatcher.addObject(
×
41
        e,
42
        (pos) => {
43
          // If pointer bounds defined
UNCOV
44
          if (pointer && pointer.localBounds) {
×
UNCOV
45
            const pointerBounds = pointer.localBounds.transform(tx.get().matrix);
×
UNCOV
46
            return pointerBounds.contains(tx.coordPlane === CoordPlane.World ? pos.worldPos : pos.screenPos);
×
47
          }
UNCOV
48
          return false;
×
49
        },
UNCOV
50
        () => e.isActive
×
51
      );
UNCOV
52
      this._entityToPointer.set(e, pointer);
×
UNCOV
53
      const maybeGfx = e.get(GraphicsComponent);
×
UNCOV
54
      if (maybeGfx) {
×
UNCOV
55
        this._graphics.push(maybeGfx);
×
UNCOV
56
        this._graphicsHashGrid.track(maybeGfx);
×
57
      }
UNCOV
58
      this._sortedTransforms.push(tx);
×
UNCOV
59
      this._sortedEntities.push(tx.owner);
×
UNCOV
60
      tx.zIndexChanged$.subscribe(this._zIndexUpdate);
×
UNCOV
61
      this._zHasChanged = true;
×
62
    });
63

UNCOV
64
    this.query.entityRemoved$.subscribe((e) => {
×
UNCOV
65
      this._pointerEventDispatcher.removeObject(e);
×
UNCOV
66
      const tx = e.get(TransformComponent);
×
UNCOV
67
      this._entityToPointer.delete(e);
×
UNCOV
68
      const maybeGfx = e.get(GraphicsComponent);
×
UNCOV
69
      if (maybeGfx) {
×
UNCOV
70
        const index = this._graphics.indexOf(maybeGfx);
×
UNCOV
71
        if (index > -1) {
×
UNCOV
72
          this._graphics.splice(index, 1);
×
73
        }
UNCOV
74
        this._graphicsHashGrid.untrack(maybeGfx);
×
75
      }
UNCOV
76
      tx.zIndexChanged$.unsubscribe(this._zIndexUpdate);
×
UNCOV
77
      const index = this._sortedTransforms.indexOf(tx);
×
UNCOV
78
      if (index > -1) {
×
UNCOV
79
        this._sortedTransforms.splice(index, 1);
×
UNCOV
80
        this._sortedEntities.splice(index, 1);
×
81
      }
82
    });
83
  }
84

85
  /**
86
   * Optionally override component configuration for all entities
87
   */
UNCOV
88
  public overrideUseColliderShape = false;
×
89
  /**
90
   * Optionally override component configuration for all entities
91
   */
UNCOV
92
  public overrideUseGraphicsBounds = false;
×
93

94
  private _scene: Scene<unknown>;
95

96
  public initialize(world: World, scene: Scene): void {
UNCOV
97
    this._engine = scene.engine;
×
UNCOV
98
    this._scene = scene;
×
99
  }
100

UNCOV
101
  private _sortedTransforms: TransformComponent[] = [];
×
UNCOV
102
  private _sortedEntities: Entity[] = [];
×
103

UNCOV
104
  private _zHasChanged = false;
×
UNCOV
105
  private _zIndexUpdate = () => {
×
UNCOV
106
    this._zHasChanged = true;
×
107
  };
108

109
  public preupdate(): void {
UNCOV
110
    if (this._scene.camera.hasChanged()) {
×
111
      // if the camera has changed we want to force a transform update so pointers can be correctly calc'd
UNCOV
112
      this._scene.camera.updateTransform(this._scene.camera.pos);
×
113
    }
114

115
    // event receiver might change per frame
UNCOV
116
    this._receivers = [this._engine.input.pointers, this._scene.input.pointers];
×
UNCOV
117
    this._engineReceiver = this._engine.input.pointers;
×
UNCOV
118
    if (this._zHasChanged) {
×
UNCOV
119
      this._sortedTransforms.sort((a, b) => {
×
UNCOV
120
        return b.z - a.z;
×
121
      });
UNCOV
122
      this._sortedEntities = this._sortedTransforms.map((t) => t.owner);
×
UNCOV
123
      this._zHasChanged = false;
×
124
    }
125
  }
126

127
  public update(): void {
128
    // Update graphics
UNCOV
129
    this._graphicsHashGrid.update(this._graphics);
×
130

131
    // Locate all the pointer/entity mappings
UNCOV
132
    for (const [pointerId, pos] of this._engineReceiver.currentFramePointerCoords.entries()) {
×
UNCOV
133
      const colliders = this._scene.physics.query(pos.worldPos);
×
UNCOV
134
      for (let i = 0; i < colliders.length; i++) {
×
UNCOV
135
        const collider = colliders[i];
×
UNCOV
136
        const maybePointer = this._entityToPointer.get(collider.owner);
×
UNCOV
137
        if (maybePointer && (maybePointer.useColliderShape || this.overrideUseColliderShape)) {
×
UNCOV
138
          this._pointerEventDispatcher.addPointerToObject(collider.owner, pointerId);
×
139
        }
140
      }
141

UNCOV
142
      const graphics = this._graphicsHashGrid.query(pos.worldPos);
×
UNCOV
143
      for (let i = 0; i < graphics.length; i++) {
×
UNCOV
144
        const graphic = graphics[i];
×
UNCOV
145
        const maybePointer = this._entityToPointer.get(graphic.owner);
×
UNCOV
146
        if (maybePointer && (maybePointer.useGraphicsBounds || this.overrideUseGraphicsBounds)) {
×
UNCOV
147
          this._pointerEventDispatcher.addPointerToObject(graphic.owner, pointerId);
×
148
        }
149
      }
150
    }
151

UNCOV
152
    this._pointerEventDispatcher.processPointerToObject(this._engineReceiver, this._sortedEntities);
×
153

154
    // Dispatch pointer events on entities
UNCOV
155
    this._pointerEventDispatcher.dispatchEvents(this._engineReceiver, this._sortedEntities);
×
156

157
    // Dispatch pointer events on top level pointers
UNCOV
158
    this._receivers.forEach((r) => r.update());
×
159

160
    // Clear last frame's events
UNCOV
161
    this._pointerEventDispatcher.clear();
×
162

UNCOV
163
    this._receivers.forEach((r) => r.clear());
×
164
  }
165
}
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