FPV chat: add proximity fallback when raycaster misses agents
Browse files- web/3d.html +34 -1
web/3d.html
CHANGED
|
@@ -3301,7 +3301,10 @@ const _interactRay = new THREE.Raycaster();
|
|
| 3301 |
_interactRay.far = 15;
|
| 3302 |
function updateInteractPrompt() {
|
| 3303 |
const prompt = document.getElementById('fp-interact-prompt');
|
| 3304 |
-
if (chatTargetId
|
|
|
|
|
|
|
|
|
|
| 3305 |
_interactRay.setFromCamera(new THREE.Vector2(0, 0), fpCamera);
|
| 3306 |
const objs = [];
|
| 3307 |
for (const [, m] of agentMeshes) objs.push(m);
|
|
@@ -3316,6 +3319,21 @@ function updateInteractPrompt() {
|
|
| 3316 |
return;
|
| 3317 |
}
|
| 3318 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3319 |
prompt.style.display = 'none';
|
| 3320 |
}
|
| 3321 |
|
|
@@ -3621,6 +3639,17 @@ let chatMessages = [];
|
|
| 3621 |
let speechRecognition = null;
|
| 3622 |
let isRecording = false;
|
| 3623 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3624 |
function fpInteract() {
|
| 3625 |
if (!fpMode) return;
|
| 3626 |
if (chatTargetId) { endNpcChat(); return; }
|
|
@@ -3641,6 +3670,10 @@ function fpInteract() {
|
|
| 3641 |
}
|
| 3642 |
}
|
| 3643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3644 |
const bldgObjs = [];
|
| 3645 |
for (const [, m] of buildingMeshes) bldgObjs.push(m);
|
| 3646 |
const bHits = ray.intersectObjects(bldgObjs, true);
|
|
|
|
| 3301 |
_interactRay.far = 15;
|
| 3302 |
function updateInteractPrompt() {
|
| 3303 |
const prompt = document.getElementById('fp-interact-prompt');
|
| 3304 |
+
if (chatTargetId) { prompt.style.display = 'none'; return; }
|
| 3305 |
+
if (!fpControls.isLocked) { prompt.style.display = 'none'; return; }
|
| 3306 |
+
|
| 3307 |
+
// Try raycaster first
|
| 3308 |
_interactRay.setFromCamera(new THREE.Vector2(0, 0), fpCamera);
|
| 3309 |
const objs = [];
|
| 3310 |
for (const [, m] of agentMeshes) objs.push(m);
|
|
|
|
| 3319 |
return;
|
| 3320 |
}
|
| 3321 |
}
|
| 3322 |
+
|
| 3323 |
+
// Fallback: proximity check
|
| 3324 |
+
let best = null, bestDist = 6;
|
| 3325 |
+
const cam = fpCamera.position;
|
| 3326 |
+
for (const [id, m] of agentMeshes) {
|
| 3327 |
+
if (id === 'player') continue;
|
| 3328 |
+
const d = cam.distanceTo(m.position);
|
| 3329 |
+
if (d < bestDist) { bestDist = d; best = m; }
|
| 3330 |
+
}
|
| 3331 |
+
if (best) {
|
| 3332 |
+
const name = best.userData.data?.name || best.userData.id;
|
| 3333 |
+
prompt.innerHTML = `Press <b>E</b> to talk to <b>${name}</b>`;
|
| 3334 |
+
prompt.style.display = 'block';
|
| 3335 |
+
return;
|
| 3336 |
+
}
|
| 3337 |
prompt.style.display = 'none';
|
| 3338 |
}
|
| 3339 |
|
|
|
|
| 3639 |
let speechRecognition = null;
|
| 3640 |
let isRecording = false;
|
| 3641 |
|
| 3642 |
+
function findNearestAgent() {
|
| 3643 |
+
let best = null, bestDist = 6;
|
| 3644 |
+
const cam = fpCamera.position;
|
| 3645 |
+
for (const [id, m] of agentMeshes) {
|
| 3646 |
+
if (id === 'player') continue;
|
| 3647 |
+
const d = cam.distanceTo(m.position);
|
| 3648 |
+
if (d < bestDist) { bestDist = d; best = id; }
|
| 3649 |
+
}
|
| 3650 |
+
return best;
|
| 3651 |
+
}
|
| 3652 |
+
|
| 3653 |
function fpInteract() {
|
| 3654 |
if (!fpMode) return;
|
| 3655 |
if (chatTargetId) { endNpcChat(); return; }
|
|
|
|
| 3670 |
}
|
| 3671 |
}
|
| 3672 |
|
| 3673 |
+
// Fallback: nearest agent within 6 units
|
| 3674 |
+
const nearest = findNearestAgent();
|
| 3675 |
+
if (nearest) { startNpcChat(nearest); return; }
|
| 3676 |
+
|
| 3677 |
const bldgObjs = [];
|
| 3678 |
for (const [, m] of buildingMeshes) bldgObjs.push(m);
|
| 3679 |
const bHits = ray.intersectObjects(bldgObjs, true);
|