• 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/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 { ExcaliburGraphicsContextWebGL, pixelSnapEpsilon } from '../ExcaliburGraphicsContextWebGL';
5
import { QuadIndexBuffer } from '../quad-index-buffer';
6
import { RendererPlugin } from '../renderer';
7
import { Shader } from '../shader';
8
import { VertexBuffer } from '../vertex-buffer';
9
import { VertexLayout } from '../vertex-layout';
10

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

14
export class RectangleRenderer implements RendererPlugin {
UNCOV
15
  public readonly type = 'ex.rectangle';
×
UNCOV
16
  public priority: number = 0;
×
17

UNCOV
18
  private _maxRectangles: number = 10922; // max(uint16) / 6 verts
×
19

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

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

40
    // setup uniforms
UNCOV
41
    this._shader.use();
×
UNCOV
42
    this._shader.setUniformMatrix('u_matrix', context.ortho);
×
43

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

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

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

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

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

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

101
    // transform based on current context
UNCOV
102
    const transform = this._context.getTransform();
×
UNCOV
103
    const opacity = this._context.opacity;
×
UNCOV
104
    const snapToPixel = this._context.snapToPixel;
×
105

UNCOV
106
    const dir = end.sub(start);
×
UNCOV
107
    const length = dir.magnitude;
×
UNCOV
108
    const normal = dir.normalize().perpendicular();
×
UNCOV
109
    const halfThick = thickness / 2;
×
110

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

UNCOV
123
    if (snapToPixel) {
×
UNCOV
124
      startTop.x = ~~(startTop.x + pixelSnapEpsilon);
×
UNCOV
125
      startTop.y = ~~(startTop.y + pixelSnapEpsilon);
×
126

UNCOV
127
      endTop.x = ~~(endTop.x + pixelSnapEpsilon);
×
UNCOV
128
      endTop.y = ~~(endTop.y + pixelSnapEpsilon);
×
129

UNCOV
130
      startBottom.x = ~~(startBottom.x + pixelSnapEpsilon);
×
UNCOV
131
      startBottom.y = ~~(startBottom.y + pixelSnapEpsilon);
×
132

UNCOV
133
      endBottom.x = ~~(endBottom.x + pixelSnapEpsilon);
×
UNCOV
134
      endBottom.y = ~~(endBottom.y + pixelSnapEpsilon);
×
135
    }
136

137
    // TODO uv could be static vertex buffer
UNCOV
138
    const uvx0 = 0;
×
UNCOV
139
    const uvy0 = 0;
×
UNCOV
140
    const uvx1 = 1;
×
UNCOV
141
    const uvy1 = 1;
×
142

UNCOV
143
    const stroke = this._transparent;
×
UNCOV
144
    const strokeThickness = 0;
×
UNCOV
145
    const width = 1;
×
146

147
    // update data
UNCOV
148
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
×
149

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

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

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

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

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

236
    // transform based on current context
UNCOV
237
    const transform = this._context.getTransform();
×
UNCOV
238
    const opacity = this._context.opacity;
×
UNCOV
239
    const snapToPixel = this._context.snapToPixel;
×
240

UNCOV
241
    const topLeft = transform.multiply(pos.add(vec(0, 0)));
×
UNCOV
242
    const topRight = transform.multiply(pos.add(vec(width, 0)));
×
UNCOV
243
    const bottomRight = transform.multiply(pos.add(vec(width, height)));
×
UNCOV
244
    const bottomLeft = transform.multiply(pos.add(vec(0, height)));
×
245

UNCOV
246
    if (snapToPixel) {
×
UNCOV
247
      topLeft.x = ~~(topLeft.x + pixelSnapEpsilon);
×
UNCOV
248
      topLeft.y = ~~(topLeft.y + pixelSnapEpsilon);
×
249

UNCOV
250
      topRight.x = ~~(topRight.x + pixelSnapEpsilon);
×
UNCOV
251
      topRight.y = ~~(topRight.y + pixelSnapEpsilon);
×
252

UNCOV
253
      bottomLeft.x = ~~(bottomLeft.x + pixelSnapEpsilon);
×
UNCOV
254
      bottomLeft.y = ~~(bottomLeft.y + pixelSnapEpsilon);
×
255

UNCOV
256
      bottomRight.x = ~~(bottomRight.x + pixelSnapEpsilon);
×
UNCOV
257
      bottomRight.y = ~~(bottomRight.y + pixelSnapEpsilon);
×
258
    }
259

260
    // TODO uv could be static vertex buffer
UNCOV
261
    const uvx0 = 0;
×
UNCOV
262
    const uvy0 = 0;
×
UNCOV
263
    const uvx1 = 1;
×
UNCOV
264
    const uvy1 = 1;
×
265

266
    // update data
UNCOV
267
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
×
268

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

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

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

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

342
  hasPendingDraws(): boolean {
UNCOV
343
    return this._rectangleCount !== 0;
×
344
  }
345

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

UNCOV
352
    const gl = this._gl;
×
353
    // Bind the shader
UNCOV
354
    this._shader.use();
×
355

356
    // Bind the memory layout and upload data
UNCOV
357
    this._layout.use(true);
×
358

359
    // Update ortho matrix uniform
UNCOV
360
    this._shader.setUniformMatrix('u_matrix', this._context.ortho);
×
361

362
    // Bind index buffer
UNCOV
363
    this._quads.bind();
×
364

365
    // Draw all the quads
UNCOV
366
    gl.drawElements(gl.TRIANGLES, this._rectangleCount * 6, this._quads.bufferGlType, 0);
×
367

UNCOV
368
    GraphicsDiagnostics.DrawnImagesCount += this._rectangleCount;
×
UNCOV
369
    GraphicsDiagnostics.DrawCallCount++;
×
370

371
    // Reset
UNCOV
372
    this._rectangleCount = 0;
×
UNCOV
373
    this._vertexIndex = 0;
×
374
  }
375
}
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