@luceosports/play-rendering 2.6.3 → 2.8.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/dist/types/helpers/animation.d.ts +0 -2
- package/dist/types/models/PlayerModel.d.ts +3 -6
- package/dist/types/types/index.d.ts +9 -12
- package/package.json +1 -1
- package/src/helpers/animation.ts +4 -33
- package/src/models/PlayerModel.ts +20 -30
- package/src/types/index.ts +14 -15
|
@@ -9,9 +9,7 @@ export declare const getEffectiveTimeline: (anim: HasKeyTimes, timeline: Timelin
|
|
|
9
9
|
export declare const keyTimesToChunks: (keyTimes: number[]) => [number, number][];
|
|
10
10
|
export declare const deriveLineChunksWithFallback: (params: {
|
|
11
11
|
timeline: TimelineKey;
|
|
12
|
-
playerAnimation: HasKeyTimes;
|
|
13
12
|
lineAnimation: HasKeyTimes;
|
|
14
13
|
lineType: LineType;
|
|
15
14
|
lineParts: LinePart[];
|
|
16
|
-
legacySupport?: boolean;
|
|
17
15
|
}) => [number, number][];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, CourtPoint, Line, Player,
|
|
1
|
+
import { Color, CourtPoint, Line, Player, Position, AnimationMode } from "../../play-rendering";
|
|
2
2
|
|
|
3
3
|
export class PlayerModel {
|
|
4
4
|
constructor(data: Player);
|
|
@@ -9,13 +9,10 @@ export class PlayerModel {
|
|
|
9
9
|
get isDefender(): boolean;
|
|
10
10
|
get possession(): boolean;
|
|
11
11
|
set possession(data: boolean);
|
|
12
|
+
get startBallsCount();
|
|
13
|
+
set startBallsCount(data: number);
|
|
12
14
|
get position(): Position;
|
|
13
15
|
get location(): CourtPoint;
|
|
14
16
|
get color(): Color;
|
|
15
|
-
get animations(): PlayerAnimation[];
|
|
16
|
-
animationsByEndTime(mode: AnimationMode): number[];
|
|
17
|
-
lastAnimEndTime(mode: AnimationMode): number;
|
|
18
|
-
get lastAnimation(): PlayerAnimation;
|
|
19
|
-
get lastAnimationLastLinePartControlPoint(): CourtPoint;
|
|
20
17
|
setPossession(prevPassLines: Line[]): void;
|
|
21
18
|
}
|
|
@@ -59,27 +59,24 @@ export interface Court {
|
|
|
59
59
|
type: CourtType;
|
|
60
60
|
courtRect: CourtRect;
|
|
61
61
|
}
|
|
62
|
-
export type PlayerAnimationType = 'POSITION';
|
|
63
|
-
export interface Animation {
|
|
64
|
-
id: string;
|
|
65
|
-
keyTimes: number[];
|
|
66
|
-
keyTimesSeq?: number[];
|
|
67
|
-
}
|
|
68
|
-
export interface PlayerAnimation extends Animation {
|
|
69
|
-
type: PlayerAnimationType;
|
|
70
|
-
lineParts: LinePart[];
|
|
71
|
-
}
|
|
72
62
|
export interface Player {
|
|
73
63
|
id: string;
|
|
74
64
|
possession: boolean;
|
|
65
|
+
startBallsCount?: number;
|
|
75
66
|
color: Color;
|
|
76
67
|
position: Position;
|
|
77
68
|
location: CourtPoint;
|
|
78
69
|
textOverride?: string;
|
|
79
|
-
|
|
70
|
+
}
|
|
71
|
+
export interface Animation {
|
|
72
|
+
id: string;
|
|
73
|
+
keyTimes: number[];
|
|
80
74
|
}
|
|
81
75
|
export type LineAnimationType = 'LINESTROKE';
|
|
82
|
-
export interface LineAnimation
|
|
76
|
+
export interface LineAnimation {
|
|
77
|
+
id: string;
|
|
78
|
+
keyTimes: number[];
|
|
79
|
+
keyTimesSeq?: number[];
|
|
83
80
|
type: LineAnimationType;
|
|
84
81
|
strokeStartValues: [number, number, number];
|
|
85
82
|
}
|
package/package.json
CHANGED
package/src/helpers/animation.ts
CHANGED
|
@@ -66,13 +66,11 @@ const splitChunkByPartDurations = (params: {
|
|
|
66
66
|
|
|
67
67
|
export const deriveLineChunksWithFallback = (params: {
|
|
68
68
|
timeline: TimelineKey;
|
|
69
|
-
playerAnimation: HasKeyTimes;
|
|
70
69
|
lineAnimation: HasKeyTimes;
|
|
71
70
|
lineType: LineType;
|
|
72
71
|
lineParts: LinePart[];
|
|
73
|
-
legacySupport?: boolean;
|
|
74
72
|
}): [number, number][] => {
|
|
75
|
-
const { timeline,
|
|
73
|
+
const { timeline, lineAnimation, lineType, lineParts } = params;
|
|
76
74
|
const linePartsCount = lineParts.length ?? 0;
|
|
77
75
|
|
|
78
76
|
const chunks = keyTimesToChunks(getEffectiveTimeline(lineAnimation, timeline));
|
|
@@ -81,32 +79,15 @@ export const deriveLineChunksWithFallback = (params: {
|
|
|
81
79
|
// If there are no chunks (bad/partial data), just return as-is.
|
|
82
80
|
if (!chunks.length) return chunks;
|
|
83
81
|
|
|
84
|
-
//
|
|
82
|
+
// Geometry-based splitting fallback.
|
|
85
83
|
// Typical legacy case: multi-part line stored as a single [start,end] chunk.
|
|
86
84
|
if (chunks.length === 1 && lineParts?.length) {
|
|
87
85
|
const [[start, end]] = chunks;
|
|
88
86
|
const split = splitChunkByPartDurations({ start, end, lineParts, lineType });
|
|
89
|
-
console.log('geometry based', { chunks, split, linePartsCount });
|
|
90
87
|
if (split.length === linePartsCount) return split;
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// If legacy support is disabled, do not attempt reconstruction from player timeline.
|
|
96
|
-
if (!legacySupport) return chunks;
|
|
97
|
-
|
|
98
|
-
// Backward compatibility:
|
|
99
|
-
// legacy multi-part lines stored only [start,end] in the line animation timeline,
|
|
100
|
-
// so reconstruct per-part timings from the player animation timeline slice.
|
|
101
|
-
const [[start, end]] = chunks;
|
|
102
|
-
const playerTimeline = getEffectiveTimeline(playerAnimation, timeline);
|
|
103
|
-
|
|
104
|
-
const startIndex = playerTimeline.indexOf(start);
|
|
105
|
-
const endIndex = playerTimeline.indexOf(end);
|
|
106
|
-
if (startIndex < 0 || endIndex < 0) return chunks;
|
|
107
|
-
|
|
108
|
-
const sliced = playerTimeline.slice(startIndex, endIndex + 1);
|
|
109
|
-
return keyTimesToChunks(sliced);
|
|
90
|
+
return chunks;
|
|
110
91
|
};
|
|
111
92
|
|
|
112
93
|
export const getLineAnimationChunks = (params: {
|
|
@@ -121,22 +102,12 @@ export const getLineAnimationChunks = (params: {
|
|
|
121
102
|
const lineAnimation = line.animations?.[0] as unknown as HasKeyTimes | undefined;
|
|
122
103
|
if (!lineAnimation) return [];
|
|
123
104
|
|
|
124
|
-
const linePlayer = players.find(p => p.position === line.playerPositionOrigin);
|
|
125
|
-
const playerAnimation = (linePlayer?.animations?.[0] as unknown as HasKeyTimes | undefined) ?? undefined;
|
|
126
|
-
|
|
127
|
-
// Fast path: no player timeline available, just chunk the line timeline as-is.
|
|
128
|
-
if (!playerAnimation) {
|
|
129
|
-
return keyTimesToChunks(getEffectiveTimeline(lineAnimation, timeline));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
105
|
// Full path: handle chunking + legacy reconstruction.
|
|
133
106
|
return deriveLineChunksWithFallback({
|
|
134
107
|
timeline,
|
|
135
|
-
playerAnimation,
|
|
136
108
|
lineAnimation,
|
|
137
109
|
lineType: line.type,
|
|
138
|
-
lineParts: line.lineParts
|
|
139
|
-
legacySupport: false
|
|
110
|
+
lineParts: line.lineParts
|
|
140
111
|
});
|
|
141
112
|
};
|
|
142
113
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import Model from './Base/InternalFrameModel';
|
|
3
|
-
import {
|
|
2
|
+
import { Line, Player as PlayerData } from '../types';
|
|
4
3
|
|
|
5
4
|
export default class PlayerModel extends Model<PlayerData, PlayerData> {
|
|
6
5
|
get id() {
|
|
@@ -31,6 +30,19 @@ export default class PlayerModel extends Model<PlayerData, PlayerData> {
|
|
|
31
30
|
this._setAttr('possession', data);
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
get startBallsCount() {
|
|
34
|
+
const raw = this._getAttr('startBallsCount');
|
|
35
|
+
if (typeof raw === 'number') return raw;
|
|
36
|
+
|
|
37
|
+
// Backward-compatible fallback:
|
|
38
|
+
// legacy model encoded "starts with 1 ball" as possession=true.
|
|
39
|
+
return this.possession ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set startBallsCount(data) {
|
|
43
|
+
this._setAttr('startBallsCount', data);
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
get position() {
|
|
35
47
|
return this._getAttr('position');
|
|
36
48
|
}
|
|
@@ -47,35 +59,13 @@ export default class PlayerModel extends Model<PlayerData, PlayerData> {
|
|
|
47
59
|
return this._getAttr('color');
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
private getAnimationKeyTimes(animation: PlayerAnimation, mode: AnimationMode) {
|
|
55
|
-
return mode === AnimationMode.SEQUENTIAL ? animation.keyTimesSeq || animation.keyTimes : animation.keyTimes;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
animationsByEndTime(mode: AnimationMode) {
|
|
59
|
-
return this._getAttr('animations').map(a => _.last(this.getAnimationKeyTimes(a, mode)));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
lastAnimEndTime(mode: AnimationMode) {
|
|
63
|
-
return _.max(this.animationsByEndTime(mode));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get lastAnimation() {
|
|
67
|
-
return [...this.animations].pop();
|
|
68
|
-
}
|
|
62
|
+
setPossession(prevPassLines: Line[]) {
|
|
63
|
+
// Support multiple balls: start from startBallsCount, then apply transfers.
|
|
64
|
+
let ballsCount = this.startBallsCount;
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
66
|
+
ballsCount += prevPassLines.filter(l => l.playerPositionTerminus === this.position).length;
|
|
67
|
+
ballsCount -= prevPassLines.filter(l => l.playerPositionOrigin === this.position).length;
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
if (!prevPassLines.length) return;
|
|
76
|
-
let possessionCount = this.possession ? 1 : 0;
|
|
77
|
-
possessionCount += prevPassLines.filter(l => l.playerPositionTerminus === this.position).length;
|
|
78
|
-
possessionCount -= prevPassLines.filter(l => l.playerPositionOrigin === this.position).length;
|
|
79
|
-
this.possession = possessionCount > 0;
|
|
69
|
+
this.possession = ballsCount > 0;
|
|
80
70
|
}
|
|
81
71
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -133,32 +133,31 @@ export interface Court {
|
|
|
133
133
|
courtRect: CourtRect;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export type PlayerAnimationType = 'POSITION';
|
|
137
|
-
|
|
138
|
-
export interface Animation {
|
|
139
|
-
id: string;
|
|
140
|
-
keyTimes: number[];
|
|
141
|
-
keyTimesSeq?: number[];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface PlayerAnimation extends Animation {
|
|
145
|
-
type: PlayerAnimationType;
|
|
146
|
-
lineParts: LinePart[];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
136
|
export interface Player {
|
|
150
137
|
id: string;
|
|
151
138
|
possession: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* How many balls the player starts with before applying PASS/HANDOFF/SHOT transfers.
|
|
141
|
+
* If omitted, legacy data falls back to `possession ? 1 : 0`.
|
|
142
|
+
*/
|
|
143
|
+
startBallsCount?: number;
|
|
152
144
|
color: Color;
|
|
153
145
|
position: Position;
|
|
154
146
|
location: CourtPoint;
|
|
155
147
|
textOverride?: string;
|
|
156
|
-
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface Animation {
|
|
151
|
+
id: string;
|
|
152
|
+
keyTimes: number[];
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
export type LineAnimationType = 'LINESTROKE';
|
|
160
156
|
|
|
161
|
-
export interface LineAnimation
|
|
157
|
+
export interface LineAnimation {
|
|
158
|
+
id: string;
|
|
159
|
+
keyTimes: number[];
|
|
160
|
+
keyTimesSeq?: number[];
|
|
162
161
|
type: LineAnimationType;
|
|
163
162
|
strokeStartValues: [number, number, number];
|
|
164
163
|
}
|