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

excaliburjs / Excalibur / 11646099157

02 Nov 2024 09:47PM UTC coverage: 90.212% (-0.1%) from 90.352%
11646099157

push

github

eonarheim
test: update raycast test

5862 of 7457 branches covered (78.61%)

12839 of 14232 relevant lines covered (90.21%)

25247.84 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 { 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 {
15
  public readonly type = 'ex.rectangle';
782✔
16
  public priority: number = 0;
782✔
17

18
  private _maxRectangles: number = 10922; // max(uint16) / 6 verts
782✔
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;
26
  private _rectangleCount: number = 0;
782✔
27
  private _vertexIndex: number = 0;
782✔
28

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

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

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

50
    this._layout = new VertexLayout({
782✔
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
    });
64
    this._quads = new QuadIndexBuffer(gl, this._maxRectangles, true);
782✔
65
  }
66

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

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

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

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

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

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

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

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

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

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

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

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

143
    const stroke = this._transparent;
65✔
144
    const strokeThickness = 0;
65✔
145
    const width = 1;
65✔
146

147
    // update data
148
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
65✔
149

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

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

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

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

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

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

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

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

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

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

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

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

266
    // update data
267
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
5✔
268

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

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

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

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

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

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

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

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

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

362
    // Bind index buffer
363
    this._quads.bind();
15✔
364

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

368
    GraphicsDiagnostics.DrawnImagesCount += this._rectangleCount;
15✔
369
    GraphicsDiagnostics.DrawCallCount++;
15✔
370

371
    // Reset
372
    this._rectangleCount = 0;
15✔
373
    this._vertexIndex = 0;
15✔
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