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

excaliburjs / Excalibur / 15354777440

30 May 2025 08:03PM UTC coverage: 87.858% (-1.5%) from 89.344%
15354777440

Pull #3385

github

web-flow
Merge a00f57733 into e6ec66358
Pull Request #3385: updated Meet action to add tolerance

5002 of 6948 branches covered (71.99%)

3 of 5 new or added lines in 2 files covered. (60.0%)

872 existing lines in 83 files now uncovered.

13661 of 15549 relevant lines covered (87.86%)

25187.01 hits per line

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

98.23
/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts
1
import { Color } from '../../../Color';
2
import { vec, Vector } from '../../../Math/vector';
3
import { GraphicsDiagnostics } from '../../GraphicsDiagnostics';
4
import type { ExcaliburGraphicsContextWebGL } from '../ExcaliburGraphicsContextWebGL';
5
import { pixelSnapEpsilon } from '../ExcaliburGraphicsContextWebGL';
6
import { QuadIndexBuffer } from '../quad-index-buffer';
7
import type { RendererPlugin } from '../renderer';
8
import { Shader } from '../shader';
9
import { VertexBuffer } from '../vertex-buffer';
10
import { VertexLayout } from '../vertex-layout';
11

12
import frag from './rectangle-renderer.frag.glsl';
13
import vert from './rectangle-renderer.vert.glsl';
14

