feat(gesture): 优化移动端手势识别与控制交互
- 调整移动端手势识别阈值,提升识别准确率 - 增加移动端专属手势指令:左右滑动控制前后移动 - 优化手势影响范围计算逻辑,增强触摸屏设备体验 - 完善摄像头权限检查机制,改善用户授权流程 - 支持通过点击提示文字唤起控制台界面 - 统一控制台显示/隐藏逻辑,提高代码可维护性 - 调整响应式布局参数,适配更多设备屏幕尺寸
This commit is contained in:
113
christmas.html
113
christmas.html
@@ -330,7 +330,7 @@
|
||||
.controls-hint {
|
||||
position: fixed;
|
||||
left: 15%;
|
||||
bottom: 5%;
|
||||
bottom: 3%;
|
||||
color: rgba(252, 238, 167, 0.7);
|
||||
font-size: 12px;
|
||||
z-index: 11;
|
||||
@@ -396,7 +396,7 @@
|
||||
<span id="audio-icon">🔇</span>
|
||||
</div>
|
||||
<!-- 添加左侧提示文字 -->
|
||||
<div class="controls-hint">按 H 键唤起操作台</div>
|
||||
<div class="controls-hint" id="controls-hint">按 H 键/点击唤起操作台</div>
|
||||
|
||||
<div id="webcam-wrapper">
|
||||
<video id="webcam" autoplay playsinline style="display:none;"></video>
|
||||
@@ -405,6 +405,7 @@
|
||||
|
||||
<audio id="bg-music" loop preload="auto">
|
||||
<source src="data/christmas.mp3" type="audio/mpeg">
|
||||
您的浏览器不支持音频元素。
|
||||
</audio>
|
||||
|
||||
<script type="module">
|
||||
@@ -584,7 +585,7 @@
|
||||
|
||||
// 实时手势影响 - 只关注当前手势位置,不使用历史轨迹
|
||||
// 在触摸屏设备上增加手势灵敏度
|
||||
if (handState.detected && ('ontouchstart' in window || navigator.maxTouchPoints > 0)) {
|
||||
if (handState.detected) {
|
||||
// 将2D手势坐标转换为3D空间中的影响点
|
||||
// 根据屏幕尺寸调整手势影响范围
|
||||
let handInfluenceRange = 15;
|
||||
@@ -600,6 +601,11 @@
|
||||
0
|
||||
);
|
||||
|
||||
// 在触摸屏设备上增强手势影响
|
||||
if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
|
||||
handInfluenceRange *= 1.5; // 触摸屏设备增强影响范围
|
||||
}
|
||||
|
||||
// 计算粒子与手势影响点之间的距离
|
||||
const distance = this.mesh.position.distanceTo(handInfluencePoint);
|
||||
// 根据屏幕尺寸调整最大影响距离
|
||||
@@ -648,8 +654,8 @@
|
||||
// 应用影响到目标位置
|
||||
// 在触摸屏设备上增强手势影响
|
||||
let influenceMultiplier = 2.0;
|
||||
if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
|
||||
influenceMultiplier = 2.5; // 触摸屏设备增强影响
|
||||
if (('ontouchstart' in window || navigator.maxTouchPoints > 0) && window.innerWidth <= 1024) {
|
||||
influenceMultiplier = 3.0; // 触摸屏设备增强影响
|
||||
}
|
||||
|
||||
target.add(
|
||||
@@ -938,6 +944,27 @@
|
||||
numHands: 1
|
||||
});
|
||||
|
||||
// 检查摄像头权限状态
|
||||
try {
|
||||
const permissionStatus = await navigator.permissions.query({name: 'camera'});
|
||||
if (permissionStatus.state === 'granted') {
|
||||
// 如果已经授予权限,直接启动摄像头
|
||||
startWebcam();
|
||||
} else if (permissionStatus.state === 'prompt') {
|
||||
// 如果需要用户授权,显示提示让用户主动点击启动
|
||||
showCameraPermissionPrompt();
|
||||
} else {
|
||||
// 权限被拒绝,显示提示
|
||||
console.warn("摄像头权限已被拒绝");
|
||||
}
|
||||
} catch (error) {
|
||||
// 浏览器不支持权限查询API,直接尝试启动摄像头
|
||||
startWebcam();
|
||||
}
|
||||
}
|
||||
|
||||
// 启动摄像头
|
||||
async function startWebcam() {
|
||||
if (navigator.mediaDevices?.getUserMedia) {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({video: true});
|
||||
@@ -949,6 +976,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 显示摄像头权限提示
|
||||
function showCameraPermissionPrompt() {
|
||||
// 创建提示元素
|
||||
const promptDiv = document.createElement('div');
|
||||
promptDiv.id = 'camera-prompt';
|
||||
promptDiv.innerHTML = `
|
||||
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.8); color: white; padding: 15px; border-radius: 10px;
|
||||
text-align: center; z-index: 1000; backdrop-filter: blur(5px);">
|
||||
<p>需要摄像头权限以启用手势控制功能</p>
|
||||
<button id="enable-camera" style="background: #4CAF50; color: white; border: none;
|
||||
padding: 10px 20px; border-radius: 5px; cursor: pointer;
|
||||
margin-top: 10px;">启用摄像头</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(promptDiv);
|
||||
|
||||
// 添加点击事件
|
||||
document.getElementById('enable-camera').addEventListener('click', async () => {
|
||||
try {
|
||||
await startWebcam();
|
||||
promptDiv.remove();
|
||||
} catch (error) {
|
||||
console.error("启动摄像头失败:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let lastVideoTime = -1;
|
||||
async function predictWebcam() {
|
||||
if (video.currentTime !== lastVideoTime) {
|
||||
@@ -1002,13 +1057,22 @@
|
||||
let avgTipDist = tips.reduce((sum, t) => sum + Math.hypot(t.x - wrist.x, t.y - wrist.y), 0) / 4;
|
||||
|
||||
// 更精确的手势识别逻辑(调整阈值以便更容易识别)
|
||||
const isIndexPinching = thumbIndexDist < 0.07; // 稍微放宽条件
|
||||
const isMiddlePinching = thumbMiddleDist < 0.07; // 稍微放宽条件
|
||||
const isRingPinching = thumbRingDist < 0.07; // 稍微放宽条件
|
||||
const isPinkyPinching = thumbPinkyDist < 0.07; // 稍微放宽条件
|
||||
// 根据屏幕尺寸调整手势识别阈值,在移动设备上适当放宽条件
|
||||
let pinchThreshold = 0.07;
|
||||
let fistThreshold = 0.25;
|
||||
|
||||
if (window.innerWidth <= 1024) { // 移动端和小屏幕设备
|
||||
pinchThreshold = 0.09; // 放宽捏合条件
|
||||
fistThreshold = 0.30; // 放宽握拳条件
|
||||
}
|
||||
|
||||
const isIndexPinching = thumbIndexDist < pinchThreshold;
|
||||
const isMiddlePinching = thumbMiddleDist < pinchThreshold;
|
||||
const isRingPinching = thumbRingDist < pinchThreshold;
|
||||
const isPinkyPinching = thumbPinkyDist < pinchThreshold;
|
||||
|
||||
// 握拳检测(所有指尖都靠近手腕)
|
||||
const isFist = avgTipDist < 0.25;
|
||||
const isFist = avgTipDist < fistThreshold;
|
||||
|
||||
// 手势指令识别
|
||||
if (isIndexPinching && !isMiddlePinching && !isRingPinching && !isPinkyPinching) {
|
||||
@@ -1023,6 +1087,14 @@
|
||||
} else if (!isFist && tips.some(t => t.y > wrist.y + 0.1)) {
|
||||
// 有些指尖在手腕下方(手向下)-> 左旋转
|
||||
newGestureCommand = 'LEFT_ROTATE';
|
||||
|
||||
// 为移动设备增加简单的手势识别
|
||||
} else if (window.innerWidth <= 1024 && !isFist && tips.some(t => t.x < wrist.x - 0.1)) {
|
||||
// 在移动设备上,手指在手腕左侧 -> 向前移动
|
||||
newGestureCommand = 'MOVE_FORWARD';
|
||||
} else if (window.innerWidth <= 1024 && !isFist && tips.some(t => t.x > wrist.x + 0.1)) {
|
||||
// 在移动设备上,手指在手腕右侧 -> 向后移动
|
||||
newGestureCommand = 'MOVE_BACKWARD';
|
||||
}
|
||||
|
||||
// 模式切换逻辑(修复冲突问题)
|
||||
@@ -1104,15 +1176,19 @@
|
||||
|
||||
// H 键控制控制台的显示/隐藏 (标题保持不变)
|
||||
const controlsContainer = document.getElementById('controls-container');
|
||||
const controlsHint = document.getElementById('controls-hint');
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key.toLowerCase() === 'h') {
|
||||
if (controlsContainer) {
|
||||
controlsContainer.classList.toggle('ui-hidden');
|
||||
STATE.uiVisible = !controlsContainer.classList.contains('ui-hidden');
|
||||
}
|
||||
toggleControls();
|
||||
}
|
||||
});
|
||||
|
||||
// 为提示文字添加点击事件
|
||||
if (controlsHint) {
|
||||
controlsHint.addEventListener('click', toggleControls);
|
||||
}
|
||||
|
||||
// 页面加载完成后,默认隐藏控制面板
|
||||
window.addEventListener('load', () => {
|
||||
if (controlsContainer) {
|
||||
@@ -1122,6 +1198,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 控制操作台显示/隐藏的函数
|
||||
function toggleControls() {
|
||||
const controlsContainer = document.getElementById('controls-container');
|
||||
if (controlsContainer) {
|
||||
controlsContainer.classList.toggle('ui-hidden');
|
||||
STATE.uiVisible = !controlsContainer.classList.contains('ui-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
const dt = Math.min(clock.getDelta(), 0.1);
|
||||
|
||||
Reference in New Issue
Block a user