@starcried/starlite-vrm 0.0.0-stage → 0.1.0-alpha.2

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/config.js ADDED
@@ -0,0 +1,114 @@
1
+ import { resolveDialogueSentences } from "@starcried/starlite-core";
2
+ function finite(value, min, max) {
3
+ return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
4
+ }
5
+ export function validateVrmWidgetConfig(config) {
6
+ if (!config || (config.backend !== undefined && config.backend !== "vrm"))
7
+ throw new Error("invalid VRM backend");
8
+ if (config.interactions?.passThrough !== undefined && typeof config.interactions.passThrough !== "boolean")
9
+ throw new Error("interactions.passThrough must be boolean");
10
+ if (config.interactions?.passThrough && config.interactions.drag)
11
+ throw new Error("passThrough and drag cannot both be enabled");
12
+ for (const key of ["model", "fallback"])
13
+ if (typeof config[key] !== "string" || !config[key].trim())
14
+ throw new Error(`${key} is required`);
15
+ if (config.render?.fps !== undefined && config.render.fps !== 30 && config.render.fps !== 60 && config.render.fps !== "display")
16
+ throw new Error("frame rate must be 30, 60 or display");
17
+ const checks = [
18
+ [config.render?.maxDevicePixelRatio ?? 1.5, 1, 1.5], [config.render?.initialScale ?? 1, .25, 3],
19
+ [config.render?.fitPadding ?? .9, .1, 1], [config.display?.width ?? 320, 1, 4096], [config.display?.height ?? 480, 1, 4096],
20
+ [config.camera?.yawDegrees ?? 0, -45, 45], [config.camera?.pitchDegrees ?? 0, -20, 20],
21
+ [config.camera?.fovDegrees ?? 30, 15, 50], [config.camera?.targetHeight ?? .5, .35, .65],
22
+ [config.gaze?.maxYawDegrees ?? 25, 0, 45], [config.gaze?.maxPitchDegrees ?? 15, 0, 30],
23
+ [config.gaze?.smoothingMs ?? 100, 20, 1000],
24
+ [config.mobile?.breakpoint ?? 768, 1, 4096], [config.network?.requestTimeoutMs ?? 30000, 100, 120000],
25
+ [config.network?.modelMaxBytes ?? 64 * 1024 * 1024, 1, 64 * 1024 * 1024],
26
+ ];
27
+ if (checks.some(([value, min, max]) => !finite(value, min, max)))
28
+ throw new Error("invalid VRM display or network limits");
29
+ const padding = config.render?.viewportPadding;
30
+ if (padding !== undefined) {
31
+ if (!padding || typeof padding !== "object" || Array.isArray(padding)
32
+ || [padding.left, padding.top, padding.right, padding.bottom].some(value => !finite(value, 0, 1024))
33
+ || (config.display?.width ?? 320) + padding.left + padding.right > 4096
34
+ || (config.display?.height ?? 480) + padding.top + padding.bottom > 4096)
35
+ throw new Error("invalid fixed viewport padding");
36
+ }
37
+ const animations = config.animations ?? [];
38
+ if (!Array.isArray(animations) || animations.length > 64)
39
+ throw new Error("at most 64 animations are supported");
40
+ const names = new Set();
41
+ if (config.embeddedAnimations !== undefined && (!Array.isArray(config.embeddedAnimations) || config.embeddedAnimations.length > 64))
42
+ throw new Error("invalid embedded animation catalog");
43
+ for (const name of config.embeddedAnimations ?? []) {
44
+ if (typeof name !== "string" || !name.trim() || name.length > 128 || names.has(name))
45
+ throw new Error("invalid or duplicate embedded animation");
46
+ names.add(name);
47
+ }
48
+ for (const animation of animations) {
49
+ if (!animation || typeof animation.name !== "string" || !animation.name.trim() || animation.name.length > 128 || names.has(animation.name) || typeof animation.url !== "string" || !animation.url.trim())
50
+ throw new Error("invalid or duplicate animation");
51
+ if (animation.durationMs !== undefined && !finite(animation.durationMs, 1, 600000))
52
+ throw new Error("invalid animation duration");
53
+ names.add(animation.name);
54
+ }
55
+ names.add("StarLiteIdle");
56
+ for (const name of config.ambient?.animations ?? [])
57
+ if (!names.has(name))
58
+ throw new Error(`unknown ambient animation: ${name}`);
59
+ if ((config.ambient?.animations?.length ?? 0) > 1)
60
+ throw new Error("VRM supports one explicitly configured ambient clip");
61
+ if (config.neutralAnimation !== undefined && !names.has(config.neutralAnimation))
62
+ throw new Error("unknown neutral animation");
63
+ const click = config.interactions?.click;
64
+ const pool = (value) => Array.isArray(value) && value.length > 0 && value.length <= 20 && new Set(value).size === value.length && value.every(name => typeof name === "string" && names.has(name));
65
+ if (click && (click.animations !== undefined ? !pool(click.animations) : !click.animation || !names.has(click.animation)))
66
+ throw new Error("Click must name available animations");
67
+ for (const [name, scene] of Object.entries(config.dialogue ?? {})) {
68
+ if (!scene)
69
+ continue;
70
+ if (scene.animations !== undefined && !pool(scene.animations))
71
+ throw new Error("invalid dialogue animation pool");
72
+ if (scene.animation !== undefined && !names.has(scene.animation))
73
+ throw new Error("invalid dialogue scene");
74
+ resolveDialogueSentences(scene, scene.animations ?? (scene.animation ? [scene.animation] : []), `dialogue.${name}`);
75
+ }
76
+ if (config.profile && typeof config.profile !== "string")
77
+ validateCharacterProfile(config.profile);
78
+ }
79
+ export function validateCharacterProfile(input) {
80
+ if (!input || typeof input !== "object")
81
+ throw new Error("invalid character profile");
82
+ const profile = input;
83
+ if (profile.schemaVersion !== 1 || profile.type !== "weiguang")
84
+ throw new Error("unsupported character profile");
85
+ const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
86
+ const pose = (value) => {
87
+ if (!record(value) || Object.keys(value).length > 256)
88
+ return false;
89
+ return Object.values(value).every((item) => Array.isArray(item?.rotation) && item.rotation.length === 4 && item.rotation.every((v) => finite(v, -1, 1)) && Math.abs(Math.hypot(...item.rotation) - 1) < .01 && (item.position === undefined || (Array.isArray(item.position) && item.position.length === 3 && item.position.every((v) => finite(v, -10, 10)))));
90
+ };
91
+ if (!pose(profile.neutralPose) || !record(profile.earPoses) || !Object.values(profile.earPoses).every(pose))
92
+ throw new Error("invalid character bone poses");
93
+ if (!Array.isArray(profile.earBones) || profile.earBones.length > 32 || profile.earBones.some((name) => typeof name !== "string" || !name))
94
+ throw new Error("invalid ear bones");
95
+ if (!record(profile.expressions) || Object.keys(profile.expressions).length > 64 || !Object.values(profile.expressions).every((v) => typeof v?.expression === "string" && (v.blink === undefined || typeof v.blink === "string")))
96
+ throw new Error("invalid expressions");
97
+ if (profile.neutralAnimation !== undefined && (typeof profile.neutralAnimation !== "string" || !profile.neutralAnimation.trim() || profile.neutralAnimation.length > 128))
98
+ throw new Error("invalid neutral animation");
99
+ for (const emotion of ["Neutral", ...Object.keys(profile.expressions)])
100
+ if (!profile.earPoses[emotion] || profile.earBones.some(name => !profile.earPoses[emotion]?.[name]))
101
+ throw new Error("incomplete ear poses");
102
+ const a = profile.accessories;
103
+ if (!a || typeof a.collar !== "string" || typeof a.cone !== "string" || typeof a.collarVisible !== "boolean" || typeof a.coneVisible !== "boolean")
104
+ throw new Error("invalid accessories");
105
+ const blink = profile.blink;
106
+ if (!blink || !finite(blink.period, 1, 60) || !Array.isArray(blink.curve) || blink.curve.length < 2 || blink.curve.length > 64)
107
+ throw new Error("invalid blink timing");
108
+ let previous = -1;
109
+ for (const point of blink.curve) {
110
+ if (!Array.isArray(point) || point.length !== 2 || !finite(point[0], 0, blink.period) || point[0] <= previous || !finite(point[1], 0, 1))
111
+ throw new Error("invalid blink curve");
112
+ previous = point[0];
113
+ }
114
+ }
@@ -0,0 +1 @@
1
+ export declare const defaultIdleBase64: string;