Files
grok-icon-study/replica/src/eyes.js
T
Edom 1ba126c92b feat:加上 Vue 学习站与可嵌入机芯
站点按 bloub 三栏做自定义、动画时间线和设置,支持序列导出。
形体只留常见几种并配中文名;动画页右栏拉满时间线旁空位,滚动条隐藏。
机芯补 GROK.create / <grok-bot>,以及暂停冻结。
2026-08-19 01:29:13 +08:00

222 lines
8.3 KiB
JavaScript

/* L3 — eye morph, wink, blink queue, placement. Source en / ks / mn / P2. */
(function (g) {
const {
clamp, lerpPoly, centroid, polyPath, Dke, spanAt, spanPoly, Rn,
} = g.GROK_MATH;
function queueBlink(q, now) {
q.push(
{ at: now, v: 0.05 },
{ at: now + 70, v: 0.05 },
{ at: now + 150, v: 1.08 },
{ at: now + 300, v: 1 }
);
if (Math.random() < 0.14) {
q.push({ at: now + 370, v: 0.05 }, { at: now + 480, v: 1 });
}
}
function consumeBlink(q, now) {
let key = null;
while (q.length && now >= q[0].at) key = q.shift().v;
return key;
}
function winkLid(base, now, winkAt, winkEye, i) {
let lid = Math.max(base, 0.04);
if (i === winkEye && now < winkAt + 320) {
const xr = (now - winkAt) / 320;
const Fr = xr < 0.42 ? 1 - xr / 0.42 : (xr - 0.42) / 0.58;
lid = Math.max(lid * clamp(Fr, 0, 1), 0.04);
}
return lid;
}
function paintEyes(opt) {
const {
now, polys, morphT, shape, face, faceTune, uniformEyes, eyeScaleProp,
blinkX, gazeX, gazeY, winkAt, winkEye, turn, cr, pointer, notifyX,
overlayX, eyeEls, badgeEl, badgeColor, Re, G9e, VJt, extras, ringHint,
} = opt;
const pulse = 1 + 0.07 * Math.sin(morphT * Math.PI);
const $i = {
x: face.x,
y: face.y,
sx: face.sx * (faceTune?.gap ?? 1),
sy: face.sy * (faceTune?.height ?? 1),
eye: face.eye * (faceTune?.size ?? 1),
leftDX: face.leftDX ?? 0,
};
const sX = uniformEyes ? $i.leftDX : 0;
const cents = [centroid(polys[0]), centroid(polys[1])];
let a1 = 0, o1 = 0;
for (const p of polys[0]) a1 = Math.max(a1, Math.abs(p[0] - cents[0][0]));
for (const p of polys[1]) o1 = Math.max(o1, Math.abs(p[0] - cents[1][0]));
const l1 = Math.abs(cents[1][0] - (cents[0][0] + sX)) * $i.sx;
const pre = uniformEyes ? 0 : VJt;
const _ee = a1 + o1 > 0.5 ? clamp((l1 - pre) / (a1 + o1), 0.35, 4) : 4;
const Uee = (uniformEyes ? 1 : $i.eye) * clamp(eyeScaleProp, 0.25, 4);
const oX = Math.min(clamp(opt.eyeBoostX, 0.2, 2) * Uee, _ee / pulse);
const Hee = Math.min(oX * clamp(faceTune?.eyeWidth ?? 1, 0.2, 3), _ee / pulse);
const u1 = oX * clamp(faceTune?.eyeHeight ?? 1, 0.2, 3);
const liveSpan = ringHint
? (y) => spanPoly(ringHint, y, Re)
: spanAt(shape.path, Re);
const top = opt.top ?? shape.top;
const bottom = opt.bottom ?? shape.bottom;
const Vn = opt.emphasisBlend || 0;
const midX = (cents[0][0] + cents[1][0]) / 2;
const midY = (cents[0][1] + cents[1][1]) / 2;
const pullX = (Re - midX) * 0.42 * Vn;
const pullY = (Re - midY) * 0.42 * Vn;
const gazeW = opt.tracking ? 0.2 : 1;
const badgeRing = opt.badgeRing || ringHint;
const Yl = badgeRing
? badgeRing[Math.round(badgeRing.length * 7 / 8) % badgeRing.length]
: [Re, shape.top];
for (let i = 0; i < 2; i++) {
const poly = polys[i];
const [Gn, Ti] = cents[i];
eyeEls[i].setAttribute("d", polyPath(poly));
const lid = winkLid(blinkX, now, winkAt, winkEye, i);
const Ea = Gn + (i === 0 ? sX : 0);
let Ca = Re + $i.x;
let Wo = (Ea - Re) * $i.sx;
let _c = 1, vre = 1, km = 1, Ree = 0, Fee = 0, zee = 1, bre = true, Tre = 1;
let Sre = clamp(Re + $i.y + (Ti - Re) * $i.sy, top + 2, bottom - 2);
const use3d = !!cr;
if (use3d) {
const xr = (Ea - Re) / Re;
const Fr = (Re - Ti) / Re;
const Ia = Math.sqrt(Math.max(0, 1 - xr * xr - Fr * Fr)) || 0.02;
const li = cr[0] * xr + cr[1] * Fr + cr[2] * Ia;
const bl = cr[3] * xr + cr[4] * Fr + cr[5] * Ia;
const Io = cr[6] * xr + cr[7] * Fr + cr[8] * Ia;
Wo = li * Re * $i.sx;
Sre = clamp(Re + $i.y - bl * Re * $i.sy, top + 2, bottom - 2);
let uo = -Fr * xr, Tl = 1 - Fr * Fr, Zi = -Fr * Ia;
const Yo = Math.hypot(uo, Tl, Zi);
if (Yo < 1e-6) {
uo = 0; Tl = 0; Zi = 1;
} else {
uo /= Yo; Tl /= Yo; Zi /= Yo;
}
const md = Fr * Zi - Ia * Tl, Oc = Ia * uo - xr * Zi, yu = xr * Tl - Fr * uo;
const vm = cr[0] * uo + cr[1] * Tl + cr[2] * Zi;
const Hme = cr[3] * uo + cr[4] * Tl + cr[5] * Zi;
const Nre = cr[0] * md + cr[1] * Oc + cr[2] * yu;
const Ere = cr[3] * md + cr[4] * Oc + cr[5] * yu;
const Cre = md, Ha = -Oc, ci = uo, Ys = -Tl;
const ku = Cre * Ys - ci * Ha || 1e-6;
const Ql = Ys / ku, Gee = -ci / ku, bm = -Ha / ku, Ire = Cre / ku;
km = Nre * Ql + vm * bm;
Fee = Nre * Gee + vm * Ire;
Ree = -Ere * Ql + -Hme * bm;
zee = -Ere * Gee + -Hme * Ire;
_c = Math.max(Math.hypot(km, Ree), 0.02);
vre = Math.max(Math.hypot(Fee, zee), 0.02);
bre = Io > 0.02;
Tre = Dke(clamp(Io / 0.5, 0, 1));
}
if (turn != null) {
const [spL, spR] = liveSpan(Sre);
const rad = Math.max((spR - spL) / 2, 12);
Ca = (spL + spR) / 2;
const li0 = Math.asin(clamp(Wo / rad, -1, 1));
const bl0 = li0 + turn;
const Io0 = Math.cos(bl0);
const uo0 = Math.max(Math.cos(li0), 0.02);
bre = Io0 > 0.02;
_c = Math.max(Io0, 0.02) / uo0;
Wo = rad * Math.sin(bl0);
Tre = Dke(clamp(Io0 / 0.5, 0, 1));
}
let Kj = Math.sin(now * 42e-5 + i) * 1.4 + Math.sin(now * 0.001 + i * 2) * 0.5;
let Ko = Math.sin(now * 58e-5 + i) * 0.9;
if (pointer) {
const Zl = Rn(0.16);
pointer.x += (pointer.tx - pointer.x) * Zl;
pointer.y += (pointer.ty - pointer.y) * Zl;
Kj += pointer.x * (1 - 0.6 * Vn) + pullX;
Ko += pointer.y * (1 - 0.6 * Vn) + pullY;
} else {
Kj += pullX;
Ko += pullY;
}
Kj += gazeX * gazeW + (extras.Zr || 0);
Ko += gazeY * gazeW + (extras.wi || 0);
const $ee = clamp(notifyX, 0, 1);
Kj -= 10 * $ee;
Ko += 7 * $ee;
const Vee = clamp(_c * Hee * pulse, 0.02, 2.4);
const _2 = clamp(vre * lid * u1 * pulse, 0.02, 2.4);
eyeEls[i].style.display = bre && overlayX < 0.5 ? "" : "none";
const useTurnOr3d = turn != null || use3d;
const Ume = G9e * _2 + 2;
const vl = clamp(
useTurnOr3d ? Sre + Ko * $i.sy : Re + $i.y + (Ti + Ko - Re) * $i.sy,
top + Ume,
bottom - Ume
);
let O2 = -Infinity, Xl = Infinity;
for (let p = 0; p < poly.length; p += 2) {
const Frp = (poly[p][0] - Gn) * Vee;
const [Ia2, li2] = liveSpan(vl + (poly[p][1] - Ti) * _2);
if (Ia2 - Frp > O2) O2 = Ia2 - Frp;
if (li2 - Frp < Xl) Xl = li2 - Frp;
}
const xre = Ca + Wo + Kj * $i.sx;
const lX = O2 <= Xl ? clamp(xre, O2, Xl) : (O2 + Xl) / 2;
let dd = lX + (xre - lX) * (1 - Tre);
let Yj = vl;
if (notifyX > 0.01) {
const xr = 20 * clamp(notifyX, 0, 1.4);
const Fr = dd - Yl[0], Ia = Yj - Yl[1];
const li = Math.hypot(Fr, Ia) || 1;
const bl = Fr / li, Io = Ia / li;
const uo = (i === 0 ? a1 : o1) * Vee;
const Tl = Math.hypot(uo * bl, G9e * _2 * Io);
const Zi = xr + Tl + 5;
if (li < Zi) {
dd += bl * (Zi - li);
Yj += Io * (Zi - li);
}
}
if (use3d) {
const FrM = clamp((turn != null ? _c : 1) * Hee * pulse, 0.02, 2.4);
const IaM = clamp(lid * u1 * pulse, 0.02, 2.4);
const liM = km * FrM, blM = Ree * FrM, IoM = Fee * IaM, uoM = zee * IaM;
eyeEls[i].setAttribute(
"transform",
`translate(${dd.toFixed(2)} ${Yj.toFixed(2)}) matrix(${liM.toFixed(4)} ${blM.toFixed(4)} ${IoM.toFixed(4)} ${uoM.toFixed(4)} 0 0) translate(${(-Gn).toFixed(2)} ${(-Ti).toFixed(2)})`
);
} else {
eyeEls[i].setAttribute(
"transform",
`translate(${dd.toFixed(2)} ${Yj.toFixed(2)}) scale(${Vee.toFixed(4)} ${_2.toFixed(4)}) translate(${(-Gn).toFixed(2)} ${(-Ti).toFixed(2)})`
);
}
}
if (badgeEl) {
const amt = clamp(notifyX, 0, 1.4);
if (amt <= 0.01) badgeEl.style.display = "none";
else {
badgeEl.style.display = "";
badgeEl.style.fill = badgeColor || "var(--gb-badge, #1d9bf0)";
badgeEl.setAttribute("cx", Yl[0].toFixed(1));
badgeEl.setAttribute("cy", Yl[1].toFixed(1));
badgeEl.setAttribute("r", (20 * amt).toFixed(2));
}
}
}
g.GROK_EYES = { queueBlink, consumeBlink, paintEyes, lerpPoly };
})(window);