提问人:WSA 提问时间:10/6/2023 最后编辑:WSA 更新时间:10/6/2023 访问量:78
从法线到智能手机背面的 AbsoluteOrientationSensor 四元数的 2D 罗盘航向。云台锁定问题
2D Compass heading from AbsoluteOrientationSensor quaternion for the normal to the back of the smartphone. Gimbal lock issue
问:
我需要获取智能手机(设备)相对于北方的旋转角度(0 - 360 度) 我对法线的角度感兴趣,从设备的后盖(相机眼睛所在的位置)。无论设备如何不旋转,我都对后盖相对于 0 度的确切外观感兴趣。 让我们想象一下,手机是一个窗口。我想知道我透过这扇窗户看哪里,是北还是南。也就是说,我可以自由地向各个方向旋转我的智能手机。
我觉得返回 AbsoluteOrientationSensor 的四元数具有我需要的所有信息,但是我发现转换为指南针方向的公式容易出现云台锁定问题。 以下是使用此公式(javascript)的重新计算:
const quaternionToHeading = function(q) {
let [x, y, z, w] = q;
let a = Math.atan2(2*x*y + 2*z*w, 1 - 2*y*y - 2*z*z)*(180/Math.PI);
if(a < 0) a = 360 + a;
return (360 - a).toFixed(1);
}
因此,我有正确的指南针值,但并非在所有情况下都如此。
- 例如,当我将屏幕放在正前方的智能手机并旋转(偏航)时,我可以看到正确的指南针值。
- 当我将智能手机平行于地面转动并在这个位置围绕我旋转(偏航)时,我也看到了正确的值。
- 但是,如果我从纵向位置转动(滚动)智能手机,但在后盖正常的情况下以相同的方式向北握住它,指南针值会发生不可预测的变化。
请帮我找到解决方案。
演示应用程序(滚动测试需要从智能手机运行,PC浏览器不支持方向锁定):https://www.arkon.solutions/static/quaternioncompass/index.html
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.board {
color: white;
width: 5em;
height: 5em;
align-items: center;
justify-content: center;
position: absolute;
display: flex;
flex-direction: column;
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
font-size: 3em;
}
.control-block {
padding: 1em;
position: absolute;
bottom:0;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.control-block__message {
color: orange;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css"/>
<title>Document</title>
</head>
<script type="module">
const board = document.querySelector(".board");
const lblHeading = document.querySelector("#lblHeading");
const lblSide = document.querySelector("#lblSide");
const options = { frequency: 60, referenceFrame: "device" };
const sensor = new AbsoluteOrientationSensor(options);
sensor.addEventListener("reading", () => {
let heading = quaternionToHeading(sensor.quaternion);
let side = headingToSide(heading);
let color = headingToColor(heading);
lblHeading.innerHTML = Math.round(heading) + '°';
lblSide.innerHTML = side;
board.style.backgroundColor = color;
});
sensor.addEventListener("error", (error) => {
if (event.error.name === "NotReadableError") {
console.log("Sensor is not available.");
}
});
sensor.start();
//Magic here
const quaternionToHeading = function(q) {
let [x, y, z, w] = q;
let a = Math.atan2(2*x*y + 2*z*w, 1 - 2*y*y - 2*z*z)*(180/Math.PI);
if(a < 0) a = 360 + a;
return (360 - a).toFixed(1);
}
const headingToSide = function(heading) {
//N
//E 45 - 145
//S 135 - 225
//W 225 - 315
//NE 22.5 - 67,5
//E 67,5 - 112,5
//ES 112,5 - 157,5
//S 157,5 - 202,5
//WS 202,5 - 247,5
//W 247,5 - 292,5
//WN 292,5 - 337,5
//N - rest
let result = 'N';
if (heading > 22.5 && heading <= 67.5) {
result = 'NE';
} else if (heading > 67.5 && heading <= 112.5) {
result = 'E'
} else if (heading > 112.5 && heading <= 157.5) {
result = 'SE';
} else if (heading > 157.5 && heading <= 202.5) {
result = 'S';
} else if (heading > 202.5 && heading <= 247.5) {
result = 'SW';
} else if (heading > 247.5 && heading <= 292.5) {
result = 'W';
} else if (heading > 292.5 && heading <= 337.5) {
result = 'NW';
}
return result;
}
const headingToColor = function(heading) {
let southLevel = heading;
if (heading > 180) {
southLevel = 360 - heading;
}
let r = 0,
g = 0,
b = 0;
r = Math.round(255 * southLevel / 180);
b = 255 - r;
return `rgb(${r},${g},${b})`;
}
</script>
<script>
function showMessage(msg) {
document.querySelector('.control-block__message').innerHTML = msg;
}
function switchFullScreen(elem = document.body) {
if (!!document.fullscreenElement) {
let fnRequestExitFullScreen = null;
if (document.exitFullscreen) {
fnRequestExitFullScreen = document.exitFullscreen();
} else if (document.webkitExitFullscreen) { /* Safari */
fnRequestExitFullScreen = document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { /* IE11 */
fnRequestExitFullScreen = document.msExitFullscreen();
}
fnRequestExitFullScreen.then(() => {
screen.orientation.unlock();
this.showMessage("Please, switch app to fullscreen mode");
}).catch(ex => {
alert(ex);
});
} else {
let fnRequestFullscreen = null;
if (elem.requestFullscreen) {
fnRequestFullscreen = elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) { /* Safari */
fnRequestFullscreen = elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) { /* IE11 */
fnRequestFullscreen = elem.msRequestFullscreen();
}
fnRequestFullscreen.then(() => {
return screen.orientation
.lock("portrait")
.then(() => {
this.showMessage("Ok, app is ready to test now");
}).catch((ex) => {
alert(ex);
});
}).catch(ex => {
alert(JSON.stringify(ex));
});
}
}
function onFullscreenClick() {
this.switchFullScreen(document.body);
}
</script>
<body>
<div class="board">
<span id="lblHeading"></span>
<span id="lblSide"></span>
</div>
<div class="control-block">
<div class="control-block__message">Please, switch app to fullscreen mode</div>
<button onclick="onFullscreenClick()">Full screen + Lock orientation</button>
</div>
</body>
</html>
故障项目:https://glitch.com/edit/#!/frequent-parallel-antimatter
答: 暂无答案
评论