@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/DEFAULT_IDLE.md +21 -0
- package/LICENSE +25 -0
- package/README.md +45 -2
- package/THIRD_PARTY_LICENSES.txt +80 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/camera.d.ts +17 -0
- package/dist/camera.js +112 -0
- package/dist/character.d.ts +10 -0
- package/dist/character.js +145 -0
- package/dist/config.cjs +152 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +114 -0
- package/dist/default-idle-data.d.ts +1 -0
- package/dist/default-idle-data.js +2 -0
- package/dist/default-idle.d.ts +2 -0
- package/dist/default-idle.js +5 -0
- package/dist/embedded.d.ts +11 -0
- package/dist/embedded.js +154 -0
- package/dist/gaze.d.ts +28 -0
- package/dist/gaze.js +102 -0
- package/dist/host-motion.d.ts +14 -0
- package/dist/host-motion.js +97 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5243 -0
- package/dist/inspect.cjs +245 -0
- package/dist/inspect.d.ts +21 -0
- package/dist/inspect.js +272 -0
- package/dist/playback.d.ts +17 -0
- package/dist/playback.js +122 -0
- package/dist/pose-bounds.d.ts +8 -0
- package/dist/pose-bounds.js +80 -0
- package/dist/prepare.cjs +387 -0
- package/dist/prepare.d.ts +42 -0
- package/dist/prepare.js +134 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.js +1 -0
- package/dist/widget.css +69 -0
- package/package.json +56 -4
package/dist/prepare.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
+
import { inspectVrm, inspectVrma, MAX_MODEL_BYTES, MAX_ANIMATION_BYTES } from "./inspect.js";
|
|
6
|
+
import { validateCharacterProfile } from "./config.js";
|
|
7
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
8
|
+
const arrayBuffer = (bytes) => Uint8Array.from(bytes).buffer;
|
|
9
|
+
async function bounded(path, maximum) {
|
|
10
|
+
const info = await lstat(path);
|
|
11
|
+
if (!info.isFile() || info.size > maximum)
|
|
12
|
+
throw new Error("invalid asset size or file type");
|
|
13
|
+
const bytes = await readFile(path);
|
|
14
|
+
if (bytes.byteLength > maximum)
|
|
15
|
+
throw new Error("asset exceeds byte limit");
|
|
16
|
+
return bytes;
|
|
17
|
+
}
|
|
18
|
+
function animationNames(animations) {
|
|
19
|
+
if (!Array.isArray(animations) || animations.length > 64)
|
|
20
|
+
throw new Error("invalid animation collection");
|
|
21
|
+
const names = new Set();
|
|
22
|
+
for (const a of animations) {
|
|
23
|
+
if (!a || typeof a.name !== "string" || !a.name.trim() || a.name.length > 128 || names.has(a.name))
|
|
24
|
+
throw new Error("duplicate or invalid animation name");
|
|
25
|
+
names.add(a.name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function profileNames(profile, animations) {
|
|
29
|
+
if (profile.neutralAnimation && !animations.some(a => a.name === profile.neutralAnimation))
|
|
30
|
+
throw new Error("profile neutral animation is missing");
|
|
31
|
+
}
|
|
32
|
+
/** Validate every source before creating output. Never overwrite an existing bundle. */
|
|
33
|
+
export async function prepareModel(request) {
|
|
34
|
+
const bytes = await bounded(request.model, MAX_MODEL_BYTES);
|
|
35
|
+
const inspected = inspectVrm(arrayBuffer(bytes));
|
|
36
|
+
const animations = request.animations ?? [];
|
|
37
|
+
animationNames(animations);
|
|
38
|
+
const files = new Map([["model.vrm", bytes]]);
|
|
39
|
+
const record = (file, data) => ({ file, sha256: hash(data), bytes: data.byteLength });
|
|
40
|
+
const manifest = { schemaVersion: 1, tool: "vrm-companion-assets", backend: "vrm", model: { ...record("model.vrm", bytes), sourceSha256: hash(bytes) }, animations: [], publication: { allowRedistribution: request.allowRedistribution === true } };
|
|
41
|
+
let total = 0;
|
|
42
|
+
for (const [i, source] of animations.entries()) {
|
|
43
|
+
const data = await bounded(source.path, MAX_ANIMATION_BYTES);
|
|
44
|
+
total += data.byteLength;
|
|
45
|
+
if (total > 64 * 1024 * 1024)
|
|
46
|
+
throw new Error("animation collection exceeds 64 MiB");
|
|
47
|
+
const { durationMs } = inspectVrma(arrayBuffer(data));
|
|
48
|
+
const file = `motions/${i}.vrma`;
|
|
49
|
+
files.set(file, data);
|
|
50
|
+
manifest.animations.push({ ...record(file, data), name: source.name, durationMs });
|
|
51
|
+
}
|
|
52
|
+
if (request.profile) {
|
|
53
|
+
const data = await bounded(request.profile, 1024 * 1024);
|
|
54
|
+
const profile = JSON.parse(data.toString("utf8"));
|
|
55
|
+
validateCharacterProfile(profile);
|
|
56
|
+
profileNames(profile, [...animations, ...inspected.animations, { name: "StarLiteIdle" }]);
|
|
57
|
+
files.set("profile.json", data);
|
|
58
|
+
manifest.profile = record("profile.json", data);
|
|
59
|
+
}
|
|
60
|
+
const output = resolve(request.output);
|
|
61
|
+
await mkdir(dirname(output), { recursive: true });
|
|
62
|
+
try {
|
|
63
|
+
await lstat(output);
|
|
64
|
+
throw new Error("output already exists");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== "ENOENT")
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const stage = await mkdtemp(join(dirname(output), ".vrm-stage-"));
|
|
71
|
+
try {
|
|
72
|
+
for (const [file, data] of files) {
|
|
73
|
+
const path = join(stage, file);
|
|
74
|
+
await mkdir(dirname(path), { recursive: true });
|
|
75
|
+
await writeFile(path, data, { flag: "wx" });
|
|
76
|
+
}
|
|
77
|
+
await writeFile(join(stage, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", { flag: "wx" });
|
|
78
|
+
await rename(stage, output);
|
|
79
|
+
return manifest;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
await rm(stage, { recursive: true, force: true });
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Read only declared, hashed files contained in the prepared bundle. */
|
|
87
|
+
export async function readPreparedBundle(directory) { return readPreparedBundleSync(directory); }
|
|
88
|
+
function boundedSync(path, maximum) {
|
|
89
|
+
const info = lstatSync(path);
|
|
90
|
+
if (!info.isFile() || info.size > maximum)
|
|
91
|
+
throw new Error("invalid asset size or file type");
|
|
92
|
+
const bytes = readFileSync(path);
|
|
93
|
+
if (bytes.byteLength > maximum)
|
|
94
|
+
throw new Error("asset exceeds byte limit");
|
|
95
|
+
return bytes;
|
|
96
|
+
}
|
|
97
|
+
export function readPreparedBundleSync(directory) {
|
|
98
|
+
const root = realpathSync(directory), raw = boundedSync(join(root, "manifest.json"), 1024 * 1024);
|
|
99
|
+
const manifest = JSON.parse(raw.toString("utf8"));
|
|
100
|
+
if (manifest.schemaVersion !== 1 || manifest.tool !== "vrm-companion-assets" || manifest.backend !== "vrm" || typeof manifest.publication?.allowRedistribution !== "boolean" || !/^[a-f0-9]{64}$/.test(manifest.model?.sourceSha256))
|
|
101
|
+
throw new Error("invalid VRM manifest");
|
|
102
|
+
animationNames(manifest.animations);
|
|
103
|
+
const files = new Map();
|
|
104
|
+
const load = (item, maximum) => {
|
|
105
|
+
if (!item || typeof item.file !== "string" || !item.file || isAbsolute(item.file) || /[\\:?%#]/.test(item.file) || item.file.split("/").some(p => !p || p === "." || p === "..") || files.has(item.file) || !/^[a-f0-9]{64}$/.test(item.sha256) || !Number.isSafeInteger(item.bytes) || item.bytes < 1 || item.bytes > maximum)
|
|
106
|
+
throw new Error("invalid manifest asset");
|
|
107
|
+
const path = realpathSync(join(root, item.file)), part = relative(root, path);
|
|
108
|
+
if (isAbsolute(part) || part === ".." || part.startsWith("../") || part.startsWith("..\\"))
|
|
109
|
+
throw new Error("manifest asset leaves bundle");
|
|
110
|
+
const bytes = boundedSync(path, maximum);
|
|
111
|
+
if (bytes.byteLength !== item.bytes || hash(bytes) !== item.sha256)
|
|
112
|
+
throw new Error("manifest asset hash mismatch");
|
|
113
|
+
files.set(item.file, bytes);
|
|
114
|
+
return bytes;
|
|
115
|
+
};
|
|
116
|
+
const inspected = inspectVrm(arrayBuffer(load(manifest.model, MAX_MODEL_BYTES)));
|
|
117
|
+
let total = 0;
|
|
118
|
+
for (const item of manifest.animations) {
|
|
119
|
+
const bytes = load(item, MAX_ANIMATION_BYTES);
|
|
120
|
+
total += bytes.byteLength;
|
|
121
|
+
if (total > 64 * 1024 * 1024)
|
|
122
|
+
throw new Error("animation collection exceeds 64 MiB");
|
|
123
|
+
const inspected = inspectVrma(arrayBuffer(bytes));
|
|
124
|
+
if (!Number.isFinite(item.durationMs) || Math.abs(inspected.durationMs - item.durationMs) > 1)
|
|
125
|
+
throw new Error("manifest animation duration mismatch");
|
|
126
|
+
}
|
|
127
|
+
if (manifest.profile) {
|
|
128
|
+
const bytes = load(manifest.profile, 1024 * 1024);
|
|
129
|
+
const profile = JSON.parse(bytes.toString("utf8"));
|
|
130
|
+
validateCharacterProfile(profile);
|
|
131
|
+
profileNames(profile, [...manifest.animations, ...inspected.animations, { name: "StarLiteIdle" }]);
|
|
132
|
+
}
|
|
133
|
+
return { manifest, files };
|
|
134
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { DialogueSceneConfig, ModelController } from "@starcried/starlite-core";
|
|
2
|
+
export type { ModelController, AnimationPlaybackResult, AnimationPlaybackOptions } from "@starcried/starlite-core";
|
|
3
|
+
export interface VrmAnimationSource {
|
|
4
|
+
name: string;
|
|
5
|
+
url: string;
|
|
6
|
+
durationMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface BonePose {
|
|
9
|
+
rotation: [number, number, number, number];
|
|
10
|
+
position?: [number, number, number];
|
|
11
|
+
}
|
|
12
|
+
/** Data only: imported profiles cannot execute scripts. All rotations use glTF coordinates. */
|
|
13
|
+
export interface CharacterProfile {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
type: "weiguang";
|
|
16
|
+
neutralPose: Record<string, BonePose>;
|
|
17
|
+
neutralAnimation?: string;
|
|
18
|
+
expressions: Record<string, {
|
|
19
|
+
expression: string;
|
|
20
|
+
blink?: string;
|
|
21
|
+
}>;
|
|
22
|
+
earPoses: Record<string, Record<string, BonePose>>;
|
|
23
|
+
earBones: string[];
|
|
24
|
+
accessories: {
|
|
25
|
+
collar: string;
|
|
26
|
+
cone: string;
|
|
27
|
+
collarVisible: boolean;
|
|
28
|
+
coneVisible: boolean;
|
|
29
|
+
};
|
|
30
|
+
blink: {
|
|
31
|
+
period: number;
|
|
32
|
+
curve: [number, number][];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface VrmWidgetConfig {
|
|
36
|
+
backend?: "vrm";
|
|
37
|
+
model: string;
|
|
38
|
+
fallback: string;
|
|
39
|
+
animations?: readonly VrmAnimationSource[];
|
|
40
|
+
/** Names returned by inspectVrm for animations embedded in the model. */
|
|
41
|
+
embeddedAnimations?: readonly string[];
|
|
42
|
+
/** Initial sample used as the relaxed pose when playback is stopped. */
|
|
43
|
+
neutralAnimation?: string;
|
|
44
|
+
profile?: CharacterProfile | string;
|
|
45
|
+
display?: {
|
|
46
|
+
width?: number;
|
|
47
|
+
height?: number;
|
|
48
|
+
};
|
|
49
|
+
render?: {
|
|
50
|
+
fps?: number | "display";
|
|
51
|
+
maxDevicePixelRatio?: number;
|
|
52
|
+
initialScale?: number;
|
|
53
|
+
autoFit?: boolean;
|
|
54
|
+
fitPadding?: number;
|
|
55
|
+
/** Fixed CSS pixel margins; when supplied, animation never resizes the canvas. */
|
|
56
|
+
viewportPadding?: {
|
|
57
|
+
left: number;
|
|
58
|
+
top: number;
|
|
59
|
+
right: number;
|
|
60
|
+
bottom: number;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
camera?: {
|
|
64
|
+
yawDegrees?: number;
|
|
65
|
+
pitchDegrees?: number;
|
|
66
|
+
fovDegrees?: number;
|
|
67
|
+
targetHeight?: number;
|
|
68
|
+
};
|
|
69
|
+
gaze?: {
|
|
70
|
+
maxYawDegrees?: number;
|
|
71
|
+
maxPitchDegrees?: number;
|
|
72
|
+
smoothingMs?: number;
|
|
73
|
+
};
|
|
74
|
+
mobile?: {
|
|
75
|
+
show?: boolean;
|
|
76
|
+
breakpoint?: number;
|
|
77
|
+
};
|
|
78
|
+
network?: {
|
|
79
|
+
requestTimeoutMs?: number;
|
|
80
|
+
modelMaxBytes?: number;
|
|
81
|
+
};
|
|
82
|
+
interactions?: {
|
|
83
|
+
pointer?: boolean;
|
|
84
|
+
click?: false | {
|
|
85
|
+
animation?: string;
|
|
86
|
+
animations?: readonly string[];
|
|
87
|
+
};
|
|
88
|
+
drag?: boolean;
|
|
89
|
+
/** Observe native page clicks without intercepting them; incompatible with drag. */
|
|
90
|
+
passThrough?: boolean;
|
|
91
|
+
};
|
|
92
|
+
ambient?: {
|
|
93
|
+
animations?: readonly string[];
|
|
94
|
+
};
|
|
95
|
+
dialogue?: {
|
|
96
|
+
enter?: false | (DialogueSceneConfig & {
|
|
97
|
+
delayMs?: number;
|
|
98
|
+
});
|
|
99
|
+
idle?: false | (DialogueSceneConfig & {
|
|
100
|
+
intervalMinMs?: number;
|
|
101
|
+
intervalMaxMs?: number;
|
|
102
|
+
});
|
|
103
|
+
click?: false | DialogueSceneConfig;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export interface ModelViewport {
|
|
107
|
+
left: number;
|
|
108
|
+
top: number;
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
baseWidth: number;
|
|
112
|
+
baseHeight: number;
|
|
113
|
+
}
|
|
114
|
+
export interface VrmDiagnostics {
|
|
115
|
+
renderedFrames: number;
|
|
116
|
+
frameTimesMs: number[];
|
|
117
|
+
renderCostsMs: number[];
|
|
118
|
+
calls: number;
|
|
119
|
+
triangles: number;
|
|
120
|
+
geometries: number;
|
|
121
|
+
textures: number;
|
|
122
|
+
renderer: string;
|
|
123
|
+
loadMs: number;
|
|
124
|
+
activeAnimation?: string | null;
|
|
125
|
+
gaze?: {
|
|
126
|
+
yawDegrees: number;
|
|
127
|
+
pitchDegrees: number;
|
|
128
|
+
headYawDegrees: number;
|
|
129
|
+
headPitchDegrees: number;
|
|
130
|
+
};
|
|
131
|
+
framing?: {
|
|
132
|
+
distance: number;
|
|
133
|
+
maxAbsX: number;
|
|
134
|
+
maxAbsY: number;
|
|
135
|
+
viewport: ModelViewport;
|
|
136
|
+
};
|
|
137
|
+
springPose?: readonly {
|
|
138
|
+
name: string;
|
|
139
|
+
rotation: [number, number, number, number];
|
|
140
|
+
}[];
|
|
141
|
+
}
|
|
142
|
+
export interface VrmController extends ModelController {
|
|
143
|
+
diagnostics(): VrmDiagnostics;
|
|
144
|
+
captureFrame(): string;
|
|
145
|
+
getExpressions(): readonly string[];
|
|
146
|
+
setExpression(name: string, weight: number): void;
|
|
147
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/widget.css
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.model-vrm-widget { position:relative; width:320px; height:480px; background:transparent; touch-action:none; }
|
|
2
|
+
.model-vrm-widget canvas { position:absolute; display:block; left:0; top:0; background:transparent; }
|
|
3
|
+
.model-vrm-widget[data-model-pass-through],
|
|
4
|
+
.model-vrm-widget[data-model-pass-through] > canvas,
|
|
5
|
+
.model-vrm-widget[data-model-pass-through] > img { pointer-events:none; touch-action:auto; }
|
|
6
|
+
.model-vrm-widget > img { display:block; width:100%; height:100%; object-fit:contain; }
|
|
7
|
+
.model-vrm-widget > img[hidden], .model-vrm-widget > canvas[hidden] { display:none; }
|
|
8
|
+
|
|
9
|
+
.model-widget__bubble {
|
|
10
|
+
--model-bubble-shift-x: 0px;
|
|
11
|
+
position: absolute;
|
|
12
|
+
left: 50%;
|
|
13
|
+
bottom: calc(100% + 12px);
|
|
14
|
+
z-index: 2;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
width: max-content;
|
|
17
|
+
max-width: min(280px, calc(100vw - 24px));
|
|
18
|
+
padding: 10px 14px;
|
|
19
|
+
border: 1px solid rgb(255 255 255 / 65%);
|
|
20
|
+
border-radius: 14px;
|
|
21
|
+
color: #20242b;
|
|
22
|
+
background: rgb(255 255 255 / 94%);
|
|
23
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
|
|
24
|
+
font: 14px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
25
|
+
overflow-wrap: anywhere;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
transform: translateX(calc(-50% + var(--model-bubble-shift-x)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.model-widget__bubble[data-model-placement="below"] {
|
|
31
|
+
top: calc(100% + 12px);
|
|
32
|
+
bottom: auto;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.model-widget__bubble::after {
|
|
36
|
+
position: absolute;
|
|
37
|
+
left: calc(50% - var(--model-bubble-shift-x));
|
|
38
|
+
bottom: -7px;
|
|
39
|
+
width: 12px;
|
|
40
|
+
height: 12px;
|
|
41
|
+
border-right: 1px solid rgb(255 255 255 / 65%);
|
|
42
|
+
border-bottom: 1px solid rgb(255 255 255 / 65%);
|
|
43
|
+
background: rgb(255 255 255 / 94%);
|
|
44
|
+
content: "";
|
|
45
|
+
transform: translateX(-50%) rotate(45deg);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.model-widget__bubble[data-model-placement="below"]::after {
|
|
49
|
+
top: -7px;
|
|
50
|
+
bottom: auto;
|
|
51
|
+
transform: translateX(-50%) rotate(225deg);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.model-widget__bubble[hidden] {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.model-widget__bubble-text {
|
|
59
|
+
display: -webkit-box;
|
|
60
|
+
-webkit-box-orient: vertical;
|
|
61
|
+
-webkit-line-clamp: 6;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
animation: model-bubble-reveal 180ms ease-out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@keyframes model-bubble-reveal { from { opacity: 0.35; } to { opacity: 1; } }
|
|
67
|
+
@media (prefers-reduced-motion: reduce) {
|
|
68
|
+
.model-widget__bubble-text { animation: none; }
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starcried/starlite-vrm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "BSD-2-Clause",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"README.md",
|
|
10
|
+
"DEFAULT_IDLE.md",
|
|
11
|
+
"THIRD_PARTY_NOTICES.md",
|
|
12
|
+
"THIRD_PARTY_LICENSES.txt"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./config": {
|
|
20
|
+
"types": "./dist/config.d.ts",
|
|
21
|
+
"import": "./dist/config.js",
|
|
22
|
+
"require": "./dist/config.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./inspect": {
|
|
25
|
+
"types": "./dist/inspect.d.ts",
|
|
26
|
+
"import": "./dist/inspect.js",
|
|
27
|
+
"require": "./dist/inspect.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./widget.css": "./dist/widget.css",
|
|
30
|
+
"./prepare": {
|
|
31
|
+
"types": "./dist/prepare.d.ts",
|
|
32
|
+
"default": "./dist/prepare.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.build.json && node scripts/build.mjs",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
38
|
+
"test": "vitest run"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@pixiv/three-vrm": "3.5.5",
|
|
42
|
+
"@pixiv/three-vrm-animation": "3.5.5",
|
|
43
|
+
"@starcried/starlite-core": "0.1.0-alpha.2",
|
|
44
|
+
"three": "0.186.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/three": "0.183.1",
|
|
48
|
+
"esbuild": "0.28.2"
|
|
49
|
+
},
|
|
50
|
+
"description": "WebGL2 VRM and VRMA backend for lightweight StarLite companions",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/StarCried/starlite.git",
|
|
54
|
+
"directory": "packages/vrm"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/StarCried/starlite#readme",
|
|
57
|
+
"bugs": "https://github.com/StarCried/starlite/issues"
|
|
58
|
+
}
|