lecodes-sdk 0.19.1 → 0.20.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/dist/global.d.ts +31 -0
- package/dist/host.d.ts +3 -0
- package/dist/inject.js +260 -361
- package/dist/types/gl/Locomotion.d.ts +3 -1
- package/dist/types/gl/animation/Animator.d.ts +51 -183
- package/dist/types/gl/animation/Feet.d.ts +85 -0
- package/dist/types/gl/animation/Warp.d.ts +53 -0
- package/dist/types/gl/animation/core.d.ts +46 -7
- package/dist/types/inject.d.ts +7 -2
- package/dist/types/plugins/map.d.ts +174 -0
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/dist/2d-game.md +197 -408
- package/prompts/dist/3d-app.md +166 -491
- package/prompts/dist/ar-app.md +163 -373
- package/prompts/dist/design.md +87 -83
- package/prompts/dist/ui-app.md +136 -325
- package/src/bridges.d.ts +13 -6
- package/src/compile/__tests__/compile.test.ts +12 -0
- package/src/compile/compileProject.ts +5 -0
- package/src/core/Aspect.ts +1 -1
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Locomotion.ts +7 -5
- package/src/gl/Particles.ts +3 -3
- package/src/gl/Ragdoll.ts +270 -270
- package/src/gl/Scene.ts +1 -1
- package/src/gl/Trigger.ts +45 -45
- package/src/gl/animation/Animator.ts +137 -329
- package/src/gl/animation/Feet.ts +134 -0
- package/src/gl/animation/Loop.ts +3 -1
- package/src/gl/animation/Warp.ts +96 -0
- package/src/gl/animation/core.ts +736 -670
- package/src/gl/scenarios.ts +291 -291
- package/src/gl/state.ts +6 -6
- package/src/host.d.ts +3 -0
- package/src/inject.ts +11 -2
- package/src/plugins/map.ts +396 -0
- package/src/scene/gizmos.ts +148 -148
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Feet — `anim.feet`: which bones the feet are, and what the engine does with them after the clips are
|
|
2
|
+
// composited — the foot LOCK (a planted foot stays where it landed while the body moves on) and GROUND
|
|
3
|
+
// IK (each foot on the ground probed under it, the pelvis lowered). Docs: docs/animation-v2-plan.md §2.9.
|
|
4
|
+
//
|
|
5
|
+
// model.anim.feet.lock = true // (a Locomotion does this itself)
|
|
6
|
+
// model.anim.feet.set({ lock: true, ik: true, pelvis: 0.3 }) // stairs and slopes
|
|
7
|
+
// model.anim.feet.set({ left: 'LeftFoot', right: 'RightFoot' }) // a rig the classifier misses
|
|
8
|
+
// model.anim.feet.onStep(({ side, position }) => dust(position))
|
|
9
|
+
//
|
|
10
|
+
// The engine probes the ground itself (against what a character can stand on) and reads the
|
|
11
|
+
// CharacterController's ground state; nothing is fed per frame.
|
|
12
|
+
|
|
13
|
+
import { Vec3 } from "../../math/vec"
|
|
14
|
+
import type { Core, KneeAxisReport, StepHandler } from "./core"
|
|
15
|
+
|
|
16
|
+
/** The feet settings (`anim.feet.set()` / the properties of `anim.feet`). Distances are metres, times
|
|
17
|
+
* seconds. Every key is optional and only the keys given change. */
|
|
18
|
+
export type FeetOptions = {
|
|
19
|
+
/** The contact bones per side — `'LeftFoot'`, or with a toe / ball `['LeftFoot', 'LeftToeBase']`.
|
|
20
|
+
* Default: classified from the bone names (Mixamo / Unity / Blender / UE). Setting them re-bakes
|
|
21
|
+
* every clip's contacts and phase. */
|
|
22
|
+
left?: string | string[]
|
|
23
|
+
right?: string | string[]
|
|
24
|
+
/** FOOT LOCK: a foot the shown clip calls planted is pinned where it landed — heel to ball, rolling
|
|
25
|
+
* as the clip rolls — and the leg re-solved to keep it there while the body moves on. Off by default. */
|
|
26
|
+
lock?: boolean
|
|
27
|
+
/** GROUND IK: each foot is put on the ground the engine probes under it (stairs, a slope, a kerb),
|
|
28
|
+
* aligned to its normal, the pelvis lowered so the leg can reach. Needs a physics world to probe;
|
|
29
|
+
* without one the ground is the node's own plane. Off by default. */
|
|
30
|
+
ik?: boolean
|
|
31
|
+
/** How far the pelvis may drop for the ground (default 0.35). */
|
|
32
|
+
pelvis?: number
|
|
33
|
+
/** A locked foot never absorbs more residual than this — beyond it the anchor follows the animation
|
|
34
|
+
* instead of fighting it (default 0.10). */
|
|
35
|
+
unlockDistance?: number
|
|
36
|
+
/** The lock's ease in / out, seconds (default 0.08 / 0.12). */
|
|
37
|
+
lockIn?: number
|
|
38
|
+
lockOut?: number
|
|
39
|
+
/** 0..1: how much the foot tilts onto the ground normal (default 1). */
|
|
40
|
+
align?: number
|
|
41
|
+
/** The probe ray's reach above and below the ankle (default 0.6). */
|
|
42
|
+
probe?: number
|
|
43
|
+
/** The lock plants only once the animated ankle moves slower than this, m/s (default 0.2). Raise it
|
|
44
|
+
* to pin a foot a transition is still dragging. */
|
|
45
|
+
plantSpeed?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One foot after this frame's evaluation (`anim.feet.state(side)`): whether the lock holds it, the
|
|
49
|
+
* lock's weight (eased 0…1), where it was pinned and where the leg was asked to put the ankle — all world. */
|
|
50
|
+
export type FootState = { locked: boolean, weight: number, anchor: Vec3, target: Vec3 }
|
|
51
|
+
|
|
52
|
+
const P = { IK: 0, LOCK: 1, PELVIS: 2, UNLOCK_DIST: 3, LOCK_IN: 4, LOCK_OUT: 5, PELVIS_TIME: 6, ALIGN: 7, PROBE: 8, DETECT_SPEED: 9, DETECT_HEIGHT: 10, PLANT_SPEED: 11, COUNT: 12 }
|
|
53
|
+
const DEFAULTS = { pelvis: 0.35, unlockDistance: 0.10, lockIn: 0.08, lockOut: 0.12, align: 1, probe: 0.6, plantSpeed: 0.2 }
|
|
54
|
+
const footBuf = new Float32Array(8)
|
|
55
|
+
|
|
56
|
+
export class Feet {
|
|
57
|
+
private readonly _c: Core
|
|
58
|
+
private readonly _o: FeetOptions = {}
|
|
59
|
+
/** @internal — created by the Animator. */
|
|
60
|
+
constructor(core: Core) { this._c = core }
|
|
61
|
+
|
|
62
|
+
/** Change several settings at once; only the keys given change. Chainable. */
|
|
63
|
+
set(options: FeetOptions): this {
|
|
64
|
+
Object.assign(this._o, options)
|
|
65
|
+
if (options.left !== undefined || options.right !== undefined) this.pushBones()
|
|
66
|
+
this.push()
|
|
67
|
+
return this
|
|
68
|
+
}
|
|
69
|
+
/** The current settings (what was set; unset keys are the engine's defaults). */
|
|
70
|
+
get options(): Readonly<FeetOptions> { return this._o }
|
|
71
|
+
|
|
72
|
+
get lock(): boolean { return !!this._o.lock }
|
|
73
|
+
set lock(v: boolean) { this._o.lock = v; this.push() }
|
|
74
|
+
get ik(): boolean { return !!this._o.ik }
|
|
75
|
+
set ik(v: boolean) { this._o.ik = v; this.push() }
|
|
76
|
+
get pelvis(): number { return this._o.pelvis ?? DEFAULTS.pelvis }
|
|
77
|
+
set pelvis(v: number) { this._o.pelvis = v; this.push() }
|
|
78
|
+
get unlockDistance(): number { return this._o.unlockDistance ?? DEFAULTS.unlockDistance }
|
|
79
|
+
set unlockDistance(v: number) { this._o.unlockDistance = v; this.push() }
|
|
80
|
+
get lockIn(): number { return this._o.lockIn ?? DEFAULTS.lockIn }
|
|
81
|
+
set lockIn(v: number) { this._o.lockIn = v; this.push() }
|
|
82
|
+
get lockOut(): number { return this._o.lockOut ?? DEFAULTS.lockOut }
|
|
83
|
+
set lockOut(v: number) { this._o.lockOut = v; this.push() }
|
|
84
|
+
get align(): number { return this._o.align ?? DEFAULTS.align }
|
|
85
|
+
set align(v: number) { this._o.align = v; this.push() }
|
|
86
|
+
get probe(): number { return this._o.probe ?? DEFAULTS.probe }
|
|
87
|
+
set probe(v: number) { this._o.probe = v; this.push() }
|
|
88
|
+
get plantSpeed(): number { return this._o.plantSpeed ?? DEFAULTS.plantSpeed }
|
|
89
|
+
set plantSpeed(v: number) { this._o.plantSpeed = v; this.push() }
|
|
90
|
+
/** The contact bones (`undefined` = classified from the names). */
|
|
91
|
+
get left(): string | string[] | undefined { return this._o.left }
|
|
92
|
+
set left(v: string | string[] | undefined) { this._o.left = v; this.pushBones() }
|
|
93
|
+
get right(): string | string[] | undefined { return this._o.right }
|
|
94
|
+
set right(v: string | string[] | undefined) { this._o.right = v; this.pushBones() }
|
|
95
|
+
|
|
96
|
+
/** One foot's state after this frame's evaluation — where the lock holds it and with what weight
|
|
97
|
+
* (a debug beam under the foot). `undefined` on a host without the feet stage, or before anything played. */
|
|
98
|
+
state(side: "left" | "right"): FootState | undefined {
|
|
99
|
+
if (!this._c.footState(side === "left" ? 0 : 1, footBuf)) return undefined
|
|
100
|
+
return { locked: footBuf[0]! > 0.5, weight: footBuf[1]!, anchor: new Vec3(footBuf[2]!, footBuf[3]!, footBuf[4]!), target: new Vec3(footBuf[5]!, footBuf[6]!, footBuf[7]!) }
|
|
101
|
+
}
|
|
102
|
+
/** The skeleton's calibrated KNEE HINGE AXIS for a side (thigh-local, unit) with its confidence report —
|
|
103
|
+
* measured once over every bound clip's knee rotation track. `undefined` = no leg chain, or no knee
|
|
104
|
+
* motion bound to calibrate from. */
|
|
105
|
+
kneeAxis(side: "left" | "right"): KneeAxisReport | undefined { return this._c.kneeAxis(side) }
|
|
106
|
+
|
|
107
|
+
/** A foot planted (world position) — audio, dust, decals. Fires for what the BASE layer shows, from
|
|
108
|
+
* the clip's own contacts; a clip with no contacts fires nothing. */
|
|
109
|
+
onStep(cb: StepHandler): this { this._c.onStepHandler(cb); return this }
|
|
110
|
+
offStep(cb: StepHandler): this { this._c.offStepHandler(cb); return this }
|
|
111
|
+
|
|
112
|
+
private pushBones(): void {
|
|
113
|
+
const join = (v: string | string[] | undefined): string => v === undefined ? "" : Array.isArray(v) ? v.join("\n") : v
|
|
114
|
+
this._c.setFeet(join(this._o.left), join(this._o.right))
|
|
115
|
+
}
|
|
116
|
+
private push(): void {
|
|
117
|
+
const o = this._o
|
|
118
|
+
const p = new Float32Array(P.COUNT)
|
|
119
|
+
// the engine's own defaults (canimAnimatorFeetDefaults)
|
|
120
|
+
p[P.PELVIS] = DEFAULTS.pelvis; p[P.UNLOCK_DIST] = DEFAULTS.unlockDistance; p[P.LOCK_IN] = DEFAULTS.lockIn; p[P.LOCK_OUT] = DEFAULTS.lockOut
|
|
121
|
+
p[P.PELVIS_TIME] = 0.15; p[P.ALIGN] = DEFAULTS.align; p[P.PROBE] = DEFAULTS.probe; p[P.DETECT_SPEED] = 0.35; p[P.DETECT_HEIGHT] = 0.12
|
|
122
|
+
p[P.PLANT_SPEED] = DEFAULTS.plantSpeed
|
|
123
|
+
if (o.ik) p[P.IK] = 1
|
|
124
|
+
if (o.lock) p[P.LOCK] = 1
|
|
125
|
+
if (o.pelvis !== undefined) p[P.PELVIS] = o.pelvis
|
|
126
|
+
if (o.unlockDistance !== undefined) p[P.UNLOCK_DIST] = o.unlockDistance
|
|
127
|
+
if (o.lockIn !== undefined) p[P.LOCK_IN] = o.lockIn
|
|
128
|
+
if (o.lockOut !== undefined) p[P.LOCK_OUT] = o.lockOut
|
|
129
|
+
if (o.align !== undefined) p[P.ALIGN] = o.align
|
|
130
|
+
if (o.probe !== undefined) p[P.PROBE] = o.probe
|
|
131
|
+
if (o.plantSpeed !== undefined) p[P.PLANT_SPEED] = o.plantSpeed
|
|
132
|
+
this._c.setFeetParams(p)
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/gl/animation/Loop.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Loop — a layer's LOOP: what it shows when no one-shot plays (`anim.playLoop('Idle')` — one looping clip,
|
|
2
2
|
// or `anim.playLoop({ Idle: 0, Walking: 2, Run: 6 })` — a blend whose members cycle in phase and `value`
|
|
3
|
-
// picks the mix: 1D linear between neighbours, `[x, y]` positions = a 2D gradient band
|
|
3
|
+
// picks the mix: 1D linear between neighbours, `[x, y]` positions = a 2D gradient band; a member given as
|
|
4
|
+
// `{ at, offset, cycles }` carries its gait cycle, measured offline from its foot marks; a bare position
|
|
5
|
+
// = normalized time, no cycle). A one-shot takes
|
|
4
6
|
// the layer over and hands it back; `playLoop(next)` transitions to another; `stop()` removes it. The
|
|
5
7
|
// object returned by `playLoop()` (also `anim.loop`) is the handle; a replaced loop's object goes dead.
|
|
6
8
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Warp — `anim.warp`: the pose fitted to what the body actually does, after the clips are composited and
|
|
2
|
+
// before the feet (docs/animation-v2-plan.md §2.7). `stride` scales each leg's step to the speed the body
|
|
3
|
+
// really travels at (a walk played at 1.9 m/s stops skating); `orientation` turns the lower body toward
|
|
4
|
+
// where the body really goes while the spine counter-turns (arcs and strafing without a clip per angle).
|
|
5
|
+
// Both need the body's motion, which a `Locomotion` feeds every frame; without one leave it off. Off by
|
|
6
|
+
// default. `step` is the STEP WARP: stride / lift / pitch / slope dials solved in the knee hinge plane.
|
|
7
|
+
//
|
|
8
|
+
// model.anim.warp.set(true) // both, with the defaults
|
|
9
|
+
// model.anim.warp.set({ stride: [0.7, 1.4], orientation: 45 })
|
|
10
|
+
// model.anim.warp.orientation = 12 // a lean only
|
|
11
|
+
// model.anim.warp.step = { slope: 15 } // an invisible staircase
|
|
12
|
+
|
|
13
|
+
import type { Core } from "./core"
|
|
14
|
+
|
|
15
|
+
/** What `anim.warp` turns on. Speeds are m/s, angles degrees, distances metres. */
|
|
16
|
+
export type WarpOptions = {
|
|
17
|
+
/** Fit the stride to the speed the body actually travels at. `[min, max]` clamps the scale (default
|
|
18
|
+
* 0.85…1.2). A CORRECTION: a pack whose takes already read right at the speeds it is played at wants
|
|
19
|
+
* none of this; open the range for a pack that must cover speeds it was never recorded at. */
|
|
20
|
+
stride?: boolean | [number, number]
|
|
21
|
+
/** Turn the lower body toward where the body really travels; a number caps the turn in degrees
|
|
22
|
+
* (default 20). The whole twist lives in one joint: a few degrees read as a lean, a lot as a broken
|
|
23
|
+
* back. Only applied while the gait LOOP shows — a start, a turn or a stop walks a path of its own. */
|
|
24
|
+
orientation?: boolean | number
|
|
25
|
+
/** Below this speed — the game's or the clip's — both warps are off (default 0.2). */
|
|
26
|
+
minSpeed?: number
|
|
27
|
+
/** How far the pelvis may drop to keep a stretched leg from locking straight (default 0.25). */
|
|
28
|
+
pelvis?: number
|
|
29
|
+
/** The stride scale's own spring, seconds (default 0.15): the shown clip's recorded speed steps at every
|
|
30
|
+
* switch, so the scale is smoothed rather than followed. */
|
|
31
|
+
strideTime?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** STEP WARP dials (`anim.warp.step`): `stride` scales each foot's travel-direction offset from its hip,
|
|
35
|
+
* `lift` = metres ADDED to its height (swing-gated; negative = a shuffle; half of it raises the pelvis),
|
|
36
|
+
* `pitch` (degrees, + = toes up) rotates each foot about its lateral axis, `slope` (degrees, + = ascending)
|
|
37
|
+
* the invisible staircase — feet on the incline, auto pitch, the character raised to hold each planted
|
|
38
|
+
* foot on its tread. A tuning bench's dial; locomotion will drive this itself later. */
|
|
39
|
+
export type StepWarpOptions = { stride?: number, lift?: number, pitch?: number, slope?: number }
|
|
40
|
+
|
|
41
|
+
const P = { STRIDE: 0, STRIDE_MIN: 1, STRIDE_MAX: 2, ORIENT: 3, ORIENT_MAX: 4, ORIENT_TIME: 5, MIN_SPEED: 6, PELVIS: 7, STRIDE_TIME: 8, COUNT: 9 }
|
|
42
|
+
const DEFAULTS = { strideMin: 0.85, strideMax: 1.2, orientMax: 20, minSpeed: 0.2, pelvis: 0.25, strideTime: 0.15 }
|
|
43
|
+
|
|
44
|
+
export class Warp {
|
|
45
|
+
private readonly _c: Core
|
|
46
|
+
private _o: WarpOptions = {}
|
|
47
|
+
private _step: StepWarpOptions | null = null
|
|
48
|
+
/** @internal — created by the Animator. */
|
|
49
|
+
constructor(core: Core) { this._c = core }
|
|
50
|
+
|
|
51
|
+
/** Replace the settings: `true` = both warps with the defaults, `false` / `{}` = off, or the options.
|
|
52
|
+
* (Replaces, does not merge — what is not asked for is off.) Chainable. */
|
|
53
|
+
set(w: WarpOptions | boolean): this {
|
|
54
|
+
this._o = w === true ? { stride: true, orientation: true } : w === false ? {} : { ...w }
|
|
55
|
+
this.push()
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
/** The current settings. */
|
|
59
|
+
get options(): Readonly<WarpOptions> { return this._o }
|
|
60
|
+
|
|
61
|
+
get stride(): boolean | [number, number] { return this._o.stride ?? false }
|
|
62
|
+
set stride(v: boolean | [number, number]) { this._o.stride = v; this.push() }
|
|
63
|
+
get orientation(): boolean | number { return this._o.orientation ?? false }
|
|
64
|
+
set orientation(v: boolean | number) { this._o.orientation = v; this.push() }
|
|
65
|
+
get minSpeed(): number { return this._o.minSpeed ?? DEFAULTS.minSpeed }
|
|
66
|
+
set minSpeed(v: number) { this._o.minSpeed = v; this.push() }
|
|
67
|
+
get pelvis(): number { return this._o.pelvis ?? DEFAULTS.pelvis }
|
|
68
|
+
set pelvis(v: number) { this._o.pelvis = v; this.push() }
|
|
69
|
+
get strideTime(): number { return this._o.strideTime ?? DEFAULTS.strideTime }
|
|
70
|
+
set strideTime(v: number) { this._o.strideTime = v; this.push() }
|
|
71
|
+
|
|
72
|
+
/** The step warp dials; `null` = off. */
|
|
73
|
+
get step(): StepWarpOptions | null { return this._step }
|
|
74
|
+
set step(v: StepWarpOptions | null | undefined) { this._step = v ?? null; this._c.setStepWarp(this._step) }
|
|
75
|
+
|
|
76
|
+
private push(): void {
|
|
77
|
+
const o = this._o
|
|
78
|
+
const p = new Float32Array(P.COUNT)
|
|
79
|
+
// the engine's own defaults (canimAnimatorWarpDefaults): what is not asked for stays neutral
|
|
80
|
+
p[P.STRIDE_MIN] = DEFAULTS.strideMin; p[P.STRIDE_MAX] = DEFAULTS.strideMax; p[P.ORIENT_MAX] = DEFAULTS.orientMax
|
|
81
|
+
p[P.ORIENT_TIME] = 0.15; p[P.MIN_SPEED] = DEFAULTS.minSpeed; p[P.PELVIS] = DEFAULTS.pelvis
|
|
82
|
+
p[P.STRIDE_TIME] = DEFAULTS.strideTime
|
|
83
|
+
if (o.stride) {
|
|
84
|
+
p[P.STRIDE] = 1
|
|
85
|
+
if (Array.isArray(o.stride)) { p[P.STRIDE_MIN] = o.stride[0]; p[P.STRIDE_MAX] = o.stride[1] }
|
|
86
|
+
}
|
|
87
|
+
if (o.orientation) {
|
|
88
|
+
p[P.ORIENT] = 1
|
|
89
|
+
if (typeof o.orientation === "number") p[P.ORIENT_MAX] = o.orientation
|
|
90
|
+
}
|
|
91
|
+
if (o.minSpeed !== undefined) p[P.MIN_SPEED] = o.minSpeed
|
|
92
|
+
if (o.pelvis !== undefined) p[P.PELVIS] = o.pelvis
|
|
93
|
+
if (o.strideTime !== undefined) p[P.STRIDE_TIME] = o.strideTime
|
|
94
|
+
this._c.setWarp(p)
|
|
95
|
+
}
|
|
96
|
+
}
|