@luceosports/play-rendering 2.5.2 → 2.5.5
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/.eslintrc +7 -1
- package/.prettierrc +3 -1
- package/dist/play-rendering.js +1 -1
- package/dist/play-rendering.js.map +1 -1
- package/dist/types/types/index.d.ts +141 -140
- package/package.json +2 -2
- package/src/layers/PlayerLayer.ts +1 -1
- package/src/models/AnimationModel.ts +149 -152
- package/src/playerHatsConfig.ts +39 -39
- package/src/shapesConfig.ts +1 -1
- package/src/types/index.ts +5 -3
|
@@ -1,140 +1,141 @@
|
|
|
1
|
-
export type PlayerPosition =
|
|
2
|
-
export type DefenderPosition =
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
7
|
-
export type
|
|
8
|
-
export type
|
|
9
|
-
export type
|
|
10
|
-
export type
|
|
11
|
-
export type
|
|
12
|
-
export type
|
|
13
|
-
export type
|
|
14
|
-
export type
|
|
15
|
-
export type
|
|
16
|
-
export type
|
|
17
|
-
export type
|
|
18
|
-
export type
|
|
19
|
-
export type
|
|
20
|
-
export type
|
|
21
|
-
export type
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
export type PlayerPosition = `${number}`;
|
|
2
|
+
export type DefenderPosition = `x${number}`;
|
|
3
|
+
export type ExtraPosition = `+${number}`;
|
|
4
|
+
export type CoachPosition = 'C';
|
|
5
|
+
export type Position = PlayerPosition | DefenderPosition | ExtraPosition | CoachPosition;
|
|
6
|
+
export type LineType = 'PASS' | 'CUT' | 'SCREEN' | 'DRIBBLE' | 'HANDOFF' | 'SHOT';
|
|
7
|
+
export type LineShapeType = 'LINE.CUT' | 'LINE.SCREEN' | 'LINE.DRIBBLE' | 'LINE.PASS' | 'LINE.HANDOFF';
|
|
8
|
+
export type ShapeType = 'CIRCLE' | 'SQUARE' | 'TRIANGLE' | 'FOV' | 'XMARK' | 'STRAIGHT' | 'CONE' | LineShapeType;
|
|
9
|
+
export type SportType = 'FOOTBALL' | 'BASKETBALL' | 'VOLLEYBALL' | 'LACROSSE' | 'LACROSSE_BOX' | 'SOCCER' | 'HOCKEY' | 'BASEBALL' | 'SOFTBALL';
|
|
10
|
+
export type CourtTypeSportBasketball = 'BIG3' | 'NBA' | 'WNBA' | 'FIBA' | 'NCAAM' | 'NCAAW' | 'US_HIGH_SCHOOL' | 'US_JUNIOR_HIGH';
|
|
11
|
+
export type CourtTypeSportVolleyball = 'VOLLEYBALL_INDOOR';
|
|
12
|
+
export type CourtTypeSportSoccer = 'SOCCER_FIFA' | 'SOCCER_NCAA' | 'SOCCER_NFHS' | 'SOCCER_U10' | 'SOCCER_U12' | 'SOCCER_U19';
|
|
13
|
+
export type CourtTypeSportHockey = 'HOCKEY_NHL' | 'HOCKEY_INTERNATIONAL';
|
|
14
|
+
export type CourtTypeSportBaseball = 'BASEBALL_HIGH_SCHOOL';
|
|
15
|
+
export type CourtTypeSportSoftball = 'SOFTBALL_FP_COLLEGE' | 'SOFTBALL_FP_HS';
|
|
16
|
+
export type CourtTypeSportLacrosse = 'LACROSSE_US_M' | 'LACROSSE_US_W';
|
|
17
|
+
export type CourtTypeSportLacrosseBox = 'LACROSSE_BOX_US' | 'LACROSSE_BOX_CLA';
|
|
18
|
+
export type CourtTypeSportFootball = 'FOOTBALL_HIGH_SCHOOL' | 'FOOTBALL_COLLEGE' | 'FOOTBALL_NFL';
|
|
19
|
+
export type CourtTypeSportFootballLegacy = 'FOOTBALL';
|
|
20
|
+
export type CourtType = CourtTypeSportBasketball | CourtTypeSportVolleyball | CourtTypeSportLacrosse | CourtTypeSportLacrosseBox | CourtTypeSportSoccer | CourtTypeSportHockey | CourtTypeSportBaseball | CourtTypeSportSoftball | CourtTypeSportFootball | CourtTypeSportFootballLegacy;
|
|
21
|
+
export type ShapeControlPoints = [CourtPoint, CourtPoint] | [CourtPoint, CourtPoint, CourtPoint] | [CourtPoint, CourtPoint, CourtPoint, CourtPoint];
|
|
22
|
+
export type NoteDisplayModes = ['onCourt'] | ['playNote'] | ['onCourt', 'playNote'];
|
|
23
|
+
export interface SportConstants {
|
|
24
|
+
PLAYER_TOKEN_RADIUS: number;
|
|
25
|
+
PLAYER_TOKEN_SCALE: number;
|
|
26
|
+
}
|
|
27
|
+
export interface CourtTypeConstants {
|
|
28
|
+
COURT_RECT_WIDTH: number;
|
|
29
|
+
COURT_RECT_HEIGHT: number;
|
|
30
|
+
}
|
|
31
|
+
export interface CourtPoint {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CourtSize {
|
|
36
|
+
height: number;
|
|
37
|
+
width: number;
|
|
38
|
+
}
|
|
39
|
+
export interface Scale {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
}
|
|
43
|
+
export interface LinePart {
|
|
44
|
+
controlPoints: [CourtPoint, CourtPoint] | [CourtPoint, CourtPoint, CourtPoint] | [CourtPoint, CourtPoint, CourtPoint, CourtPoint];
|
|
45
|
+
}
|
|
46
|
+
export interface Color {
|
|
47
|
+
red: number;
|
|
48
|
+
green: number;
|
|
49
|
+
blue: number;
|
|
50
|
+
alpha: number;
|
|
51
|
+
}
|
|
52
|
+
export interface CourtRect {
|
|
53
|
+
origin: CourtPoint;
|
|
54
|
+
size: CourtSize;
|
|
55
|
+
}
|
|
56
|
+
export interface Court {
|
|
57
|
+
type: CourtType;
|
|
58
|
+
courtRect: CourtRect;
|
|
59
|
+
}
|
|
60
|
+
export type PlayerAnimationType = 'POSITION';
|
|
61
|
+
export interface Animation {
|
|
62
|
+
id: string;
|
|
63
|
+
keyTimes: number[];
|
|
64
|
+
}
|
|
65
|
+
export interface PlayerAnimation extends Animation {
|
|
66
|
+
type: PlayerAnimationType;
|
|
67
|
+
lineParts: LinePart[];
|
|
68
|
+
}
|
|
69
|
+
export interface Player {
|
|
70
|
+
id: string;
|
|
71
|
+
possession: boolean;
|
|
72
|
+
color: Color;
|
|
73
|
+
position: Position;
|
|
74
|
+
location: CourtPoint;
|
|
75
|
+
textOverride?: string;
|
|
76
|
+
animations: PlayerAnimation[];
|
|
77
|
+
}
|
|
78
|
+
export type LineAnimationType = 'LINESTROKE';
|
|
79
|
+
export interface LineAnimation {
|
|
80
|
+
id: string;
|
|
81
|
+
type: LineAnimationType;
|
|
82
|
+
keyTimes: number[];
|
|
83
|
+
strokeStartValues: [number, number, number];
|
|
84
|
+
}
|
|
85
|
+
export interface Line {
|
|
86
|
+
id: string;
|
|
87
|
+
type: LineType;
|
|
88
|
+
phase: number;
|
|
89
|
+
playerPositionOrigin: Position;
|
|
90
|
+
playerPositionTerminus: Position | null;
|
|
91
|
+
playerLineSequence: number;
|
|
92
|
+
lineParts: LinePart[];
|
|
93
|
+
color: Color;
|
|
94
|
+
hideLineTip?: boolean;
|
|
95
|
+
animations: LineAnimation[];
|
|
96
|
+
}
|
|
97
|
+
export interface Shape {
|
|
98
|
+
id: string;
|
|
99
|
+
type: ShapeType;
|
|
100
|
+
location: CourtPoint;
|
|
101
|
+
color: Color;
|
|
102
|
+
scale: Scale;
|
|
103
|
+
angle?: number;
|
|
104
|
+
showBorder?: boolean;
|
|
105
|
+
linePart?: LinePart;
|
|
106
|
+
animations?: Animation[];
|
|
107
|
+
hideForStatic?: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface Note {
|
|
110
|
+
id: string;
|
|
111
|
+
location: CourtPoint;
|
|
112
|
+
displayModes: NoteDisplayModes;
|
|
113
|
+
text: string;
|
|
114
|
+
animations?: Animation[];
|
|
115
|
+
font: NoteFont;
|
|
116
|
+
color: Color;
|
|
117
|
+
showBorder: boolean;
|
|
118
|
+
hideForStatic?: boolean;
|
|
119
|
+
}
|
|
120
|
+
export interface NoteFont {
|
|
121
|
+
bold: boolean;
|
|
122
|
+
italic: boolean;
|
|
123
|
+
underline: boolean;
|
|
124
|
+
strikethrough: boolean;
|
|
125
|
+
fontSize: number;
|
|
126
|
+
}
|
|
127
|
+
export interface PlayConstructData {
|
|
128
|
+
id?: string;
|
|
129
|
+
lastUpdtTS?: string;
|
|
130
|
+
name: string;
|
|
131
|
+
playData: PlayData;
|
|
132
|
+
}
|
|
133
|
+
export interface PlayData {
|
|
134
|
+
animationDuration: number;
|
|
135
|
+
sport: SportType;
|
|
136
|
+
court: Court;
|
|
137
|
+
players: Player[];
|
|
138
|
+
lines: Line[];
|
|
139
|
+
shapes?: Shape[];
|
|
140
|
+
notes?: Note[];
|
|
141
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luceosports/play-rendering",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.5",
|
|
4
4
|
"main": "dist/play-rendering.js",
|
|
5
5
|
"types": "dist/play-rendering.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"husky": "^4.3.6",
|
|
41
41
|
"lint-staged": "^10.5.3",
|
|
42
42
|
"lodash": "^4.17.15",
|
|
43
|
-
"prettier": "^
|
|
43
|
+
"prettier": "^2.8.8",
|
|
44
44
|
"typescript": "^5.4.5",
|
|
45
45
|
"url-loader": "^4.0.0",
|
|
46
46
|
"webpack": "^4.42.1",
|
|
@@ -77,7 +77,7 @@ export default class PlayerLayer extends BaseLayer {
|
|
|
77
77
|
if (player.isDefender && !payerInPosition && isDefaultInputColor) {
|
|
78
78
|
color = '#bb271b';
|
|
79
79
|
}
|
|
80
|
-
if (player.possession
|
|
80
|
+
if (player.possession) color = '#ff8000';
|
|
81
81
|
|
|
82
82
|
if (this.options.legacyPrintStyle) {
|
|
83
83
|
this.ctx.lineWidth = this.courtTypeConstants.LINE_WIDTH;
|
|
@@ -1,152 +1,149 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import { debug } from '../config';
|
|
3
|
-
import FrameModel from './FrameModel';
|
|
4
|
-
import LineModel from './LineModel';
|
|
5
|
-
import PlayModel from './PlayModel';
|
|
6
|
-
|
|
7
|
-
export default class AnimationModel {
|
|
8
|
-
private play: PlayModel;
|
|
9
|
-
private readonly playBase: PlayModel;
|
|
10
|
-
private animationFrame: FrameModel;
|
|
11
|
-
private loopId: number | null;
|
|
12
|
-
private running: boolean;
|
|
13
|
-
private framesLoadTime: number[];
|
|
14
|
-
private timeStart: number | null;
|
|
15
|
-
private timeElapsed: number | null;
|
|
16
|
-
private timeElapsedSaved: number | null;
|
|
17
|
-
|
|
18
|
-
constructor(private ctx: CanvasRenderingContext2D, play: PlayModel) {
|
|
19
|
-
this.play = _.cloneDeep(play);
|
|
20
|
-
this.playBase = _.cloneDeep(play);
|
|
21
|
-
this.animationFrame = new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx);
|
|
22
|
-
|
|
23
|
-
this.loopId = null;
|
|
24
|
-
this.running = false;
|
|
25
|
-
|
|
26
|
-
this.framesLoadTime = [];
|
|
27
|
-
|
|
28
|
-
this.timeStart = null;
|
|
29
|
-
this.timeElapsed = 0;
|
|
30
|
-
this.timeElapsedSaved = 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get animationDuration() {
|
|
34
|
-
return this.play.playData.animationDuration / this.play.options.speed;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get globalProgress() {
|
|
38
|
-
return (this.timeElapsed || 0) / (this.animationDuration * 1000);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get currentPlayPhase() {
|
|
42
|
-
const phaseInterval = Object.entries(this.linesPhaseIntervals).find(([, interval]) => {
|
|
43
|
-
return _.inRange(this.globalProgress, interval.min, interval.max);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
return parseInt(phaseInterval ? phaseInterval[0] : Object.keys(this.linesPhaseIntervals).pop()!);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
get linesPhaseIntervals(): Record<number, { min: number; max: number }> {
|
|
50
|
-
const result: Record<number, { min: number; max: number }> = {};
|
|
51
|
-
this.play.playData.lines.forEach(line => {
|
|
52
|
-
if (!result[line.phase]) result[line.phase] = { min: 0, max: 0 };
|
|
53
|
-
line.animations.forEach(({ keyTimes }) => {
|
|
54
|
-
const [from] = keyTimes;
|
|
55
|
-
const to = [...keyTimes].pop()!;
|
|
56
|
-
if (from < result[line.phase].min || (!result[line.phase].min && line.phase > 1)) result[line.phase].min = from;
|
|
57
|
-
if (to > result[line.phase].max) result[line.phase].max = to;
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
get lastLineAnimationMax() {
|
|
64
|
-
return this.play.playData.lines.reduce<number>((result, line) => {
|
|
65
|
-
const lineModel = new LineModel(line);
|
|
66
|
-
return result > lineModel.lastAnimEndTime! ? result : lineModel.lastAnimEndTime!;
|
|
67
|
-
}, 0);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
start(finishCallback: () => void, progressCallback: (progress: number) => void) {
|
|
71
|
-
if (debug) {
|
|
72
|
-
console.log('Animate stared with duration', this.play.playData.animationDuration);
|
|
73
|
-
console.log('this.play.playData.players', this.play.playData.players);
|
|
74
|
-
console.log('this.play.playData.lines', this.play.playData.lines);
|
|
75
|
-
}
|
|
76
|
-
this.running = true;
|
|
77
|
-
this.loopId = requestAnimationFrame(this.drawFrame.bind(this, finishCallback, progressCallback));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
pause() {
|
|
81
|
-
cancelAnimationFrame(this.loopId!);
|
|
82
|
-
this.running = false;
|
|
83
|
-
this.timeStart = null;
|
|
84
|
-
this.timeElapsedSaved = this.timeElapsed;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setProgress(progress: number) {
|
|
88
|
-
const elapsed = this.countTimeElapsedFromProgress(progress);
|
|
89
|
-
this.timeElapsed = elapsed;
|
|
90
|
-
this.timeElapsedSaved = elapsed;
|
|
91
|
-
this.refreshAnimationFrameProgress();
|
|
92
|
-
return this;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
refreshAnimationFrameProgress() {
|
|
96
|
-
this.animationFrame.setPhase(this.currentPlayPhase).setAnimationGlobalProgress(this.globalProgress);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
drawPreviewFrame() {
|
|
100
|
-
new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx).draw();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
reset() {
|
|
104
|
-
this.timeStart = null;
|
|
105
|
-
this.timeElapsed = null;
|
|
106
|
-
this.timeElapsedSaved = null;
|
|
107
|
-
this.play = _.cloneDeep(this.playBase);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
countTimeElapsedFromProgress(progress: number) {
|
|
111
|
-
return this.animationDuration * 1000 * progress;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
drawFrame(finishCallback: () => void, progressCallback: (progress: number) => void, timestamp: number) {
|
|
115
|
-
if (!this.running) return;
|
|
116
|
-
|
|
117
|
-
if (this.globalProgress >= this.lastLineAnimationMax) {
|
|
118
|
-
progressCallback(this.lastLineAnimationMax);
|
|
119
|
-
this.reset();
|
|
120
|
-
cancelAnimationFrame(this.loopId!);
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
finishCallback();
|
|
123
|
-
}, 1000);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
progressCallback(this.globalProgress);
|
|
128
|
-
|
|
129
|
-
if (!this.timeStart) this.timeStart = timestamp;
|
|
130
|
-
|
|
131
|
-
const frameTime = timestamp - this.timeStart + (this.timeElapsedSaved || 0) - (this.timeElapsed || 0);
|
|
132
|
-
this.framesLoadTime.push(frameTime);
|
|
133
|
-
|
|
134
|
-
this.timeElapsed = timestamp - this.timeStart + (this.timeElapsedSaved || 0);
|
|
135
|
-
|
|
136
|
-
if (debug) {
|
|
137
|
-
console.log(
|
|
138
|
-
`Frames: ${this.framesLoadTime.length}, Avg frame time:
|
|
139
|
-
|
|
140
|
-
);
|
|
141
|
-
console.log('Progress:', `${(this.globalProgress * 100).toFixed(2)}% (${Math.round(this.timeElapsed)}ms)`);
|
|
142
|
-
console.log('this.currentPlayPhase', this.currentPlayPhase);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
this.animationFrame
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
requestAnimationFrame(this.drawFrame.bind(this, finishCallback, progressCallback));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { debug } from '../config';
|
|
3
|
+
import FrameModel from './FrameModel';
|
|
4
|
+
import LineModel from './LineModel';
|
|
5
|
+
import PlayModel from './PlayModel';
|
|
6
|
+
|
|
7
|
+
export default class AnimationModel {
|
|
8
|
+
private play: PlayModel;
|
|
9
|
+
private readonly playBase: PlayModel;
|
|
10
|
+
private animationFrame: FrameModel;
|
|
11
|
+
private loopId: number | null;
|
|
12
|
+
private running: boolean;
|
|
13
|
+
private framesLoadTime: number[];
|
|
14
|
+
private timeStart: number | null;
|
|
15
|
+
private timeElapsed: number | null;
|
|
16
|
+
private timeElapsedSaved: number | null;
|
|
17
|
+
|
|
18
|
+
constructor(private ctx: CanvasRenderingContext2D, play: PlayModel) {
|
|
19
|
+
this.play = _.cloneDeep(play);
|
|
20
|
+
this.playBase = _.cloneDeep(play);
|
|
21
|
+
this.animationFrame = new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx);
|
|
22
|
+
|
|
23
|
+
this.loopId = null;
|
|
24
|
+
this.running = false;
|
|
25
|
+
|
|
26
|
+
this.framesLoadTime = [];
|
|
27
|
+
|
|
28
|
+
this.timeStart = null;
|
|
29
|
+
this.timeElapsed = 0;
|
|
30
|
+
this.timeElapsedSaved = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get animationDuration() {
|
|
34
|
+
return this.play.playData.animationDuration / this.play.options.speed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get globalProgress() {
|
|
38
|
+
return (this.timeElapsed || 0) / (this.animationDuration * 1000);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get currentPlayPhase() {
|
|
42
|
+
const phaseInterval = Object.entries(this.linesPhaseIntervals).find(([, interval]) => {
|
|
43
|
+
return _.inRange(this.globalProgress, interval.min, interval.max);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return parseInt(phaseInterval ? phaseInterval[0] : Object.keys(this.linesPhaseIntervals).pop()!);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get linesPhaseIntervals(): Record<number, { min: number; max: number }> {
|
|
50
|
+
const result: Record<number, { min: number; max: number }> = {};
|
|
51
|
+
this.play.playData.lines.forEach(line => {
|
|
52
|
+
if (!result[line.phase]) result[line.phase] = { min: 0, max: 0 };
|
|
53
|
+
line.animations.forEach(({ keyTimes }) => {
|
|
54
|
+
const [from] = keyTimes;
|
|
55
|
+
const to = [...keyTimes].pop()!;
|
|
56
|
+
if (from < result[line.phase].min || (!result[line.phase].min && line.phase > 1)) result[line.phase].min = from;
|
|
57
|
+
if (to > result[line.phase].max) result[line.phase].max = to;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get lastLineAnimationMax() {
|
|
64
|
+
return this.play.playData.lines.reduce<number>((result, line) => {
|
|
65
|
+
const lineModel = new LineModel(line);
|
|
66
|
+
return result > lineModel.lastAnimEndTime! ? result : lineModel.lastAnimEndTime!;
|
|
67
|
+
}, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
start(finishCallback: () => void, progressCallback: (progress: number) => void) {
|
|
71
|
+
if (debug) {
|
|
72
|
+
console.log('Animate stared with duration', this.play.playData.animationDuration);
|
|
73
|
+
console.log('this.play.playData.players', this.play.playData.players);
|
|
74
|
+
console.log('this.play.playData.lines', this.play.playData.lines);
|
|
75
|
+
}
|
|
76
|
+
this.running = true;
|
|
77
|
+
this.loopId = requestAnimationFrame(this.drawFrame.bind(this, finishCallback, progressCallback));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pause() {
|
|
81
|
+
cancelAnimationFrame(this.loopId!);
|
|
82
|
+
this.running = false;
|
|
83
|
+
this.timeStart = null;
|
|
84
|
+
this.timeElapsedSaved = this.timeElapsed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setProgress(progress: number) {
|
|
88
|
+
const elapsed = this.countTimeElapsedFromProgress(progress);
|
|
89
|
+
this.timeElapsed = elapsed;
|
|
90
|
+
this.timeElapsedSaved = elapsed;
|
|
91
|
+
this.refreshAnimationFrameProgress();
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
refreshAnimationFrameProgress() {
|
|
96
|
+
this.animationFrame.setPhase(this.currentPlayPhase).setAnimationGlobalProgress(this.globalProgress);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
drawPreviewFrame() {
|
|
100
|
+
new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx).draw();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
reset() {
|
|
104
|
+
this.timeStart = null;
|
|
105
|
+
this.timeElapsed = null;
|
|
106
|
+
this.timeElapsedSaved = null;
|
|
107
|
+
this.play = _.cloneDeep(this.playBase);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
countTimeElapsedFromProgress(progress: number) {
|
|
111
|
+
return this.animationDuration * 1000 * progress;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
drawFrame(finishCallback: () => void, progressCallback: (progress: number) => void, timestamp: number) {
|
|
115
|
+
if (!this.running) return;
|
|
116
|
+
|
|
117
|
+
if (this.globalProgress >= this.lastLineAnimationMax) {
|
|
118
|
+
progressCallback(this.lastLineAnimationMax);
|
|
119
|
+
this.reset();
|
|
120
|
+
cancelAnimationFrame(this.loopId!);
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
finishCallback();
|
|
123
|
+
}, 1000);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
progressCallback(this.globalProgress);
|
|
128
|
+
|
|
129
|
+
if (!this.timeStart) this.timeStart = timestamp;
|
|
130
|
+
|
|
131
|
+
const frameTime = timestamp - this.timeStart + (this.timeElapsedSaved || 0) - (this.timeElapsed || 0);
|
|
132
|
+
this.framesLoadTime.push(frameTime);
|
|
133
|
+
|
|
134
|
+
this.timeElapsed = timestamp - this.timeStart + (this.timeElapsedSaved || 0);
|
|
135
|
+
|
|
136
|
+
if (debug) {
|
|
137
|
+
console.log(
|
|
138
|
+
`Frames: ${this.framesLoadTime.length}, Avg frame time:
|
|
139
|
+
${this.framesLoadTime.reduce((a, b) => a + b) / this.framesLoadTime.length}`
|
|
140
|
+
);
|
|
141
|
+
console.log('Progress:', `${(this.globalProgress * 100).toFixed(2)}% (${Math.round(this.timeElapsed)}ms)`);
|
|
142
|
+
console.log('this.currentPlayPhase', this.currentPlayPhase);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.animationFrame.setPhase(this.currentPlayPhase).setAnimationGlobalProgress(this.globalProgress).draw();
|
|
146
|
+
|
|
147
|
+
requestAnimationFrame(this.drawFrame.bind(this, finishCallback, progressCallback));
|
|
148
|
+
}
|
|
149
|
+
}
|