agent-robot-avatar 0.3.1 → 0.5.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/CHANGELOG.md +170 -111
- package/LICENSE +21 -21
- package/README.md +186 -446
- package/agent-robot-avatar.js +28 -25
- package/docs/README.de.md +186 -166
- package/docs/README.es.md +186 -225
- package/docs/README.fr.md +186 -225
- package/docs/README.ja.md +186 -225
- package/docs/README.ko.md +186 -225
- package/docs/README.pt.md +186 -225
- package/docs/README.ru.md +186 -0
- package/docs/README.zh-CN.md +190 -396
- package/docs/README.zh-TW.md +186 -227
- package/index.d.ts +128 -108
- package/package.json +77 -77
- package/src/agent-robot-avatar-actions.js +400 -400
- package/src/agent-robot-avatar-antenna-flash.js +127 -127
- package/src/agent-robot-avatar-antenna.js +152 -152
- package/src/agent-robot-avatar-core.js +1155 -1135
- package/src/agent-robot-avatar-extension-host.js +78 -78
- package/src/agent-robot-avatar-failure.js +131 -131
- package/src/agent-robot-avatar-geometry.js +40 -40
- package/src/agent-robot-avatar-gestures.js +161 -0
- package/src/agent-robot-avatar-head-roundness.js +103 -103
- package/src/agent-robot-avatar-inspect.js +242 -242
- package/src/agent-robot-avatar-love.js +226 -0
- package/src/agent-robot-avatar-random.js +272 -0
- package/src/agent-robot-avatar-runtime.js +905 -875
- package/src/agent-robot-avatar-version.js +3 -3
- package/src/agent-robot-avatar-waiting.js +130 -121
- package/src/agent-robot-avatar-wrap.js +66 -0
|
@@ -1,242 +1,242 @@
|
|
|
1
|
-
import AgentRobotAvatar, { registerAvatarExtension } from './agent-robot-avatar-extension-host.js';
|
|
2
|
-
|
|
3
|
-
const proto = AgentRobotAvatar.prototype;
|
|
4
|
-
|
|
5
|
-
const clamp01 = value => Math.max(0, Math.min(1, value));
|
|
6
|
-
const lerp = (a, b, t) => a + (b - a) * t;
|
|
7
|
-
const smooth = t => {
|
|
8
|
-
t = clamp01(t);
|
|
9
|
-
return t * t * (3 - 2 * t);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Finalized from the R59 tuner values supplied by the user.
|
|
13
|
-
const INSPECT_DEFAULTS = Object.freeze({
|
|
14
|
-
aperture: 16,
|
|
15
|
-
scanOffset: 16,
|
|
16
|
-
prepPause: 120,
|
|
17
|
-
close: 220,
|
|
18
|
-
holdClosed: 880,
|
|
19
|
-
down1: 400,
|
|
20
|
-
holdDown1: 560,
|
|
21
|
-
up1: 160,
|
|
22
|
-
holdUp1: 580,
|
|
23
|
-
center1: 140,
|
|
24
|
-
holdCenter: 760,
|
|
25
|
-
down2: 140,
|
|
26
|
-
holdDown2: 620,
|
|
27
|
-
up2: 180,
|
|
28
|
-
holdUp2: 620,
|
|
29
|
-
center2: 100,
|
|
30
|
-
holdAfter: 940,
|
|
31
|
-
open: 260,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const INSPECT_BOUNDS = Object.freeze({
|
|
35
|
-
aperture: [6, 40],
|
|
36
|
-
scanOffset: [0, 24],
|
|
37
|
-
prepPause: [0, 2400],
|
|
38
|
-
close: [40, 2400],
|
|
39
|
-
holdClosed: [0, 2400],
|
|
40
|
-
down1: [40, 2400],
|
|
41
|
-
holdDown1: [0, 2400],
|
|
42
|
-
up1: [40, 2400],
|
|
43
|
-
holdUp1: [0, 2400],
|
|
44
|
-
center1: [40, 2400],
|
|
45
|
-
holdCenter: [0, 2400],
|
|
46
|
-
down2: [40, 2400],
|
|
47
|
-
holdDown2: [0, 2400],
|
|
48
|
-
up2: [40, 2400],
|
|
49
|
-
holdUp2: [0, 2400],
|
|
50
|
-
center2: [40, 2400],
|
|
51
|
-
holdAfter: [0, 2400],
|
|
52
|
-
open: [40, 2400],
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const runtimeWindow = typeof window !== 'undefined' ? window : null;
|
|
56
|
-
const inspectConfig = (runtimeWindow?.AgentRobotAvatarInspectConfig && typeof runtimeWindow.AgentRobotAvatarInspectConfig === 'object')
|
|
57
|
-
? runtimeWindow.AgentRobotAvatarInspectConfig
|
|
58
|
-
: {};
|
|
59
|
-
|
|
60
|
-
function normalizeConfigValue(key, value) {
|
|
61
|
-
const fallback = INSPECT_DEFAULTS[key];
|
|
62
|
-
const numeric = Number(value);
|
|
63
|
-
if (!Number.isFinite(numeric)) return fallback;
|
|
64
|
-
const [min, max] = INSPECT_BOUNDS[key];
|
|
65
|
-
return Math.max(min, Math.min(max, numeric));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
69
|
-
inspectConfig[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
70
|
-
}
|
|
71
|
-
if (runtimeWindow) {
|
|
72
|
-
runtimeWindow.AgentRobotAvatarInspectConfig = inspectConfig;
|
|
73
|
-
runtimeWindow.AgentRobotAvatarInspectDefaults = INSPECT_DEFAULTS;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function currentInspectConfig() {
|
|
77
|
-
const config = {};
|
|
78
|
-
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
79
|
-
config[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
80
|
-
}
|
|
81
|
-
return config;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function dispatchInspectState(instance, state) {
|
|
85
|
-
instance.dispatchEvent(new CustomEvent('face-state', {
|
|
86
|
-
detail: { state }
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function makeTimeline(config) {
|
|
91
|
-
let cursor = 0;
|
|
92
|
-
const phases = {};
|
|
93
|
-
const add = key => {
|
|
94
|
-
phases[`${key}Start`] = cursor;
|
|
95
|
-
cursor += config[key];
|
|
96
|
-
phases[`${key}End`] = cursor;
|
|
97
|
-
};
|
|
98
|
-
for (const key of [
|
|
99
|
-
'close','holdClosed','down1','holdDown1','up1','holdUp1','center1','holdCenter',
|
|
100
|
-
'down2','holdDown2','up2','holdUp2','center2','holdAfter','open'
|
|
101
|
-
]) add(key);
|
|
102
|
-
phases.duration = cursor;
|
|
103
|
-
return phases;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function move(elapsed, start, end, from, to) {
|
|
107
|
-
if (elapsed <= start) return from;
|
|
108
|
-
if (elapsed >= end) return to;
|
|
109
|
-
const span = Math.max(1, end - start);
|
|
110
|
-
return lerp(from, to, smooth((elapsed - start) / span));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function verticalShift(elapsed, phases, offset) {
|
|
114
|
-
if (elapsed < phases.down1Start) return 0;
|
|
115
|
-
if (elapsed < phases.down1End) return move(elapsed, phases.down1Start, phases.down1End, 0, offset);
|
|
116
|
-
if (elapsed < phases.holdDown1End) return offset;
|
|
117
|
-
if (elapsed < phases.up1End) return move(elapsed, phases.up1Start, phases.up1End, offset, -offset);
|
|
118
|
-
if (elapsed < phases.holdUp1End) return -offset;
|
|
119
|
-
if (elapsed < phases.center1End) return move(elapsed, phases.center1Start, phases.center1End, -offset, 0);
|
|
120
|
-
if (elapsed < phases.holdCenterEnd) return 0;
|
|
121
|
-
|
|
122
|
-
// Second pass: down to the lowest point, hold, then return directly to center.
|
|
123
|
-
// Do not overshoot upward again.
|
|
124
|
-
if (elapsed < phases.down2End) return move(elapsed, phases.down2Start, phases.down2End, 0, offset);
|
|
125
|
-
if (elapsed < phases.holdDown2End) return offset;
|
|
126
|
-
if (elapsed < phases.up2End) return move(elapsed, phases.up2Start, phases.up2End, offset, 0);
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Keep the R60 head motion unchanged: down, up past center, then back to center.
|
|
131
|
-
function animateInspectHead(instance, phases) {
|
|
132
|
-
if (!instance._headMotion?.animate) return;
|
|
133
|
-
const token = instance._transitionToken;
|
|
134
|
-
const start = phases.down1Start;
|
|
135
|
-
const end = phases.center1End;
|
|
136
|
-
const duration = Math.max(1, end - start);
|
|
137
|
-
const offset = time => Math.max(0, Math.min(1, (time - start) / duration));
|
|
138
|
-
|
|
139
|
-
instance._headMotion.style.transformBox = 'view-box';
|
|
140
|
-
instance._headMotion.style.transformOrigin = '120px 120px';
|
|
141
|
-
const anim = instance._headMotion.animate([
|
|
142
|
-
{ transform: 'translateY(0px)', offset: 0, easing: 'cubic-bezier(.38,0,.25,1)' },
|
|
143
|
-
{ transform: 'translateY(5px)', offset: offset(phases.down1End) },
|
|
144
|
-
{ transform: 'translateY(5px)', offset: offset(phases.holdDown1End), easing: 'cubic-bezier(.35,0,.22,1)' },
|
|
145
|
-
{ transform: 'translateY(-4px)', offset: offset(phases.up1End) },
|
|
146
|
-
{ transform: 'translateY(-4px)', offset: offset(phases.holdUp1End), easing: 'cubic-bezier(.35,0,.22,1)' },
|
|
147
|
-
{ transform: 'translateY(0px)', offset: 1 },
|
|
148
|
-
], {
|
|
149
|
-
duration,
|
|
150
|
-
delay: start,
|
|
151
|
-
easing: 'linear',
|
|
152
|
-
fill: 'forwards',
|
|
153
|
-
});
|
|
154
|
-
anim.onfinish = () => {
|
|
155
|
-
if (token === instance._transitionToken) instance._headMotion.style.transform = 'translateY(0px)';
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
proto.inspect = async function() {
|
|
160
|
-
this.noteActivity();
|
|
161
|
-
this._inputWanted = false;
|
|
162
|
-
|
|
163
|
-
const config = currentInspectConfig();
|
|
164
|
-
this.reset();
|
|
165
|
-
if (!(await this._prepareExpression({ normalizePose: true, duration: 160, pause: config.prepPause }))) return;
|
|
166
|
-
|
|
167
|
-
const token = this._transitionToken;
|
|
168
|
-
this._expressionLock = true;
|
|
169
|
-
this._look.x = 0;
|
|
170
|
-
this._look.y = 0;
|
|
171
|
-
|
|
172
|
-
const phases = makeTimeline(config);
|
|
173
|
-
this._inspectFx = { start: performance.now(), duration: phases.duration, config, phases };
|
|
174
|
-
dispatchInspectState(this, 'inspect');
|
|
175
|
-
animateInspectHead(this, phases);
|
|
176
|
-
|
|
177
|
-
await this._wait(phases.duration + 30);
|
|
178
|
-
if (token !== this._transitionToken) return;
|
|
179
|
-
|
|
180
|
-
this._inspectFx = null;
|
|
181
|
-
this._look.x = 0;
|
|
182
|
-
this._look.y = 0;
|
|
183
|
-
this._releaseExpressionLock();
|
|
184
|
-
dispatchInspectState(this, 'idle');
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
function drawInspect(now) {
|
|
188
|
-
const fx = this._inspectFx;
|
|
189
|
-
if (!fx) return;
|
|
190
|
-
|
|
191
|
-
const config = fx.config || currentInspectConfig();
|
|
192
|
-
const phases = fx.phases || makeTimeline(config);
|
|
193
|
-
const elapsed = Math.max(0, Math.min(phases.duration, now - fx.start));
|
|
194
|
-
|
|
195
|
-
let lidBlend = 1;
|
|
196
|
-
if (elapsed < phases.closeEnd) {
|
|
197
|
-
lidBlend = smooth(elapsed / Math.max(1, config.close));
|
|
198
|
-
} else if (elapsed >= phases.openStart) {
|
|
199
|
-
lidBlend = 1 - smooth((elapsed - phases.openStart) / Math.max(1, config.open));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const yShift = verticalShift(elapsed, phases, config.scanOffset);
|
|
203
|
-
|
|
204
|
-
// Keep the original eye geometry. The squint is made only by the top/bottom masks.
|
|
205
|
-
this._leftEye.setAttribute('transform', `translate(86 ${(126 + yShift).toFixed(2)}) scale(1 1)`);
|
|
206
|
-
this._rightEye.setAttribute('transform', `translate(154 ${(126 + yShift).toFixed(2)}) scale(1 1)`);
|
|
207
|
-
|
|
208
|
-
const apertureHalf = config.aperture / 2;
|
|
209
|
-
const topEdge = lerp(-36, -apertureHalf, lidBlend);
|
|
210
|
-
const bottomEdge = lerp(36, apertureHalf, lidBlend);
|
|
211
|
-
|
|
212
|
-
for (const top of [this._leftTop, this._rightTop]) {
|
|
213
|
-
top.setAttribute('y', (topEdge - 90).toFixed(2));
|
|
214
|
-
top.setAttribute('height', '90');
|
|
215
|
-
top.setAttribute('transform', `rotate(0 0 ${topEdge.toFixed(2)})`);
|
|
216
|
-
}
|
|
217
|
-
for (const bottom of [this._leftBottom, this._rightBottom]) {
|
|
218
|
-
bottom.setAttribute('y', bottomEdge.toFixed(2));
|
|
219
|
-
bottom.setAttribute('height', '90');
|
|
220
|
-
bottom.setAttribute('transform', `rotate(0 0 ${bottomEdge.toFixed(2)})`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
registerAvatarExtension({
|
|
225
|
-
name: 'inspect',
|
|
226
|
-
actions: {
|
|
227
|
-
inspect() { return this.inspect(); },
|
|
228
|
-
verify() { return this.inspect(); },
|
|
229
|
-
review() { return this.inspect(); },
|
|
230
|
-
},
|
|
231
|
-
beforePlay(action) {
|
|
232
|
-
if (action === 'inspect' || action === 'verify' || action === 'review') return;
|
|
233
|
-
this._inspectFx = null;
|
|
234
|
-
},
|
|
235
|
-
reset() {
|
|
236
|
-
this._inspectFx = null;
|
|
237
|
-
},
|
|
238
|
-
draw: drawInspect,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
export { AgentRobotAvatar, INSPECT_DEFAULTS };
|
|
242
|
-
export default AgentRobotAvatar;
|
|
1
|
+
import AgentRobotAvatar, { registerAvatarExtension } from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
|
|
3
|
+
const proto = AgentRobotAvatar.prototype;
|
|
4
|
+
|
|
5
|
+
const clamp01 = value => Math.max(0, Math.min(1, value));
|
|
6
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
7
|
+
const smooth = t => {
|
|
8
|
+
t = clamp01(t);
|
|
9
|
+
return t * t * (3 - 2 * t);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Finalized from the R59 tuner values supplied by the user.
|
|
13
|
+
const INSPECT_DEFAULTS = Object.freeze({
|
|
14
|
+
aperture: 16,
|
|
15
|
+
scanOffset: 16,
|
|
16
|
+
prepPause: 120,
|
|
17
|
+
close: 220,
|
|
18
|
+
holdClosed: 880,
|
|
19
|
+
down1: 400,
|
|
20
|
+
holdDown1: 560,
|
|
21
|
+
up1: 160,
|
|
22
|
+
holdUp1: 580,
|
|
23
|
+
center1: 140,
|
|
24
|
+
holdCenter: 760,
|
|
25
|
+
down2: 140,
|
|
26
|
+
holdDown2: 620,
|
|
27
|
+
up2: 180,
|
|
28
|
+
holdUp2: 620,
|
|
29
|
+
center2: 100,
|
|
30
|
+
holdAfter: 940,
|
|
31
|
+
open: 260,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const INSPECT_BOUNDS = Object.freeze({
|
|
35
|
+
aperture: [6, 40],
|
|
36
|
+
scanOffset: [0, 24],
|
|
37
|
+
prepPause: [0, 2400],
|
|
38
|
+
close: [40, 2400],
|
|
39
|
+
holdClosed: [0, 2400],
|
|
40
|
+
down1: [40, 2400],
|
|
41
|
+
holdDown1: [0, 2400],
|
|
42
|
+
up1: [40, 2400],
|
|
43
|
+
holdUp1: [0, 2400],
|
|
44
|
+
center1: [40, 2400],
|
|
45
|
+
holdCenter: [0, 2400],
|
|
46
|
+
down2: [40, 2400],
|
|
47
|
+
holdDown2: [0, 2400],
|
|
48
|
+
up2: [40, 2400],
|
|
49
|
+
holdUp2: [0, 2400],
|
|
50
|
+
center2: [40, 2400],
|
|
51
|
+
holdAfter: [0, 2400],
|
|
52
|
+
open: [40, 2400],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const runtimeWindow = typeof window !== 'undefined' ? window : null;
|
|
56
|
+
const inspectConfig = (runtimeWindow?.AgentRobotAvatarInspectConfig && typeof runtimeWindow.AgentRobotAvatarInspectConfig === 'object')
|
|
57
|
+
? runtimeWindow.AgentRobotAvatarInspectConfig
|
|
58
|
+
: {};
|
|
59
|
+
|
|
60
|
+
function normalizeConfigValue(key, value) {
|
|
61
|
+
const fallback = INSPECT_DEFAULTS[key];
|
|
62
|
+
const numeric = Number(value);
|
|
63
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
64
|
+
const [min, max] = INSPECT_BOUNDS[key];
|
|
65
|
+
return Math.max(min, Math.min(max, numeric));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
69
|
+
inspectConfig[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
70
|
+
}
|
|
71
|
+
if (runtimeWindow) {
|
|
72
|
+
runtimeWindow.AgentRobotAvatarInspectConfig = inspectConfig;
|
|
73
|
+
runtimeWindow.AgentRobotAvatarInspectDefaults = INSPECT_DEFAULTS;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function currentInspectConfig() {
|
|
77
|
+
const config = {};
|
|
78
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
79
|
+
config[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
80
|
+
}
|
|
81
|
+
return config;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function dispatchInspectState(instance, state) {
|
|
85
|
+
instance.dispatchEvent(new CustomEvent('face-state', {
|
|
86
|
+
detail: { state }
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function makeTimeline(config) {
|
|
91
|
+
let cursor = 0;
|
|
92
|
+
const phases = {};
|
|
93
|
+
const add = key => {
|
|
94
|
+
phases[`${key}Start`] = cursor;
|
|
95
|
+
cursor += config[key];
|
|
96
|
+
phases[`${key}End`] = cursor;
|
|
97
|
+
};
|
|
98
|
+
for (const key of [
|
|
99
|
+
'close','holdClosed','down1','holdDown1','up1','holdUp1','center1','holdCenter',
|
|
100
|
+
'down2','holdDown2','up2','holdUp2','center2','holdAfter','open'
|
|
101
|
+
]) add(key);
|
|
102
|
+
phases.duration = cursor;
|
|
103
|
+
return phases;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function move(elapsed, start, end, from, to) {
|
|
107
|
+
if (elapsed <= start) return from;
|
|
108
|
+
if (elapsed >= end) return to;
|
|
109
|
+
const span = Math.max(1, end - start);
|
|
110
|
+
return lerp(from, to, smooth((elapsed - start) / span));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function verticalShift(elapsed, phases, offset) {
|
|
114
|
+
if (elapsed < phases.down1Start) return 0;
|
|
115
|
+
if (elapsed < phases.down1End) return move(elapsed, phases.down1Start, phases.down1End, 0, offset);
|
|
116
|
+
if (elapsed < phases.holdDown1End) return offset;
|
|
117
|
+
if (elapsed < phases.up1End) return move(elapsed, phases.up1Start, phases.up1End, offset, -offset);
|
|
118
|
+
if (elapsed < phases.holdUp1End) return -offset;
|
|
119
|
+
if (elapsed < phases.center1End) return move(elapsed, phases.center1Start, phases.center1End, -offset, 0);
|
|
120
|
+
if (elapsed < phases.holdCenterEnd) return 0;
|
|
121
|
+
|
|
122
|
+
// Second pass: down to the lowest point, hold, then return directly to center.
|
|
123
|
+
// Do not overshoot upward again.
|
|
124
|
+
if (elapsed < phases.down2End) return move(elapsed, phases.down2Start, phases.down2End, 0, offset);
|
|
125
|
+
if (elapsed < phases.holdDown2End) return offset;
|
|
126
|
+
if (elapsed < phases.up2End) return move(elapsed, phases.up2Start, phases.up2End, offset, 0);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Keep the R60 head motion unchanged: down, up past center, then back to center.
|
|
131
|
+
function animateInspectHead(instance, phases) {
|
|
132
|
+
if (!instance._headMotion?.animate) return;
|
|
133
|
+
const token = instance._transitionToken;
|
|
134
|
+
const start = phases.down1Start;
|
|
135
|
+
const end = phases.center1End;
|
|
136
|
+
const duration = Math.max(1, end - start);
|
|
137
|
+
const offset = time => Math.max(0, Math.min(1, (time - start) / duration));
|
|
138
|
+
|
|
139
|
+
instance._headMotion.style.transformBox = 'view-box';
|
|
140
|
+
instance._headMotion.style.transformOrigin = '120px 120px';
|
|
141
|
+
const anim = instance._headMotion.animate([
|
|
142
|
+
{ transform: 'translateY(0px)', offset: 0, easing: 'cubic-bezier(.38,0,.25,1)' },
|
|
143
|
+
{ transform: 'translateY(5px)', offset: offset(phases.down1End) },
|
|
144
|
+
{ transform: 'translateY(5px)', offset: offset(phases.holdDown1End), easing: 'cubic-bezier(.35,0,.22,1)' },
|
|
145
|
+
{ transform: 'translateY(-4px)', offset: offset(phases.up1End) },
|
|
146
|
+
{ transform: 'translateY(-4px)', offset: offset(phases.holdUp1End), easing: 'cubic-bezier(.35,0,.22,1)' },
|
|
147
|
+
{ transform: 'translateY(0px)', offset: 1 },
|
|
148
|
+
], {
|
|
149
|
+
duration,
|
|
150
|
+
delay: start,
|
|
151
|
+
easing: 'linear',
|
|
152
|
+
fill: 'forwards',
|
|
153
|
+
});
|
|
154
|
+
anim.onfinish = () => {
|
|
155
|
+
if (token === instance._transitionToken) instance._headMotion.style.transform = 'translateY(0px)';
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
proto.inspect = async function() {
|
|
160
|
+
this.noteActivity();
|
|
161
|
+
this._inputWanted = false;
|
|
162
|
+
|
|
163
|
+
const config = currentInspectConfig();
|
|
164
|
+
this.reset();
|
|
165
|
+
if (!(await this._prepareExpression({ normalizePose: true, duration: 160, pause: config.prepPause }))) return;
|
|
166
|
+
|
|
167
|
+
const token = this._transitionToken;
|
|
168
|
+
this._expressionLock = true;
|
|
169
|
+
this._look.x = 0;
|
|
170
|
+
this._look.y = 0;
|
|
171
|
+
|
|
172
|
+
const phases = makeTimeline(config);
|
|
173
|
+
this._inspectFx = { start: performance.now(), duration: phases.duration, config, phases };
|
|
174
|
+
dispatchInspectState(this, 'inspect');
|
|
175
|
+
animateInspectHead(this, phases);
|
|
176
|
+
|
|
177
|
+
await this._wait(phases.duration + 30);
|
|
178
|
+
if (token !== this._transitionToken) return;
|
|
179
|
+
|
|
180
|
+
this._inspectFx = null;
|
|
181
|
+
this._look.x = 0;
|
|
182
|
+
this._look.y = 0;
|
|
183
|
+
this._releaseExpressionLock();
|
|
184
|
+
dispatchInspectState(this, 'idle');
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
function drawInspect(now) {
|
|
188
|
+
const fx = this._inspectFx;
|
|
189
|
+
if (!fx) return;
|
|
190
|
+
|
|
191
|
+
const config = fx.config || currentInspectConfig();
|
|
192
|
+
const phases = fx.phases || makeTimeline(config);
|
|
193
|
+
const elapsed = Math.max(0, Math.min(phases.duration, now - fx.start));
|
|
194
|
+
|
|
195
|
+
let lidBlend = 1;
|
|
196
|
+
if (elapsed < phases.closeEnd) {
|
|
197
|
+
lidBlend = smooth(elapsed / Math.max(1, config.close));
|
|
198
|
+
} else if (elapsed >= phases.openStart) {
|
|
199
|
+
lidBlend = 1 - smooth((elapsed - phases.openStart) / Math.max(1, config.open));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const yShift = verticalShift(elapsed, phases, config.scanOffset);
|
|
203
|
+
|
|
204
|
+
// Keep the original eye geometry. The squint is made only by the top/bottom masks.
|
|
205
|
+
this._leftEye.setAttribute('transform', `translate(86 ${(126 + yShift).toFixed(2)}) scale(1 1)`);
|
|
206
|
+
this._rightEye.setAttribute('transform', `translate(154 ${(126 + yShift).toFixed(2)}) scale(1 1)`);
|
|
207
|
+
|
|
208
|
+
const apertureHalf = config.aperture / 2;
|
|
209
|
+
const topEdge = lerp(-36, -apertureHalf, lidBlend);
|
|
210
|
+
const bottomEdge = lerp(36, apertureHalf, lidBlend);
|
|
211
|
+
|
|
212
|
+
for (const top of [this._leftTop, this._rightTop]) {
|
|
213
|
+
top.setAttribute('y', (topEdge - 90).toFixed(2));
|
|
214
|
+
top.setAttribute('height', '90');
|
|
215
|
+
top.setAttribute('transform', `rotate(0 0 ${topEdge.toFixed(2)})`);
|
|
216
|
+
}
|
|
217
|
+
for (const bottom of [this._leftBottom, this._rightBottom]) {
|
|
218
|
+
bottom.setAttribute('y', bottomEdge.toFixed(2));
|
|
219
|
+
bottom.setAttribute('height', '90');
|
|
220
|
+
bottom.setAttribute('transform', `rotate(0 0 ${bottomEdge.toFixed(2)})`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
registerAvatarExtension({
|
|
225
|
+
name: 'inspect',
|
|
226
|
+
actions: {
|
|
227
|
+
inspect() { return this.inspect(); },
|
|
228
|
+
verify() { return this.inspect(); },
|
|
229
|
+
review() { return this.inspect(); },
|
|
230
|
+
},
|
|
231
|
+
beforePlay(action) {
|
|
232
|
+
if (action === 'inspect' || action === 'verify' || action === 'review') return;
|
|
233
|
+
this._inspectFx = null;
|
|
234
|
+
},
|
|
235
|
+
reset() {
|
|
236
|
+
this._inspectFx = null;
|
|
237
|
+
},
|
|
238
|
+
draw: drawInspect,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export { AgentRobotAvatar, INSPECT_DEFAULTS };
|
|
242
|
+
export default AgentRobotAvatar;
|