@luceosports/play-rendering 1.11.21 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luceosports/play-rendering",
3
- "version": "1.11.21",
3
+ "version": "1.12.0",
4
4
  "description": "",
5
5
  "main": "dist/play-rendering.js",
6
6
  "scripts": {
@@ -6,11 +6,11 @@ class ShapeControlPointLayer extends BaseLayer {
6
6
 
7
7
  this.ctx.fillStyle = 'blue';
8
8
 
9
- this.playData.shapes.forEach(({ id, color, rectWrapPointsTranslated }) => {
9
+ this.playData.shapes.forEach(({ id, color, controlPointsTranslated }) => {
10
10
  if (this.options.shapeSelectedId !== id) return;
11
11
  if (color.alpha === 0) return;
12
12
 
13
- rectWrapPointsTranslated.forEach(point => {
13
+ controlPointsTranslated.forEach(point => {
14
14
  this.ctx.beginPath();
15
15
  this.ctx.arc(point.x, point.y, 0.4, 0, Math.PI * 2, true);
16
16
  this.ctx.fill();
@@ -7,7 +7,10 @@ class ShapeLayer extends BaseLayer {
7
7
  this.ctx.save();
8
8
 
9
9
  this.playData.shapes.forEach(shape => {
10
- const layerKey = `${_.capitalize(shape.type)}ShapeLayer`;
10
+ const shapeType = shape.type.match('LINE.')
11
+ ? `${_.capitalize(shape.type.replace('LINE.', ''))}Line`
12
+ : _.capitalize(shape.type);
13
+ const layerKey = `${shapeType}ShapeLayer`;
11
14
  if (shapeLayers[layerKey]) {
12
15
  new shapeLayers[layerKey](this, shape).apply();
13
16
  }
@@ -1,5 +1,6 @@
1
1
  const InternalBaseLayer = require('../../base/InternalBaseLayer');
2
2
  const LineDrawingMath = require('../../../math/LineDrawingMath');
3
+ const LineDrawOperationsTrait = require('../../../traits/LineDrawOperationsTrait');
3
4
 
4
5
  class InternalLineLayer extends InternalBaseLayer {
5
6
  constructor(parentLayer, line) {
@@ -20,6 +21,31 @@ class InternalLineLayer extends InternalBaseLayer {
20
21
  this.drawLineCap();
21
22
  }
22
23
 
24
+ setColor(alphaOverride = null) {
25
+ let lineForPlayerInPosition = false;
26
+ if (this.options.position) {
27
+ const { playerPositionOrigin, playerPositionTerminus } = this.line.originalData;
28
+ if (playerPositionOrigin === this.options.position || playerPositionTerminus === this.options.position) {
29
+ lineForPlayerInPosition = true;
30
+ }
31
+ }
32
+
33
+ const { alpha, blue, green, red } = this.line.color;
34
+
35
+ const r = lineForPlayerInPosition ? 0 : Math.ceil(red * 255);
36
+ const g = lineForPlayerInPosition ? 0 : Math.ceil(green * 255);
37
+ const b = lineForPlayerInPosition ? 255 : Math.ceil(blue * 255);
38
+
39
+ const color = `rgba(${r}, ${g}, ${b}, ${alphaOverride || alpha})`;
40
+
41
+ this.ctx.fillStyle = color;
42
+ this.ctx.strokeStyle = color;
43
+ }
44
+
45
+ getProcessedLinePaths() {
46
+ return this.line.getLineParts();
47
+ }
48
+
23
49
  applyMasking() {
24
50
  this.line.getLineParts().forEach((lp, index) => {
25
51
  let lineControlPoints = lp.controlPoints;
@@ -54,144 +80,6 @@ class InternalLineLayer extends InternalBaseLayer {
54
80
  });
55
81
  }
56
82
 
57
- setColor(alphaOverride = null) {
58
- let lineForPlayerInPosition = false;
59
- if (this.options.position) {
60
- const { playerPositionOrigin, playerPositionTerminus } = this.line.originalData;
61
- if (playerPositionOrigin === this.options.position || playerPositionTerminus === this.options.position) {
62
- lineForPlayerInPosition = true;
63
- }
64
- }
65
-
66
- const { alpha, blue, green, red } = this.line.color;
67
-
68
- const r = lineForPlayerInPosition ? 0 : Math.ceil(red * 255);
69
- const g = lineForPlayerInPosition ? 0 : Math.ceil(green * 255);
70
- const b = lineForPlayerInPosition ? 255 : Math.ceil(blue * 255);
71
-
72
- const color = `rgba(${r}, ${g}, ${b}, ${alphaOverride || alpha})`;
73
-
74
- this.ctx.fillStyle = color;
75
- this.ctx.strokeStyle = color;
76
- }
77
-
78
- drawLineFromControlPoints() {
79
- this.ctx.save();
80
-
81
- this.setLineOptions();
82
-
83
- this.ctx.lineJoin = 'round';
84
-
85
- this.getProcessedLinePaths().forEach(linePart => {
86
- this.ctx.save();
87
-
88
- if (linePart.alpha) {
89
- this.setColor(linePart.alpha); // setting color for each line path (animation)
90
- }
91
-
92
- this.ctx.beginPath();
93
-
94
- const cp = linePart.controlPoints;
95
- this.ctx.moveTo(cp[0].x, cp[0].y);
96
-
97
- if (cp.length === 2) {
98
- if (this.line.type === 'DRIBBLE') this.ctx.lineCap = 'round'; // fix last straight line cap segment
99
- this.ctx.lineTo(cp[1].x, cp[1].y);
100
- }
101
- if (cp.length === 3) {
102
- this.ctx.quadraticCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y);
103
- }
104
- if (cp.length === 4) {
105
- this.ctx.bezierCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y, cp[3].x, cp[3].y);
106
- }
107
-
108
- this.ctx.stroke();
109
-
110
- this.ctx.restore();
111
- });
112
-
113
- this.ctx.restore();
114
- }
115
-
116
- drawArrowLineCap() {
117
- this.ctx.save();
118
-
119
- const arrowTipLength = 1.4;
120
- const angle = this.angleBetweenLastTwoPoints();
121
- const courtArrowTipPoint = this.arrowTipPoint();
122
-
123
- const courtLineEndPoint = { x: courtArrowTipPoint.x - arrowTipLength / 1.3, y: courtArrowTipPoint.y };
124
- const courtArrowEdgePoint1 = {
125
- x: courtArrowTipPoint.x - arrowTipLength - arrowTipLength / 6.0,
126
- y: courtArrowTipPoint.y - arrowTipLength / 2.0
127
- };
128
- const courtArrowEdgePoint2 = {
129
- x: courtArrowTipPoint.x - arrowTipLength - arrowTipLength / 6.0,
130
- y: courtArrowTipPoint.y + arrowTipLength / 2.0
131
- };
132
-
133
- this.ctx.beginPath();
134
-
135
- this.ctx.translate(courtArrowTipPoint.x, courtArrowTipPoint.y);
136
- this.ctx.rotate(angle);
137
- this.ctx.translate(-courtArrowTipPoint.x, -courtArrowTipPoint.y);
138
-
139
- this.ctx.moveTo(courtLineEndPoint.x, courtLineEndPoint.y);
140
- this.ctx.lineTo(courtArrowEdgePoint1.x, courtArrowEdgePoint1.y);
141
- this.ctx.lineTo(courtArrowTipPoint.x, courtArrowTipPoint.y);
142
- this.ctx.lineTo(courtArrowEdgePoint2.x, courtArrowEdgePoint2.y);
143
- this.ctx.lineTo(courtLineEndPoint.x, courtLineEndPoint.y);
144
-
145
- this.ctx.fill();
146
- this.ctx.stroke();
147
-
148
- this.ctx.restore();
149
- }
150
-
151
- drawPerpendicularLineCap() {
152
- this.drawPerpendicularLineAtCourtPoint(this.arrowTipPoint(), this.angleBetweenLastTwoPoints());
153
- }
154
-
155
- drawPerpendicularLineAtCourtPoint(courtPoint, angle) {
156
- this.ctx.save();
157
-
158
- const lineCapLength = 3.0;
159
- const lineCapStartPoint = { x: courtPoint.x, y: courtPoint.y - lineCapLength / 2.0 };
160
- const lineCapEndPoint = { x: courtPoint.x, y: courtPoint.y + lineCapLength / 2.0 };
161
-
162
- this.ctx.beginPath();
163
-
164
- this.ctx.translate(courtPoint.x, courtPoint.y);
165
- this.ctx.rotate(angle);
166
- this.ctx.translate(-courtPoint.x, -courtPoint.y);
167
-
168
- this.ctx.moveTo(lineCapStartPoint.x, lineCapStartPoint.y);
169
- this.ctx.lineTo(lineCapEndPoint.x, lineCapEndPoint.y);
170
-
171
- this.ctx.fill();
172
- this.ctx.stroke();
173
-
174
- this.ctx.restore();
175
- }
176
-
177
- angleBetweenLastTwoPoints() {
178
- const lastLinePart = [...this.getProcessedLinePaths()].pop();
179
- const cp = lastLinePart.controlPoints;
180
-
181
- const cpTo = cp[cp.length - 1];
182
- const cpFrom = cp[cp.length - 2];
183
-
184
- const dx = cpTo.x - cpFrom.x;
185
- const dy = cpTo.y - cpFrom.y;
186
- return Math.atan2(dy, dx);
187
- }
188
-
189
- arrowTipPoint() {
190
- const lastLinePart = [...this.line.getLineParts()].pop();
191
- const cp = lastLinePart.controlPoints;
192
- return cp[cp.length - 1];
193
- }
194
-
195
83
  setLineMasking() {
196
84
  // Right now we take care only about only one line part
197
85
  // Adjust line masking
@@ -246,10 +134,6 @@ class InternalLineLayer extends InternalBaseLayer {
246
134
  this.showMaskingDebug();
247
135
  }
248
136
 
249
- getProcessedLinePaths() {
250
- return this.line.getLineParts();
251
- }
252
-
253
137
  showMaskingDebug() {
254
138
  if (this.debugMasking === false) return;
255
139
  this.ctx.save();
@@ -273,13 +157,11 @@ class InternalLineLayer extends InternalBaseLayer {
273
157
  this.ctx.restore();
274
158
  }
275
159
 
276
- setLineOptions() {
277
- // Override this method in a subclass
278
- }
279
-
280
160
  drawLineCap() {
281
161
  // Override this method in a subclass
282
162
  }
283
163
  }
284
164
 
165
+ Object.assign(InternalLineLayer.prototype, LineDrawOperationsTrait);
166
+
285
167
  module.exports = InternalLineLayer;
@@ -14,17 +14,21 @@ class InternalShapeLayer extends InternalBaseLayer {
14
14
 
15
15
  drawLogic() {
16
16
  const { location, scale, angleRad } = this.shape;
17
- this.ctx.lineWidth = this.courtTypeConstants.COURT_LINE_WIDTH;
18
17
 
19
18
  this.ctx.translate(location.x, location.y);
20
19
  this.ctx.rotate(angleRad);
21
20
  this.ctx.scale(scale.x, scale.y);
22
21
 
22
+ this.setLineWidth();
23
23
  this.setColor();
24
24
  this.drawShape();
25
25
  this.drawShapeWrapPoints();
26
26
  }
27
27
 
28
+ setLineWidth() {
29
+ this.ctx.lineWidth = this.courtTypeConstants.COURT_LINE_WIDTH;
30
+ }
31
+
28
32
  setColor() {
29
33
  const { alpha, blue, green, red } = this.shape.color;
30
34
 
@@ -1,5 +1,6 @@
1
1
  // require all modules on the path and with the pattern defined
2
- const req = require.context('./layers/', true, /.js$/);
2
+ const req = require.context('./layers/', false, /.js$/);
3
+ const lineReq = require.context('./layers/line/', false, /.js$/);
3
4
 
4
5
  const modules = {};
5
6
 
@@ -8,4 +9,9 @@ req.keys().forEach(key => {
8
9
  modules[mname] = req(key);
9
10
  });
10
11
 
12
+ lineReq.keys().forEach(key => {
13
+ const mname = key.replace('./', '').replace('.js', '');
14
+ modules[mname] = lineReq(key);
15
+ });
16
+
11
17
  module.exports = modules;
@@ -0,0 +1,13 @@
1
+ const InternalLineShapeLayer = require('./base/InternalLineShapeLayer');
2
+
3
+ class CutLineShapeLayer extends InternalLineShapeLayer {
4
+ drawLine() {
5
+ this.drawLineFromControlPoints();
6
+ }
7
+
8
+ drawLineCap() {
9
+ this.drawArrowLineCap();
10
+ }
11
+ }
12
+
13
+ module.exports = CutLineShapeLayer;
@@ -0,0 +1,13 @@
1
+ const InternalLineShapeLayer = require('./base/InternalLineShapeLayer');
2
+
3
+ class CutLineShapeLayer extends InternalLineShapeLayer {
4
+ drawLine() {
5
+ this.drawLineFromControlPoints();
6
+ }
7
+
8
+ drawLineCap() {
9
+ this.drawArrowLineCap();
10
+ }
11
+ }
12
+
13
+ module.exports = CutLineShapeLayer;
@@ -0,0 +1,13 @@
1
+ const InternalLineShapeLayer = require('./base/InternalLineShapeLayer');
2
+
3
+ class ScreenLineShapeLayer extends InternalLineShapeLayer {
4
+ drawLine() {
5
+ this.drawLineFromControlPoints();
6
+ }
7
+
8
+ drawLineCap() {
9
+ this.drawPerpendicularLineCap();
10
+ }
11
+ }
12
+
13
+ module.exports = ScreenLineShapeLayer;
@@ -0,0 +1,34 @@
1
+ const InternalShapeLayer = require('../../../base/InternalShapeLayer');
2
+ const LineDrawOperationsTrait = require('../../../../../traits/LineDrawOperationsTrait');
3
+
4
+ class InternalLineShapeLayer extends InternalShapeLayer {
5
+ constructor(parentLayer, shape) {
6
+ super(parentLayer);
7
+ this.shape = shape;
8
+ }
9
+
10
+ setLineWidth() {
11
+ this.ctx.lineWidth = this.courtTypeConstants.LINE_WIDTH;
12
+ }
13
+
14
+ getProcessedLinePaths() {
15
+ return [this.shape.linePart];
16
+ }
17
+
18
+ drawShapeLogic() {
19
+ this.drawLine();
20
+ this.drawLineCap();
21
+ }
22
+
23
+ drawLine() {
24
+ // Override this method in a subclass
25
+ }
26
+
27
+ drawLineCap() {
28
+ // Override this method in a subclass
29
+ }
30
+ }
31
+
32
+ Object.assign(InternalLineShapeLayer.prototype, LineDrawOperationsTrait);
33
+
34
+ module.exports = InternalLineShapeLayer;
@@ -458,3 +458,99 @@ function controlPointsMatFromPoints(controlPoints) {
458
458
  });
459
459
  return math.matrix(controlPointsVec);
460
460
  }
461
+
462
+ module.exports.bezierBoundingBox = function(controlPoints) {
463
+ if (controlPoints.length !== 4) {
464
+ throw new Error('bezierBoundingBox function supports only cubic bezier curves for now');
465
+ }
466
+ const bMinMax = cubicBezierMinMax(controlPoints);
467
+ return [
468
+ {
469
+ x: bMinMax.min.x,
470
+ y: bMinMax.min.y
471
+ },
472
+ {
473
+ x: bMinMax.max.x,
474
+ y: bMinMax.min.y
475
+ },
476
+ {
477
+ x: bMinMax.max.x,
478
+ y: bMinMax.max.y
479
+ },
480
+ {
481
+ x: bMinMax.min.x,
482
+ y: bMinMax.max.y
483
+ }
484
+ ];
485
+ };
486
+
487
+ function cubicBezierMinMax([p0, p1, p2, p3]) {
488
+ const tValues = [];
489
+ const xValues = [];
490
+ const yValues = [];
491
+ let a;
492
+ let b;
493
+ let c;
494
+ let t;
495
+ let t1;
496
+ let t2;
497
+ let b2ac;
498
+ let sqrtb2ac;
499
+ for (let i = 0; i < 2; ++i) {
500
+ if (i === 0) {
501
+ b = 6 * p0.x - 12 * p1.x + 6 * p2.x;
502
+ a = -3 * p0.x + 9 * p1.x - 9 * p2.x + 3 * p3.x;
503
+ c = 3 * p1.x - 3 * p0.x;
504
+ } else {
505
+ b = 6 * p0.y - 12 * p1.y + 6 * p2.y;
506
+ a = -3 * p0.y + 9 * p1.y - 9 * p2.y + 3 * p3.y;
507
+ c = 3 * p1.y - 3 * p0.y;
508
+ }
509
+ if (Math.abs(a) < 1e-12) {
510
+ if (Math.abs(b) < 1e-12) {
511
+ continue;
512
+ }
513
+ t = -c / b;
514
+ if (t > 0 && t < 1) {
515
+ tValues.push(t);
516
+ }
517
+ continue;
518
+ }
519
+ b2ac = b * b - 4 * c * a;
520
+ if (b2ac < 0) {
521
+ if (Math.abs(b2ac) < 1e-12) {
522
+ t = -b / (2 * a);
523
+ if (t > 0 && t < 1) {
524
+ tValues.push(t);
525
+ }
526
+ }
527
+ continue;
528
+ }
529
+ sqrtb2ac = Math.sqrt(b2ac);
530
+ t1 = (-b + sqrtb2ac) / (2 * a);
531
+ if (t1 > 0 && t1 < 1) {
532
+ tValues.push(t1);
533
+ }
534
+ t2 = (-b - sqrtb2ac) / (2 * a);
535
+ if (t2 > 0 && t2 < 1) {
536
+ tValues.push(t2);
537
+ }
538
+ }
539
+
540
+ let j = tValues.length;
541
+ let mt;
542
+ while (j--) {
543
+ t = tValues[j];
544
+ mt = 1 - t;
545
+ xValues[j] = mt * mt * mt * p0.x + 3 * mt * mt * t * p1.x + 3 * mt * t * t * p2.x + t * t * t * p3.x;
546
+ yValues[j] = mt * mt * mt * p0.y + 3 * mt * mt * t * p1.y + 3 * mt * t * t * p2.y + t * t * t * p3.y;
547
+ }
548
+
549
+ xValues.push(p0.x, p3.x);
550
+ yValues.push(p0.y, p3.y);
551
+
552
+ return {
553
+ min: { x: Math.min.apply(0, xValues), y: Math.min.apply(0, yValues) },
554
+ max: { x: Math.max.apply(0, xValues), y: Math.max.apply(0, yValues) }
555
+ };
556
+ }
@@ -44,6 +44,10 @@ class Shape extends Model {
44
44
  return 10;
45
45
  }
46
46
 
47
+ get linePart() {
48
+ return this._getAttr('linePart');
49
+ }
50
+
47
51
  get rectWrapPointsPure() {
48
52
  const topLeft = { x: -this.outerCircleRadius / 2, y: -this.outerCircleRadius / 2 };
49
53
  const topRight = { x: this.outerCircleRadius / 2, y: -this.outerCircleRadius / 2 };
@@ -60,6 +64,10 @@ class Shape extends Model {
60
64
  });
61
65
  }
62
66
 
67
+ get controlPointsTranslated() {
68
+ return this.rectWrapPointsTranslated;
69
+ }
70
+
63
71
  get rectWrapAreaTranslated() {
64
72
  const [topLeft, topRight, botRight] = this.rectWrapPointsTranslated;
65
73
  const aDistance = distanceBetweenPoints(topLeft, topRight);
@@ -148,6 +156,11 @@ class Shape extends Model {
148
156
 
149
157
  return result.map(p => rotatePoint(p.x, p.y, this.location.x, this.location.y, this.angle));
150
158
  }
159
+
160
+ revertPointTranslation(point) {
161
+ const translated = { x: point.x - this.location.x, y: point.y - this.location.y };
162
+ return rotatePoint(translated.x, translated.y, 0, 0, -this.angle);
163
+ }
151
164
  }
152
165
 
153
166
  module.exports = Shape;
@@ -0,0 +1,20 @@
1
+ const Shape = require('../Shape');
2
+ const { rotatePoint } = require('../../helpers/common');
3
+ const { bezierBoundingBox } = require('../../math/LineDrawingMath');
4
+
5
+ class LineShape extends Shape {
6
+ get rectWrapPointsPure() {
7
+ const { controlPoints } = this.linePart;
8
+ return bezierBoundingBox(controlPoints);
9
+ }
10
+
11
+ get controlPointsTranslated() {
12
+ return this.linePart.controlPoints.map(point => {
13
+ const scaled = { x: point.x * this.scale.x, y: point.y * this.scale.y };
14
+ const rotated = rotatePoint(scaled.x, scaled.y, 0, 0, this.angle);
15
+ return { x: rotated.x + this.location.x, y: rotated.y + this.location.y };
16
+ });
17
+ }
18
+ }
19
+
20
+ module.exports = LineShape;
@@ -1,5 +1,6 @@
1
1
  // require all modules on the path and with the pattern defined
2
2
  const req = require.context('./', true, /.js$/);
3
+ const lineReq = require.context('./line', true, /.js$/);
3
4
 
4
5
  const modules = {};
5
6
 
@@ -9,4 +10,9 @@ req.keys().forEach(key => {
9
10
  modules[mname.toUpperCase()] = req(key);
10
11
  });
11
12
 
13
+ lineReq.keys().forEach(key => {
14
+ const mname = key.replace('./', '').replace('LineShape.js', '');
15
+ modules[`LINE.${mname.toUpperCase()}`] = lineReq(key);
16
+ });
17
+
12
18
  module.exports = modules;
@@ -0,0 +1,5 @@
1
+ const LineShape = require('../LineShape');
2
+
3
+ class CutLineShape extends LineShape {}
4
+
5
+ module.exports = CutLineShape;
@@ -0,0 +1,5 @@
1
+ const LineShape = require('../LineShape');
2
+
3
+ class DribbleLineShape extends LineShape {}
4
+
5
+ module.exports = DribbleLineShape;
@@ -0,0 +1,5 @@
1
+ const LineShape = require('../LineShape');
2
+
3
+ class ScreenLineShape extends LineShape {}
4
+
5
+ module.exports = ScreenLineShape;
@@ -0,0 +1,122 @@
1
+ module.exports = {
2
+ drawLineFromControlPoints() {
3
+ this.ctx.save();
4
+
5
+ this.setLineOptions();
6
+
7
+ this.ctx.lineJoin = 'round';
8
+
9
+ this.getProcessedLinePaths().forEach(linePart => {
10
+ this.ctx.save();
11
+
12
+ if (linePart.alpha) {
13
+ this.setColor(linePart.alpha); // setting color for each line path (animation)
14
+ }
15
+
16
+ this.ctx.beginPath();
17
+
18
+ const cp = linePart.controlPoints;
19
+ this.ctx.moveTo(cp[0].x, cp[0].y);
20
+
21
+ if (cp.length === 2) {
22
+ if (this.line.type === 'DRIBBLE') this.ctx.lineCap = 'round'; // fix last straight line cap segment
23
+ this.ctx.lineTo(cp[1].x, cp[1].y);
24
+ }
25
+ if (cp.length === 3) {
26
+ this.ctx.quadraticCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y);
27
+ }
28
+ if (cp.length === 4) {
29
+ this.ctx.bezierCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y, cp[3].x, cp[3].y);
30
+ }
31
+
32
+ this.ctx.stroke();
33
+
34
+ this.ctx.restore();
35
+ });
36
+
37
+ this.ctx.restore();
38
+ },
39
+
40
+ drawArrowLineCap() {
41
+ this.ctx.save();
42
+
43
+ const arrowTipLength = 1.4;
44
+ const angle = this.angleBetweenLastTwoPoints();
45
+ const courtArrowTipPoint = this.arrowTipPoint();
46
+
47
+ const courtLineEndPoint = { x: courtArrowTipPoint.x - arrowTipLength / 1.3, y: courtArrowTipPoint.y };
48
+ const courtArrowEdgePoint1 = {
49
+ x: courtArrowTipPoint.x - arrowTipLength - arrowTipLength / 6.0,
50
+ y: courtArrowTipPoint.y - arrowTipLength / 2.0
51
+ };
52
+ const courtArrowEdgePoint2 = {
53
+ x: courtArrowTipPoint.x - arrowTipLength - arrowTipLength / 6.0,
54
+ y: courtArrowTipPoint.y + arrowTipLength / 2.0
55
+ };
56
+
57
+ this.ctx.beginPath();
58
+
59
+ this.ctx.translate(courtArrowTipPoint.x, courtArrowTipPoint.y);
60
+ this.ctx.rotate(angle);
61
+ this.ctx.translate(-courtArrowTipPoint.x, -courtArrowTipPoint.y);
62
+
63
+ this.ctx.moveTo(courtLineEndPoint.x, courtLineEndPoint.y);
64
+ this.ctx.lineTo(courtArrowEdgePoint1.x, courtArrowEdgePoint1.y);
65
+ this.ctx.lineTo(courtArrowTipPoint.x, courtArrowTipPoint.y);
66
+ this.ctx.lineTo(courtArrowEdgePoint2.x, courtArrowEdgePoint2.y);
67
+ this.ctx.lineTo(courtLineEndPoint.x, courtLineEndPoint.y);
68
+
69
+ this.ctx.fill();
70
+ this.ctx.stroke();
71
+
72
+ this.ctx.restore();
73
+ },
74
+
75
+ drawPerpendicularLineCap() {
76
+ this.drawPerpendicularLineAtCourtPoint(this.arrowTipPoint(), this.angleBetweenLastTwoPoints());
77
+ },
78
+
79
+ drawPerpendicularLineAtCourtPoint(courtPoint, angle) {
80
+ this.ctx.save();
81
+
82
+ const lineCapLength = 3.0;
83
+ const lineCapStartPoint = { x: courtPoint.x, y: courtPoint.y - lineCapLength / 2.0 };
84
+ const lineCapEndPoint = { x: courtPoint.x, y: courtPoint.y + lineCapLength / 2.0 };
85
+
86
+ this.ctx.beginPath();
87
+
88
+ this.ctx.translate(courtPoint.x, courtPoint.y);
89
+ this.ctx.rotate(angle);
90
+ this.ctx.translate(-courtPoint.x, -courtPoint.y);
91
+
92
+ this.ctx.moveTo(lineCapStartPoint.x, lineCapStartPoint.y);
93
+ this.ctx.lineTo(lineCapEndPoint.x, lineCapEndPoint.y);
94
+
95
+ this.ctx.fill();
96
+ this.ctx.stroke();
97
+
98
+ this.ctx.restore();
99
+ },
100
+
101
+ angleBetweenLastTwoPoints() {
102
+ const lastLinePart = [...this.getProcessedLinePaths()].pop();
103
+ const cp = lastLinePart.controlPoints;
104
+
105
+ const cpTo = cp[cp.length - 1];
106
+ const cpFrom = cp[cp.length - 2];
107
+
108
+ const dx = cpTo.x - cpFrom.x;
109
+ const dy = cpTo.y - cpFrom.y;
110
+ return Math.atan2(dy, dx);
111
+ },
112
+
113
+ arrowTipPoint() {
114
+ const lastLinePart = [...this.getProcessedLinePaths()].pop();
115
+ const cp = lastLinePart.controlPoints;
116
+ return cp[cp.length - 1];
117
+ },
118
+
119
+ setLineOptions() {
120
+ // Override this method in a subclass
121
+ }
122
+ };