@ikijs/engine 0.1.0 → 0.2.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 +7 -4
- package/dist/index.d.mts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +42 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @ikijs/engine
|
|
2
2
|
|
|
3
|
-
WebGL2 runtime that plays a [`.iki`](
|
|
3
|
+
WebGL2 runtime that plays a [`.iki`](https://github.com/zeikar/iki/tree/main/packages/format) puppet model in the browser.
|
|
4
4
|
|
|
5
5
|
The engine is **host-agnostic**: it depends only on
|
|
6
|
-
[`@ikijs/format`](
|
|
6
|
+
[`@ikijs/format`](https://github.com/zeikar/iki/tree/main/packages/format) and knows nothing about any particular app. A host
|
|
7
7
|
drives it by setting parameters (from lip-sync, gaze, blink, expressions); the
|
|
8
8
|
engine renders the result each frame.
|
|
9
9
|
|
|
@@ -30,8 +30,11 @@ player.setParameter(StandardParameter.MouthOpen, 0.7);
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
`load()` decodes and uploads every texture before swapping the model in, so a
|
|
33
|
-
frame is never half-textured.
|
|
34
|
-
|
|
33
|
+
frame is never half-textured. That swap is also why it must be awaited before
|
|
34
|
+
`getParameters()`: an un-awaited `load()` leaves the parameter store empty for
|
|
35
|
+
the rest of the tick, and the engine reports that case rather than let a host
|
|
36
|
+
read `[]` and conclude the model declares no parameters. Parameter writes are
|
|
37
|
+
clamped to the declared range; unknown ids and non-finite values are ignored.
|
|
35
38
|
|
|
36
39
|
## API
|
|
37
40
|
|
package/dist/index.d.mts
CHANGED
|
@@ -50,6 +50,10 @@ declare class IkiPlayer {
|
|
|
50
50
|
private textures;
|
|
51
51
|
/** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
|
|
52
52
|
private loadGeneration;
|
|
53
|
+
/** True from the moment `load()` is entered until it resolves or throws. */
|
|
54
|
+
private loadPending;
|
|
55
|
+
/** Latches the un-awaited-load report, so a repeat caller says it once. */
|
|
56
|
+
private warnedLoadUnfinished;
|
|
53
57
|
private destroyed;
|
|
54
58
|
/**
|
|
55
59
|
* Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
|
|
@@ -78,8 +82,14 @@ declare class IkiPlayer {
|
|
|
78
82
|
* Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
|
|
79
83
|
* textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
|
|
80
84
|
* buffers have none — there is no partial-mesh concept in the format.
|
|
85
|
+
*
|
|
86
|
+
* AWAIT THIS before reading {@link getParameters}. The swap happens after
|
|
87
|
+
* texture decoding, so an un-awaited `load()` leaves the parameter store
|
|
88
|
+
* empty for the rest of the tick; `getParameters` reports that case rather
|
|
89
|
+
* than letting a host conclude the model drives nothing.
|
|
81
90
|
*/
|
|
82
91
|
load(model: IkiModel): Promise<IkiLoadResult>;
|
|
92
|
+
private adoptModel;
|
|
83
93
|
/**
|
|
84
94
|
* Start the render loop. Safe to call more than once, and a no-op after
|
|
85
95
|
* {@link destroy} — the program and buffers the loop draws with are gone, so
|
|
@@ -101,7 +111,17 @@ declare class IkiPlayer {
|
|
|
101
111
|
* mirror's clamping in step with {@link ParameterStore} by hand.
|
|
102
112
|
*/
|
|
103
113
|
getParameter(id: string): number;
|
|
104
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* The model's parameter descriptors, for building UI or host wiring.
|
|
116
|
+
*
|
|
117
|
+
* Empty until the first {@link load} resolves. Reaching it through an
|
|
118
|
+
* un-awaited `load()` is the one mistake in this class that produces no
|
|
119
|
+
* error and no motion: the caller gets `[]`, concludes the model has no
|
|
120
|
+
* parameters, and drives nothing — so that case is reported instead of
|
|
121
|
+
* being indistinguishable from a model that really declares none. A
|
|
122
|
+
* reload is deliberately NOT reported: those parameters are stale rather
|
|
123
|
+
* than absent, and warning there would fire on legitimate concurrent reads.
|
|
124
|
+
*/
|
|
105
125
|
getParameters(): IkiParameter[];
|
|
106
126
|
destroy(): void;
|
|
107
127
|
private renderFrame;
|
|
@@ -212,6 +232,8 @@ declare class IdleMotion {
|
|
|
212
232
|
private swayX;
|
|
213
233
|
/** Vertical head sway in degrees, pure function of the internal clock. */
|
|
214
234
|
private swayY;
|
|
235
|
+
/** Head roll sway in degrees, pure function of the internal clock. */
|
|
236
|
+
private swayZ;
|
|
215
237
|
/** Ease gaze current toward target; pick a new target on the internal clock. */
|
|
216
238
|
private advanceGaze;
|
|
217
239
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -50,6 +50,10 @@ declare class IkiPlayer {
|
|
|
50
50
|
private textures;
|
|
51
51
|
/** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
|
|
52
52
|
private loadGeneration;
|
|
53
|
+
/** True from the moment `load()` is entered until it resolves or throws. */
|
|
54
|
+
private loadPending;
|
|
55
|
+
/** Latches the un-awaited-load report, so a repeat caller says it once. */
|
|
56
|
+
private warnedLoadUnfinished;
|
|
53
57
|
private destroyed;
|
|
54
58
|
/**
|
|
55
59
|
* Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
|
|
@@ -78,8 +82,14 @@ declare class IkiPlayer {
|
|
|
78
82
|
* Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
|
|
79
83
|
* textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
|
|
80
84
|
* buffers have none — there is no partial-mesh concept in the format.
|
|
85
|
+
*
|
|
86
|
+
* AWAIT THIS before reading {@link getParameters}. The swap happens after
|
|
87
|
+
* texture decoding, so an un-awaited `load()` leaves the parameter store
|
|
88
|
+
* empty for the rest of the tick; `getParameters` reports that case rather
|
|
89
|
+
* than letting a host conclude the model drives nothing.
|
|
81
90
|
*/
|
|
82
91
|
load(model: IkiModel): Promise<IkiLoadResult>;
|
|
92
|
+
private adoptModel;
|
|
83
93
|
/**
|
|
84
94
|
* Start the render loop. Safe to call more than once, and a no-op after
|
|
85
95
|
* {@link destroy} — the program and buffers the loop draws with are gone, so
|
|
@@ -101,7 +111,17 @@ declare class IkiPlayer {
|
|
|
101
111
|
* mirror's clamping in step with {@link ParameterStore} by hand.
|
|
102
112
|
*/
|
|
103
113
|
getParameter(id: string): number;
|
|
104
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* The model's parameter descriptors, for building UI or host wiring.
|
|
116
|
+
*
|
|
117
|
+
* Empty until the first {@link load} resolves. Reaching it through an
|
|
118
|
+
* un-awaited `load()` is the one mistake in this class that produces no
|
|
119
|
+
* error and no motion: the caller gets `[]`, concludes the model has no
|
|
120
|
+
* parameters, and drives nothing — so that case is reported instead of
|
|
121
|
+
* being indistinguishable from a model that really declares none. A
|
|
122
|
+
* reload is deliberately NOT reported: those parameters are stale rather
|
|
123
|
+
* than absent, and warning there would fire on legitimate concurrent reads.
|
|
124
|
+
*/
|
|
105
125
|
getParameters(): IkiParameter[];
|
|
106
126
|
destroy(): void;
|
|
107
127
|
private renderFrame;
|
|
@@ -212,6 +232,8 @@ declare class IdleMotion {
|
|
|
212
232
|
private swayX;
|
|
213
233
|
/** Vertical head sway in degrees, pure function of the internal clock. */
|
|
214
234
|
private swayY;
|
|
235
|
+
/** Head roll sway in degrees, pure function of the internal clock. */
|
|
236
|
+
private swayZ;
|
|
215
237
|
/** Ease gaze current toward target; pick a new target on the internal clock. */
|
|
216
238
|
private advanceGaze;
|
|
217
239
|
}
|
package/dist/index.js
CHANGED
|
@@ -452,6 +452,10 @@ var IkiPlayer = class {
|
|
|
452
452
|
textures = [];
|
|
453
453
|
/** Bumped by every `load` and by `destroy`; lets a stale async load bail. */
|
|
454
454
|
loadGeneration = 0;
|
|
455
|
+
/** True from the moment `load()` is entered until it resolves or throws. */
|
|
456
|
+
loadPending = false;
|
|
457
|
+
/** Latches the un-awaited-load report, so a repeat caller says it once. */
|
|
458
|
+
warnedLoadUnfinished = false;
|
|
455
459
|
destroyed = false;
|
|
456
460
|
/**
|
|
457
461
|
* Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`
|
|
@@ -479,8 +483,21 @@ var IkiPlayer = class {
|
|
|
479
483
|
* Mesh buffer allocation failure IS fatal (unlike per-texture skip) because
|
|
480
484
|
* textures have an `IkiLoadResult.failedTextures` reporting surface and mesh
|
|
481
485
|
* buffers have none — there is no partial-mesh concept in the format.
|
|
486
|
+
*
|
|
487
|
+
* AWAIT THIS before reading {@link getParameters}. The swap happens after
|
|
488
|
+
* texture decoding, so an un-awaited `load()` leaves the parameter store
|
|
489
|
+
* empty for the rest of the tick; `getParameters` reports that case rather
|
|
490
|
+
* than letting a host conclude the model drives nothing.
|
|
482
491
|
*/
|
|
483
492
|
async load(model) {
|
|
493
|
+
this.loadPending = true;
|
|
494
|
+
try {
|
|
495
|
+
return await this.adoptModel(model);
|
|
496
|
+
} finally {
|
|
497
|
+
this.loadPending = false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async adoptModel(model) {
|
|
484
501
|
const { gl } = this;
|
|
485
502
|
const generation = ++this.loadGeneration;
|
|
486
503
|
const sources = model.textures ?? [];
|
|
@@ -692,8 +709,24 @@ var IkiPlayer = class {
|
|
|
692
709
|
getParameter(id) {
|
|
693
710
|
return this.params.get(id);
|
|
694
711
|
}
|
|
695
|
-
/**
|
|
712
|
+
/**
|
|
713
|
+
* The model's parameter descriptors, for building UI or host wiring.
|
|
714
|
+
*
|
|
715
|
+
* Empty until the first {@link load} resolves. Reaching it through an
|
|
716
|
+
* un-awaited `load()` is the one mistake in this class that produces no
|
|
717
|
+
* error and no motion: the caller gets `[]`, concludes the model has no
|
|
718
|
+
* parameters, and drives nothing — so that case is reported instead of
|
|
719
|
+
* being indistinguishable from a model that really declares none. A
|
|
720
|
+
* reload is deliberately NOT reported: those parameters are stale rather
|
|
721
|
+
* than absent, and warning there would fire on legitimate concurrent reads.
|
|
722
|
+
*/
|
|
696
723
|
getParameters() {
|
|
724
|
+
if (this.loadPending && this.model === void 0 && !this.warnedLoadUnfinished) {
|
|
725
|
+
this.warnedLoadUnfinished = true;
|
|
726
|
+
console.error(
|
|
727
|
+
"Iki: getParameters() ran before load() finished, so it returned an empty list \u2014 await load() before reading parameters."
|
|
728
|
+
);
|
|
729
|
+
}
|
|
697
730
|
return this.params.list();
|
|
698
731
|
}
|
|
699
732
|
destroy() {
|
|
@@ -1051,6 +1084,8 @@ var SWAY_X_AMP_B_DEG = 1.3;
|
|
|
1051
1084
|
var SWAY_X_PERIOD_B_MS = 9700;
|
|
1052
1085
|
var SWAY_Y_AMP_DEG = 1.6;
|
|
1053
1086
|
var SWAY_Y_PERIOD_MS = 7300;
|
|
1087
|
+
var SWAY_Z_AMP_DEG = 1.1;
|
|
1088
|
+
var SWAY_Z_PERIOD_MS = 11300;
|
|
1054
1089
|
function lerp(a, b, t) {
|
|
1055
1090
|
return a + (b - a) * t;
|
|
1056
1091
|
}
|
|
@@ -1124,6 +1159,7 @@ var IdleMotion = class {
|
|
|
1124
1159
|
this.sink(import_format.StandardParameter.EyeballY, this.gazeCurrentY);
|
|
1125
1160
|
this.sink(import_format.StandardParameter.AngleX, this.swayX());
|
|
1126
1161
|
this.sink(import_format.StandardParameter.AngleY, this.swayY());
|
|
1162
|
+
this.sink(import_format.StandardParameter.AngleZ, this.swayZ());
|
|
1127
1163
|
}
|
|
1128
1164
|
// ---------------------------------------------------------------------------
|
|
1129
1165
|
// Private helpers
|
|
@@ -1136,6 +1172,7 @@ var IdleMotion = class {
|
|
|
1136
1172
|
this.sink(import_format.StandardParameter.EyeballY, 0);
|
|
1137
1173
|
this.sink(import_format.StandardParameter.AngleX, 0);
|
|
1138
1174
|
this.sink(import_format.StandardParameter.AngleY, 0);
|
|
1175
|
+
this.sink(import_format.StandardParameter.AngleZ, 0);
|
|
1139
1176
|
}
|
|
1140
1177
|
/** Returns the current eye-open value (0..1) and advances blink state. */
|
|
1141
1178
|
advanceBlink() {
|
|
@@ -1167,6 +1204,10 @@ var IdleMotion = class {
|
|
|
1167
1204
|
swayY() {
|
|
1168
1205
|
return SWAY_Y_AMP_DEG * Math.sin(2 * Math.PI * this.clockMs / SWAY_Y_PERIOD_MS);
|
|
1169
1206
|
}
|
|
1207
|
+
/** Head roll sway in degrees, pure function of the internal clock. */
|
|
1208
|
+
swayZ() {
|
|
1209
|
+
return SWAY_Z_AMP_DEG * Math.sin(2 * Math.PI * this.clockMs / SWAY_Z_PERIOD_MS);
|
|
1210
|
+
}
|
|
1170
1211
|
/** Ease gaze current toward target; pick a new target on the internal clock. */
|
|
1171
1212
|
advanceGaze(dt) {
|
|
1172
1213
|
if (this.clockMs >= this.nextGazeRetargetMs) {
|