1// ============================================================ 2// LAGRANGE routemap3d v2 â ããªã¼ãã£ã³ã°/ãããªã¼ãã£ã³ã°ã®3Dã«ã¼ãé£è¡æ¼åº 3// æå³ãã¤ããªãã: é»éã°ãªããï¼çºå èªè·¯ï¼å®ææãããªã¢ã«ï¼world.jsã®çæãã¯ã¹ãã£ãå ±æï¼ 4// åºçº=åè´åçã³ããã¼ï¼å®æ©ã·ã«ã¨ããæºæ ï¼â èªè·¯è¿½å¾ â ç®æ¨å¤©ä½ã§æåã¢ããã¼ãã 5// mountRoute3D(container, {origin, dest, wpName, mode:'depart'|'return', destColor, bodyId, shipType}) 6// unmountRoute3D() â ã¬ã³ãã©ç ´æ£ï¼2ã¤ç®ã®WebGLã³ã³ããã¹ããæ®ããªãï¼ 7// ============================================================ 8import * as THREE from 'three'; 9import { getBody } from './world.js?b=1786340351'; 10import { ship as gameShip, buildShipPreview } from './ship.js?b=1786340351'; 11 12let R = null; 13const gsapOk = () => typeof window.gsap !== 'undefined'; 14 15function starfield() { 16 const g = new THREE.BufferGeometry(), N = 1400, p = new Float32Array(N * 3), c = new Float32Array(N * 3); 17 let seed = 91; 18 const rnd = () => { seed = (seed * 16807) % 2147483647; return seed / 2147483647; }; 19 for (let i = 0; i < N; i++) { 20 const r = 420 + rnd() * 980, th = rnd() * Math.PI * 2, ph = Math.acos(rnd() * 2 - 1); 21 p[i * 3] = r * Math.sin(ph) * Math.cos(th); p[i * 3 + 1] = r * Math.cos(ph); p[i * 3 + 2] = r * Math.sin(ph) * Math.sin(th); 22 const t = rnd(); const cr = t < .12 ? [1, .85, .7] : t < .24 ? [.75, .85, 1] : [.85, .95, 1]; 23 c[i * 3] = cr[0]; c[i * 3 + 1] = cr[1]; c[i * 3 + 2] = cr[2]; 24 } 25 g.setAttribute('position', new THREE.BufferAttribute(p, 3)); 26 g.setAttribute('color', new THREE.BufferAttribute(c, 3)); 27 return new THREE.Points(g, new THREE.PointsMaterial({ vertexColors: true, size: 2.1, sizeAttenuation: false, transparent: true, opacity: .9 })); 28} 29 30// æå³é¢¨ã®é»éã°ãªããï¼åå¿å3æ¬ï¼æ¾å°ç®çï¼ 31function chartGrid() { 32 const grp = new THREE.Group(); 33 const mkRing = (r, op) => { 34 const pts = []; 35 for (let i = 0; i <= 128; i++) { const a = i / 128 * Math.PI * 2; pts.push(new THREE.Vector3(Math.cos(a) * r, 0, Math.sin(a) * r)); } 36 const l = new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), 37 new THREE.LineBasicMaterial({ color: 0x79e7dd, transparent: true, opacity: op })); 38 grp.add(l); 39 }; 40 mkRing(46, .14); mkRing(74, .1); mkRing(102, .07); 41 const seg = []; 42 for (let i = 0; i < 24; i++) { 43 const a = i / 24 * Math.PI * 2, c = Math.cos(a), s = Math.sin(a); 44 seg.push(new THREE.Vector3(c * 44, 0, s * 44), new THREE.Vector3(c * (i % 6 === 0 ? 40 : 42.5), 0, s * (i % 6 === 0 ? 40 : 42.5))); 45 } 46 const ticks = new THREE.LineSegments(new THREE.BufferGeometry().setFromPoints(seg), 47 new THREE.LineBasicMaterial({ color: 0x79e7dd, transparent: true, opacity: .18 })); 48 grp.add(ticks); 49 grp.position.y = -14; 50 return grp; 51} 52 53// ã«ãã»ã«åã³ããã¼ï¼ã¢ããã©ã¹å®æ©æºæ : ç½ã«ãã»ã«+ãã£ã¼ã«çªå¸¯3å+ä¸é¨ã©ã¸ã¨ã¼ã¿+端é¨ããã¯ãªã³ã°ï¼ 54function colony() { 55 const grp = new THREE.Group(); 56 const hullMat = new THREE.MeshStandardMaterial({ color: 0xe6e2d8, roughness: .55, metalness: .25 }); 57 const bandMat = new THREE.MeshBasicMaterial({ color: 0x59d8c8, transparent: true, opacity: .85, side: THREE.DoubleSide }); 58 const grayMat = new THREE.MeshStandardMaterial({ color: 0x3a4650, roughness: .6, metalness: .6 }); 59 const hull = new THREE.Mesh(new THREE.CylinderGeometry(3.6, 3.6, 13, 40), hullMat); 60 hull.rotation.z = Math.PI / 2; grp.add(hull); 61 for (const sd of [-1, 1]) { 62 const cap = new THREE.Mesh(new THREE.SphereGeometry(3.6, 40, 24, 0, Math.PI * 2, 0, Math.PI / 2), hullMat); 63 cap.rotation.z = -sd * Math.PI / 2; cap.position.x = sd * 6.5; grp.add(cap); 64 } 65 for (const x of [-4.2, 0, 4.2]) { 66 const band = new THREE.Mesh(new THREE.CylinderGeometry(3.66, 3.66, 2.1, 40, 1, true), bandMat); 67 band.rotation.z = Math.PI / 2; band.position.x = x; grp.add(band); 68 } 69 const panel = new THREE.Mesh(new THREE.BoxGeometry(8.6, .18, 2.8), grayMat); 70 panel.position.y = 4.5; grp.add(panel); 71 for (const x of [-3, 3]) { 72 const strut = new THREE.Mesh(new THREE.BoxGeometry(.16, .9, .16), grayMat); 73 strut.position.set(x, 4.0, 0); grp.add(strut); 74 } 75 const dockRing = new THREE.Mesh(new THREE.TorusGeometry(2.3, .3, 10, 36), grayMat); 76 dockRing.rotation.y = Math.PI / 2; dockRing.position.x = 8.2; grp.add(dockRing); 77 const glow = new THREE.Mesh(new THREE.CircleGeometry(1.5, 24), 78 new THREE.MeshBasicMaterial({ color: 0x8fe6ff, transparent: true, op
78acity: .9, side: THREE.DoubleSide })); 79 glow.rotation.y = Math.PI / 2; glow.position.x = 8.05; grp.add(glow); 80 return grp; 81} 82 83// 宿æãããªã¢ã« â world.jsã®ã©ã¤ã天ä½ãããã¯ã¹ãã£ãå ±æï¼æªæ§ç¯æã¯è²ãã©ã¼ã«ããã¯ï¼ 84function planetMat(bodyId, fallbackColor) { 85 try { 86 const b = bodyId && getBody(bodyId); 87 const src = b && b.mesh && b.mesh.material; 88 const map = src && (src.map || (src.uniforms && ((src.uniforms.map && src.uniforms.map.value) || (src.uniforms.tDay && src.uniforms.tDay.value)))); 89 const cloudsMap = b && b.clouds && b.clouds.material && (b.clouds.material.map || (b.clouds.material.uniforms && b.clouds.material.uniforms.map && b.clouds.material.uniforms.map.value)); 90 if (map) return { mat: new THREE.MeshStandardMaterial({ map, roughness: .92, metalness: 0 }), cloudsMap: cloudsMap || null }; 91 if (src && src.color) return { mat: new THREE.MeshStandardMaterial({ color: src.color.getHex(), roughness: .92 }), cloudsMap: null }; 92 } catch (e) { } 93 return { mat: new THREE.MeshStandardMaterial({ color: fallbackColor, roughness: .95, metalness: 0 }), cloudsMap: null }; 94} 95 96function planet(bodyId, fallbackColor) { 97 const grp = new THREE.Group(); 98 const { mat, cloudsMap } = planetMat(bodyId, fallbackColor); 99 const p = new THREE.Mesh(new THREE.SphereGeometry(14, 64, 48), mat); 100 grp.add(p); grp.userData.body = p; 101 if (cloudsMap) { 102 const cl = new THREE.Mesh(new THREE.SphereGeometry(14.35, 48, 36), 103 new THREE.MeshStandardMaterial({ map: cloudsMap, transparent: true, opacity: .85, depthWrite: false, roughness: 1 })); 104 grp.add(cl); grp.userData.clouds = cl; 105 } 106 const atmoColor = bodyId === 'MARS' ? 0xd8926a : bodyId === 'EARTH' ? 0x7fc4ff : 0x9fc4d8; 107 const atmo = new THREE.Mesh(new THREE.SphereGeometry(14.9, 48, 36), 108 new THREE.MeshBasicMaterial({ color: atmoColor, transparent: true, opacity: .12, side: THREE.BackSide, blending: THREE.AdditiveBlending })); 109 grp.add(atmo); 110 if (bodyId === 'SATURN') { 111 const ring = new THREE.Mesh(new THREE.RingGeometry(17.5, 26, 72), 112 new THREE.MeshBasicMaterial({ color: 0xd9c9a8, transparent: true, opacity: .5, side: THREE.DoubleSide })); 113 ring.rotation.x = Math.PI / 2.25; grp.add(ring); 114 } 115 return grp; 116} 117 118// ç®æ¨ãã¼ã«ã¼ï¼ç¥çãã¤ã¤ï¼ãã«ã¹ãªã³ã°ï¼ 119function destMarker() { 120 const grp = new THREE.Group(); 121 const dia = new THREE.Mesh(new THREE.OctahedronGeometry(2.4), 122 new THREE.MeshBasicMaterial({ color: 0xe8b94d, transparent: true, opacity: .9, wireframe: true })); 123 grp.add(dia); grp.userData.dia = dia; 124 const ring = new THREE.Mesh(new THREE.TorusGeometry(19, .22, 8, 64), 125 new THREE.MeshBasicMaterial({ color: 0xe8b94d, transparent: true, opacity: .55 })); 126 ring.rotation.x = Math.PI / 2.3; grp.add(ring); grp.userData.ring = ring; 127 return grp; 128} 129 130// é·ç¿¼æ©ï¼ãã«ãã£ãã¹03ç³»ã·ã«ã¨ãã: 細身è´+åå¾ã«ä¼¸ã³ãé·ç¿¼+ã¨ã³ã¸ã³å ï¼ 131// 宿©ã¢ãã«ï¼ããã¯UIã¨åã buildShipPreviewï¼ãå ¨é·ç´6ã¦ãããã¸æ£è¦åãã¦ä½¿ãã 132// ã²ã¼ã æ©ä½ã¯-Zãæ©é¦âroutemap3dã¯+Zåé²(lookAtåæ)ãªã®ã§å å´ã180°å転ã 133// ã¢ãã«ã¯éåæ(GLB)ãã¼ãã®ãããåå2ç§ã¯æ¯ãã¬ã¼ã renorm() ã§bbox追å¾ã 134function realShip(type) { 135 const holder = new THREE.Group(); 136 const inner = new THREE.Group(); 137 let m; try { m = buildShipPreview(type); } catch (e) { m = null; } 138 if (m) { m.rotation.y = Math.PI; inner.add(m); } 139 holder.add(inner); 140 // â æ©ä½æ£è¦åãæ§å®è£ ã¯Box3ããã¯ã¼ã«ã空éãã§æ¸¬ãããã®ä¸å¿ããã¼ã«ã«ã®inner.positionãã 141 // å¼ãã¦ãããããèªè·¯ä¸ã§holder(æ©ä½)ãç§»åã»å転ããç¶æ ã§renormãèµ°ãã¨æ©ä½ã®ã¯ã¼ã«ã座æ¨å 142 // ã¢ãã«ãç»é¢å¤ã¸å¹ãé£ãã ï¼ï¼è»éãé£ã°ãã«æ¶ãã主å ï¼ãholderãã¼ã«ã«ç©ºéã§è¨æ¸¬ãã¦æ ¹æ²»ããã 143 const _invHolder = new THREE.Matrix4(), _mm = new THREE.Matrix4(), _tmp = new THREE.Box3(); 144 const measureLocal = () => { 145 holder.updateWorldMatrix(true, false); 146 inner.updateWorldMatrix(false, true); 147 _invHolder.copy(holder.matrixWorld).invert(); 148 const box = new THREE.Box3(); 149 inner.traverse(o => { 150 if (!o.isMesh || !o.geometry) return; 151 if (!o.geometry.boundingBox) o.geometry.computeBoundingBox(); 152 _mm.multiplyMatrices(_invHolder, o.matrixWorld); 153 _tmp.copy(o.geometry.boundingBox).applyMatrix4(_mm); 154 box.union(_tmp); 155 }); 156 return box; 157 }; 158 holder.userData.renorm = () => { 159 inner.scale.set(1, 1, 1); inner.position.set(0, 0, 0); 160 const box = measureLocal(); 161 if (box.isEmpty()) return false;
162 const size = new THREE.Vector3(); box.getSize(size); 163 const len = Math.max(size.x, size.y, size.z); 164 if (!(len > 0)) return false; 165 inner.scale.setScalar(6 / len); 166 const box2 = measureLocal(); 167 const c = new THREE.Vector3(); box2.getCenter(c); inner.position.sub(c); 168 return true; 169 }; 170 holder.userData.renorm(); 171 return holder; 172} 173 174function label(container, cls, text) { 175 const el = document.createElement('div'); 176 el.className = 'sbR3Lbl ' + cls; el.textContent = text; 177 container.appendChild(el); 178 return el; 179} 180 181export function unmountRoute3D() { 182 if (!R) return; 183 cancelAnimationFrame(R.raf); 184 try { R.tl && R.tl.kill(); } catch { } 185 try { R.tl2 && R.tl2.kill(); } catch { } 186 try { window.removeEventListener('resize', R.onResize); } catch { } 187 try { R.renderer.dispose(); R.renderer.forceContextLoss?.(); } catch { } 188 try { R.canvas.remove(); } catch { } 189 R.labels.forEach(l => { try { l.remove(); } catch { } }); 190 R = null; 191} 192 193let cssInjected = false; 194function injectCss() { 195 if (cssInjected) return; cssInjected = true; 196 const s = document.createElement('style'); 197 s.textContent = ` 198.sbRoute3D{position:absolute;inset:0;width:100%;height:100%;z-index:2;display:block} 199.sbR3Lbl{position:absolute;z-index:3;font-family:var(--mono,monospace);font-size:11px;letter-spacing:.14em; 200 padding:5px 11px;background:rgba(4,12,14,.72);border-left:2px solid #79e7dd;color:#cfeeea;opacity:0;pointer-events:none;white-space:nowrap} 201.sbR3Lbl.orig{top:44px;left:14px;border-left-color:#79e7dd;color:#bfeeea} 202.sbR3Lbl.dst{bottom:14px;right:14px;border-left:0;border-right:2px solid #e8b94d;color:#ffe9a8;text-align:right} 203.sbR3Lbl.prog{bottom:12px;left:50%;transform:translateX(-50%);border-left:0;border-bottom:1px solid rgba(121,231,221,.5); 204 background:rgba(4,12,14,.55);font-size:10px;letter-spacing:.26em;color:#9fdede;padding:4px 14px} 205`; 206 document.head.appendChild(s); 207} 208 209// destColor: ãã©ã¼ã«ããã¯è² / bodyId: 宿æã®ãã¯ã¹ãã£å ±æã«ä½¿ç¨ 210export function mountRoute3D(container, opts = {}) { 211 unmountRoute3D(); 212 injectCss(); 213 const w = container.clientWidth || 640, h = container.clientHeight || 300; 214 const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'low-power' }); 215 renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1)); 216 renderer.setSize(w, h); 217 const canvas = renderer.domElement; canvas.className = 'sbRoute3D'; 218 container.appendChild(canvas); 219 220 const scene = new THREE.Scene(); 221 scene.fog = new THREE.FogExp2(0x02060a, 0.0012); 222 const cam = new THREE.PerspectiveCamera(46, w / h, 0.1, 5000); 223 scene.add(new THREE.AmbientLight(0x46586a, 1.15)); 224 const key = new THREE.DirectionalLight(0xfff2dd, 1.9); key.position.set(-30, 22, 40); scene.add(key); 225 const rim = new THREE.DirectionalLight(0x79e7dd, .35); rim.position.set(6, -4, -8); scene.add(rim); 226 scene.add(starfield()); 227 scene.add(chartGrid()); 228 // 太é½ï¼é æ¯ã®å ç®ã¹ãã©ã¤ãï¼ 229 const sunTex = (() => { 230 const cv = document.createElement('canvas'); cv.width = cv.height = 128; 231 const ctx = cv.getContext('2d'); 232 const g = ctx.createRadialGradient(64, 64, 4, 64, 64, 64); 233 g.addColorStop(0, 'rgba(255,246,224,1)'); g.addColorStop(.25, 'rgba(255,224,160,.85)'); g.addColorStop(1, 'rgba(255,200,120,0)'); 234 ctx.fillStyle = g; ctx.fillRect(0, 0, 128, 128); 235 return new THREE.CanvasTexture(cv); 236 })(); 237 const sun = new THREE.Sprite(new THREE.SpriteMaterial({ map: sunTex, transparent: true, opacity: .95, depthWrite: false })); 238 sun.scale.set(60, 60, 1); sun.position.set(-260, 90, -160); scene.add(sun); 239 240 const isReturn = opts.mode === 'return'; 241 // depart: ã³ããã¼(å·¦)â天ä½(å³) / return: 天ä½(å·¦)âã³ããã¼(å³) 242 const originPos = new THREE.Vector3(-46, 0, 0); 243 const destPos = new THREE.Vector3(48, 8, -14); 244 const colonyGrp = colony(); 245 const planetGrp = planet(opts.bodyId, opts.destColor != null ? opts.destColor : 0x4a7fbf); 246 const org = isReturn ? planetGrp : colonyGrp; 247 const dst = isReturn ? colonyGrp : planetGrp; 248 org.position.copy(originPos); scene.add(org); 249 dst.position.copy(destPos); scene.add(dst); 250 const marker = destMarker(); marker.position.copy(destPos); scene.add(marker); // ãªã³ã°ã¯å¤©ä½å¨åè»éã®ã¾ã¾ 251 const shipType = opts.shipType || (gameShip && gameShip.type) || 'hayabusa'; 252 const shipModel = realShip(shipType); scene.add(shipModel); 253 254 // èªè·¯: ãã¼ã¹ç· + çºå ããã·ã¥ï¼ãªãã»ããã¢ãã¡ï¼ + ä¸éãã¤ã¤ 255 const mid = new THREE.Vector3(2, 30, 26); 256 const curve = new THREE.QuadraticBezierCurve3(originPos.clone(), mid, destPos.clone()); 257 // ã¹ã¿ã³ããªã: ææ(åå¾14+大æ°)ã®æåã»ã³ããã¼å ¨é·ã®å¤ã§èªè·¯ãæã¡åãï¼å¤©ä½ã«åºãããªãï¼ 258 const clearOrg = isReturn ? 21 : 13.5, clearDst = isReturn ? 13.5 : 21;
259 let tStart = 0, tEnd = 1; 260 for (let t = 0; t <= 0.5; t += 0.004) { if (curve.getPoint(t).distanceTo(originPos) >= clearOrg) { tStart = t; break; } } 261 for (let t = 1; t >= 0.5; t -= 0.004) { if (curve.getPoint(t).distanceTo(destPos) >= clearDst) { tEnd = t; break; } } 262 const RT = (u) => curve.getPoint(tStart + (tEnd - tStart) * u); 263 const pts = Array.from({ length: 97 }, (_, i) => RT(i / 96)); 264 scene.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), 265 new THREE.LineBasicMaterial({ color: 0x79e7dd, transparent: true, opacity: .3 }))); 266 const dashGeo = new THREE.BufferGeometry().setFromPoints(pts); 267 const dashMat = new THREE.LineDashedMaterial({ color: 0xbdfff5, transparent: true, opacity: .95, dashSize: 2.4, gapSize: 3.2 }); 268 const dash = new THREE.Line(dashGeo, dashMat); 269 dash.computeLineDistances(); scene.add(dash); 270 for (const t of [.25, .5, .75]) { 271 const d = new THREE.Mesh(new THREE.OctahedronGeometry(.8), 272 new THREE.MeshBasicMaterial({ color: 0x79e7dd, transparent: true, opacity: .8, wireframe: true })); 273 d.position.copy(RT(t)); scene.add(d); 274 } 275 // ã¨ã³ã¸ã³ãã¬ã¤ã«ï¼ãªãã³ï¼ 276 const TRAIL_N = 26; 277 const trailPts = Array.from({ length: TRAIL_N }, () => RT(0).clone()); 278 const trailGeo = new THREE.BufferGeometry().setFromPoints(trailPts); 279 const trail = new THREE.Line(trailGeo, new THREE.LineBasicMaterial({ color: 0x8fe6ff, transparent: true, opacity: .75, blending: THREE.AdditiveBlending })); 280 scene.add(trail); 281 282 const labels = [ 283 label(container, 'orig', `åºçº ï¼ ${opts.origin || 'æ¯æ¸¯'}`), 284 label(container, 'dst', `å°ç ï¼ ${opts.dest || 'ç®æ¨'}${opts.wpName ? ' ã' + opts.wpName + 'ã' : ''}`), 285 label(container, 'prog', 'ROUTE ⸠0%'), 286 ]; 287 288 const camTarget = new THREE.Vector3().copy(originPos); 289 const st = { shipT: 0, following: false, t: 0 }; 290 shipModel.position.copy(RT(0)); 291 marker.userData.dia.position.copy(RT(1).clone().sub(destPos)); // ãã¤ã¤ã¯æ¥è¿ç¹ï¼è¡¨é¢æåï¼ã«æµ®ããã 292 cam.position.set(originPos.x - 6, 7, 24); 293 cam.lookAt(camTarget); 294 295 R = { renderer, canvas, scene, cam, raf: 0, tl: null, labels, onResize: null }; 296 R.onResize = () => { 297 const nw = container.clientWidth || w, nh = container.clientHeight || h; 298 renderer.setSize(nw, nh); cam.aspect = nw / nh; cam.updateProjectionMatrix(); 299 }; 300 window.addEventListener('resize', R.onResize); 301 302 const setLbl = (el, on) => { if (gsapOk()) gsap.to(el, { opacity: on ? 1 : 0, duration: .5 }); else el.style.opacity = on ? 1 : 0; }; 303 if (gsapOk()) { 304 // â 䏿ãé£ãã§ããåçã®ã«ã¡ã©æ¼åºï¼ã¯ãã¼ãºã¢ããâãã«ããã¯âèªè·¯è¿½å¾âå°çæåï¼ãæ©ä½ã ã宿©ã¢ãã«ã«æè£ ã 305 const tl = gsap.timeline({ onComplete: () => { R && (st.following = false); } }); 306 R.tl = tl; 307 // 1) åºçºç¹ã®ã¯ãã¼ãºã¢ãã 308 tl.call(() => { setLbl(labels[0], true); setLbl(labels[2], true); }, null, .2); 309 tl.to(cam.position, { x: originPos.x + 10, y: 5, z: 17, duration: 1.25, ease: 'power1.inOut' }, 0); 310 // 2) æ¥éãã«ããã¯ï¼æå³ã®é«åº¦ã¸ï¼ 311 tl.to(cam.position, { x: 0, y: 58, z: 88, duration: 1.0, ease: 'power3.in' }, 1.3); 312 tl.to(camTarget, { x: 4, y: 4, z: 4, duration: 1.0, ease: 'power2.out' }, 1.3); 313 tl.call(() => setLbl(labels[0], false), null, 2.1); 314 // 3) èªè·¯è¿½å¾ï¼æ©ä½ãæã徿¹ããï¼èæ¯ãæµãã¦é£ãã§ããã®ãåããï¼ 315 tl.call(() => { st.following = true; }, null, 2.3); 316 tl.to(st, { shipT: 1, duration: 4.4, ease: 'power1.inOut' }, 2.3); 317 // 4) ç®æ¨æåã¢ããã¼ã 318 tl.call(() => { st.following = false; }, null, 6.7); 319 tl.call(() => setLbl(labels[1], true), null, 6.8); 320 tl.to(cam.position, { x: destPos.x + 20, y: destPos.y + 9, z: destPos.z + 30, duration: 1.8, ease: 'power2.out' }, 6.7); 321 tl.to(camTarget, { x: destPos.x, y: destPos.y, z: destPos.z, duration: 1.8, ease: 'power2.out' }, 6.7); 322 } else { 323 st.shipT = 1; shipModel.position.copy(RT(1)); labels[1].style.opacity = 1; 324 cam.position.set(destPos.x + 20, destPos.y + 10, destPos.z + 40); cam.lookAt(destPos); 325 } 326 327 const tmpAhead = new THREE.Vector3(), tmpBack = new THREE.Vector3(), tmpFwd = new THREE.Vector3(), tmpDesired = new THREE.Vector3(); 328 const render = () => { 329 if (!R) return; 330 R.raf = requestAnimationFrame(render); 331 st.t += 1 / 60; 332 colonyGrp.rotation.x += 0.004; // åè´ã®å転ï¼äººå·¥éåï¼ 333 planetGrp.rotation.y += 0.0014; 334 if (planetGrp.userData.clouds) planetGrp.userData.clouds.rotation.y += 0.0022;
335 marker.userData.dia.rotation.y += 0.02; 336 const pulse = 1 + Math.sin(st.t * 2.6) * .04; 337 marker.userData.ring.scale.set(pulse, pulse, 1); 338 dashMat.dashSize = 2.4; dashMat.gapSize = 3.2; 339 dash.material.opacity = .75 + Math.sin(st.t * 3.1) * .2; 340 dashGeo.attributes.position.needsUpdate = false; 341 const p = RT(st.shipT); shipModel.position.copy(p); 342 if (st.t < 2.0) shipModel.userData.renorm(); // éåæGLBå°çã«è¿½å¾ 343 tmpAhead.copy(RT(Math.min(1, st.shipT + 0.015))); 344 shipModel.lookAt(tmpAhead); 345 // ãã¬ã¤ã«æ´æ° 346 trailPts.pop(); trailPts.unshift(p.clone()); 347 trailGeo.setFromPoints(trailPts); 348 // é²æè¡¨ç¤º 349 if (labels[2]) labels[2].textContent = `ROUTE ⸠${Math.round(st.shipT * 100)}%`; 350 if (st.following) { 351 tmpBack.copy(RT(Math.max(0, st.shipT - 0.05))); 352 tmpFwd.subVectors(p, tmpBack).normalize(); 353 tmpDesired.set(p.x - tmpFwd.x * 18 + 5, p.y + 9, p.z - tmpFwd.z * 18 + 11); 354 cam.position.lerp(tmpDesired, 0.12); 355 cam.lookAt(tmpAhead); 356 } else { 357 cam.lookAt(camTarget); 358 } 359 renderer.render(scene, cam); 360 }; 361 render(); 362 return R; 363} 364 365 366// ============================================================ 367// çºé²åç¹æ¤ã«ã¼ãç¨: 宿©ã¢ãã«ãå°çªã§å転表示ï¼ç»åã¹ãã©ã¤ã廿¢ï¼ 368// ============================================================ 369let SR = null; 370export function unmountShip3D() { 371 if (!SR) return; 372 cancelAnimationFrame(SR.raf); 373 try { SR.renderer.dispose(); SR.renderer.forceContextLoss?.(); } catch { } 374 try { SR.canvas.remove(); } catch { } 375 SR = null; 376} 377export function mountShip3D(container, type) { 378 unmountShip3D(); 379 const w = container.clientWidth || 360, h = container.clientHeight || 200; 380 const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'low-power' }); 381 renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1)); 382 renderer.setSize(w, h); 383 const canvas = renderer.domElement; canvas.style.cssText = 'position:absolute;inset:0;width:100%;height:100%'; 384 container.appendChild(canvas); 385 const scene = new THREE.Scene(); 386 const cam = new THREE.PerspectiveCamera(40, w / h, 0.1, 1000); 387 scene.add(new THREE.AmbientLight(0x64788c, 1.2)); 388 const key = new THREE.DirectionalLight(0xfff2dd, 2.3); key.position.set(-4, 5, 6); scene.add(key); 389 const rim = new THREE.DirectionalLight(0x79e7dd, .85); rim.position.set(6, -2, -4); scene.add(rim); 390 const holder = realShip(type); scene.add(holder); // 宿©ã¢ãã«ï¼å ¨é·6ã¦ãããæ£è¦åï¼ 391 cam.position.set(5, 2.6, 9.6); cam.lookAt(0, 0, 0); // å¯ãã¦æ©ä½ã大ãã 392 SR = { renderer, canvas, scene, cam, holder, raf: 0, t: 0 }; 393 const onResize = () => { 394 const nw = container.clientWidth || w, nh = container.clientHeight || h; 395 renderer.setSize(nw, nh); cam.aspect = nw / nh; cam.updateProjectionMatrix(); 396 }; 397 SR.onResize = onResize; window.addEventListener('resize', onResize); 398 const render = () => { 399 if (!SR) return; 400 SR.raf = requestAnimationFrame(render); 401 SR.t += 1 / 60; 402 holder.rotation.y = SR.t * .5; // ãã£ããå転 403 if (SR.t < 2.2) holder.userData.renorm(); // éåæGLBå°çã«è¿½å¾ 404 renderer.render(scene, cam); 405 }; 406 render(); 407}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.