|
| 1 | +import puppeteer from 'puppeteer'; |
| 2 | +import { mkdirSync } from 'node:fs'; |
| 3 | +const OUT = 'docs/sdlc/screenshots/'; |
| 4 | +mkdirSync(OUT, { recursive: true }); |
| 5 | + |
| 6 | +const browser = await puppeteer.launch({ |
| 7 | + executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe', |
| 8 | + headless: true, |
| 9 | + args: ['--no-sandbox','--use-angle=swiftshader','--enable-unsafe-swiftshader', |
| 10 | + '--window-size=1920,1080','--hide-scrollbars','--disable-setuid-sandbox'], |
| 11 | + defaultViewport: { width: 1920, height: 1080 } |
| 12 | +}); |
| 13 | +const page = await browser.newPage(); |
| 14 | +const sleep = ms => new Promise(r => setTimeout(r, ms)); |
| 15 | + |
| 16 | +async function waitArrival(ms=30000) { |
| 17 | + const t0=Date.now(); |
| 18 | + while(Date.now()-t0<ms){try{if(!(await page.evaluate(()=>!!window.__game?.orbitCam?.flight)))return true;}catch{}await sleep(300);} |
| 19 | +} |
| 20 | +async function waitMode(t,ms=15000){ |
| 21 | + const t0=Date.now(); |
| 22 | + while(Date.now()-t0<ms){try{if((await page.evaluate(()=>window.__game?.getMode?.()))===t)return true;}catch{}await sleep(250);} |
| 23 | +} |
| 24 | + |
| 25 | +await page.goto('http://localhost:5173/?quality=high',{waitUntil:'networkidle2',timeout:60000}); |
| 26 | +await page.waitForSelector('#start-btn',{visible:true,timeout:30000}); |
| 27 | +await page.click('#start-btn'); |
| 28 | +await sleep(2500); |
| 29 | + |
| 30 | +await page.evaluate(()=>window.__game.flyTo('mars')); |
| 31 | +await waitArrival(20000); |
| 32 | +await sleep(800); |
| 33 | + |
| 34 | +// ── 白昼:降落后直接仰望 |
| 35 | +await page.evaluate(()=>{ window.__game.orbitCam.distTarget=1; }); |
| 36 | +await waitMode('walk',15000); |
| 37 | +await sleep(2000); |
| 38 | + |
| 39 | +// 找正午(太阳仰角 > 50°) |
| 40 | +await page.evaluate(async ()=>{ |
| 41 | + const game=window.__game, w=game.ship.walk, clock=game.simClock; |
| 42 | + const STEP=88642.663/86400*0.05; |
| 43 | + function elev(){ |
| 44 | + const me=game.builder.bodies.get('mars'), mp=me.posKm; |
| 45 | + const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2); |
| 46 | + const s=[-mp[0]/len,mp[2]/-len,-mp[1]/len]; |
| 47 | + const lp=w.localPos, ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1; |
| 48 | + const u={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll}; |
| 49 | + const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q; |
| 50 | + const vx=u.x,vy=u.y,vz=u.z; |
| 51 | + const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx; |
| 52 | + const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx; |
| 53 | + const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz); |
| 54 | + return Math.asin(Math.max(-1,Math.min(1,s[0]*ux+s[1]*uy+s[2]*uz)))*180/Math.PI; |
| 55 | + } |
| 56 | + for(let i=0;i<40;i++){ |
| 57 | + await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r))); |
| 58 | + if(elev()>50) break; |
| 59 | + clock.jdTT+=STEP; |
| 60 | + await new Promise(r=>requestAnimationFrame(r)); |
| 61 | + } |
| 62 | + // 朝背太阳方向仰望 60°(典型白昼天空色调) |
| 63 | + const me=game.builder.bodies.get('mars'),mp=me.posKm; |
| 64 | + const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2); |
| 65 | + const seEcl=[-mp[0]/len,-mp[1]/len,-mp[2]/len]; |
| 66 | + const sun3=[seEcl[0],seEcl[2],-seEcl[1]]; |
| 67 | + const lp=w.localPos,ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1; |
| 68 | + const upL={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll}; |
| 69 | + const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q; |
| 70 | + const vx=upL.x,vy=upL.y,vz=upL.z; |
| 71 | + const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx; |
| 72 | + const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx; |
| 73 | + const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz); |
| 74 | + const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz; |
| 75 | + const hx=sun3[0]-dot*ux,hy=sun3[1]-dot*uy,hz=sun3[2]-dot*uz; |
| 76 | + const hl=Math.sqrt(hx*hx+hy*hy+hz*hz)||1; |
| 77 | + w.yaw=Math.atan2(hx/hl,hz/hl)+Math.PI; // 背太阳 |
| 78 | + w.pitch=1.0; // 约 57° 仰望天空 |
| 79 | + w.smx=w.yaw; w.smy=w.pitch; |
| 80 | +}); |
| 81 | +await sleep(4000); |
| 82 | +await page.screenshot({ path: OUT+'mars-day-zenith.png' }); |
| 83 | +console.log('✅ 白昼天顶截图'); |
| 84 | + |
| 85 | +// ── 日落 |
| 86 | +await page.evaluate(async ()=>{ |
| 87 | + const game=window.__game, w=game.ship.walk, clock=game.simClock; |
| 88 | + const STEP=88642.663/86400*0.05; |
| 89 | + function sunElevAndDir(){ |
| 90 | + const me=game.builder.bodies.get('mars'), mp=me.posKm; |
| 91 | + const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2); |
| 92 | + const seEcl=[-mp[0]/len,-mp[1]/len,-mp[2]/len]; |
| 93 | + const sun3=[seEcl[0],seEcl[2],-seEcl[1]]; |
| 94 | + const lp=w.localPos,ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1; |
| 95 | + const upL={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll}; |
| 96 | + const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q; |
| 97 | + const vx=upL.x,vy=upL.y,vz=upL.z; |
| 98 | + const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx; |
| 99 | + const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx; |
| 100 | + const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz); |
| 101 | + const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz; |
| 102 | + const hx=sun3[0]-dot*ux,hy=sun3[1]-dot*uy,hz=sun3[2]-dot*uz; |
| 103 | + const hl=Math.sqrt(hx*hx+hy*hy+hz*hz)||1; |
| 104 | + return { elev:Math.asin(Math.max(-1,Math.min(1,dot)))*180/Math.PI, hx:hx/hl, hz:hz/hl }; |
| 105 | + } |
| 106 | + for(let i=0;i<40;i++){ |
| 107 | + await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r))); |
| 108 | + const {elev}=sunElevAndDir(); |
| 109 | + if(elev>=4&&elev<=14) break; |
| 110 | + clock.jdTT+=STEP; |
| 111 | + await new Promise(r=>requestAnimationFrame(r)); |
| 112 | + } |
| 113 | + const {elev,hx,hz}=sunElevAndDir(); |
| 114 | + // 朝向太阳,仰角约 3° 看地平线 |
| 115 | + w.yaw=Math.atan2(hx,hz); |
| 116 | + w.pitch=0.05; |
| 117 | + w.smx=w.yaw; w.smy=w.pitch; |
| 118 | + return elev; |
| 119 | +}); |
| 120 | +await sleep(5000); |
| 121 | +await page.screenshot({ path: OUT+'mars-sunset-final.png' }); |
| 122 | +console.log('✅ 日落截图'); |
| 123 | + |
| 124 | +// 仰头 45° 看太阳周围天空 |
| 125 | +await page.evaluate(()=>{ const w=window.__game.ship.walk; w.pitch=0.8; w.smy=0.8; }); |
| 126 | +await sleep(2000); |
| 127 | +await page.screenshot({ path: OUT+'mars-sunset-corona.png' }); |
| 128 | +console.log('✅ 日落光晕截图'); |
| 129 | + |
| 130 | +await browser.close(); |
0 commit comments