distark-render 1.0.5 → 1.0.7
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/README.md +158 -154
- package/dist/cli/animate.d.ts +8 -0
- package/dist/cli/animate.d.ts.map +1 -0
- package/dist/cli/animate.js +121 -0
- package/dist/cli/animate.js.map +1 -0
- package/dist/cli/diff.d.ts +8 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +124 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/distark-check.d.ts +13 -0
- package/dist/cli/distark-check.d.ts.map +1 -0
- package/dist/cli/distark-check.js +69 -0
- package/dist/cli/distark-check.js.map +1 -0
- package/dist/cli/query.d.ts +12 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +64 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/record.d.ts +13 -0
- package/dist/cli/record.d.ts.map +1 -0
- package/dist/cli/record.js +97 -0
- package/dist/cli/record.js.map +1 -0
- package/dist/cli/render.d.ts +40 -0
- package/dist/cli/render.d.ts.map +1 -0
- package/dist/cli/render.js +75 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/shared.d.ts +6 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +54 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/test.d.ts +13 -0
- package/dist/cli/test.d.ts.map +1 -0
- package/dist/cli/test.js +130 -0
- package/dist/cli/test.js.map +1 -0
- package/dist/cli/verify.d.ts +21 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +175 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/modules/adapters/p5Renderer.d.ts +0 -0
- package/dist/modules/adapters/p5Renderer.d.ts.map +0 -0
- package/dist/modules/adapters/p5Renderer.js +0 -0
- package/dist/modules/adapters/p5Renderer.js.map +0 -0
- package/dist/modules/adapters/skiaRenderer.d.ts +0 -0
- package/dist/modules/adapters/skiaRenderer.d.ts.map +0 -0
- package/dist/modules/adapters/skiaRenderer.js +0 -0
- package/dist/modules/adapters/skiaRenderer.js.map +0 -0
- package/dist/modules/animationDiff.d.ts +101 -0
- package/dist/modules/animationDiff.d.ts.map +1 -0
- package/dist/modules/animationDiff.js +327 -0
- package/dist/modules/animationDiff.js.map +1 -0
- package/dist/modules/eyeSystem.d.ts +0 -0
- package/dist/modules/eyeSystem.d.ts.map +0 -0
- package/dist/modules/eyeSystem.js +0 -0
- package/dist/modules/eyeSystem.js.map +0 -0
- package/dist/modules/imageLoad.d.ts +0 -0
- package/dist/modules/imageLoad.d.ts.map +0 -0
- package/dist/modules/imageLoad.js +0 -0
- package/dist/modules/imageLoad.js.map +0 -0
- package/dist/modules/mouthSystem.d.ts +0 -0
- package/dist/modules/mouthSystem.d.ts.map +0 -0
- package/dist/modules/mouthSystem.js +0 -0
- package/dist/modules/mouthSystem.js.map +0 -0
- package/dist/modules/renderRig.d.ts +0 -0
- package/dist/modules/renderRig.d.ts.map +0 -0
- package/dist/modules/renderRig.js +0 -0
- package/dist/modules/renderRig.js.map +0 -0
- package/dist/tests/test-animation-diff-rendering.d.ts +7 -0
- package/dist/tests/test-animation-diff-rendering.d.ts.map +1 -0
- package/dist/tests/test-animation-diff-rendering.js +444 -0
- package/dist/tests/test-animation-diff-rendering.js.map +1 -0
- package/dist/tests/test-animation-diff.d.ts +7 -0
- package/dist/tests/test-animation-diff.d.ts.map +1 -0
- package/dist/tests/test-animation-diff.js +488 -0
- package/dist/tests/test-animation-diff.js.map +1 -0
- package/dist/tests/test-cli.d.ts +8 -0
- package/dist/tests/test-cli.d.ts.map +1 -0
- package/dist/tests/test-cli.js +307 -0
- package/dist/tests/test-cli.js.map +1 -0
- package/dist/tests/test-joint-movement.d.ts +0 -0
- package/dist/tests/test-joint-movement.d.ts.map +0 -0
- package/dist/tests/test-joint-movement.js +0 -0
- package/dist/tests/test-joint-movement.js.map +0 -0
- package/dist/tests/test-skia.d.ts +0 -0
- package/dist/tests/test-skia.d.ts.map +0 -0
- package/dist/tests/test-skia.js +0 -0
- package/dist/tests/test-skia.js.map +0 -0
- package/dist/tests/test-visual-verification.d.ts +0 -0
- package/dist/tests/test-visual-verification.d.ts.map +0 -0
- package/dist/tests/test-visual-verification.js +0 -0
- package/dist/tests/test-visual-verification.js.map +0 -0
- package/dist/types.d.ts +0 -0
- package/dist/types.d.ts.map +0 -0
- package/dist/types.js +0 -0
- package/dist/types.js.map +0 -0
- package/package.json +13 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify command - Math-based sanity checks on rig data (no rendering, no API calls)
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* distark-check verify <rig.json> [--checks all|bounds,visibility,proportions,zorder]
|
|
6
|
+
*/
|
|
7
|
+
interface CheckResult {
|
|
8
|
+
check: string;
|
|
9
|
+
status: 'passed' | 'failed' | 'warning';
|
|
10
|
+
detail?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface VerifyReport {
|
|
13
|
+
input: string;
|
|
14
|
+
passed: string[];
|
|
15
|
+
failed: CheckResult[];
|
|
16
|
+
warnings: CheckResult[];
|
|
17
|
+
all_results: CheckResult[];
|
|
18
|
+
}
|
|
19
|
+
export declare function runVerify(args: string[]): Promise<void>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../cli/verify.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,UAAU,WAAW;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,WAAW,EAAE,CAAC;CAC9B;AAwID,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD7D"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify command - Math-based sanity checks on rig data (no rendering, no API calls)
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* distark-check verify <rig.json> [--checks all|bounds,visibility,proportions,zorder]
|
|
6
|
+
*/
|
|
7
|
+
import { readFile } from 'fs/promises';
|
|
8
|
+
import { CharacterRigRenderer } from '../modules/renderRig.js';
|
|
9
|
+
import { ImageLoader } from '../modules/imageLoad.js';
|
|
10
|
+
function checkBounds(objects, width, height) {
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const obj of objects) {
|
|
13
|
+
const left = obj.x - obj.width * obj.anchorX;
|
|
14
|
+
const top = obj.y - obj.height * obj.anchorY;
|
|
15
|
+
const right = left + obj.width;
|
|
16
|
+
const bottom = top + obj.height;
|
|
17
|
+
if (right < 0 || left > width || bottom < 0 || top > height) {
|
|
18
|
+
results.push({
|
|
19
|
+
check: 'bounds',
|
|
20
|
+
status: 'failed',
|
|
21
|
+
detail: `${obj.name} is completely offscreen (${Math.round(left)},${Math.round(top)} to ${Math.round(right)},${Math.round(bottom)})`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (left < 0 || top < 0 || right > width || bottom > height) {
|
|
25
|
+
results.push({
|
|
26
|
+
check: 'bounds',
|
|
27
|
+
status: 'warning',
|
|
28
|
+
detail: `${obj.name} is partially offscreen (${Math.round(left)},${Math.round(top)} to ${Math.round(right)},${Math.round(bottom)})`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (results.length === 0) {
|
|
33
|
+
results.push({ check: 'bounds', status: 'passed' });
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
function checkVisibility(rigData, objects) {
|
|
38
|
+
const results = [];
|
|
39
|
+
const expectedParts = ['torso', 'head'];
|
|
40
|
+
for (const part of expectedParts) {
|
|
41
|
+
const visible = rigData.visibility?.[part] !== false;
|
|
42
|
+
const rendered = objects.some(o => o.name === part);
|
|
43
|
+
if (visible && !rendered) {
|
|
44
|
+
results.push({
|
|
45
|
+
check: 'visibility',
|
|
46
|
+
status: 'failed',
|
|
47
|
+
detail: `${part} should be visible but is not in render objects`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Check if imagePaths are defined for limbs (we don't load images in verify)
|
|
52
|
+
const limbParts = objects.filter(o => o.type === 'limb');
|
|
53
|
+
const missingPaths = [];
|
|
54
|
+
for (const limb of limbParts) {
|
|
55
|
+
const pathKey = limb.name;
|
|
56
|
+
const hasPath = rigData.imagePaths?.[pathKey];
|
|
57
|
+
if (!hasPath) {
|
|
58
|
+
missingPaths.push(limb.name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (missingPaths.length > 0) {
|
|
62
|
+
results.push({
|
|
63
|
+
check: 'visibility',
|
|
64
|
+
status: 'warning',
|
|
65
|
+
detail: `${missingPaths.length} limbs have no imagePath defined: ${missingPaths.join(', ')}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (results.length === 0) {
|
|
69
|
+
results.push({ check: 'visibility', status: 'passed' });
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
function checkProportions(objects) {
|
|
74
|
+
const results = [];
|
|
75
|
+
const torso = objects.find(o => o.name === 'torso');
|
|
76
|
+
const head = objects.find(o => o.name === 'head');
|
|
77
|
+
if (torso && head) {
|
|
78
|
+
const headToTorsoRatio = (head.width * head.height) / (torso.width * torso.height);
|
|
79
|
+
if (headToTorsoRatio > 10) {
|
|
80
|
+
results.push({
|
|
81
|
+
check: 'proportions',
|
|
82
|
+
status: 'warning',
|
|
83
|
+
detail: `Head is very large relative to torso (ratio: ${headToTorsoRatio.toFixed(2)})`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (headToTorsoRatio < 0.01) {
|
|
87
|
+
results.push({
|
|
88
|
+
check: 'proportions',
|
|
89
|
+
status: 'warning',
|
|
90
|
+
detail: `Head is very small relative to torso (ratio: ${headToTorsoRatio.toFixed(2)})`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const obj of objects) {
|
|
95
|
+
if (obj.width <= 0 || obj.height <= 0) {
|
|
96
|
+
results.push({
|
|
97
|
+
check: 'proportions',
|
|
98
|
+
status: 'failed',
|
|
99
|
+
detail: `${obj.name} has invalid dimensions: ${obj.width}x${obj.height}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (results.length === 0) {
|
|
104
|
+
results.push({ check: 'proportions', status: 'passed' });
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
function checkZOrder(objects) {
|
|
109
|
+
const results = [];
|
|
110
|
+
const sorted = [...objects].sort((a, b) => a.zIndex - b.zIndex);
|
|
111
|
+
// Eyes/irises should render above head
|
|
112
|
+
const head = sorted.find(o => o.name === 'head');
|
|
113
|
+
const eyeParts = sorted.filter(o => o.type === 'eye' || o.type === 'iris' || o.type === 'eyelid');
|
|
114
|
+
if (head) {
|
|
115
|
+
for (const eye of eyeParts) {
|
|
116
|
+
if (eye.zIndex < head.zIndex) {
|
|
117
|
+
results.push({
|
|
118
|
+
check: 'zorder',
|
|
119
|
+
status: 'warning',
|
|
120
|
+
detail: `${eye.name} (z:${eye.zIndex}) renders behind head (z:${head.zIndex})`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (results.length === 0) {
|
|
126
|
+
results.push({ check: 'zorder', status: 'passed' });
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
export async function runVerify(args) {
|
|
131
|
+
const inputFile = args.find(a => !a.startsWith('-')) || '';
|
|
132
|
+
if (!inputFile) {
|
|
133
|
+
console.error('Usage: distark-check verify <rig.json> [--checks all|bounds,visibility,proportions,zorder]');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const checksIdx = args.indexOf('--checks');
|
|
137
|
+
const checksStr = checksIdx !== -1 ? args[checksIdx + 1] : 'all';
|
|
138
|
+
const enabledChecks = checksStr === 'all'
|
|
139
|
+
? ['bounds', 'visibility', 'proportions', 'zorder']
|
|
140
|
+
: checksStr.split(',');
|
|
141
|
+
const width = parseInt(args[args.indexOf('--width') + 1]) || 1000;
|
|
142
|
+
const height = parseInt(args[args.indexOf('--height') + 1]) || 1000;
|
|
143
|
+
const rigData = JSON.parse(await readFile(inputFile, 'utf-8'));
|
|
144
|
+
// Compute render data (no actual rendering needed)
|
|
145
|
+
const renderer = new CharacterRigRenderer(new ImageLoader());
|
|
146
|
+
const renderData = renderer.computeCharacterRigData(rigData, {
|
|
147
|
+
canvasWidth: width,
|
|
148
|
+
canvasHeight: height,
|
|
149
|
+
});
|
|
150
|
+
const allResults = [];
|
|
151
|
+
if (enabledChecks.includes('bounds')) {
|
|
152
|
+
allResults.push(...checkBounds(renderData.objects, width, height));
|
|
153
|
+
}
|
|
154
|
+
if (enabledChecks.includes('visibility')) {
|
|
155
|
+
allResults.push(...checkVisibility(rigData, renderData.objects));
|
|
156
|
+
}
|
|
157
|
+
if (enabledChecks.includes('proportions')) {
|
|
158
|
+
allResults.push(...checkProportions(renderData.objects));
|
|
159
|
+
}
|
|
160
|
+
if (enabledChecks.includes('zorder')) {
|
|
161
|
+
allResults.push(...checkZOrder(renderData.objects));
|
|
162
|
+
}
|
|
163
|
+
const report = {
|
|
164
|
+
input: inputFile,
|
|
165
|
+
passed: allResults.filter(r => r.status === 'passed').map(r => r.check),
|
|
166
|
+
failed: allResults.filter(r => r.status === 'failed'),
|
|
167
|
+
warnings: allResults.filter(r => r.status === 'warning'),
|
|
168
|
+
all_results: allResults,
|
|
169
|
+
};
|
|
170
|
+
console.log(JSON.stringify(report, null, 2));
|
|
171
|
+
if (report.failed.length > 0) {
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../cli/verify.ts"],"names":[],"mappings":"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"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation Diff Utility
|
|
3
|
+
*
|
|
4
|
+
* Converts animation frames from full-state to diff-based format to reduce file size.
|
|
5
|
+
* Instead of storing complete frame data for each frame, only stores changes from the previous frame.
|
|
6
|
+
*
|
|
7
|
+
* Format (compact array):
|
|
8
|
+
* [
|
|
9
|
+
* { ...complete first frame data... }, // Index 0: Base frame (full data)
|
|
10
|
+
* { rotationValues: { head: 0.5 }, ... }, // Index 1: Only changed values
|
|
11
|
+
* { selfRotationValues: { leftArm: 0.2 } }, // Index 2: Only changed values
|
|
12
|
+
* ...
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* Also supports legacy verbose format:
|
|
16
|
+
* {
|
|
17
|
+
* version: "1.0",
|
|
18
|
+
* baseFrame: { ...complete first frame data... },
|
|
19
|
+
* diffs: [null, {...}, {...}, ...]
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
import type { RigData } from '../types.js';
|
|
23
|
+
/**
|
|
24
|
+
* Frame data type - can be complete RigData or partial diff
|
|
25
|
+
*/
|
|
26
|
+
export type FrameData = Partial<RigData>;
|
|
27
|
+
/**
|
|
28
|
+
* Diff-based animation format (compact array)
|
|
29
|
+
*/
|
|
30
|
+
export type DiffBasedAnimation = FrameData[];
|
|
31
|
+
/**
|
|
32
|
+
* Legacy verbose format
|
|
33
|
+
*/
|
|
34
|
+
export interface VerboseDiffAnimation {
|
|
35
|
+
version: string;
|
|
36
|
+
baseFrame: FrameData;
|
|
37
|
+
diffs: (FrameData | null)[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Animation data that can be in either format
|
|
41
|
+
*/
|
|
42
|
+
export type AnimationData = DiffBasedAnimation | VerboseDiffAnimation;
|
|
43
|
+
/**
|
|
44
|
+
* Compression statistics
|
|
45
|
+
*/
|
|
46
|
+
export interface CompressionStats {
|
|
47
|
+
originalSize: number;
|
|
48
|
+
compressedSize: number;
|
|
49
|
+
savedBytes: number;
|
|
50
|
+
compressionRatio: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert full-state animation frames to diff-based format
|
|
54
|
+
* @param frames - Array of complete frame objects
|
|
55
|
+
* @returns Diff-based animation array [baseFrame, diff1, diff2, ...]
|
|
56
|
+
*/
|
|
57
|
+
export declare const createDiffBasedAnimation: (frames: FrameData[]) => DiffBasedAnimation;
|
|
58
|
+
/**
|
|
59
|
+
* Convert diff-based animation back to full-state frames
|
|
60
|
+
* Supports both formats:
|
|
61
|
+
* - New format: [baseFrame, diff1, diff2, ...]
|
|
62
|
+
* - Old format: { baseFrame, diffs: [...] }
|
|
63
|
+
* @param diffBasedAnimation - Diff-based animation
|
|
64
|
+
* @returns Array of complete frame objects
|
|
65
|
+
*/
|
|
66
|
+
export declare const expandDiffBasedAnimation: (diffBasedAnimation: AnimationData) => FrameData[];
|
|
67
|
+
/**
|
|
68
|
+
* Calculate compression ratio
|
|
69
|
+
* @param originalFrames - Original full-state frames
|
|
70
|
+
* @param diffBasedAnimation - Diff-based animation
|
|
71
|
+
* @returns Statistics about compression
|
|
72
|
+
*/
|
|
73
|
+
export declare const calculateCompressionStats: (originalFrames: FrameData[], diffBasedAnimation: DiffBasedAnimation) => CompressionStats;
|
|
74
|
+
/**
|
|
75
|
+
* Validate that a diff-based animation can be properly expanded
|
|
76
|
+
* Supports both new array format and old object format
|
|
77
|
+
* @param diffBasedAnimation - Diff-based animation to validate
|
|
78
|
+
* @returns True if valid
|
|
79
|
+
*/
|
|
80
|
+
export declare const validateDiffBasedAnimation: (diffBasedAnimation: AnimationData) => boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Helper to check if animation data is in diff format
|
|
83
|
+
* Detects both new array format and old object format
|
|
84
|
+
* @param animationData - Animation data to check
|
|
85
|
+
* @returns True if in diff format
|
|
86
|
+
*/
|
|
87
|
+
export declare const isDiffBasedFormat: (animationData: any) => animationData is AnimationData;
|
|
88
|
+
/**
|
|
89
|
+
* Smart loader that handles both formats
|
|
90
|
+
* @param animationData - Animation data in either format
|
|
91
|
+
* @returns Full-state frames array
|
|
92
|
+
*/
|
|
93
|
+
export declare const loadAnimation: (animationData: any) => FrameData[];
|
|
94
|
+
/**
|
|
95
|
+
* Export animation in diff format with optional compression stats
|
|
96
|
+
* @param frames - Full-state frames
|
|
97
|
+
* @param showStats - Whether to log compression stats
|
|
98
|
+
* @returns Diff-based animation
|
|
99
|
+
*/
|
|
100
|
+
export declare const exportDiffAnimation: (frames: FrameData[], showStats?: boolean) => DiffBasedAnimation;
|
|
101
|
+
//# sourceMappingURL=animationDiff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animationDiff.d.ts","sourceRoot":"","sources":["../../modules/animationDiff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,EAAE,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AA6FD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,QAAQ,SAAS,EAAE,KAAG,kBAiB9D,CAAC;AA+BF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GAAI,oBAAoB,aAAa,KAAG,SAAS,EAyCrF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GAClC,gBAAgB,SAAS,EAAE,EAC3B,oBAAoB,kBAAkB,KACvC,gBAWF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GAAI,oBAAoB,aAAa,KAAG,OAmB9E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,eAAe,GAAG,KAAG,aAAa,IAAI,aAgDvE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,eAAe,GAAG,KAAG,SAAS,EAiB3D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,SAAS,EAAE,EAAE,YAAW,OAAc,KAAG,kBASpF,CAAC"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation Diff Utility
|
|
3
|
+
*
|
|
4
|
+
* Converts animation frames from full-state to diff-based format to reduce file size.
|
|
5
|
+
* Instead of storing complete frame data for each frame, only stores changes from the previous frame.
|
|
6
|
+
*
|
|
7
|
+
* Format (compact array):
|
|
8
|
+
* [
|
|
9
|
+
* { ...complete first frame data... }, // Index 0: Base frame (full data)
|
|
10
|
+
* { rotationValues: { head: 0.5 }, ... }, // Index 1: Only changed values
|
|
11
|
+
* { selfRotationValues: { leftArm: 0.2 } }, // Index 2: Only changed values
|
|
12
|
+
* ...
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* Also supports legacy verbose format:
|
|
16
|
+
* {
|
|
17
|
+
* version: "1.0",
|
|
18
|
+
* baseFrame: { ...complete first frame data... },
|
|
19
|
+
* diffs: [null, {...}, {...}, ...]
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Deep comparison to check if two values are equal
|
|
24
|
+
*/
|
|
25
|
+
const isEqual = (a, b) => {
|
|
26
|
+
if (a === b)
|
|
27
|
+
return true;
|
|
28
|
+
if (a == null || b == null)
|
|
29
|
+
return false;
|
|
30
|
+
if (typeof a !== typeof b)
|
|
31
|
+
return false;
|
|
32
|
+
if (typeof a === 'object') {
|
|
33
|
+
const keysA = Object.keys(a);
|
|
34
|
+
const keysB = Object.keys(b);
|
|
35
|
+
if (keysA.length !== keysB.length)
|
|
36
|
+
return false;
|
|
37
|
+
return keysA.every(key => isEqual(a[key], b[key]));
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Create a diff object containing only changed properties
|
|
43
|
+
* @param prevFrame - Previous frame data
|
|
44
|
+
* @param currentFrame - Current frame data
|
|
45
|
+
* @returns Diff object or null if no changes
|
|
46
|
+
*/
|
|
47
|
+
const createFrameDiff = (prevFrame, currentFrame) => {
|
|
48
|
+
if (!prevFrame || !currentFrame)
|
|
49
|
+
return currentFrame;
|
|
50
|
+
const diff = {};
|
|
51
|
+
let hasChanges = false;
|
|
52
|
+
// Properties that might change between frames
|
|
53
|
+
const diffableProps = [
|
|
54
|
+
'rotationValues',
|
|
55
|
+
'selfRotationValues',
|
|
56
|
+
'dimensionValues',
|
|
57
|
+
'pivotPoints',
|
|
58
|
+
'jointOffset',
|
|
59
|
+
'zIndexValues',
|
|
60
|
+
'eyes',
|
|
61
|
+
'mouth',
|
|
62
|
+
'visibility',
|
|
63
|
+
'imageScale',
|
|
64
|
+
'flipX',
|
|
65
|
+
'flipHead'
|
|
66
|
+
];
|
|
67
|
+
diffableProps.forEach(prop => {
|
|
68
|
+
if (!currentFrame.hasOwnProperty(prop))
|
|
69
|
+
return;
|
|
70
|
+
const prevValue = prevFrame[prop];
|
|
71
|
+
const currentValue = currentFrame[prop];
|
|
72
|
+
// If the property doesn't exist in previous frame, include it entirely
|
|
73
|
+
if (!prevFrame.hasOwnProperty(prop)) {
|
|
74
|
+
diff[prop] = currentValue;
|
|
75
|
+
hasChanges = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// For objects, diff at the property level
|
|
79
|
+
if (typeof currentValue === 'object' && !Array.isArray(currentValue) && currentValue !== null) {
|
|
80
|
+
const propDiff = {};
|
|
81
|
+
let hasPropChanges = false;
|
|
82
|
+
Object.keys(currentValue).forEach(key => {
|
|
83
|
+
const prevVal = prevValue?.[key];
|
|
84
|
+
const currVal = currentValue[key];
|
|
85
|
+
if (!isEqual(prevVal, currVal)) {
|
|
86
|
+
propDiff[key] = currVal;
|
|
87
|
+
hasPropChanges = true;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (hasPropChanges) {
|
|
91
|
+
diff[prop] = propDiff;
|
|
92
|
+
hasChanges = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// For non-objects, include if different
|
|
97
|
+
if (!isEqual(prevValue, currentValue)) {
|
|
98
|
+
diff[prop] = currentValue;
|
|
99
|
+
hasChanges = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return hasChanges ? diff : null;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Convert full-state animation frames to diff-based format
|
|
107
|
+
* @param frames - Array of complete frame objects
|
|
108
|
+
* @returns Diff-based animation array [baseFrame, diff1, diff2, ...]
|
|
109
|
+
*/
|
|
110
|
+
export const createDiffBasedAnimation = (frames) => {
|
|
111
|
+
if (!frames || frames.length === 0) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
// First element is the complete base frame
|
|
115
|
+
const result = [{ ...frames[0] }];
|
|
116
|
+
// Subsequent elements are diffs from the previous frame
|
|
117
|
+
for (let i = 1; i < frames.length; i++) {
|
|
118
|
+
const prevFrame = frames[i - 1];
|
|
119
|
+
const currentFrame = frames[i];
|
|
120
|
+
const diff = createFrameDiff(prevFrame, currentFrame);
|
|
121
|
+
result.push(diff || {}); // Use empty object if no changes
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Apply a diff to a frame to get the next frame state
|
|
127
|
+
* @param baseFrame - Base frame to apply diff to
|
|
128
|
+
* @param diff - Diff object
|
|
129
|
+
* @returns Resulting frame
|
|
130
|
+
*/
|
|
131
|
+
const applyDiff = (baseFrame, diff) => {
|
|
132
|
+
if (!diff)
|
|
133
|
+
return { ...baseFrame };
|
|
134
|
+
const result = { ...baseFrame };
|
|
135
|
+
Object.keys(diff).forEach(prop => {
|
|
136
|
+
const diffValue = diff[prop];
|
|
137
|
+
// If the diff value is an object, merge it with the base
|
|
138
|
+
if (typeof diffValue === 'object' && !Array.isArray(diffValue) && diffValue !== null) {
|
|
139
|
+
result[prop] = {
|
|
140
|
+
...result[prop],
|
|
141
|
+
...diffValue
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Otherwise, replace entirely
|
|
146
|
+
result[prop] = diffValue;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return result;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Convert diff-based animation back to full-state frames
|
|
153
|
+
* Supports both formats:
|
|
154
|
+
* - New format: [baseFrame, diff1, diff2, ...]
|
|
155
|
+
* - Old format: { baseFrame, diffs: [...] }
|
|
156
|
+
* @param diffBasedAnimation - Diff-based animation
|
|
157
|
+
* @returns Array of complete frame objects
|
|
158
|
+
*/
|
|
159
|
+
export const expandDiffBasedAnimation = (diffBasedAnimation) => {
|
|
160
|
+
if (!diffBasedAnimation) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
// Handle new compact array format: [baseFrame, diff1, diff2, ...]
|
|
164
|
+
if (Array.isArray(diffBasedAnimation)) {
|
|
165
|
+
if (diffBasedAnimation.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
const frames = [];
|
|
168
|
+
let currentFrame = { ...diffBasedAnimation[0] }; // First element is base frame
|
|
169
|
+
frames.push(currentFrame);
|
|
170
|
+
// Apply each subsequent diff
|
|
171
|
+
for (let i = 1; i < diffBasedAnimation.length; i++) {
|
|
172
|
+
currentFrame = applyDiff(currentFrame, diffBasedAnimation[i]);
|
|
173
|
+
frames.push({ ...currentFrame });
|
|
174
|
+
}
|
|
175
|
+
return frames;
|
|
176
|
+
}
|
|
177
|
+
// Handle old verbose format: { baseFrame, diffs: [...] }
|
|
178
|
+
const verboseFormat = diffBasedAnimation;
|
|
179
|
+
if (verboseFormat.baseFrame && verboseFormat.diffs) {
|
|
180
|
+
const { baseFrame, diffs } = verboseFormat;
|
|
181
|
+
const frames = [];
|
|
182
|
+
let currentFrame = { ...baseFrame };
|
|
183
|
+
frames.push(currentFrame);
|
|
184
|
+
// Apply each diff to reconstruct frames
|
|
185
|
+
for (let i = 1; i < diffs.length; i++) {
|
|
186
|
+
currentFrame = applyDiff(currentFrame, diffs[i]);
|
|
187
|
+
frames.push({ ...currentFrame });
|
|
188
|
+
}
|
|
189
|
+
return frames;
|
|
190
|
+
}
|
|
191
|
+
return [];
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Calculate compression ratio
|
|
195
|
+
* @param originalFrames - Original full-state frames
|
|
196
|
+
* @param diffBasedAnimation - Diff-based animation
|
|
197
|
+
* @returns Statistics about compression
|
|
198
|
+
*/
|
|
199
|
+
export const calculateCompressionStats = (originalFrames, diffBasedAnimation) => {
|
|
200
|
+
const originalSize = JSON.stringify(originalFrames).length;
|
|
201
|
+
const compressedSize = JSON.stringify(diffBasedAnimation).length;
|
|
202
|
+
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
|
|
203
|
+
return {
|
|
204
|
+
originalSize,
|
|
205
|
+
compressedSize,
|
|
206
|
+
savedBytes: originalSize - compressedSize,
|
|
207
|
+
compressionRatio: ratio + '%'
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Validate that a diff-based animation can be properly expanded
|
|
212
|
+
* Supports both new array format and old object format
|
|
213
|
+
* @param diffBasedAnimation - Diff-based animation to validate
|
|
214
|
+
* @returns True if valid
|
|
215
|
+
*/
|
|
216
|
+
export const validateDiffBasedAnimation = (diffBasedAnimation) => {
|
|
217
|
+
if (!diffBasedAnimation)
|
|
218
|
+
return false;
|
|
219
|
+
// New compact array format: [baseFrame, diff1, diff2, ...]
|
|
220
|
+
if (Array.isArray(diffBasedAnimation)) {
|
|
221
|
+
if (diffBasedAnimation.length === 0)
|
|
222
|
+
return false;
|
|
223
|
+
// First element should be a complete frame (has rotationValues or other base properties)
|
|
224
|
+
if (!diffBasedAnimation[0] || typeof diffBasedAnimation[0] !== 'object')
|
|
225
|
+
return false;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
// Old verbose format: { version, baseFrame, diffs }
|
|
229
|
+
const verboseFormat = diffBasedAnimation;
|
|
230
|
+
if (!verboseFormat.version)
|
|
231
|
+
return false;
|
|
232
|
+
if (!verboseFormat.baseFrame)
|
|
233
|
+
return false;
|
|
234
|
+
if (!Array.isArray(verboseFormat.diffs))
|
|
235
|
+
return false;
|
|
236
|
+
if (verboseFormat.diffs.length === 0)
|
|
237
|
+
return false;
|
|
238
|
+
return true;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Helper to check if animation data is in diff format
|
|
242
|
+
* Detects both new array format and old object format
|
|
243
|
+
* @param animationData - Animation data to check
|
|
244
|
+
* @returns True if in diff format
|
|
245
|
+
*/
|
|
246
|
+
export const isDiffBasedFormat = (animationData) => {
|
|
247
|
+
if (!animationData)
|
|
248
|
+
return false;
|
|
249
|
+
// New compact array format: [baseFrame, diff1, diff2, ...]
|
|
250
|
+
// Heuristic: If it's an array where first element has full frame data
|
|
251
|
+
// and second element (if exists) has partial data, it's likely diff format
|
|
252
|
+
if (Array.isArray(animationData)) {
|
|
253
|
+
if (animationData.length === 0)
|
|
254
|
+
return false;
|
|
255
|
+
const firstFrame = animationData[0];
|
|
256
|
+
if (!firstFrame || typeof firstFrame !== 'object')
|
|
257
|
+
return false;
|
|
258
|
+
// If first frame has rotationValues or other key properties, check if it's diff format
|
|
259
|
+
const hasKeyProperties = firstFrame.rotationValues ||
|
|
260
|
+
firstFrame.dimensionValues ||
|
|
261
|
+
firstFrame.imagePaths ||
|
|
262
|
+
firstFrame.rotation !== undefined ||
|
|
263
|
+
firstFrame.width !== undefined;
|
|
264
|
+
if (hasKeyProperties) {
|
|
265
|
+
// If there's a second element
|
|
266
|
+
if (animationData.length > 1) {
|
|
267
|
+
const secondFrame = animationData[1];
|
|
268
|
+
// If second frame has fewer top-level keys than first, likely a diff
|
|
269
|
+
const firstKeys = Object.keys(firstFrame).length;
|
|
270
|
+
const secondKeys = Object.keys(secondFrame).length;
|
|
271
|
+
// Diff format has fewer keys in subsequent frames (only changed properties)
|
|
272
|
+
if (secondKeys < firstKeys)
|
|
273
|
+
return true;
|
|
274
|
+
// Also check if second frame is missing metadata that first frame has
|
|
275
|
+
if (firstFrame.imagePaths && !secondFrame.imagePaths)
|
|
276
|
+
return true;
|
|
277
|
+
if (firstFrame.imageScale !== undefined && !secondFrame.hasOwnProperty('imageScale'))
|
|
278
|
+
return true;
|
|
279
|
+
if (firstFrame.dimensionValues && !secondFrame.dimensionValues)
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
// Single frame could be either format, default to full-state
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Old verbose format: { version, baseFrame, diffs }
|
|
287
|
+
return (animationData &&
|
|
288
|
+
typeof animationData === 'object' &&
|
|
289
|
+
'baseFrame' in animationData &&
|
|
290
|
+
'diffs' in animationData);
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Smart loader that handles both formats
|
|
294
|
+
* @param animationData - Animation data in either format
|
|
295
|
+
* @returns Full-state frames array
|
|
296
|
+
*/
|
|
297
|
+
export const loadAnimation = (animationData) => {
|
|
298
|
+
if (!animationData)
|
|
299
|
+
return [];
|
|
300
|
+
// Check if it's diff-based format first
|
|
301
|
+
if (isDiffBasedFormat(animationData)) {
|
|
302
|
+
console.log('📊 Detected diff-based animation format, expanding...');
|
|
303
|
+
return expandDiffBasedAnimation(animationData);
|
|
304
|
+
}
|
|
305
|
+
// If it's already an array and not diff-based, assume it's full-state format
|
|
306
|
+
if (Array.isArray(animationData)) {
|
|
307
|
+
return animationData;
|
|
308
|
+
}
|
|
309
|
+
// Unknown format
|
|
310
|
+
console.warn('Unknown animation format');
|
|
311
|
+
return [];
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Export animation in diff format with optional compression stats
|
|
315
|
+
* @param frames - Full-state frames
|
|
316
|
+
* @param showStats - Whether to log compression stats
|
|
317
|
+
* @returns Diff-based animation
|
|
318
|
+
*/
|
|
319
|
+
export const exportDiffAnimation = (frames, showStats = true) => {
|
|
320
|
+
const diffAnimation = createDiffBasedAnimation(frames);
|
|
321
|
+
if (showStats && frames.length > 0) {
|
|
322
|
+
const stats = calculateCompressionStats(frames, diffAnimation);
|
|
323
|
+
console.log('Animation Compression Stats:', stats);
|
|
324
|
+
}
|
|
325
|
+
return diffAnimation;
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=animationDiff.js.map
|