diff options
| author | prospanclaudebot <[email protected]> | 2026-07-11 01:17:05 +0000 |
|---|---|---|
| committer | prospanclaudebot <[email protected]> | 2026-07-11 01:17:05 +0000 |
| commit | 31791588403d86e13a4dab4ae0b31ac719dea500 (patch) | |
| tree | efb5ce28983926388283be71ecaa70e00378a014 | |
| parent | 7366ab10acc2800a175de2d876c4fc71db7532ab (diff) | |
| download | planeta-zhopa-31791588403d86e13a4dab4ae0b31ac719dea500.tar.gz planeta-zhopa-31791588403d86e13a4dab4ae0b31ac719dea500.zip | |
trails: 🌭 радужные светящиеся исчезающие хвосты за игроком и пирами (билборд-лента, аддитив, гаснет к хвосту)
| -rw-r--r-- | serve/index.html | 42 |
1 files changed, 41 insertions, 1 deletions
diff --git a/serve/index.html b/serve/index.html index f0105e3..32c195b 100644 --- a/serve/index.html +++ b/serve/index.html @@ -2342,9 +2342,11 @@ function tick(now) { } } // interpolate other players' drones toward their latest state + myTrail.update(S.p, camera); // радужный хвост игрока for (const id in peers) { const p = peers[id]; p.group.position.lerp(p.tp, 0.2); + if (p.trail) p.trail.update(p.group.position, camera); // хвост пира p.group.quaternion.slerp(p.tq, 0.25); for (let i = 0; i < 4; i++) { p.discs[i].material.color.setHSL((p.h + i * 0.25) % 1, 1, 0.55); p.discs[i].rotation.z += dt * 12 * (p.rf[i] || 1) * (p.ts || 1); } } @@ -2412,6 +2414,43 @@ function makeLabel(text) { const spr = new THREE.Sprite(new THREE.SpriteMaterial({ map: new THREE.CanvasTexture(cv), depthTest: false, transparent: true })); spr.scale.set(9, 2.25, 1); return spr; } +// радужный светящийся исчезающий хвост (билборд-лента) за игроком +function makeTrail() { + const N = 46; + const geo = new THREE.BufferGeometry(); + const pos = new Float32Array(N * 2 * 3), col = new Float32Array(N * 2 * 3); + geo.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + geo.setAttribute('color', new THREE.BufferAttribute(col, 3)); + const idx = []; + for (let i = 0; i < N - 1; i++) { const a = i * 2, b = a + 1, c = a + 2, d = a + 3; idx.push(a, b, c, b, d, c); } + geo.setIndex(idx); + const mat = new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide, opacity: 0.6 }); + const mesh = new THREE.Mesh(geo, mat); mesh.frustumCulled = false; scene.add(mesh); + const hist = [], _d = new THREE.Vector3(), _v = new THREE.Vector3(), _off = new THREE.Vector3(), _c = new THREE.Color(); + let hue = 0; + return { + mesh, + update(p, cam) { + hist.unshift(p.clone()); if (hist.length > N) hist.pop(); + hue = (hue + 0.018) % 1; + for (let i = 0; i < N; i++) { + const h = hist[Math.min(i, hist.length - 1)], nx = hist[Math.min(i + 1, hist.length - 1)]; + _d.subVectors(h, nx); if (_d.lengthSq() < 1e-6) _d.set(0, 0, 1); _d.normalize(); + _v.subVectors(h, cam.position).normalize(); + _off.crossVectors(_d, _v); if (_off.lengthSq() < 1e-6) _off.set(1, 0, 0); _off.normalize(); + const fade = 1 - i / N, w = 0.5 + fade * 1.9, A = i * 6, B = A + 3; + pos[A] = h.x + _off.x * w; pos[A + 1] = h.y + _off.y * w; pos[A + 2] = h.z + _off.z * w; + pos[B] = h.x - _off.x * w; pos[B + 1] = h.y - _off.y * w; pos[B + 2] = h.z - _off.z * w; + _c.setHSL((hue + i / N) % 1, 1.0, 0.45); const f = fade * fade; + col[A] = _c.r * f; col[A + 1] = _c.g * f; col[A + 2] = _c.b * f; + col[B] = _c.r * f; col[B + 1] = _c.g * f; col[B + 2] = _c.b * f; + } + geo.attributes.position.needsUpdate = true; geo.attributes.color.needsUpdate = true; + } + }; +} +const myTrail = makeTrail(); + function makePeerDrone() { const g = new THREE.Group(); g.add(new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.4, 1.5), new THREE.MeshStandardMaterial({ color: 0x333842, roughness: 0.5 }))); @@ -2431,11 +2470,12 @@ function updatePeer(id, name, s) { const d = makePeerDrone(); scene.add(d.group); const lbl = makeLabel(name); lbl.position.set(0, 3.2, 0); d.group.add(lbl); p = peers[id] = { group: d.group, discs: d.discs, label: lbl, tp: new THREE.Vector3(), tq: new THREE.Quaternion(), rf: [1, 1, 1, 1], h: 0 }; + p.trail = makeTrail(); } p.tp.set(s.x, s.y, s.z); if (s.q) p.tq.set(s.q[0], s.q[1], s.q[2], s.q[3]); p.rf = s.rf || [1, 1, 1, 1]; p.h = s.h || 0; p.ts = s.ts || 1; if (s.dm && DRONES[s.dm]) p.group.scale.setScalar(DRONES[s.dm].scale); // peer видят твою модель по размеру } -function removePeer(id) { if (peers[id]) { scene.remove(peers[id].group); delete peers[id]; } } +function removePeer(id) { if (peers[id]) { scene.remove(peers[id].group); if (peers[id].trail) scene.remove(peers[id].trail.mesh); delete peers[id]; } } function connectMP() { try { const proto = location.protocol === 'https:' ? 'wss' : 'ws'; |