15
export class RectangleRenderer implements RendererPlugin {
16
  public readonly type = 'ex.rectangle';
850✔
17
  public priority: number = 0;
850✔
18

19
  private _maxRectangles: number = 10922; // max(uint16) / 6 verts
850✔
20

21
  private _shader!: Shader;
22
  private _gl!: WebGLRenderingContext;
23
  private _context!: ExcaliburGraphicsContextWebGL;
24
  private _buffer!: VertexBuffer;
25
  private _layout!: VertexLayout;
26
  private _quads!: QuadIndexBuffer;
27
  private _rectangleCount: number = 0;
850✔
28
  private _vertexIndex: number = 0;
850✔
29

30
  initialize(gl: WebGL2RenderingContext, context: ExcaliburGraphicsContextWebGL): void {
31
    this._gl = gl;
850✔
32
    this._context = context;
850✔
33
    // https://stackoverflow.com/questions/59197671/glsl-rounded-rectangle-with-variable-border
34
    this._shader = new Shader({
850✔
35
      graphicsContext: context,
36
      fragmentSource: frag,
37
      vertexSource: vert
38
    });
39
    this._shader.compile();
850✔
40

41
    // setup uniforms
42
    this._shader.use();
850✔
43
    this._shader.setUniformMatrix('u_matrix', context.ortho);
850✔
44

45
    this._buffer = new VertexBuffer({
850✔
46
      gl,
47
      size: 16 * 4 * this._maxRectangles,
48
      type: 'dynamic'
49
    });
50

51
    this._layout = new VertexLayout({
850✔
52
      gl,
53
      shader: this._shader,
54
      vertexBuffer: this._buffer,
55
      attributes: [
56
        ['a_position', 2],
57
        ['a_uv', 2],
58
        ['a_size', 2],
59
        ['a_opacity', 1],
60
        ['a_color', 4],
61
        ['a_strokeColor', 4],
62
        ['a_strokeThickness', 1]
63
      ]
64
    });
65
    this._quads = new QuadIndexBuffer(gl, this._maxRectangles, true);
850✔
66
  }
67

68
  public dispose() {
69
    this._buffer.dispose();
758✔
70
    this._quads.dispose();
758✔
71
    this._shader.dispose();
758✔
72
    this._context = null as any;
758✔
73
    this._gl = null as any;
758✔
74
  }
75

76
  private _isFull() {
77
    if (this._rectangleCount >= this._maxRectangles) {
74!
UNCOV
78
      return true;
×
79
    }
80
    return false;
74✔
81
  }
82

83
  draw(...args: any[]): void {
84
    if (args[0] instanceof Vector && args[1] instanceof Vector) {
74✔
85
      this.drawLine.apply(this, args as any);
67✔
86
    } else {
87
      this.drawRectangle.apply(this, args as any);
7✔
88
    }
89
  }
90

91
  private _transparent = Color.Transparent;
850✔
92
  private _scratch1 = vec(0, 0);
850✔
93
  private _scratch2 = vec(0, 0);
850✔
94
  private _scratch3 = vec(0, 0);
850✔
95
  private _scratch4 = vec(0, 0);
850✔
96
  drawLine(start: Vector, end: Vector, color: Color, thickness: number = 1) {
×
97
    if (this._isFull()) {
67!
UNCOV
98
      this.flush();
×
99
    }
100
    this._rectangleCount++;
67✔
101

102
    // transform based on current context
103
    const transform = this._context.getTransform();
67✔
104
    const opacity = this._context.opacity;
67✔
105
    const snapToPixel = this._context.snapToPixel;
67✔
106

107
    const dir = end.sub(start);
67✔
108
    const length = dir.magnitude;
67✔
109
    const normal = dir.normalize().perpendicular();
67✔
110
    const halfThick = thickness / 2;
67✔
111

112
    /**
113
     *    +---------------------^----------------------+
114
     *    |                     | (normal)             |
115
     *   (startX, startY)------------------>(endX, endY)
116
     *    |                                            |
117
     *    + -------------------------------------------+
118
     */
119
    const startTop = transform.multiply(normal.scale(halfThick, this._scratch1).add(start, this._scratch1), this._scratch1);
67✔
120
    const startBottom = transform.multiply(normal.scale(-halfThick, this._scratch2).add(start, this._scratch2), this._scratch2);
67✔
121
    const endTop = transform.multiply(normal.scale(halfThick, this._scratch3).add(end, this._scratch3), this._scratch3);
67✔
122
    const endBottom = transform.multiply(normal.scale(-halfThick, this._scratch4).add(end, this._scratch4), this._scratch4);
67✔
123

124
    if (snapToPixel) {
67✔
125
      startTop.x = ~~(startTop.x + pixelSnapEpsilon);
1✔
126
      startTop.y = ~~(startTop.y + pixelSnapEpsilon);
1✔
127

128
      endTop.x = ~~(endTop.x + pixelSnapEpsilon);
1✔
129
      endTop.y = ~~(endTop.y + pixelSnapEpsilon);
1✔
130

131
      startBottom.x = ~~(startBottom.x + pixelSnapEpsilon);
1✔
132
      startBottom.y = ~~(startBottom.y + pixelSnapEpsilon);
1✔
133

134
      endBottom.x = ~~(endBottom.x + pixelSnapEpsilon);
1✔
135
      endBottom.y = ~~(endBottom.y + pixelSnapEpsilon);
1✔
136
    }
137

138
    // TODO uv could be static vertex buffer
139
    const uvx0 = 0;
67✔
140
    const uvy0 = 0;
67✔
141
    const uvx1 = 1;
67✔
142
    const uvy1 = 1;
67✔
143

144
    const stroke = this._transparent;
67✔
145
    const strokeThickness = 0;
67✔
146
    const width = 1;
67✔
147

148
    // update data
149
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
67✔
150

151
    // (0, 0) - 0
152
    vertexBuffer[this._vertexIndex++] = startTop.x;
67✔
153
    vertexBuffer[this._vertexIndex++] = startTop.y;
67✔
154
    vertexBuffer[this._vertexIndex++] = uvx0;
67✔
155
    vertexBuffer[this._vertexIndex++] = uvy0;
67✔
156
    vertexBuffer[this._vertexIndex++] = length;
67✔
157
    vertexBuffer[this._vertexIndex++] = thickness;
67✔
158
    vertexBuffer[this._vertexIndex++] = opacity;
67✔
159
    vertexBuffer[this._vertexIndex++] = color.r / 255;
67✔
160
    vertexBuffer[this._vertexIndex++] = color.g / 255;
67✔
161
    vertexBuffer[this._vertexIndex++] = color.b / 255;
67✔
162
    vertexBuffer[this._vertexIndex++] = color.a;
67✔
163
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
67✔
164
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
67✔
165
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
67✔
166
    vertexBuffer[this._vertexIndex++] = stroke.a;
67✔
167
    vertexBuffer[this._vertexIndex++] = strokeThickness / width;
67✔
168

169
    // (0, 1) - 1
170
    vertexBuffer[this._vertexIndex++] = startBottom.x;
67✔
171
    vertexBuffer[this._vertexIndex++] = startBottom.y;
67✔
172
    vertexBuffer[this._vertexIndex++] = uvx0;
67✔
173
    vertexBuffer[this._vertexIndex++] = uvy1;
67✔
174
    vertexBuffer[this._vertexIndex++] = length;
67✔
175
    vertexBuffer[this._vertexIndex++] = thickness;
67✔
176
    vertexBuffer[this._vertexIndex++] = opacity;
67✔
177
    vertexBuffer[this._vertexIndex++] = color.r / 255;
67✔
178
    vertexBuffer[this._vertexIndex++] = color.g / 255;
67✔
179
    vertexBuffer[this._vertexIndex++] = color.b / 255;
67✔
180
    vertexBuffer[this._vertexIndex++] = color.a;
67✔
181
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
67✔
182
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
67✔
183
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
67✔
184
    vertexBuffer[this._vertexIndex++] = stroke.a;
67✔
185
    vertexBuffer[this._vertexIndex++] = strokeThickness / width;
67✔
186

187
    // (1, 0) - 2
188
    vertexBuffer[this._vertexIndex++] = endTop.x;
67✔
189
    vertexBuffer[this._vertexIndex++] = endTop.y;
67✔
190
    vertexBuffer[this._vertexIndex++] = uvx1;
67✔
191
    vertexBuffer[this._vertexIndex++] = uvy0;
67✔
192
    vertexBuffer[this._vertexIndex++] = length;
67✔
193
    vertexBuffer[this._vertexIndex++] = thickness;
67✔
194
    vertexBuffer[this._vertexIndex++] = opacity;
67✔
195
    vertexBuffer[this._vertexIndex++] = color.r / 255;
67✔
196
    vertexBuffer[this._vertexIndex++] = color.g / 255;
67✔
197
    vertexBuffer[this._vertexIndex++] = color.b / 255;
67✔
198
    vertexBuffer[this._vertexIndex++] = color.a;
67✔
199
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
67✔
200
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
67✔
201
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
67✔
202
    vertexBuffer[this._vertexIndex++] = stroke.a;
67✔
203
    vertexBuffer[this._vertexIndex++] = strokeThickness / width;
67✔
204

205
    // (1, 1) - 3
206
    vertexBuffer[this._vertexIndex++] = endBottom.x;
67✔
207
    vertexBuffer[this._vertexIndex++] = endBottom.y;
67✔
208
    vertexBuffer[this._vertexIndex++] = uvx1;
67✔
209
    vertexBuffer[this._vertexIndex++] = uvy1;
67✔
210
    vertexBuffer[this._vertexIndex++] = length;
67✔
211
    vertexBuffer[this._vertexIndex++] = thickness;
67✔
212
    vertexBuffer[this._vertexIndex++] = opacity;
67✔
213
    vertexBuffer[this._vertexIndex++] = color.r / 255;
67✔
214
    vertexBuffer[this._vertexIndex++] = color.g / 255;
67✔
215
    vertexBuffer[this._vertexIndex++] = color.b / 255;
67✔
216
    vertexBuffer[this._vertexIndex++] = color.a;
67✔
217
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
67✔
218
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
67✔
219
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
67✔
220
    vertexBuffer[this._vertexIndex++] = stroke.a;
67✔
221
    vertexBuffer[this._vertexIndex++] = strokeThickness / width;
67✔
222
  }
223

224
  drawRectangle(
225
    pos: Vector,
226
    width: number,
227
    height: number,
228
    color: Color,
229
    stroke: Color = Color.Transparent,
3✔
230
    strokeThickness: number = 0
3✔
231
  ): void {
232
    if (this._isFull()) {
7!
UNCOV
233
      this.flush();
×
234
    }
235
    this._rectangleCount++;
7✔
236

237
    // transform based on current context
238
    const transform = this._context.getTransform();
7✔
239
    const opacity = this._context.opacity;
7✔
240
    const snapToPixel = this._context.snapToPixel;
7✔
241

242
    const topLeft = transform.multiply(pos.add(vec(0, 0)));
7✔
243
    const topRight = transform.multiply(pos.add(vec(width, 0)));
7✔
244
    const bottomRight = transform.multiply(pos.add(vec(width, height)));
7✔
245
    const bottomLeft = transform.multiply(pos.add(vec(0, height)));
7✔
246

247
    if (snapToPixel) {
7✔
248
      topLeft.x = ~~(topLeft.x + pixelSnapEpsilon);
1✔
249
      topLeft.y = ~~(topLeft.y + pixelSnapEpsilon);
1✔
250

251
      topRight.x = ~~(topRight.x + pixelSnapEpsilon);
1✔
252
      topRight.y = ~~(topRight.y + pixelSnapEpsilon);
1✔
253

254
      bottomLeft.x = ~~(bottomLeft.x + pixelSnapEpsilon);
1✔
255
      bottomLeft.y = ~~(bottomLeft.y + pixelSnapEpsilon);
1✔
256

257
      bottomRight.x = ~~(bottomRight.x + pixelSnapEpsilon);
1✔
258
      bottomRight.y = ~~(bottomRight.y + pixelSnapEpsilon);
1✔
259
    }
260

261
    // TODO uv could be static vertex buffer
262
    const uvx0 = 0;
7✔
263
    const uvy0 = 0;
7✔
264
    const uvx1 = 1;
7✔
265
    const uvy1 = 1;
7✔
266

267
    // update data
268
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
7✔
269

270
    // (0, 0) - 0
271
    vertexBuffer[this._vertexIndex++] = topLeft.x;
7✔
272
    vertexBuffer[this._vertexIndex++] = topLeft.y;
7✔
273
    vertexBuffer[this._vertexIndex++] = uvx0;
7✔
274
    vertexBuffer[this._vertexIndex++] = uvy0;
7✔
275
    vertexBuffer[this._vertexIndex++] = width;
7✔
276
    vertexBuffer[this._vertexIndex++] = height;
7✔
277
    vertexBuffer[this._vertexIndex++] = opacity;
7✔
278
    vertexBuffer[this._vertexIndex++] = color.r / 255;
7✔
279
    vertexBuffer[this._vertexIndex++] = color.g / 255;
7✔
280
    vertexBuffer[this._vertexIndex++] = color.b / 255;
7✔
281
    vertexBuffer[this._vertexIndex++] = color.a;
7✔
282
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
7✔
283
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
7✔
284
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
7✔
285
    vertexBuffer[this._vertexIndex++] = stroke.a;
7✔
286
    vertexBuffer[this._vertexIndex++] = strokeThickness;
7✔
287

288
    // (0, 1) - 1
289
    vertexBuffer[this._vertexIndex++] = bottomLeft.x;
7✔
290
    vertexBuffer[this._vertexIndex++] = bottomLeft.y;
7✔
291
    vertexBuffer[this._vertexIndex++] = uvx0;
7✔
292
    vertexBuffer[this._vertexIndex++] = uvy1;
7✔
293
    vertexBuffer[this._vertexIndex++] = width;
7✔
294
    vertexBuffer[this._vertexIndex++] = height;
7✔
295
    vertexBuffer[this._vertexIndex++] = opacity;
7✔
296
    vertexBuffer[this._vertexIndex++] = color.r / 255;
7✔
297
    vertexBuffer[this._vertexIndex++] = color.g / 255;
7✔
298
    vertexBuffer[this._vertexIndex++] = color.b / 255;
7✔
299
    vertexBuffer[this._vertexIndex++] = color.a;
7✔
300
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
7✔
301
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
7✔
302
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
7✔
303
    vertexBuffer[this._vertexIndex++] = stroke.a;
7✔
304
    vertexBuffer[this._vertexIndex++] = strokeThickness;
7✔
305

306
    // (1, 0) - 2
307
    vertexBuffer[this._vertexIndex++] = topRight.x;
7✔
308
    vertexBuffer[this._vertexIndex++] = topRight.y;
7✔
309
    vertexBuffer[this._vertexIndex++] = uvx1;
7✔
310
    vertexBuffer[this._vertexIndex++] = uvy0;
7✔
311
    vertexBuffer[this._vertexIndex++] = width;
7✔
312
    vertexBuffer[this._vertexIndex++] = height;
7✔
313
    vertexBuffer[this._vertexIndex++] = opacity;
7✔
314
    vertexBuffer[this._vertexIndex++] = color.r / 255;
7✔
315
    vertexBuffer[this._vertexIndex++] = color.g / 255;
7✔
316
    vertexBuffer[this._vertexIndex++] = color.b / 255;
7✔
317
    vertexBuffer[this._vertexIndex++] = color.a;
7✔
318
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
7✔
319
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
7✔
320
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
7✔
321
    vertexBuffer[this._vertexIndex++] = stroke.a;
7✔
322
    vertexBuffer[this._vertexIndex++] = strokeThickness;
7✔
323

324
    // (1, 1) - 3
325
    vertexBuffer[this._vertexIndex++] = bottomRight.x;
7✔
326
    vertexBuffer[this._vertexIndex++] = bottomRight.y;
7✔
327
    vertexBuffer[this._vertexIndex++] = uvx1;
7✔
328
    vertexBuffer[this._vertexIndex++] = uvy1;
7✔
329
    vertexBuffer[this._vertexIndex++] = width;
7✔
330
    vertexBuffer[this._vertexIndex++] = height;
7✔
331
    vertexBuffer[this._vertexIndex++] = opacity;
7✔
332
    vertexBuffer[this._vertexIndex++] = color.r / 255;
7✔
333
    vertexBuffer[this._vertexIndex++] = color.g / 255;
7✔
334
    vertexBuffer[this._vertexIndex++] = color.b / 255;
7✔
335
    vertexBuffer[this._vertexIndex++] = color.a;
7✔
336
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
7✔
337
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
7✔
338
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
7✔
339
    vertexBuffer[this._vertexIndex++] = stroke.a;
7✔
340
    vertexBuffer[this._vertexIndex++] = strokeThickness;
7✔
341
  }
342

343
  hasPendingDraws(): boolean {
344
    return this._rectangleCount !== 0;
12✔
345
  }
346

347
  flush(): void {
348
    // nothing to draw early exit
349
    if (this._rectangleCount === 0) {
19!
UNCOV
350
      return;
×
351
    }
352

353
    const gl = this._gl;
19✔
354
    // Bind the shader
355
    this._shader.use();
19✔
356

357
    // Bind the memory layout and upload data
358
    this._layout.use(true);
19✔
359

360
    // Update ortho matrix uniform
361
    this._shader.setUniformMatrix('u_matrix', this._context.ortho);
19✔
362

363
    // Bind index buffer
364
    this._quads.bind();
19✔
365

366
    // Draw all the quads
367
    gl.drawElements(gl.TRIANGLES, this._rectangleCount * 6, this._quads.bufferGlType, 0);
19✔
368

369
    GraphicsDiagnostics.DrawnImagesCount += this._rectangleCount;
19✔
370
    GraphicsDiagnostics.DrawCallCount++;
19✔
371

372
    // Reset
373
    this._rectangleCount = 0;
19✔
374
    this._vertexIndex = 0;
19✔
375
  }
376
}
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