lecodes-cli 0.16.1 → 0.17.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/README.md +27 -4
- package/dist/index.js +171621 -382
- package/package.json +13 -7
- package/runtime/sdk/compile/bundler.ts +10 -2
- package/runtime/sdk/compile/compileProject.ts +10 -2
- package/runtime/sdk/compile/detectEntry.ts +5 -2
- package/runtime/sdk/compile/index.ts +17 -1
- package/runtime/sdk/compile/serverSplit.ts +233 -0
- package/runtime/sdk/compile/serverTypes.ts +227 -0
- package/runtime/sdk/gl/AnimationClip.ts +174 -0
- package/runtime/sdk/gl/Animator.ts +642 -0
- package/runtime/sdk/gl/CharacterController.ts +6 -1
- package/runtime/sdk/gl/IK.ts +170 -0
- package/runtime/sdk/gl/Material.ts +1 -1
- package/runtime/sdk/gl/ModelAnimation.ts +34 -3
- package/runtime/sdk/gl/Node.ts +27 -1
- package/runtime/sdk/gl/Scene.ts +291 -289
- package/runtime/sdk/gl/state.ts +6 -6
- package/runtime/sdk/inject.ts +10 -0
- package/runtime/sdk/plugins/oauth.ts +61 -0
- package/runtime/sdk/runtime/rpc.ts +218 -0
- package/runtime/sdk/server/auth/appConfig.ts +12 -0
- package/runtime/sdk/server/auth/global.ts +80 -0
- package/runtime/sdk/server/auth/host.ts +318 -0
- package/runtime/sdk/server/auth/models.ts +83 -0
- package/runtime/sdk/server/auth/types.ts +50 -0
- package/runtime/sdk/server/channel.ts +56 -0
- package/runtime/sdk/server/context.ts +36 -0
- package/runtime/sdk/server/db/defineDb.ts +237 -0
- package/runtime/sdk/server/db/fields.ts +132 -0
- package/runtime/sdk/server/db/httpTransport.ts +93 -0
- package/runtime/sdk/server/db/index.ts +7 -0
- package/runtime/sdk/server/db/marci/query.ts +412 -0
- package/runtime/sdk/server/db/types.ts +202 -0
- package/runtime/sdk/server/errors.ts +12 -0
- package/runtime/sdk/server/host.ts +74 -0
- package/runtime/sdk/server/inject.ts +13 -0
- package/runtime/sdk/server/runtime.ts +133 -0
- package/runtime/sdk/server/validate.ts +87 -0
- package/runtime/sdk-types.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// AnimationClip — the unit of the animation system: immutable data (name, duration, tracks that
|
|
2
|
+
// target bone NAMES, not entities). One representation for a GLB's embedded clips (model.anim.clips),
|
|
3
|
+
// clips loaded from other files (Mixamo: one GLB per animation) and procedural clips built from
|
|
4
|
+
// curves. An Animator binds clips to a skeleton by name. See docs/animation-plan.md.
|
|
5
|
+
//
|
|
6
|
+
// const [hero, idle, run] = await Promise.all([
|
|
7
|
+
// Model.load(asset('./hero.glb')),
|
|
8
|
+
// AnimationClip.load(asset('./idle.glb')), // the file's only/first clip
|
|
9
|
+
// AnimationClip.load(asset('./run.glb')),
|
|
10
|
+
// ])
|
|
11
|
+
// const slash = await AnimationClip.load(asset('./attacks.glb'), 'Slash') // one out of many
|
|
12
|
+
// const bob = AnimationClip.from({ tracks: { Hips: { position: [[0, [0,0,0]], [1, [0,0.05,0]]] } } })
|
|
13
|
+
|
|
14
|
+
import { fetch, type FetchResponse } from "../runtime/fetch"
|
|
15
|
+
import type { Vec3Like } from "../math/vec"
|
|
16
|
+
import type { QuatLike } from "../math/quat"
|
|
17
|
+
|
|
18
|
+
/** A keyframe: `[time (s), value]`. */
|
|
19
|
+
export type ClipKey<V> = [number, V]
|
|
20
|
+
export type ClipInterpolation = "linear" | "step"
|
|
21
|
+
|
|
22
|
+
/** Curves for one target node (bone name), any subset of the three channels. */
|
|
23
|
+
export type ClipTrackDef = {
|
|
24
|
+
position?: ClipKey<Vec3Like | [number, number, number]>[]
|
|
25
|
+
rotation?: ClipKey<QuatLike | [number, number, number, number]>[]
|
|
26
|
+
scale?: ClipKey<Vec3Like | [number, number, number]>[]
|
|
27
|
+
interpolation?: ClipInterpolation
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ClipDef = {
|
|
31
|
+
/** Clip length in seconds — defaults to the last key time across all tracks. */
|
|
32
|
+
duration?: number
|
|
33
|
+
/** Target node name → its curves. Names bind to bones (or any child node) at Animator attach. */
|
|
34
|
+
tracks: Record<string, ClipTrackDef>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type ClipInfo = { name: string, duration: number, trackCount: number }
|
|
38
|
+
|
|
39
|
+
const PATH = { position: 0, rotation: 1, scale: 2 } as const
|
|
40
|
+
const INTERP = { linear: 0, step: 1 } as const
|
|
41
|
+
|
|
42
|
+
const readInfo = (setId: number): ClipInfo[] =>
|
|
43
|
+
((_creator.getClipSetInfo(setId) ?? []) as ClipInfo[]).map((c) => ({ name: c.name, duration: c.duration, trackCount: c.trackCount ?? 0 }))
|
|
44
|
+
|
|
45
|
+
const readValue = (v: unknown, comps: number, out: number[]): void => {
|
|
46
|
+
if (Array.isArray(v)) { for (let i = 0; i < comps; i++) out.push(Number(v[i] ?? (i === 3 ? 1 : 0))); return }
|
|
47
|
+
const o = v as Record<string, number>
|
|
48
|
+
out.push(o.x ?? 0, o.y ?? 0, o.z ?? 0)
|
|
49
|
+
if (comps === 4) out.push(o.w ?? 1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class AnimationClip {
|
|
53
|
+
/** Source name (the clip's name inside its file; `"clip"` for procedural clips). Informational —
|
|
54
|
+
* an Animator addresses clips by the key YOU give it. */
|
|
55
|
+
readonly name: string
|
|
56
|
+
/** Length in seconds. */
|
|
57
|
+
readonly duration: number
|
|
58
|
+
/** Number of animated (node, channel) tracks. */
|
|
59
|
+
readonly trackCount: number
|
|
60
|
+
/** @internal native clip set + index inside it. */
|
|
61
|
+
readonly _set: number
|
|
62
|
+
/** @internal */
|
|
63
|
+
readonly _index: number
|
|
64
|
+
|
|
65
|
+
private constructor(set: number, index: number, info: ClipInfo) {
|
|
66
|
+
this._set = set
|
|
67
|
+
this._index = index
|
|
68
|
+
this.name = info.name
|
|
69
|
+
this.duration = info.duration
|
|
70
|
+
this.trackCount = info.trackCount
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @internal — wrap a clip of an existing native set (a GLB's embedded clips). */
|
|
74
|
+
static _ofSet(setId: number): AnimationClip[] {
|
|
75
|
+
if (!setId) return []
|
|
76
|
+
return readInfo(setId).map((info, i) => new AnimationClip(setId, i, info))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Load ONE clip from a GLB: the file's only/first clip, or the one named / at the given index. */
|
|
80
|
+
static load(source: string | FetchResponse, clip?: string | number): Promise<AnimationClip> {
|
|
81
|
+
return AnimationClip._loadSet(source).then(([setId, infos]) => {
|
|
82
|
+
let index = 0
|
|
83
|
+
if (typeof clip === "number") index = clip
|
|
84
|
+
else if (typeof clip === "string") index = infos.findIndex((c) => c.name === clip)
|
|
85
|
+
if (index < 0 || index >= infos.length) {
|
|
86
|
+
_creator.destroyClipSet(setId)
|
|
87
|
+
throw new Error(`AnimationClip.load: no clip ${JSON.stringify(clip)} in ${AnimationClip._label(source)} — clips: ${infos.map((c) => c.name).join(", ") || "(none)"}`)
|
|
88
|
+
}
|
|
89
|
+
return new AnimationClip(setId, index, infos[index])
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Load EVERY clip of a GLB (library files), keyed by clip name. */
|
|
94
|
+
static loadAll(source: string | FetchResponse): Promise<Record<string, AnimationClip>> {
|
|
95
|
+
return AnimationClip._loadSet(source).then(([setId, infos]) => {
|
|
96
|
+
const out: Record<string, AnimationClip> = {}
|
|
97
|
+
infos.forEach((info, i) => { out[info.name] = new AnimationClip(setId, i, info) })
|
|
98
|
+
return out
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Build a clip from curves in code — no DCC needed. Keys are `[time, value]`; a track binds to the
|
|
103
|
+
* node of that name when the clip is used by an Animator (bones, or any child node). */
|
|
104
|
+
static from(def: ClipDef): AnimationClip {
|
|
105
|
+
const names: string[] = []
|
|
106
|
+
const data: number[] = [0, 0]
|
|
107
|
+
let trackCount = 0
|
|
108
|
+
let duration = 0
|
|
109
|
+
for (const [target, track] of Object.entries(def.tracks)) {
|
|
110
|
+
const interp = INTERP[track.interpolation ?? "linear"] ?? 0
|
|
111
|
+
const emit = (path: 0 | 1 | 2, comps: 3 | 4, keys: ClipKey<unknown>[] | undefined) => {
|
|
112
|
+
if (!keys || keys.length === 0) return
|
|
113
|
+
const sorted = keys.slice().sort((a, b) => a[0] - b[0])
|
|
114
|
+
names.push(target)
|
|
115
|
+
data.push(path, interp, comps, sorted.length)
|
|
116
|
+
for (const k of sorted) data.push(k[0])
|
|
117
|
+
for (const k of sorted) readValue(k[1], comps, data)
|
|
118
|
+
duration = Math.max(duration, sorted[sorted.length - 1][0])
|
|
119
|
+
trackCount++
|
|
120
|
+
}
|
|
121
|
+
emit(PATH.position, 3, track.position)
|
|
122
|
+
emit(PATH.rotation, 4, track.rotation)
|
|
123
|
+
emit(PATH.scale, 3, track.scale)
|
|
124
|
+
}
|
|
125
|
+
data[0] = trackCount
|
|
126
|
+
if (def.duration !== undefined) duration = def.duration
|
|
127
|
+
data[1] = duration
|
|
128
|
+
const setId = _creator.createClipFromTracks(names.join("\n"), new Float32Array(data))
|
|
129
|
+
if (setId === 0) throw new Error("AnimationClip.from: invalid track data")
|
|
130
|
+
const info = readInfo(setId)[0] ?? { name: "clip", duration, trackCount }
|
|
131
|
+
return new AnimationClip(setId, 0, info)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A sub-range of this clip as a NEW clip — `[start, end]` seconds of the source, re-timed to 0
|
|
135
|
+
* (`end` defaults to the clip's end; array-slice semantics). Cut a too-long take down to the
|
|
136
|
+
* action, or carve several sub-clips out of one packed timeline:
|
|
137
|
+
*
|
|
138
|
+
* clips: { Kick: kick.slice(0.2, 1.1) }
|
|
139
|
+
*
|
|
140
|
+
* Keys outside the range are dropped and exact boundary values are interpolated in, so the
|
|
141
|
+
* sliced clip starts and ends precisely on the source's pose at the cut points. Everything
|
|
142
|
+
* downstream (blend spaces, events' 0–1 times, root motion, phase sync) sees a normal clip of
|
|
143
|
+
* the new duration. */
|
|
144
|
+
slice(start: number, end?: number): AnimationClip {
|
|
145
|
+
const e = Math.min(end ?? this.duration, this.duration)
|
|
146
|
+
const st = Math.max(0, start)
|
|
147
|
+
const setId = _creator.sliceClip(this._set, this._index, st, e)
|
|
148
|
+
if (setId === 0) throw new Error(`AnimationClip.slice: bad range ${start}${end !== undefined ? `–${end}` : ""} of '${this.name}' (${this.duration.toFixed(2)}s)`)
|
|
149
|
+
const info = readInfo(setId)[0] ?? { name: this.name, duration: e - st, trackCount: this.trackCount }
|
|
150
|
+
return new AnimationClip(setId, 0, info)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private static _label(source: string | FetchResponse): string {
|
|
154
|
+
return typeof source === "string" ? source : "response"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static _loadSet(source: string | FetchResponse): Promise<[number, ClipInfo[]]> {
|
|
158
|
+
if (typeof _creator.loadClips !== "function") {
|
|
159
|
+
return Promise.reject(new Error("AnimationClip.load: this host has no animation-clip support yet"))
|
|
160
|
+
}
|
|
161
|
+
if (typeof source === "string") {
|
|
162
|
+
return fetch(source, { useOnce: true }).then((resp) => {
|
|
163
|
+
if (resp.status >= 400) throw new Error(`AnimationClip.load: failed to load ${source}. HTTP ${resp.status}`)
|
|
164
|
+
return AnimationClip._loadSet(resp)
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
return new Promise<[number, ClipInfo[]]>((resolve, reject) => {
|
|
168
|
+
_creator.loadClips((source as unknown as { _id: number })._id, (setId: number) => {
|
|
169
|
+
if (setId === 0) { reject(new Error("AnimationClip.load: the file has no animations (or is not a GLB)")); return }
|
|
170
|
+
resolve([setId, readInfo(setId)])
|
|
171
|
+
}, reject)
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|