From 208b37048bcc4eaea6aa798ccd8c952fde104685 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Tue, 29 Sep 2026 00:24:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20IK=20=E5=85=A8=E9=9D=A2=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E4=B8=89=E5=A4=84=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94?= =?UTF-8?q?NaN=20=E9=98=B2=E7=BA=BF=E3=80=81=E5=AE=9E=E6=97=B6=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E8=A2=AB=E5=85=A5=E5=9C=BA=E5=8A=A8=E7=94=BB=E8=A6=86?= =?UTF-8?q?=E5=86=99=E3=80=81=E6=BB=9A=E8=BD=AC=E8=A7=92miss=E7=AA=81?= =?UTF-8?q?=E8=B7=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 求解器 inLimit/evalAt 容差比较改否定式:原写法在 NaN 下比较恒假, NaN 目标/姿态曾产出 ok=true 且 q 含 NaN(UI 有 number 输入净化+isFinite 双保险触不到,但公开 API 与将来的 MCU 端口会中招) - glue setJoints(q,false) 补 anim=null:直接写入不再被在飞的 620ms 入场动画 覆写、结尾不再跳回旧目标(实时追踪拖拽撞上入场动画的窗口期) - glue rollAngleAt 射线错过平面改返回 null:原来返回 0 会让滚转角按 -a0 突跳, 现在该帧直接跳过 - 回归:node 六项全绿;NaN/Inf 目标三种注入均 ok=false 且 q=null; 40 组随机位形与 fast 副本仍逐位一致;浏览器冒烟已到达/零报错 --- web/arm-ik-solver.js | 6 +++--- web/ik-glue.js | 4 +++- web/index.html | 10 ++++++---- web/机器人手臂3D查看器.html | 10 ++++++---- 机器人手臂3D查看器.html | 10 ++++++---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/web/arm-ik-solver.js b/web/arm-ik-solver.js index 0384bf7..38a346c 100644 --- a/web/arm-ik-solver.js +++ b/web/arm-ik-solver.js @@ -226,10 +226,10 @@ function make(cfg) { return out; } - const inLimit = q => { + const inLimit = q => { // 取否定式:NaN 候选必须落选(原写法 NaN 比较全 false 会放行) for (let i = 0; i < 5; i++) { const t = LIM[ORDER[i]]; - if (q[i] < t.lo - 1e-9 || q[i] > t.hi + 1e-9) return false; + if (!(q[i] >= t.lo - 1e-9 && q[i] <= t.hi + 1e-9)) return false; } return true; }; @@ -309,7 +309,7 @@ function make(cfg) { const p = fk(q, tool); const dx = p[0] - target[0], dy = p[1] - target[1], dz = p[2] - target[2]; const e2 = dx * dx + dy * dy + dz * dz; - if (e2 > tol2) continue; + if (!(e2 <= tol2)) continue; // 否定式:e2 为 NaN(目标含 NaN/Inf)时继续走拒绝分支 const c = scoreBranch(q, Math.sqrt(e2)); if (c && (!best || c.rcost < best.rcost - 1e-12)) best = c; } diff --git a/web/ik-glue.js b/web/ik-glue.js index a2835f0..396da3e 100644 --- a/web/ik-glue.js +++ b/web/ik-glue.js @@ -210,6 +210,7 @@ function refreshTarget() { let anim = null; function setJoints(q, doAnim) { if (doAnim) { animTo(q, 620); return; } + anim = null; // 直接写入必须取消在飞动画,否则入场动画会覆写实时解 for (let i = 0; i < 6; i++) { const id = ORDER[i]; ang[id] = q[i]; const s = $('s' + id), v = $('v' + id); @@ -445,7 +446,7 @@ function rollAngleAt(e) { const pl = new THREE.Plane().setFromNormalAndCoplanarPoint(n, gTgt.position); const hit = new THREE.Vector3(); ray2.setFromCamera(ndc2(e), cam); // 该版本 setFromCamera 不返回 this,勿链式 - if (!ray2.ray.intersectPlane(pl, hit)) return 0; + if (!ray2.ray.intersectPlane(pl, hit)) return null; // 错过平面:该帧不参与滚转(返回 0 会按 -a0 突跳) const v = hit.sub(gTgt.position); const up = Math.abs(n.y) > 0.9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0); const u = new THREE.Vector3().crossVectors(n, up).normalize(); @@ -497,6 +498,7 @@ cv.addEventListener('pointermove', e => { if (grab.pose === 'roll') { // 拖圆环:绕轴滚转 dragged = true; const a1 = rollAngleAt(e); + if (a1 == null) return; // 射线错过平面:跳过该帧,角度不变 POSE.roll = S.wrap180(POSE.roll + S.wrap180(a1 - grab.a0)); grab.a0 = a1; syncPoseInputs(); refreshTarget(); diff --git a/web/index.html b/web/index.html index f798325..9ff8d79 100644 --- a/web/index.html +++ b/web/index.html @@ -1442,10 +1442,10 @@ function make(cfg) { return out; } - const inLimit = q => { + const inLimit = q => { // 取否定式:NaN 候选必须落选(原写法 NaN 比较全 false 会放行) for (let i = 0; i < 5; i++) { const t = LIM[ORDER[i]]; - if (q[i] < t.lo - 1e-9 || q[i] > t.hi + 1e-9) return false; + if (!(q[i] >= t.lo - 1e-9 && q[i] <= t.hi + 1e-9)) return false; } return true; }; @@ -1525,7 +1525,7 @@ function make(cfg) { const p = fk(q, tool); const dx = p[0] - target[0], dy = p[1] - target[1], dz = p[2] - target[2]; const e2 = dx * dx + dy * dy + dz * dz; - if (e2 > tol2) continue; + if (!(e2 <= tol2)) continue; // 否定式:e2 为 NaN(目标含 NaN/Inf)时继续走拒绝分支 const c = scoreBranch(q, Math.sqrt(e2)); if (c && (!best || c.rcost < best.rcost - 1e-12)) best = c; } @@ -2237,6 +2237,7 @@ function refreshTarget() { let anim = null; function setJoints(q, doAnim) { if (doAnim) { animTo(q, 620); return; } + anim = null; // 直接写入必须取消在飞动画,否则入场动画会覆写实时解 for (let i = 0; i < 6; i++) { const id = ORDER[i]; ang[id] = q[i]; const s = $('s' + id), v = $('v' + id); @@ -2472,7 +2473,7 @@ function rollAngleAt(e) { const pl = new THREE.Plane().setFromNormalAndCoplanarPoint(n, gTgt.position); const hit = new THREE.Vector3(); ray2.setFromCamera(ndc2(e), cam); // 该版本 setFromCamera 不返回 this,勿链式 - if (!ray2.ray.intersectPlane(pl, hit)) return 0; + if (!ray2.ray.intersectPlane(pl, hit)) return null; // 错过平面:该帧不参与滚转(返回 0 会按 -a0 突跳) const v = hit.sub(gTgt.position); const up = Math.abs(n.y) > 0.9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0); const u = new THREE.Vector3().crossVectors(n, up).normalize(); @@ -2524,6 +2525,7 @@ cv.addEventListener('pointermove', e => { if (grab.pose === 'roll') { // 拖圆环:绕轴滚转 dragged = true; const a1 = rollAngleAt(e); + if (a1 == null) return; // 射线错过平面:跳过该帧,角度不变 POSE.roll = S.wrap180(POSE.roll + S.wrap180(a1 - grab.a0)); grab.a0 = a1; syncPoseInputs(); refreshTarget(); diff --git a/web/机器人手臂3D查看器.html b/web/机器人手臂3D查看器.html index f798325..9ff8d79 100644 --- a/web/机器人手臂3D查看器.html +++ b/web/机器人手臂3D查看器.html @@ -1442,10 +1442,10 @@ function make(cfg) { return out; } - const inLimit = q => { + const inLimit = q => { // 取否定式:NaN 候选必须落选(原写法 NaN 比较全 false 会放行) for (let i = 0; i < 5; i++) { const t = LIM[ORDER[i]]; - if (q[i] < t.lo - 1e-9 || q[i] > t.hi + 1e-9) return false; + if (!(q[i] >= t.lo - 1e-9 && q[i] <= t.hi + 1e-9)) return false; } return true; }; @@ -1525,7 +1525,7 @@ function make(cfg) { const p = fk(q, tool); const dx = p[0] - target[0], dy = p[1] - target[1], dz = p[2] - target[2]; const e2 = dx * dx + dy * dy + dz * dz; - if (e2 > tol2) continue; + if (!(e2 <= tol2)) continue; // 否定式:e2 为 NaN(目标含 NaN/Inf)时继续走拒绝分支 const c = scoreBranch(q, Math.sqrt(e2)); if (c && (!best || c.rcost < best.rcost - 1e-12)) best = c; } @@ -2237,6 +2237,7 @@ function refreshTarget() { let anim = null; function setJoints(q, doAnim) { if (doAnim) { animTo(q, 620); return; } + anim = null; // 直接写入必须取消在飞动画,否则入场动画会覆写实时解 for (let i = 0; i < 6; i++) { const id = ORDER[i]; ang[id] = q[i]; const s = $('s' + id), v = $('v' + id); @@ -2472,7 +2473,7 @@ function rollAngleAt(e) { const pl = new THREE.Plane().setFromNormalAndCoplanarPoint(n, gTgt.position); const hit = new THREE.Vector3(); ray2.setFromCamera(ndc2(e), cam); // 该版本 setFromCamera 不返回 this,勿链式 - if (!ray2.ray.intersectPlane(pl, hit)) return 0; + if (!ray2.ray.intersectPlane(pl, hit)) return null; // 错过平面:该帧不参与滚转(返回 0 会按 -a0 突跳) const v = hit.sub(gTgt.position); const up = Math.abs(n.y) > 0.9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0); const u = new THREE.Vector3().crossVectors(n, up).normalize(); @@ -2524,6 +2525,7 @@ cv.addEventListener('pointermove', e => { if (grab.pose === 'roll') { // 拖圆环:绕轴滚转 dragged = true; const a1 = rollAngleAt(e); + if (a1 == null) return; // 射线错过平面:跳过该帧,角度不变 POSE.roll = S.wrap180(POSE.roll + S.wrap180(a1 - grab.a0)); grab.a0 = a1; syncPoseInputs(); refreshTarget(); diff --git a/机器人手臂3D查看器.html b/机器人手臂3D查看器.html index f798325..9ff8d79 100644 --- a/机器人手臂3D查看器.html +++ b/机器人手臂3D查看器.html @@ -1442,10 +1442,10 @@ function make(cfg) { return out; } - const inLimit = q => { + const inLimit = q => { // 取否定式:NaN 候选必须落选(原写法 NaN 比较全 false 会放行) for (let i = 0; i < 5; i++) { const t = LIM[ORDER[i]]; - if (q[i] < t.lo - 1e-9 || q[i] > t.hi + 1e-9) return false; + if (!(q[i] >= t.lo - 1e-9 && q[i] <= t.hi + 1e-9)) return false; } return true; }; @@ -1525,7 +1525,7 @@ function make(cfg) { const p = fk(q, tool); const dx = p[0] - target[0], dy = p[1] - target[1], dz = p[2] - target[2]; const e2 = dx * dx + dy * dy + dz * dz; - if (e2 > tol2) continue; + if (!(e2 <= tol2)) continue; // 否定式:e2 为 NaN(目标含 NaN/Inf)时继续走拒绝分支 const c = scoreBranch(q, Math.sqrt(e2)); if (c && (!best || c.rcost < best.rcost - 1e-12)) best = c; } @@ -2237,6 +2237,7 @@ function refreshTarget() { let anim = null; function setJoints(q, doAnim) { if (doAnim) { animTo(q, 620); return; } + anim = null; // 直接写入必须取消在飞动画,否则入场动画会覆写实时解 for (let i = 0; i < 6; i++) { const id = ORDER[i]; ang[id] = q[i]; const s = $('s' + id), v = $('v' + id); @@ -2472,7 +2473,7 @@ function rollAngleAt(e) { const pl = new THREE.Plane().setFromNormalAndCoplanarPoint(n, gTgt.position); const hit = new THREE.Vector3(); ray2.setFromCamera(ndc2(e), cam); // 该版本 setFromCamera 不返回 this,勿链式 - if (!ray2.ray.intersectPlane(pl, hit)) return 0; + if (!ray2.ray.intersectPlane(pl, hit)) return null; // 错过平面:该帧不参与滚转(返回 0 会按 -a0 突跳) const v = hit.sub(gTgt.position); const up = Math.abs(n.y) > 0.9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0); const u = new THREE.Vector3().crossVectors(n, up).normalize(); @@ -2524,6 +2525,7 @@ cv.addEventListener('pointermove', e => { if (grab.pose === 'roll') { // 拖圆环:绕轴滚转 dragged = true; const a1 = rollAngleAt(e); + if (a1 == null) return; // 射线错过平面:跳过该帧,角度不变 POSE.roll = S.wrap180(POSE.roll + S.wrap180(a1 - grab.a0)); grab.a0 = a1; syncPoseInputs(); refreshTarget();