demo/bump v2:头图改成妈妈肚皮,小脚印时隐时现,戳中就溜,按压肉感回弹
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BIN
demo/bump/belly.jpg
Normal file
BIN
demo/bump/belly.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
demo/bump/belly_bg.jpg
Normal file
BIN
demo/bump/belly_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB |
@@ -3,13 +3,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||||
<title>宝宝在里面顶你 · 创意预览</title>
|
||||
<!-- 2026-10-03 创意预览(不进正式首页):「首页被宝宝从里面顶起来」。
|
||||
头图当成一层软布/果冻:WebGL 把首页头图截图当纹理,按一个高斯鼓包的高度场做「放大折射 + 受光高光 + 背光阴影」,
|
||||
宝宝隔几秒往上顶一下(弹簧冲量,带回弹);点中鼓包 → 原地塌下去、荡开一圈波纹,
|
||||
鼓包贴着布面「钻」到另一个位置(顺着跑动方向被拉长),再「啵」地顶起来。
|
||||
下面的卡片是首页截图(静态),只为给头图一个真实语境。
|
||||
?rec=1:录屏模式,关掉自动循环,由 __adv / __tap 逐帧驱动(生成动图用)。 -->
|
||||
<title>宝宝在肚子里踢你 · 创意预览</title>
|
||||
<!-- 2026-10-03 创意预览(不进正式首页):「整块头图是妈妈的肚皮」v2。
|
||||
头图去掉了胎儿插画(belly.jpg:原头图渐变拟合 + 一团圆鼓鼓的肚皮柔光,再叠回 LOGO/倒计时/周数/进度条)。
|
||||
WebGL 把它当一层肉肉的肚皮:
|
||||
· 宝宝时不时从里面蹬出一只小脚印(脚掌+五个脚趾的高度场),停一会儿、扭一扭、脚趾动一动,再缩回去,隔会儿换个地方;
|
||||
· 手指戳上去 = 肚皮被按下去一个窝,松手后像肉一样 duang duang 回弹(欠阻尼弹簧,过冲时反鼓起来)+ 周围一起晃;
|
||||
· 戳中小脚印 → 脚印「嗖」地缩回去并往远离手指的方向溜走,过一两秒在别处冒出来。
|
||||
不再有水波纹。下面的卡片是首页截图(静态),只为给头图一个真实语境。
|
||||
?rec=1:录屏模式,由 __adv / __tap / __press 逐帧驱动(生成动图用)。 -->
|
||||
<style>
|
||||
html,body{margin:0;background:#F8F3EF;-webkit-tap-highlight-color:transparent;font-family:-apple-system,"PingFang SC","Noto Sans SC",sans-serif}
|
||||
.page{position:relative;max-width:480px;margin:0 auto;overflow:hidden}
|
||||
@@ -26,6 +28,7 @@
|
||||
box-shadow:0 6px 16px rgba(180,80,50,.16),inset 0 0 0 1px rgba(255,255,255,.9);transition:transform .35s cubic-bezier(.3,1.6,.5,1)}
|
||||
.hint span b{color:#F06A45}
|
||||
.hint span.pop{transform:scale(1.08)}
|
||||
.hint span{white-space:nowrap}
|
||||
/* 录屏用的手指 */
|
||||
.finger{position:absolute;width:38px;height:38px;margin:-19px 0 0 -19px;border-radius:50%;pointer-events:none;z-index:5;
|
||||
background:radial-gradient(circle,rgba(255,255,255,.85) 0 30%,rgba(255,255,255,.35) 55%,rgba(255,255,255,0) 72%);
|
||||
@@ -36,11 +39,11 @@
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="hero" id="hero">
|
||||
<img src="hero.jpg" alt="">
|
||||
<img src="belly.jpg" alt="">
|
||||
<canvas id="cv"></canvas>
|
||||
<i class="finger" id="finger"></i>
|
||||
</div>
|
||||
<div class="hint"><span id="hint">宝宝在里面顶你呢,<b>点一下鼓包</b></span></div>
|
||||
<div class="hint"><span id="hint">宝宝在里面踢你呢,<b>戳一下小脚丫</b></span></div>
|
||||
<img class="below" src="below.jpg" alt="">
|
||||
</div>
|
||||
<script>
|
||||
@@ -58,52 +61,70 @@ precision highp float;
|
||||
varying vec2 v;
|
||||
uniform sampler2D T;
|
||||
uniform vec2 S; // 逻辑尺寸 375x270
|
||||
uniform vec4 B; // 鼓包 x,y,半径,高度(px)
|
||||
uniform vec3 D; // 拉伸方向 dx,dy,拉伸量
|
||||
uniform vec4 R[4]; // 波纹 x,y,年龄(s),强度
|
||||
uniform vec4 F; // 脚印 x,y,角度,高度(px)
|
||||
uniform vec4 G; // 脚印 左右脚(±1),脚趾蜷曲(0-1),拉长量,0
|
||||
uniform vec4 P; // 手指 x,y,按压深度(负=凹下去,正=回弹鼓起),0
|
||||
uniform vec2 J; // 肚皮整体晃动的位移(px)
|
||||
uniform float Tm;
|
||||
float bump(vec2 p){
|
||||
vec2 d=p-B.xy;
|
||||
float a=dot(d,D.xy), b=dot(d,vec2(-D.y,D.x));
|
||||
a/= (1.+D.z); b*= (1.+D.z*.35);
|
||||
float r=B.z*(1.+.035*sin(Tm*2.1)); // 布面轻轻呼吸
|
||||
return B.w*exp(-(a*a+b*b)/(r*r));
|
||||
}
|
||||
float wave(vec2 p){
|
||||
float h=0.;
|
||||
for(int i=0;i<4;i++){
|
||||
vec4 w=R[i]; if(w.w<=0.) continue;
|
||||
float dist=length(p-w.xy), front=w.z*210.;
|
||||
float e=exp(-pow((dist-front)/34.,2.));
|
||||
h+=w.w*5.5*exp(-w.z*2.6)*e*sin((dist-front)*.16);
|
||||
float sdE(vec2 p,vec2 c,vec2 r){return (length((p-c)/r)-1.)*min(r.x,r.y);}
|
||||
float smin(float a,float b,float k){float h=clamp(.5+.5*(b-a)/k,0.,1.);return mix(b,a,h)-k*h*(1.-h);}
|
||||
// 小脚丫:脚尖朝 -y,长约 52px
|
||||
float foot(vec2 p){
|
||||
if(F.w<.01) return 0.;
|
||||
vec2 d=p-F.xy; float c=cos(F.z),s=sin(F.z);
|
||||
d=vec2(c*d.x+s*d.y,-s*d.x+c*d.y);
|
||||
d/=1.2; d.x*=G.x; d.y/=1.+G.z;
|
||||
float sole=sdE(d,vec2(1.,-6.),vec2(11.5,11.));
|
||||
sole=smin(sole,sdE(d,vec2(-1.5,6.),vec2(8.,11.)),10.);
|
||||
sole=smin(sole,sdE(d,vec2(-1.,15.),vec2(8.5,9.)),10.);
|
||||
float hs=smoothstep(6.,-12.,sole);
|
||||
// 五个脚趾,从大到小;蜷曲时往脚掌收、变矮
|
||||
float cu=G.y, ht=0.;
|
||||
vec3 toe[5];
|
||||
toe[0]=vec3(-6.5,-22.,4.8); toe[1]=vec3(.5,-24.5,3.7); toe[2]=vec3(6.,-23.,3.3); toe[3]=vec3(10.3,-20.,3.); toe[4]=vec3(13.4,-15.8,2.7);
|
||||
for(int i=0;i<5;i++){
|
||||
vec3 t=toe[i]; float w=sin(Tm*9.+float(i)*1.3)*.5+.5;
|
||||
vec2 tc=t.xy+vec2(0.,(3.5+1.5*w)*cu);
|
||||
float sd=length(d-tc)-t.z;
|
||||
ht=max(ht,smoothstep(2.5,-3.5,sd)*(.8-.25*cu*w));
|
||||
}
|
||||
return h;
|
||||
return F.w*max(hs,ht);
|
||||
}
|
||||
float hgt(vec2 p){return bump(p)+wave(p);}
|
||||
float dentR=33.;
|
||||
float dent(vec2 p){vec2 d=p-P.xy;return P.z*exp(-dot(d,d)/(dentR*dentR));}
|
||||
float hgt(vec2 p){return foot(p)+dent(p);}
|
||||
void main(){
|
||||
vec2 p=v*S;
|
||||
float e=1.;
|
||||
vec2 g=vec2(hgt(p+vec2(e,0.))-hgt(p-vec2(e,0.)),hgt(p+vec2(0.,e))-hgt(p-vec2(0.,e)))/(2.*e);
|
||||
float h=hgt(p);
|
||||
// 折射:往鼓包中心取样 → 鼓起来的地方被放大,像隔着一层果冻
|
||||
// 透镜:越靠近鼓包顶越被放大(顶部约 1.5 倍),文字、图案会跟着「鼓」起来
|
||||
float hn=clamp(bump(p)/22.,0.,1.3);
|
||||
vec2 q=p-(p-B.xy)*.34*hn+g*4.;
|
||||
vec3 c0=texture2D(T,v).rgb;
|
||||
// 只在橙色肚皮上生效:下缘弧线外是页面底色
|
||||
// 白字也接近页面底色,所以只在下缘弧线那一带(y>185)按颜色判断
|
||||
float m=p.y<185.?1.:smoothstep(.03,.12,distance(c0,vec3(.973,.953,.937)));
|
||||
float e=1.2;
|
||||
vec2 g=vec2(hgt(p+vec2(e,0.))-hgt(p-vec2(e,0.)),hgt(p+vec2(0.,e))-hgt(p-vec2(0.,e)))/(2.*e)*m;
|
||||
float h=hgt(p)*m;
|
||||
// 肉感:按下去时周围的皮肤被拽向手指,回弹时被推开;整块肚皮再跟着晃
|
||||
vec2 dp=p-P.xy;
|
||||
float gp=exp(-dot(dp,dp)/(dentR*dentR*2.6));
|
||||
float gw=exp(-dot(dp,dp)/(130.*130.));
|
||||
vec2 q=p+dp*(-P.z*.03)*gp - J*gw + g*2.2;
|
||||
float fh=clamp(foot(p)/12.,0.,1.);
|
||||
q-=(p-F.xy)*.10*fh; // 脚印顶起处微微放大
|
||||
q=mix(p,q,m);
|
||||
vec3 c=texture2D(T,clamp(q/S,0.,1.)).rgb;
|
||||
// 只在橙色头图区域生效(下缘弧线外是页面底色,不该被打光)
|
||||
vec3 n=normalize(vec3(-g*1.1,1.));
|
||||
vec3 n=normalize(vec3(-g*.9,1.));
|
||||
vec3 L=normalize(vec3(-.45,-.75,.9));
|
||||
float dif=dot(n,L)-dot(vec3(0.,0.,1.),L);
|
||||
c+=vec3(1.,.97,.94)*max(dif,0.)*.42;
|
||||
c*=1.+min(dif,0.)*vec3(.38,.46,.5); // 背光侧偏暖的阴影
|
||||
// 鼓包根部一圈很淡的接触阴影:布被顶起后四周微微下陷
|
||||
float ring=bump(p)/max(B.w,.001); c*=1.-.06*smoothstep(.02,.25,ring)*(1.-smoothstep(.25,.6,ring))*smoothstep(2.,10.,B.w);
|
||||
// 鼓包往右下投一层很淡的影子,让它「站」起来
|
||||
vec2 d0=p-B.xy-vec2(6.,10.); float sh=exp(-dot(d0,d0)/(B.z*B.z*1.1))*smoothstep(1.,12.,B.w);
|
||||
c*=1.-.09*sh*(1.-ring);
|
||||
c+=vec3(1.,.93,.88)*max(dif,0.)*.34;
|
||||
c*=1.+min(dif,0.)*vec3(.42,.55,.6); // 背光侧偏暖红,像皮肤
|
||||
// 脚印四周一圈很淡的凹陷(皮被撑起,边上被拉紧)
|
||||
float rim=smoothstep(.0,.25,fh)*(1.-smoothstep(.25,.7,fh));
|
||||
c*=1.-.05*rim*smoothstep(2.,8.,F.w);
|
||||
// 按下去的窝:窝底暗一点、偏红一点(像压着肉)
|
||||
float dn=clamp(-dent(p)/10.,0.,1.)*m;
|
||||
c*=1.-dn*vec3(.13,.21,.2);
|
||||
vec3 Hh=normalize(L+vec3(0.,0.,1.));
|
||||
c+=vec3(1.)*pow(max(dot(n,Hh),0.),60.)*.35*smoothstep(.5,4.,h); // 果冻感高光
|
||||
c+=vec3(1.,.9,.85)*smoothstep(2.,22.,h)*.06; // 顶部微微透亮
|
||||
c+=vec3(1.,.97,.95)*pow(max(dot(n,Hh),0.),36.)*.22*smoothstep(.5,4.,abs(h)); // 皮肤油润的柔高光
|
||||
c+=vec3(1.,.88,.8)*smoothstep(2.,12.,h)*.05; // 顶起处微微透亮
|
||||
gl_FragColor=vec4(c,1.);
|
||||
}`;
|
||||
function sh(t, s) { const o = gl.createShader(t); gl.shaderSource(o, s); gl.compileShader(o); if (!gl.getShaderParameter(o, gl.COMPILE_STATUS)) throw gl.getShaderInfoLog(o); return o; }
|
||||
@@ -113,7 +134,7 @@ void main(){
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
||||
const al = gl.getAttribLocation(pr, 'a'); gl.enableVertexAttribArray(al); gl.vertexAttribPointer(al, 2, gl.FLOAT, false, 0, 0);
|
||||
const U = n => gl.getUniformLocation(pr, n);
|
||||
const uS = U('S'), uB = U('B'), uD = U('D'), uR = U('R'), uT = U('Tm');
|
||||
const uS = U('S'), uF = U('F'), uG = U('G'), uP = U('P'), uJ = U('J'), uT = U('Tm');
|
||||
gl.uniform2f(uS, W, H);
|
||||
|
||||
const tex = gl.createTexture(); let ready = false;
|
||||
@@ -127,7 +148,7 @@ void main(){
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
ready = true; window.__ready = true;
|
||||
};
|
||||
img.src = 'hero.jpg';
|
||||
img.src = 'belly.jpg';
|
||||
|
||||
function resize() {
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 3), r = hero.getBoundingClientRect();
|
||||
@@ -136,81 +157,104 @@ void main(){
|
||||
}
|
||||
resize(); addEventListener('resize', resize);
|
||||
|
||||
/* ---------- 物理:鼓包高度是一根阻尼弹簧,宝宝顶一下 = 给它一个向上的冲量 ---------- */
|
||||
const BASE = 21, RAD = 42;
|
||||
const b = { x: 96, y: 84, amp: 0, vel: 0, target: BASE, k: 170, c: 8.5, sx: 0, sy: 1, st: 0 };
|
||||
let mode = 'idle', trip = null, nextKick = 0.6, t = 0, count = 0;
|
||||
const ripples = [];
|
||||
/* ---------- 物理:都是阻尼弹簧 ---------- */
|
||||
const spring = (o, dt) => { const a = -o.k * (o.v - o.to) - o.c * o.vel; o.vel += a * dt; o.v += o.vel * dt; };
|
||||
// 脚印:高度一根弹簧(蹬出来时带一点过冲),位置/角度跟着状态机走
|
||||
const ft = { x: 290, y: 120, ang: -.3, side: 1, h: { v: 0, vel: 0, to: 0, k: 140, c: 11 }, curl: 0, stretch: 0, life: 0, dur: 3, ox: 0, oy: 0 };
|
||||
// 手指按压:欠阻尼,松手后过冲成一个小鼓包再回来 → duang duang
|
||||
const pk = { x: 0, y: 0, d: { v: 0, vel: 0, to: 0, k: 520, c: 6.5 }, held: false };
|
||||
// 肚皮整体晃:二维弹簧,阻尼更小、频率更低
|
||||
const jg = { x: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 }, y: { v: 0, vel: 0, to: 0, k: 210, c: 4.2 } };
|
||||
let mode = 'wait', wait = .5, t = 0, count = 0;
|
||||
|
||||
function kick(power) { b.vel += power; }
|
||||
function ripple(x, y, s) { ripples.push({ x, y, age: 0, s }); if (ripples.length > 4) ripples.shift(); }
|
||||
/* 鼓包落点:挑头图里「有东西」的地方(大字、胶囊、进度条、slogan、羊水囊边),顶起来时字和图案会被撑变形,
|
||||
「界面本身被顶起来」才看得出来;落在大片纯色上只像一团光。每次加一点随机抖动,且离上一个点足够远。 */
|
||||
const SPOTS = [[96, 84], [150, 50], [82, 112], [214, 24], [120, 22], [300, 176], [330, 70], [176, 112]];
|
||||
function pickSpot() {
|
||||
const pool = SPOTS.filter(s => Math.hypot(s[0] - b.x, s[1] - b.y) > 110);
|
||||
const s = pool[Math.floor(Math.random() * pool.length)] || SPOTS[0];
|
||||
return { x: s[0] + (Math.random() - .5) * 14, y: s[1] + (Math.random() - .5) * 10 };
|
||||
for (let i = 0; i < 40; i++) {
|
||||
// 落在右半边空白肚皮上(原来画宝宝的地方),偶尔窜到左下角;不压在大字和胶囊上
|
||||
const x = 40 + Math.random() * 300, y = 78 + Math.random() * 108;
|
||||
if (x < 225 && y < 188) continue;
|
||||
if (Math.hypot(x - ft.x, y - ft.y) > 70) return { x, y };
|
||||
}
|
||||
return { x: 300, y: 120 };
|
||||
}
|
||||
function appear() {
|
||||
const s = pickSpot();
|
||||
ft.x = s.x; ft.y = s.y; ft.ox = ft.oy = 0;
|
||||
ft.ang = (Math.random() - .5) * 1.4 + (s.x > 190 ? .25 : -.25);
|
||||
ft.side = Math.random() < .5 ? 1 : -1;
|
||||
ft.h.to = 9.5; ft.h.k = 140; ft.h.c = 9; ft.h.vel += 120; // 蹬一下冒出来
|
||||
ft.life = 0; ft.dur = 2.6 + Math.random() * 1.4;
|
||||
mode = 'show';
|
||||
}
|
||||
function hide(fast) {
|
||||
ft.h.to = 0;
|
||||
if (fast) { ft.h.k = 700; ft.h.c = 45; } else { ft.h.k = 60; ft.h.c = 14; }
|
||||
mode = 'hide';
|
||||
}
|
||||
const ease = u => u < .5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
||||
|
||||
function tap(x, y) {
|
||||
if (mode !== 'idle') return;
|
||||
if (Math.hypot(x - b.x, y - b.y) < RAD * 1.35) {
|
||||
// 点中:原地塌下去 + 荡一圈波纹,贴着布面钻到别处
|
||||
count++;
|
||||
ripple(b.x, b.y, 1);
|
||||
if (navigator.vibrate) navigator.vibrate(12);
|
||||
const to = pickSpot();
|
||||
trip = { fx: b.x, fy: b.y, tx: to.x, ty: to.y, t: 0, dur: .62 };
|
||||
mode = 'sink'; b.target = 4.5; b.k = 420; b.c = 20; trip.wait = .12;
|
||||
hint.innerHTML = count < 2 ? '抓到啦!它跑到别处去了' : '又跑啦~已经逗了宝宝 <b>' + count + '</b> 次';
|
||||
hint.classList.add('pop'); setTimeout(() => hint.classList.remove('pop'), 300);
|
||||
} else {
|
||||
// 没点中:布面轻轻一荡,宝宝在原地回应一下
|
||||
ripple(x, y, .45); kick(90);
|
||||
function press(x, y, on) {
|
||||
if (on) {
|
||||
pk.x = x; pk.y = y; pk.held = true; pk.d.to = -14; pk.d.k = 380; pk.d.c = 22;
|
||||
const fx = ft.x + ft.ox, fy = ft.y + ft.oy;
|
||||
if ((mode === 'show' || (mode === 'hide' && ft.h.v > 3)) && Math.hypot(x - fx, y - fy) < 46) {
|
||||
// 戳中小脚丫:嗖地缩回去,往远离手指的方向溜
|
||||
count++;
|
||||
let dx = fx - x, dy = fy - y, l = Math.hypot(dx, dy);
|
||||
if (l < 6) { dx = -Math.sin(ft.ang); dy = Math.cos(ft.ang); l = 1; } // 正中脚心:往脚跟方向缩
|
||||
ft.flee = { dx: dx / l, dy: dy / l, t: 0 };
|
||||
hide(true); mode = 'flee'; wait = 1.1 + Math.random() * .8;
|
||||
if (navigator.vibrate) navigator.vibrate(15);
|
||||
hint.innerHTML = count < 2 ? '被你戳到啦!小脚丫缩回去了' : '又溜啦~已经戳到宝宝 <b>' + count + '</b> 次';
|
||||
hint.classList.add('pop'); setTimeout(() => hint.classList.remove('pop'), 300);
|
||||
}
|
||||
} else if (pk.held) {
|
||||
pk.held = false;
|
||||
// 松手:弹簧变软、阻尼变小,皮肉弹回来并过冲 → duang duang
|
||||
pk.d.to = 0; pk.d.k = 360; pk.d.c = 4.6; pk.d.vel += 170;
|
||||
jg.y.vel += 150; jg.x.vel += (Math.random() - .5) * 90;
|
||||
}
|
||||
}
|
||||
hero.addEventListener('pointerdown', e => {
|
||||
const r = hero.getBoundingClientRect();
|
||||
tap((e.clientX - r.left) / r.width * W, (e.clientY - r.top) / r.height * H);
|
||||
hero.setPointerCapture && hero.setPointerCapture(e.pointerId);
|
||||
press((e.clientX - r.left) / r.width * W, (e.clientY - r.top) / r.height * H, true);
|
||||
});
|
||||
const up = () => press(0, 0, false);
|
||||
hero.addEventListener('pointerup', up); hero.addEventListener('pointercancel', up);
|
||||
|
||||
function step(dt) {
|
||||
t += dt;
|
||||
if (mode === 'idle') {
|
||||
nextKick -= dt;
|
||||
if (nextKick <= 0) { kick(150 + Math.random() * 120); nextKick = 2 + Math.random() * 1.6; }
|
||||
} else if (mode === 'sink') {
|
||||
trip.wait -= dt;
|
||||
if (trip.wait <= 0) mode = 'travel';
|
||||
} else if (mode === 'travel') {
|
||||
trip.t += dt;
|
||||
const u = Math.min(trip.t / trip.dur, 1), e = ease(u);
|
||||
const nx = trip.fx + (trip.tx - trip.fx) * e, ny = trip.fy + (trip.ty - trip.fy) * e + Math.sin(u * Math.PI) * -22;
|
||||
const vx = nx - b.x, vy = ny - b.y, sp = Math.hypot(vx, vy) / Math.max(dt, 1e-3);
|
||||
if (sp > 1) { b.sx = vx / Math.hypot(vx, vy); b.sy = vy / Math.hypot(vx, vy); }
|
||||
b.st = Math.min(sp / 520, 1.1);
|
||||
b.x = nx; b.y = ny;
|
||||
if (u >= 1) { // 到了:啵地顶起来
|
||||
mode = 'idle'; b.target = BASE; b.k = 170; b.c = 8.5; b.st = 0;
|
||||
kick(420); ripple(b.x, b.y, .7); nextKick = 1.8 + Math.random();
|
||||
}
|
||||
if (mode === 'wait') { wait -= dt; if (wait <= 0) appear(); }
|
||||
else if (mode === 'show') {
|
||||
ft.life += dt;
|
||||
const u = ft.life;
|
||||
// 扭一扭:轻轻转、往脚尖方向蹭一点、脚趾蜷一蜷;中途再蹬一下
|
||||
ft.curl = Math.max(0, Math.sin(u * 3.2 - .6)) * .9;
|
||||
ft.stretch = .06 * Math.sin(u * 2.4);
|
||||
ft.wob = Math.sin(u * 2.1) * .12;
|
||||
if (u > ft.dur * .45 && !ft.kicked) { ft.kicked = true; ft.h.vel += 70; }
|
||||
if (u > ft.dur) { hide(false); ft.kicked = false; }
|
||||
} else if (mode === 'hide') {
|
||||
if (ft.h.v < .3) { mode = 'wait'; wait = .9 + Math.random() * 1.2; ft.kicked = false; }
|
||||
} else if (mode === 'flee') {
|
||||
const f = ft.flee; f.t += dt;
|
||||
const s = 26 * (1 - Math.exp(-f.t * 7));
|
||||
ft.ox = f.dx * s; ft.oy = f.dy * s;
|
||||
ft.stretch = .35 * Math.exp(-f.t * 5);
|
||||
ft.curl = 1;
|
||||
wait -= dt;
|
||||
if (wait <= 0) { ft.kicked = false; appear(); }
|
||||
}
|
||||
if (mode !== 'travel') b.st *= Math.exp(-dt * 8);
|
||||
const a = -b.k * (b.amp - b.target) - b.c * b.vel;
|
||||
b.vel += a * dt; b.amp += b.vel * dt;
|
||||
if (b.amp < 0) { b.amp = 0; b.vel *= -.3; }
|
||||
for (const r of ripples) r.age += dt;
|
||||
while (ripples.length && ripples[0].age > 2) ripples.shift();
|
||||
if (mode !== 'show' && mode !== 'flee') { ft.stretch *= Math.exp(-dt * 6); }
|
||||
spring(ft.h, dt); if (ft.h.v < 0) { ft.h.v = 0; ft.h.vel *= -.2; }
|
||||
spring(pk.d, dt); spring(jg.x, dt); spring(jg.y, dt);
|
||||
}
|
||||
function draw() {
|
||||
if (!ready) return;
|
||||
gl.uniform4f(uB, b.x, b.y, RAD * (1 + b.amp / 120), b.amp);
|
||||
gl.uniform3f(uD, b.sx, b.sy, b.st);
|
||||
const arr = new Float32Array(16);
|
||||
ripples.forEach((r, i) => arr.set([r.x, r.y, r.age, r.s], i * 4));
|
||||
gl.uniform4fv(uR, arr);
|
||||
const wob = mode === 'show' ? (ft.wob || 0) : 0;
|
||||
gl.uniform4f(uF, ft.x + ft.ox, ft.y + ft.oy, ft.ang + wob, ft.h.v);
|
||||
gl.uniform4f(uG, ft.side, ft.curl, ft.stretch, 0);
|
||||
gl.uniform4f(uP, pk.x, pk.y, pk.d.v, 0);
|
||||
gl.uniform2f(uJ, jg.x.v, jg.y.v);
|
||||
gl.uniform1f(uT, t);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
@@ -218,8 +262,9 @@ void main(){
|
||||
if (REC) {
|
||||
// 录屏:由外部按帧推进
|
||||
window.__adv = ms => { const n = Math.round(ms / (1000 / 120)); for (let i = 0; i < n; i++) step(1 / 120); draw(); };
|
||||
window.__tap = (x, y) => tap(x, y);
|
||||
window.__bump = () => ({ x: b.x, y: b.y });
|
||||
window.__press = (x, y, on) => press(x, y, on);
|
||||
window.__tap = (x, y) => press(x, y, true);
|
||||
window.__foot = () => ({ x: ft.x + ft.ox, y: ft.y + ft.oy, h: ft.h.v, mode });
|
||||
window.__finger = (x, y, on) => {
|
||||
finger.style.left = x / W * 100 + '%'; finger.style.top = y / H * 100 + '%';
|
||||
finger.classList.toggle('on', !!on);
|
||||
|
||||
Reference in New Issue
Block a user