@luceosports/play-rendering 2.0.2 → 2.0.3

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.
Files changed (37) hide show
  1. package/dist/play-rendering.js +3 -3
  2. package/dist/play-rendering.js.map +1 -1
  3. package/dist/types/models/PlayModel.d.ts +1 -1
  4. package/dist/types/models/ShapeModels/index.d.ts +2 -2
  5. package/package.json +1 -1
  6. package/src/helpers/common.ts +1 -1
  7. package/src/layers/CourtLayer.ts +9 -8
  8. package/src/layers/LineLayer.ts +2 -2
  9. package/src/layers/NoteLayer.ts +5 -3
  10. package/src/layers/ShapeLayer.ts +2 -2
  11. package/src/layers/court/layers/BASEBALL/courtTypes/BASEBALL_HIGH_SCHOOL/layers/ADirtLayer.ts +2 -1
  12. package/src/layers/court/layers/BASEBALL/courtTypes/BASEBALL_HIGH_SCHOOL/layers/BaseLineLayer.ts +1 -1
  13. package/src/layers/court/layers/BASKETBALL/common/LaneMarkingNBATrait.ts +13 -1
  14. package/src/layers/court/layers/BASKETBALL/common/LaneMarkingNCAATrait.ts +13 -1
  15. package/src/layers/court/layers/BASKETBALL/courtTypes/BIG3/layers/Big3Layer.ts +1 -1
  16. package/src/layers/court/layers/FOOTBALL/layers/FieldNumberLayer.ts +1 -1
  17. package/src/layers/court/layers/LACROSSE/courtTypes/LACROSSE_US_W/layers/FanLineLayer.ts +2 -1
  18. package/src/layers/line/base/InternalLineLayer.ts +5 -5
  19. package/src/layers/line/layers/DribbleLineLayer.ts +4 -4
  20. package/src/layers/shape/base/InternalShapeLayer.ts +1 -1
  21. package/src/layers/shape/layers/line/DribbleLineShapeLayer.ts +1 -1
  22. package/src/layers/shape/layers/line/base/InternalLineShapeLayer.ts +1 -1
  23. package/src/math/Bezier.ts +7 -5
  24. package/src/math/LineDrawingMath.ts +15 -15
  25. package/src/models/AnimationModel.ts +11 -11
  26. package/src/models/Base/InternalFrameModel.ts +2 -2
  27. package/src/models/FrameModel.ts +18 -15
  28. package/src/models/LineModel.ts +2 -2
  29. package/src/models/NoteModel.ts +7 -7
  30. package/src/models/PlayModel.ts +3 -3
  31. package/src/models/PlayerModel.ts +1 -1
  32. package/src/models/ShapeModel.ts +3 -6
  33. package/src/models/ShapeModels/CircleShape.ts +4 -0
  34. package/src/models/ShapeModels/LineShape.ts +7 -2
  35. package/src/traits/DribbleLineTrait.ts +17 -2
  36. package/src/traits/LineDrawOperationsTrait.ts +31 -7
  37. package/tsconfig.json +2 -1
@@ -9,7 +9,7 @@ import ShapeLayer from '../layers/ShapeLayer';
9
9
  import NoteLayer from '../layers/NoteLayer';
10
10
  import LineControlPointLayer from '../layers/LineControlPointLayer';
11
11
  import ShapeControlPointLayer from '../layers/ShapeControlPointLayer';
12
- import LineModel from './LineModel';
12
+ import LineModel, { LinePartAdjusted } from './LineModel';
13
13
  import PlayerModel from './PlayerModel';
14
14
  import NoteModel from './NoteModel';
15
15
  import * as ShapeModels from './ShapeModels';
@@ -22,7 +22,7 @@ import {
22
22
  } from '../constants';
23
23
 
24
24
  import PlayModel, { PlayModelOptions, PlayStaticData } from './PlayModel';
25
- import { Court, CourtPoint, SportType } from '../types';
25
+ import { Court, CourtPoint, ShapeType, SportType } from '../types';
26
26
  import ShapeModel from './ShapeModel';
27
27
 
