@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/dist/play-rendering.js +3 -3
- package/dist/play-rendering.js.map +1 -1
- package/package.json +1 -1
- package/src/layers/ShapeControlPointLayer.js +2 -2
- package/src/layers/ShapeLayer.js +4 -1
- package/src/layers/line/base/InternalLineLayer.js +28 -146
- package/src/layers/shape/base/InternalShapeLayer.js +5 -1
- package/src/layers/shape/index.js +7 -1
- package/src/layers/shape/layers/line/CutLineShapeLayer.js +13 -0
- package/src/layers/shape/layers/line/DribbleLineShapeLayer.js +13 -0
- package/src/layers/shape/layers/line/ScreenLineShapeLayer.js +13 -0
- package/src/layers/shape/layers/line/base/InternalLineShapeLayer.js +34 -0
- package/src/math/LineDrawingMath.js +96 -0
- package/src/models/Shape.js +13 -0
- package/src/models/ShapeModels/LineShape.js +20 -0
- package/src/models/ShapeModels/index.js +6 -0
- package/src/models/ShapeModels/line/CutLineShape.js +5 -0
- package/src/models/ShapeModels/line/DribbleLineShape.js +5 -0
- package/src/models/ShapeModels/line/ScreenLineShape.js +5 -0
- package/src/traits/LineDrawOperationsTrait.js +122 -0
package/package.json
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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();
|
package/src/layers/ShapeLayer.js
CHANGED
|
@@ -7,7 +7,10 @@ class ShapeLayer extends BaseLayer {
|
|
|
7
7
|
this.ctx.save();
|
|
8
8
|
|
|
9
9
|
this.playData.shapes.forEach(shape => {
|
|
10
|
-
const
|
|
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/',
|
|
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
|
+
}
|
package/src/models/Shape.js
CHANGED
|
@@ -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,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
|
+
};
|