91 lines
4.2 KiB
HTML
91 lines
4.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
|
|
<title>TurboSu - G力</title>
|
|
<style>
|
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
|
html,body{width:100%;height:100%;overflow:hidden;background:#0a0a0f;color:#fff;
|
|
font-family:'SF Pro Display',-apple-system,system-ui,sans-serif}
|
|
#root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
#box{position:relative;overflow:hidden}
|
|
#box.contain{max-width:100vw;max-height:100vh}
|
|
#box.cover{min-width:100vw;min-height:100vh}
|
|
#box.fill{width:100vw;height:100vh}
|
|
#content{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#0a0a0f}
|
|
canvas{width:100%;height:100%}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="root"><div id="box" class="contain"><div id="content"><canvas id="g-canvas"></canvas></div></div></div>
|
|
<script>
|
|
const M={aspect_ratio:"1:1",render_mode:"contain"};
|
|
(function(){
|
|
const box=document.getElementById('box'),canvas=document.getElementById('g-canvas');
|
|
const ctx=canvas.getContext('2d');
|
|
const proto=location.protocol==='https:'?'wss:':'ws:',wsUrl=proto+'//'+location.host+'/ws';
|
|
let ws,rt,ax=0,ay=0,az=0;
|
|
|
|
function pr(r){if(!r||r==='auto')return null;const p=r.split(':');if(p.length===2)return +p[0]/+p[1]}
|
|
|
|
function apply(){
|
|
const rt=pr(M.aspect_ratio||'1:1'),md=M.render_mode||'contain';
|
|
if(!rt){box.style.width='100vw';box.style.height='100vh';box.className='fill';return}
|
|
const vw=window.innerWidth,vh=window.innerHeight,vr=vw/vh;let cw,ch;
|
|
if(md==='cover'){cw=vw;ch=vw/rt;box.className='cover'}
|
|
else{box.className='contain';cw=vr>rt?vh*rt:vw;ch=vr>rt?vh:vw/rt}
|
|
box.style.width=cw+'px';box.style.height=ch+'px';draw()
|
|
}
|
|
|
|
function sz(){const r=canvas.parentElement.getBoundingClientRect(),d=window.devicePixelRatio||1;canvas.width=r.width*d;canvas.height=r.height*d;canvas.style.width=r.width+'px';canvas.style.height=r.height+'px';ctx.setTransform(d,0,0,d,0,0)}
|
|
|
|
function draw(){
|
|
sz();const W=canvas.width/devicePixelRatio,H=canvas.height/devicePixelRatio,dpr=window.devicePixelRatio||1;
|
|
ctx.clearRect(0,0,W,H);
|
|
const cx=W/2,cy=H/2,r=Math.min(W,H)*.38;
|
|
const G=9.81,limit=2.0;
|
|
|
|
// outer ring
|
|
ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.strokeStyle='rgba(255,255,255,.1)';ctx.lineWidth=2;ctx.stroke();
|
|
// crosshairs
|
|
ctx.beginPath();ctx.moveTo(cx-r,cy);ctx.lineTo(cx+r,cy);ctx.moveTo(cx,cy-r);ctx.lineTo(cx,cy+r);ctx.strokeStyle='rgba(255,255,255,.06)';ctx.lineWidth=1;ctx.stroke();
|
|
// 1G ring
|
|
ctx.beginPath();ctx.arc(cx,cy,r/limit,0,Math.PI*2);ctx.setLineDash([4,8]);ctx.strokeStyle='rgba(255,255,255,.08)';ctx.lineWidth=1;ctx.stroke();ctx.setLineDash([]);
|
|
|
|
// G dot
|
|
const x=Math.max(-limit,Math.min(limit,ax/G)),y=Math.max(-limit,Math.min(limit,az/G));
|
|
const dx=cx+(x/limit)*r,dy=cy+(y/limit)*r;
|
|
ctx.beginPath();ctx.arc(dx,dy,Math.max(6,r/25),0,Math.PI*2);
|
|
const i=Math.sqrt(x*x+y*y)/limit;
|
|
const rc=Math.round(55+i*200),gc=Math.round(130-i*130),bc=Math.round(255-i*200);
|
|
ctx.fillStyle=`rgb(${rc},${gc},${bc})`;ctx.fill();
|
|
ctx.strokeStyle='rgba(255,255,255,.5)';ctx.lineWidth=1.5;ctx.stroke();
|
|
|
|
// trail
|
|
ctx.beginPath();
|
|
for(let t=0;t<8;t++){
|
|
const tx=Math.max(-limit,Math.min(limit,(ax-t*.3)/G)),ty=Math.max(-limit,Math.min(limit,(-ay-t*.3)/G));
|
|
const px=cx+(tx/limit)*r*.7,py=cy+(ty/limit)*r*.7;
|
|
t===0?ctx.moveTo(px,py):ctx.lineTo(px,py);
|
|
}
|
|
ctx.strokeStyle='rgba(255,255,255,.04)';ctx.lineWidth=1;ctx.stroke();
|
|
|
|
// numbers
|
|
ctx.fillStyle='rgba(255,255,255,.25)';ctx.font=`${Math.max(14,W/40)|0}px sans-serif`;ctx.textAlign='center';
|
|
ctx.fillText('Lat ' + (ax/G).toFixed(2) + 'G', cx, cy-r-20);
|
|
ctx.fillText('Long ' + (az/G).toFixed(2) + 'G', cx, cy+r+30);
|
|
ctx.fillText('L', cx-r-14, cy+4);ctx.fillText('R', cx+r+14, cy+4);
|
|
ctx.fillText('Brake', cx, cy-r-10);ctx.fillText('Accel', cx, cy+r+24);
|
|
}
|
|
|
|
function cn(){ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}}
|
|
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);if(m.type==='telemetry'){ax=m.data.acceleration_x||0;az=m.data.acceleration_z||0;draw()}}catch(e){}}
|
|
ws.onclose=()=>{rt=setTimeout(cn,2000)}}
|
|
|
|
apply();window.addEventListener('resize',()=>{apply()});cn()
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|