28
28
  export type FrameData = {
@@ -56,11 +56,12 @@ type InferEntityFromObjectType<T> = T extends 'player'
56
56
  : LineModel;
57
57
 
58
58
  export default class FrameModel {
59
- private ctx: CanvasRenderingContext2D;
59
+ private ctx: CanvasRenderingContext2D | null;
60
60
  private animationGlobalProgressPrev: number;
61
61
 
62
62
  constructor(private play: PlayModel, private phase = 1, private animationGlobalProgress = 0) {
63
63
  this.animationGlobalProgressPrev = animationGlobalProgress;
64
+ this.ctx = null;
64
65
  }
65
66
 
66
67
  get sport(): SportType {
@@ -109,9 +110,9 @@ export default class FrameModel {
109
110
  }
110
111
 
111
112
  get playAnimationDuration() {
112
- return this.play.playData.lines.reduce((result, l) => {
113
+ return this.play.playData.lines.reduce<number>((result, l) => {
113
114
  const line = new LineModel(l);
114
- const lineLastAnimEndTime = line.lastAnimEndTime;
115
+ const lineLastAnimEndTime = line.lastAnimEndTime!;
115
116
  return result > lineLastAnimEndTime ? result : lineLastAnimEndTime;
116
117
  }, 0);
117
118
  }
@@ -128,10 +129,10 @@ export default class FrameModel {
128
129
  if (!this.play.playData.shapes) return [];
129
130
  return this.play.playData.shapes
130
131
  .filter(s => {
131
- return ShapeModels[transformShapeTypeToImportKey(s.type)] !== undefined;
132
+ return ShapeModels[transformShapeTypeToImportKey(s.type) as keyof typeof ShapeModels] !== undefined;
132
133
  })
133
134
  .map(s => {
134
- return new ShapeModels[transformShapeTypeToImportKey(s.type)](s);
135
+ return new ShapeModels[transformShapeTypeToImportKey(s.type) as keyof typeof ShapeModels](s);
135
136
  });
136
137
  }
137
138
 
@@ -203,7 +204,7 @@ export default class FrameModel {
203
204
  if (!player.animations.length) return player;
204
205
 
205
206
  player.animations.forEach(animation => {
206
- const keyTimesChunks = [];
207
+ const keyTimesChunks: [number, number][] = [];
207
208
  animation.keyTimes.forEach((value, index) => {
208
209
  if (index) keyTimesChunks.push([animation.keyTimes[index - 1], value]);
209
210
  });
@@ -222,8 +223,8 @@ export default class FrameModel {
222
223
  });
223
224
  });
224
225
 
225
- if (this.animationGlobalProgress >= player.lastAnimEndTime) {
226
- player.location = player.lastAnimationLastLinePartControlPoint;
226
+ if (this.animationGlobalProgress >= player.lastAnimEndTime!) {
227
+ player.location = player.lastAnimationLastLinePartControlPoint!;
227
228
  }
228
229
 
229
230
  return player;
@@ -237,10 +238,10 @@ export default class FrameModel {
237
238
  const line = new LineModel(l);
238
239
 
239
240
  line.setAnimationKeyTimesChunked(
240
- this.play.playData.players.find(p => p.position === line.playerPositionOrigin)
241
+ this.play.playData.players.find(p => p.position === line.playerPositionOrigin)!
241
242
  );
242
243
 
243
- const linePartsAdjusted = [];
244
+ const linePartsAdjusted: LinePartAdjusted[] = [];
244
245
  line.animationKeyTimeChunks.forEach(([start, end], index) => {
245
246
  if (this.animationGlobalProgressPrev < end && this.animationGlobalProgress >= end) {
246
247
  window.dispatchEvent(
@@ -254,7 +255,7 @@ export default class FrameModel {
254
255
  const linePathSplitted = splitBezierCurveAtTVal(
255
256
  line.getLineParts()[index].controlPoints,
256
257
  this.animationProgress(start, end)
257
- );
258
+ ) as LinePartAdjusted['controlPoints'][];
258
259
  linePartsAdjusted.push({ controlPoints: linePathSplitted[0], alpha: 0.1 });
259
260
  linePartsAdjusted.push({ controlPoints: linePathSplitted[1], alpha: line.color.alpha });
260
261
  } else if (this.animationGlobalProgress > end) {
@@ -265,7 +266,7 @@ export default class FrameModel {
265
266
  });
266
267
  line.setLinePartsAdjusted(linePartsAdjusted);
267
268
 
268
- if (this.animationGlobalProgress > line.lastAnimEndTime) {
269
+ if (this.animationGlobalProgress > line.lastAnimEndTime!) {
269
270
  line.color = { ...line.color, alpha: 0.1 }; // To adjust line cap opacity
270
271
  }
271
272
 
@@ -292,7 +293,7 @@ export default class FrameModel {
292
293
  }
293
294
 
294
295
  get prevAnimationLines() {
295
- return this.playDataLines.filter(l => this.animationGlobalProgress >= l.lastAnimEndTime);
296
+ return this.playDataLines.filter(l => this.animationGlobalProgress >= l.lastAnimEndTime!);
296
297
  }
297
298
 
298
299
  draw() {
@@ -319,6 +320,8 @@ export default class FrameModel {
319
320
  }
320
321
 
321
322
  _init() {
323
+ if (!this.ctx) throw new Error('Canvas context is not provided. Please use setContext() method.');
324
+
322
325
  this.ctx.canvas.width = this.frameWidth;
323
326
  this.ctx.canvas.height = this.frameHeight;
324
327
 
@@ -48,7 +48,7 @@ export default class LineModel extends Model<LineData, LineAdjusted> {
48
48
  }
49
49
 
50
50
  setAnimationKeyTimesChunked(linePlayer: Player) {
51
- let keyTimesChunks = [];
51
+ let keyTimesChunks: [number, number][] = [];
52
52
  this.animations[0].keyTimes.forEach((value, index) => {
53
53
  if (index) keyTimesChunks.push([this.animations[0].keyTimes[index - 1], value]);
54
54
  });
@@ -142,6 +142,6 @@ export default class LineModel extends Model<LineData, LineAdjusted> {
142
142
  }
143
143
 
144
144
  get lastLinePartControlPoint() {
145
- return [...[...this.lineParts].pop().controlPoints].pop();
145
+ return [...[...this.lineParts].pop()!.controlPoints].pop()!;
146
146
  }
147
147
  }
@@ -1,5 +1,5 @@
1
1
  import Model from './Base/InternalFrameModel';
2
- import { Note as NoteData } from '../types';
2
+ import { CourtPoint, Note as NoteData } from '../types';
3
3
 
4
4
  const NOTE_GREY_COLOR = { red: 0.502, green: 0.502, blue: 0.502, alpha: 0.35 };
5
5
 
@@ -84,9 +84,9 @@ export default class NoteModel extends Model<NoteData, NoteData> {
84
84
  return 'top';
85
85
  }
86
86
 
87
- get internalCtx() {
87
+ get internalCtx(): CanvasRenderingContext2D {
88
88
  // used only for measureText feature to get a text width
89
- return this.internalCanvas.getContext('2d');
89
+ return this.internalCanvas.getContext('2d')!;
90
90
  }
91
91
 
92
92
  preProcessedTextForContext() {
@@ -108,13 +108,13 @@ export default class NoteModel extends Model<NoteData, NoteData> {
108
108
  const fitWidth = NoteModel.NOTE_WRAP_MAX_WIDTH - NoteModel.NOTE_WRAP_PADDING * 2;
109
109
 
110
110
  const lines = [];
111
- const words = this.text.split(' ').reduce((result, word) => {
111
+ const words = this.text.split(' ').reduce<string[]>((result, word) => {
112
112
  if (!word.match(/\n/)) {
113
113
  result.push(word);
114
114
  return result;
115
115
  }
116
116
 
117
- const nlSegments = [];
117
+ const nlSegments: string[] = [];
118
118
  let curSeg = '';
119
119
  for (let i = 0; i < word.length; i++) {
120
120
  const char = word[i];
@@ -164,7 +164,7 @@ export default class NoteModel extends Model<NoteData, NoteData> {
164
164
  _getLineInfo(inputWords: string[]) {
165
165
  let lineWidth = 0;
166
166
  let hasLineBreak = false;
167
- const wordsResult = [];
167
+ const wordsResult: { text: string; width: number }[] = [];
168
168
  inputWords.forEach((w, i) => {
169
169
  if (w === '\n') {
170
170
  hasLineBreak = true;
@@ -186,7 +186,7 @@ export default class NoteModel extends Model<NoteData, NoteData> {
186
186
  return { words: wordsResult, width: lineWidth, hasLineBreak };
187
187
  }
188
188
 
189
- noteWrapperContains({ x, y }) {
189
+ noteWrapperContains({ x, y }: CourtPoint) {
190
190
  const { box } = this.preProcessedTextForContext();
191
191
 
192
192
  const topLeft = this.location;
@@ -58,7 +58,7 @@ export type PlayModelOptions = {
58
58
  linesSelectedIds: string[];
59
59
  shapeSelectedId: string | null;
60
60
  noteSelectedId: string | null;
61
- playersHiddenPositions?: string[];
61
+ playersHiddenPositions: string[];
62
62
  background: string;
63
63
  watermark: string | null;
64
64
  mirror: boolean;
@@ -83,13 +83,13 @@ export default class PlayModel {
83
83
  public static playerHeadshots: PlayerHeadshotItem[] = [];
84
84
  public static playerHats: readonly ImageConfigItem[];
85
85
  public static shapes: readonly ImageConfigItem[];
86
- public static watermark: { LuceoSports: HTMLImageElement; TeamLogo: HTMLImageElement };
86
+ public static watermark: { LuceoSports: HTMLImageElement; TeamLogo: HTMLImageElement | null };
87
87
  public static backgroundOptions: Record<SportType, HTMLImageElement> & { Hardwood: HTMLImageElement };
88
88
 
89
89
  constructor(data: PlayConstructData, options?: Partial<PlayModelOptions>) {
90
90
  this.name = data.name;
91
91
  this.options = useDefaults(options);
92
- this.setPlayData(data.playData);
92
+ this.playData = data.playData;
93
93
  }
94
94
 
95
95
  static async init({ teamLogoPath = '' } = {}) {
@@ -64,7 +64,7 @@ export default class PlayerModel extends Model<PlayerData, PlayerData> {
64
64
  }
65
65
 
66
66
  get lastAnimationLastLinePartControlPoint() {
67
- return [...[...this.lastAnimation.lineParts].pop().controlPoints].pop();
67
+ return [...[...this.lastAnimation!.lineParts].pop()!.controlPoints].pop();
68
68
  }
69
69
 
70
70
  setPossession(prevPassLines: Line[]) {
@@ -8,7 +8,7 @@ const WRAP_POINT_TOP_RIGHT_INDEX = 1;
8
8
  const WRAP_POINT_BOT_RIGHT_INDEX = 2;
9
9
  const WRAP_POINT_BOT_LEFT_INDEX = 3;
10
10
 
11
- export default class ShapeModel extends Model<ShapeData, ShapeData> {
11
+ export default abstract class ShapeModel extends Model<ShapeData, ShapeData> {
12
12
  get id() {
13
13
  return this._getAttr('id');
14
14
  }
@@ -76,10 +76,7 @@ export default class ShapeModel extends Model<ShapeData, ShapeData> {
76
76
  return +(aDistance * bDistance).toFixed(2);
77
77
  }
78
78
 
79
- get shapeControlPoints() {
80
- // Override this function in a subclass if needed
81
- return [];
82
- }
79
+ abstract get shapeControlPoints(): CourtPoint[];
83
80
 
84
81
  rectWrapperContains(point: CourtPoint) {
85
82
  // https://stackoverflow.com/questions/17136084/checking-if-a-point-is-inside-a-rotated-rectangle
@@ -102,7 +99,7 @@ export default class ShapeModel extends Model<ShapeData, ShapeData> {
102
99
  return trianglesAreaSum <= this.rectWrapAreaTranslated;
103
100
  }
104
101
 
105
- computeCenterForRect([topLeft, topRight, botRight, botLeft]) {
102
+ computeCenterForRect([topLeft, topRight, botRight, botLeft]: [CourtPoint, CourtPoint, CourtPoint, CourtPoint]) {
106
103
  return {
107
104
  x: (topLeft.x + botRight.x) / 2,
108
105
  y: (topRight.y + botLeft.y) / 2
@@ -12,4 +12,8 @@ export default class CircleShape extends ShapeModel {
12
12
  const botLeft = { x: -this.outerCircleRadius, y: this.outerCircleRadius };
13
13
  return [topLeft, topRight, botRight, botLeft];
14
14
  }
15
+
16
+ get shapeControlPoints() {
17
+ return [];
18
+ }
15
19
  }
@@ -1,18 +1,23 @@
1
1
  import ShapeModel from '../ShapeModel';
2
2
  import { rotatePoint } from '../../helpers/common';
3
3
  import { bezierBoundingBox } from '../../math/LineDrawingMath';
4
+ import { CourtPoint } from '../../types';
4
5
 
5
6
  export default class LineShape extends ShapeModel {
6
7
  get rectWrapPointsPure() {
7
- const { controlPoints } = this.linePart;
8
+ const { controlPoints } = this.linePart!;
8
9
  return bezierBoundingBox(controlPoints);
9
10
  }
10
11
 
11
12
  get controlPointsTranslated() {
12
- return this.linePart.controlPoints.map(point => {
13
+ return this.linePart!.controlPoints.map(point => {
13
14
  const scaled = { x: point.x * this.scale.x, y: point.y * this.scale.y };
14
15
  const rotated = rotatePoint(scaled.x, scaled.y, 0, 0, this.angle);
15
16
  return { x: rotated.x + this.location.x, y: rotated.y + this.location.y };
16
17
  });
17
18
  }
19
+
20
+ get shapeControlPoints() {
21
+ return [];
22
+ }
18
23
  }
@@ -1,7 +1,22 @@
1
1
  import Bezier from '../math/Bezier';
2
- import { LinePart } from '../types';
2
+ import { CourtPoint, LinePart } from '../types';
3
3
  import { CourtPointAdjusted, LinePartAdjusted } from '../models/LineModel';
4
4
 
5
+ interface InheritedPropsAndMethods {}
6
+
7
+ interface DribbleLineTrait extends InheritedPropsAndMethods {
8
+ convertLinePartsToDribble: (lineParts: LinePart[]) => LinePartAdjusted[];
9
+ drawArrowLineCap: () => void;
10
+ drawPerpendicularLineCap: () => void;
11
+ drawPerpendicularLineAtCourtPoint: (point: CourtPoint, angle: number) => void;
12
+ handOffLineCap: (baseLineParts: LinePart[]) => void;
13
+ calcMidPoint: (point1: CourtPoint, point2: CourtPoint) => CourtPoint;
14
+ angleBetweenLastTwoPoints: () => number;
15
+ angleBetweenTwoPoints: (cpFrom: CourtPoint, cpTo: CourtPoint) => number;
16
+ arrowTipPoint: () => CourtPoint;
17
+ setLineOptions: () => void;
18
+ }
19
+
5
20
  export default {
6
21
  convertLinePartsToDribble(lineParts: LinePart[]): LinePartAdjusted[] {
7
22
  const processedLineParts: LinePartAdjusted[] = [];
@@ -56,4 +71,4 @@ export default {
56
71
 
57
72
  return processedLineParts;
58
73
  }
59
- };
74
+ } as DribbleLineTrait;
@@ -1,5 +1,27 @@
1
1
  import { CourtPoint, LinePart } from '../types';
2
- import { LinePartAdjusted } from '../models/LineModel';
2
+ import LineModel, { LinePartAdjusted } from '../models/LineModel';
3
+ import ShapeModel from '../models/ShapeModel';
4
+
5
+ interface InheritedPropsAndMethods {
6
+ ctx: CanvasRenderingContext2D;
7
+ getProcessedLinePaths: () => LinePartAdjusted[];
8
+ setColor: (alpha?: number) => void;
9
+ line?: LineModel;
10
+ shape?: ShapeModel;
11
+ }
12
+
13
+ interface LineDrawOperationsTrait extends InheritedPropsAndMethods {
14
+ drawLineFromControlPoints: () => void;
15
+ drawArrowLineCap: () => void;
16
+ drawPerpendicularLineCap: () => void;
17
+ drawPerpendicularLineAtCourtPoint: (point: CourtPoint, angle: number) => void;
18
+ handOffLineCap: (baseLineParts: LinePart[]) => void;
19
+ calcMidPoint: (point1: CourtPoint, point2: CourtPoint) => CourtPoint;
20
+ angleBetweenLastTwoPoints: () => number;
21
+ angleBetweenTwoPoints: (cpFrom: CourtPoint, cpTo: CourtPoint) => number;
22
+ arrowTipPoint: () => CourtPoint;
23
+ setLineOptions: () => void;
24
+ }
3
25
 
4
26
  export default {
5
27
  drawLineFromControlPoints() {
@@ -9,7 +31,7 @@ export default {
9
31
 
10
32
  this.ctx.lineJoin = 'round';
11
33
 
12
- (this.getProcessedLinePaths() as LinePartAdjusted[]).forEach(linePart => {
34
+ this.getProcessedLinePaths().forEach(linePart => {
13
35
  this.ctx.save();
14
36
 
15
37
  if (linePart.alpha) {
@@ -23,7 +45,7 @@ export default {
23
45
 
24
46
  if (cp.length === 2) {
25
47
  // TODO refactor next line to avoid access to line/shape model
26
- if ((this.line || this.shape).type === 'DRIBBLE') this.ctx.lineCap = 'round'; // fix last straight line cap segment
48
+ if ((this.line || this.shape || {}).type === 'DRIBBLE') this.ctx.lineCap = 'round'; // fix last straight line cap segment
27
49
  this.ctx.lineTo(cp[1].x, cp[1].y);
28
50
  }
29
51
  if (cp.length === 3) {
@@ -41,7 +63,9 @@ export default {
41
63
  this.ctx.restore();
42
64
  },
43
65
 
44
- drawArrowLineCap(arrowTipPoint: CourtPoint = this.arrowTipPoint()) {
66
+ drawArrowLineCap(point?: CourtPoint) {
67
+ const arrowTipPoint = point || this.arrowTipPoint();
68
+
45
69
  this.ctx.save();
46
70
 
47
71
  const arrowTipLength = 1.4;
@@ -135,7 +159,7 @@ export default {
135
159
 
136
160
  angleBetweenLastTwoPoints() {
137
161
  const lastLinePart = [...this.getProcessedLinePaths()].pop();
138
- const cp = lastLinePart.controlPoints;
162
+ const cp = lastLinePart!.controlPoints;
139
163
 
140
164
  const cpTo = cp[cp.length - 1];
141
165
  const cpFrom = cp[cp.length - 2];
@@ -150,7 +174,7 @@ export default {
150
174
  },
151
175
 
152
176
  arrowTipPoint() {
153
- const lastLinePart: LinePart = [...this.getProcessedLinePaths()].pop();
177
+ const lastLinePart = [...this.getProcessedLinePaths()].pop() as LinePartAdjusted;
154
178
  const cp = lastLinePart.controlPoints;
155
179
  return cp[cp.length - 1];
156
180
  },
@@ -158,4 +182,4 @@ export default {
158
182
  setLineOptions() {
159
183
  // Override this method in a subclass
160
184
  }
161
- };
185
+ } as LineDrawOperationsTrait;
package/tsconfig.json CHANGED
@@ -14,7 +14,8 @@
14
14
  ],
15
15
  "target": "es2015",
16
16
  "moduleResolution": "node",
17
- "strictNullChecks": false // TODO make everything strict
17
+ "strictNullChecks": true,
18
+ "strict": true,
18
19
  },
19
20
  "exclude": [
20
21
  "node_modules"