fix: IK 全面审查三处修复——NaN 防线、实时拖拽被入场动画覆写、滚转角miss突跳
- 求解器 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 副本仍逐位一致;浏览器冒烟已到达/零报错
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
+6
-4
@@ -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();
|
||||
|
||||
+6
-4
@@ -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();
|
||||
|
||||
+6
-4
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user