lecodes-cli 0.16.0 → 0.16.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.
Files changed (38) hide show
  1. package/README.md +27 -4
  2. package/dist/index.js +170706 -72
  3. package/package.json +11 -6
  4. package/runtime/sdk/compile/bundler.ts +10 -2
  5. package/runtime/sdk/compile/compileProject.ts +10 -2
  6. package/runtime/sdk/compile/detectEntry.ts +5 -2
  7. package/runtime/sdk/compile/index.ts +17 -1
  8. package/runtime/sdk/compile/serverSplit.ts +233 -0
  9. package/runtime/sdk/compile/serverTypes.ts +227 -0
  10. package/runtime/sdk/gl/AnimationClip.ts +174 -0
  11. package/runtime/sdk/gl/Animator.ts +634 -0
  12. package/runtime/sdk/gl/CharacterController.ts +6 -1
  13. package/runtime/sdk/gl/IK.ts +170 -0
  14. package/runtime/sdk/gl/Material.ts +1 -1
  15. package/runtime/sdk/gl/ModelAnimation.ts +34 -3
  16. package/runtime/sdk/gl/Node.ts +21 -0
  17. package/runtime/sdk/inject.ts +10 -0
  18. package/runtime/sdk/plugins/oauth.ts +61 -0
  19. package/runtime/sdk/runtime/rpc.ts +218 -0
  20. package/runtime/sdk/server/auth/appConfig.ts +12 -0
  21. package/runtime/sdk/server/auth/global.ts +80 -0
  22. package/runtime/sdk/server/auth/host.ts +318 -0
  23. package/runtime/sdk/server/auth/models.ts +83 -0
  24. package/runtime/sdk/server/auth/types.ts +50 -0
  25. package/runtime/sdk/server/channel.ts +56 -0
  26. package/runtime/sdk/server/context.ts +36 -0
  27. package/runtime/sdk/server/db/defineDb.ts +237 -0
  28. package/runtime/sdk/server/db/fields.ts +132 -0
  29. package/runtime/sdk/server/db/httpTransport.ts +93 -0
  30. package/runtime/sdk/server/db/index.ts +7 -0
  31. package/runtime/sdk/server/db/marci/query.ts +412 -0
  32. package/runtime/sdk/server/db/types.ts +202 -0
  33. package/runtime/sdk/server/errors.ts +12 -0
  34. package/runtime/sdk/server/host.ts +74 -0
  35. package/runtime/sdk/server/inject.ts +13 -0
  36. package/runtime/sdk/server/runtime.ts +133 -0
  37. package/runtime/sdk/server/validate.ts +87 -0
  38. 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
+ }