前回、磁北の偏角を補正して真北を求め、端末の外から眺める形で確認した。 今回は実際に端末から見た状態を描画する 簡単に言ってしまえば3Dコンパスを作る感じ。
@@ -149,5 +149,5 @@ $DCL(_ => {
function drawForefront() {
// X axis, positive
- drawVector(progLine, line, [ 1, 0, 0, 100 ], matVP, {
+ drawVector(progLine, line, [ 1, 0, 0 ], matVP, {
color: [ 1, 1, 1, 1 ], thickness: [ "min", 5 ]
});
たぶん単体テストっぽくWが描画に影響しないことを確認した時の名残。
@@ -194,5 +194,5 @@ $DCL(_ => {
progLine.use();
if (newHeading)
- v.matHeading.splice(0, Infinity, ...v.mat.rotatedZ(newHeading));
+ v.matHeading.splice(0, Infinity, ...v.mat.rotatedZ(newHeading + magdec));
progLine.setUniform("uMat:m4", matNorth.affected(v.matHeading, matVP));
progLine.setUniform("uColor", ...v.color, a);
@@ -15,23 +15,4 @@ const Vec4 = VanillaVec4;
const Mat4 = VanillaMat4;
-const GL = buildClass(VanillaGL, {
-factory: {
-create(canvas, opt, ...args) {
- const glopt = {};
- if (opt.useStencil)
- glopt.stencil = true;
- return this.takeover($ID(canvas).getContext("webgl", glopt), opt, ...args);
-}
-},
-init(opt) {
- super.init(opt);
- if (opt.useStencil)
- this.clearMask |= this.STENCIL_BUFFER_BIT;
-},
-colorMaskAll(b) {
- this.colorMask(b, b, b, b);
-}
-});
-
Mat4.setToRadian(deg2rad);
Mat4.aspectMode = "min";
@@ -60,5 +41,5 @@ function drawVector(prog, line, vec, mat, opt) {
}
$DCL(_ => {
- const gl = GL.create($QS("canvas"), {
+ const gl = VanillaGL.create($QS("canvas"), {
useDepth: true, useStencil: true
});
@@ -151,5 +151,5 @@ $DCL(_ => {
matDirUp.rotXuvecZto(-Z.y, Z.z).rotZuvecXto(A.x, A.y);
matDirDn.rotXuvecZto(Z.y, -Z.z).rotZuvecXto(A.x, A.y);
- let newHeading = evDev?.webkitCompassHeading ?? 10;
+ let newHeading = evDev?.webkitCompassHeading ?? 0;
if (newHeading == evHeading)
newHeading = null;
そういう環境(≒Android)でどうやったら北を指すかどうかは後で考える。
@@ -41,5 +41,5 @@ function drawVector(prog, line, vec, mat, opt) {
}
$DCL(_ => {
- const gl = VanillaGL.create($QS("canvas"), {
+ const gl = VanillaGL.create($QS("#absolute-coord-system"), {
useDepth: true, useStencil: true
});
@@ -285,7 +285,43 @@ p:has(.device-orientation-enabled) {
text-align: center;
}
+.status {
+ pointer-events: none;
+}
+#permit-attitude {
+ pointer-events: auto;
+}
+#canvas-container {
+ display: grid;
+ position: fixed;
+ width: 100vw; height: 100dvh;
+ grid-template-rows: 2fr 3fr;
+ background-color: #088f;
+}
+#canvas-container canvas {
+ box-sizing: border-box;
+ overflow: hidden;
+ position: relative;
+ height: 100%; width: 100%;
+}
+#absolute-coord-system {
+ border: 1px #444 none;
+ border-bottom-style: solid;
+}
+@media (orientation: landscape) {
+ #canvas-container {
+ grid-template-columns: 2fr 3fr;
+ grid-template-rows: 1fr;
+ }
+ #absolute-coord-system {
+ border-right-style: solid;
+ border-bottom: none;
+ }
+}
</style>
<body class="fullscreen">
-<canvas></canvas>
+<div id="canvas-container">
+<canvas id="absolute-coord-system"></canvas>
+<canvas id="device-coord-system"></canvas>
+</div>
<div class="status">
<p><button id="permit-attitude">permit using device attitude</button>
<div>
に<canvas>
をふたつ突っ込んで、それぞれにIDを付ける
div
(コンテナ)をグリッドにして、CSSのメディアクエリで縦なら1列、横なら2列を指定
overflow: hidden
を指定しているのは、何かの拍子にキャンバスから画面がはみ出すと、外側のdiv
が画面より大きくなってキャンバスがうまく分割されなくなる
clip
だと新しい整形コンテキストができないのでうまくいかない
div
が大きくなってしまうのでbox-sizing
をborder-box
にする
@@ -74,4 +74,6 @@ $DCL(_ => {
const trianglePlate = gl.createFloatBuffer(triangleVertices);
const matNorth = Mat4.scale(0.1, 0.5).trans(0, 0.5);
+ const renderers = [];
+ const render = VanillaFrameRenderer.create(_ => renderers.forEach(r => r()));
let evDev = null;
let evHeading = null;
@@ -81,5 +83,4 @@ $DCL(_ => {
let matHeadingUp = Mat4.create();
let matHeadingDn = Mat4.create();
- let render;
let magdec = 0;
ready.then((
@@ -196,5 +197,5 @@ $DCL(_ => {
gl.disable(gl.STENCIL_TEST);
}
- render = VanillaFrameRenderer.create(_ => {
+ renderers.push(_ => {
if (evDev != null)
matAtt = Mat4.rotY(evDev.gamma).rotX(evDev.beta).rotZ(evDev.alpha);
renderers
に入れておき、VanillaFrameRendererに渡すクロージャーでこの配列に設定された関数を順に実行する
@@ -40,4 +40,8 @@ function drawVector(prog, line, vec, mat, opt) {
drawRoundCapLine(prog, line, thk);
}
+function setupDeviceCoordinateSystem(renderers) {
+ const gl = VanillaGL.create($QS("#device-coord-system"));
+ renderers.push(_ => gl.initCanvas());
+}
$DCL(_ => {
const gl = VanillaGL.create($QS("#absolute-coord-system"), {
@@ -76,4 +80,5 @@ $DCL(_ => {
const renderers = [];
const render = VanillaFrameRenderer.create(_ => renderers.forEach(r => r()));
+ setupDeviceCoordinateSystem(renderers);
let evDev = null;
let evHeading = null;
@@ -42,5 +42,21 @@ function drawVector(prog, line, vec, mat, opt) {
function setupDeviceCoordinateSystem(renderers) {
const gl = VanillaGL.create($QS("#device-coord-system"));
- renderers.push(_ => gl.initCanvas());
+ const ready = prepare(
+ gl.fetchShader("vs", "thru.vs"),
+ gl.fetchShader("fs", "fixed.fs")
+ );
+ const grid = prepareSphericalGrid(gl);
+ ready.then((
+ vs, fs
+ ) => {
+ const prog = gl.linkProgram(vs, fs);
+ renderers.push(_ => {
+ gl.initCanvas();
+ prog.use();
+ prog.setUniform("uMat:m4", Mat4.create());
+ prog.setUniform("uColor", 1, 1, 1, 1);
+ drawSphericalGrid(prog, grid);
+ });
+ });
}
$DCL(_ => {
@@ -47,4 +47,5 @@ function setupDeviceCoordinateSystem(renderers) {
);
const grid = prepareSphericalGrid(gl);
+ let evDev;
ready.then((
vs, fs
@@ -52,11 +53,19 @@ function setupDeviceCoordinateSystem(renderers) {
const prog = gl.linkProgram(vs, fs);
renderers.push(_ => {
+ if (!evDev)
+ return;
gl.initCanvas();
+ const matVP = Mat4
+ .rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
+ .trans(0, 0, -1).affect(gl.pers(90, 0.01, 2.01));
prog.use();
- prog.setUniform("uMat:m4", Mat4.create());
+ prog.setUniform("uMat:m4", matVP);
prog.setUniform("uColor", 1, 1, 1, 1);
drawSphericalGrid(prog, grid);
});
});
+ return {
+ updateDeviceOrientation: ev => { evDev = ev; }
+ }
}
$DCL(_ => {
@@ -96,5 +105,5 @@ $DCL(_ => {
const renderers = [];
const render = VanillaFrameRenderer.create(_ => renderers.forEach(r => r()));
- setupDeviceCoordinateSystem(renderers);
+ const devco = setupDeviceCoordinateSystem(renderers);
let evDev = null;
let evHeading = null;
@@ -249,4 +258,5 @@ $DCL(_ => {
});
evDev = ev;
+ devco.updateDeviceOrientation(ev);
render?.request();
});
setupDeviceOrientationEvent
で色々やっているので、deviceorientation
イベントを現状と別のハンドラで引っ掛けるのではなく、現状のハンドラからイベントオブジェクトを分けてもらう
setupDeviceCoordinateSystem
から設定用の関数を返し、この関数をイベントハンドラから呼び出す
@@ -44,12 +44,15 @@ function setupDeviceCoordinateSystem(renderers) {
const ready = prepare(
gl.fetchShader("vs", "thru.vs"),
+ gl.fetchShader("vs", "persline.vs"),
gl.fetchShader("fs", "fixed.fs")
);
const grid = prepareSphericalGrid(gl);
+ const equator = prepareEquatorThickline(gl);
let evDev;
ready.then((
- vs, fs
+ vs, vsLine, fs
) => {
const prog = gl.linkProgram(vs, fs);
+ const progLine = gl.linkProgram(vsLine, fs);
renderers.push(_ => {
if (!evDev)
@@ -63,4 +66,8 @@ function setupDeviceCoordinateSystem(renderers) {
prog.setUniform("uColor", 1, 1, 1, 1);
drawSphericalGrid(prog, grid);
+ progLine.use();
+ progLine.setUniform("uMat:m4", matVP);
+ progLine.setUniform("uColor", 1, 0, 0, 1);
+ drawEquatorThickline(progLine, equator, [ "min", 5 ]);
});
});
@@ -9,4 +9,5 @@
<script src="thick-line.js"></script>
<script src="spherical-grid.js"></script>
+<script src="axis-cube.js"></script>
<script src="wmmacc2.js"></script>
<script>
@@ -44,6 +45,9 @@ function setupDeviceCoordinateSystem(renderers) {
const ready = prepare(
gl.fetchShader("vs", "thru.vs"),
+ gl.fetchShader("vs", "tex.vs"),
gl.fetchShader("vs", "persline.vs"),
- gl.fetchShader("fs", "fixed.fs")
+ gl.fetchShader("fs", "fixed.fs"),
+ gl.fetchShader("fs", "tex.fs"),
+ prepareAxisCube(gl)
);
const grid = prepareSphericalGrid(gl);
@@ -51,8 +55,9 @@ function setupDeviceCoordinateSystem(renderers) {
let evDev;
ready.then((
- vs, vsLine, fs
+ vs, vsTex, vsPLine, fs, fsTex, cube
) => {
const prog = gl.linkProgram(vs, fs);
- const progLine = gl.linkProgram(vsLine, fs);
+ const progTex = gl.linkProgram(vsTex, fsTex);
+ const progPLine = gl.linkProgram(vsPLine, fs);
renderers.push(_ => {
if (!evDev)
@@ -66,8 +71,11 @@ function setupDeviceCoordinateSystem(renderers) {
prog.setUniform("uColor", 1, 1, 1, 1);
drawSphericalGrid(prog, grid);
- progLine.use();
- progLine.setUniform("uMat:m4", matVP);
- progLine.setUniform("uColor", 1, 0, 0, 1);
- drawEquatorThickline(progLine, equator, [ "min", 5 ]);
+ progPLine.use();
+ progPLine.setUniform("uMat:m4", matVP);
+ progPLine.setUniform("uColor", 1, 0, 0, 1);
+ drawEquatorThickline(progPLine, equator, [ "min", 5 ]);
+ progTex.use();
+ progTex.setUniform("uMat:m4", Mat4.scale(0.707).affect(matVP));
+ drawAxisCube(progTex, cube);
});
});
@@ -47,17 +47,23 @@ function setupDeviceCoordinateSystem(renderers) {
gl.fetchShader("vs", "tex.vs"),
gl.fetchShader("vs", "persline.vs"),
+ gl.fetchShader("vs", "roundcap.vs"),
gl.fetchShader("fs", "fixed.fs"),
gl.fetchShader("fs", "tex.fs"),
+ gl.fetchShader("fs", "circlepoly.fs"),
prepareAxisCube(gl)
);
const grid = prepareSphericalGrid(gl);
const equator = prepareEquatorThickline(gl);
+ const triangleVertices = [ [ -1, 0 ], [ 0, 1 ], [ 1, 0 ] ];
+ const triangleFrame = prepareRoundCapLine(gl, triangleVertices, true);
+ const matNorth = Mat4.scale(0.1, 0.5).trans(0, 0.5);
let evDev;
ready.then((
- vs, vsTex, vsPLine, fs, fsTex, cube
+ vs, vsTex, vsPLine, vsRLine, fs, fsTex, fsDisk, cube
) => {
const prog = gl.linkProgram(vs, fs);
const progTex = gl.linkProgram(vsTex, fsTex);
const progPLine = gl.linkProgram(vsPLine, fs);
+ const progRLine = gl.linkProgram(vsRLine, fsDisk);
renderers.push(_ => {
if (!evDev)
@@ -78,4 +84,8 @@ function setupDeviceCoordinateSystem(renderers) {
progTex.setUniform("uMat:m4", Mat4.scale(0.707).affect(matVP));
drawAxisCube(progTex, cube);
+ progRLine.use();
+ progRLine.setUniform("uMat:m4", matNorth.affected(matVP));
+ progRLine.setUniform("uColor", 1, 1, 1, 1);
+ drawRoundCapLine(progRLine, triangleFrame, [ "min", 10 ], "f2");
});
});
@@ -14,6 +14,14 @@
"use strict";
const Vec4 = VanillaVec4;
+Object.assign(Vec4.prototype, {
+neg(swiz = VanillaVec4.XYZ) {
+ swiz.forEach(i => this[i] = -this[i]);
+ return this;
+},
+affect(mat) {
+ return mat.mul(this);
+}
+});
const Mat4 = VanillaMat4;
-
Mat4.setToRadian(deg2rad);
Mat4.aspectMode = "min";
@@ -59,4 +67,22 @@ function setupDeviceCoordinateSystem(renderers) {
const matNorth = Mat4.scale(0.1, 0.5).trans(0, 0.5);
let evDev;
+ function calculateDeviceHeading() {
+ const mat = Mat4.rotY(evDev.gamma).rotX(evDev.beta).rotZ(evDev.alpha);
+ let Z = Vec4.create(mat.column(2));
+ const upsideDown = Z.z < -Math.sqrt(0.5);
+ const A = Vec4.create(-Z.y, Z.x, 0); // axis
+ if (A.norm() == 0)
+ A.x = 1;
+ else
+ A.normalize();
+ mat.rotZuvecXto(A.x, -A.y); // axis to X
+ Z = Vec4.create(mat.column(2));
+ if (upsideDown)
+ Z.neg();
+ return Vec4.create(mat.column(1)) // devices Y direction
+ .affect(Mat4.rotXuvecZto(-Z.y, Z.z)) // Z to Zenith
+ .affect(Mat4.rotZuvecXto(A.x, A.y)) // rotate axis back
+ .normalize();
+ }
ready.then((
vs, vsTex, vsPLine, vsRLine, fs, fsTex, fsDisk, cube
@@ -70,4 +96,6 @@ function setupDeviceCoordinateSystem(renderers) {
return;
gl.initCanvas();
+ const Y = calculateDeviceHeading();
+ console.log(Math.atan2(Y.y, Y.x) / Math.PI * 180);
const matVP = Mat4
.rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
Vec4.neg
はスイズルで指定されたベクトルの成分の符号を逆にする
Vec4.affect
は行列mat
とベクトルの積\(M\cdot\bvec{x}\)を求める
Z
は端末のZ方向をローカル座標で表したもの
A
は回転軸
Z
の計算は回転軸をローカル座標系X方向に合わせた時の端末Z方向のローカル座標で、これが天頂になるまでX軸のまわりを回転する
Z
とY
の両方を計算する必要があるので、行列で計算している
atan2
で角度に直している
@@ -61,4 +61,5 @@ function setupDeviceCoordinateSystem(renderers) {
prepareAxisCube(gl)
);
+ const line = prepareRoundCapLine(gl);
const grid = prepareSphericalGrid(gl);
const equator = prepareEquatorThickline(gl);
@@ -101,4 +102,7 @@ function setupDeviceCoordinateSystem(renderers) {
.rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
.trans(0, 0, -1).affect(gl.pers(90, 0.01, 2.01));
+ drawVector(progRLine, line, Y, matVP, {
+ color: [ 1, 1, 1, 1 ], thickness: [ "min", 5 ]
+ });
prog.use();
prog.setUniform("uMat:m4", matVP);
@@ -21,4 +21,7 @@ neg(swiz = VanillaVec4.XYZ) {
affect(mat) {
return mat.mul(this);
+},
+rotZ(ang) {
+ return this.affect(Mat4.rotZ(ang));
}
});
@@ -97,6 +100,5 @@ function setupDeviceCoordinateSystem(renderers) {
return;
gl.initCanvas();
- const Y = calculateDeviceHeading();
- console.log(Math.atan2(Y.y, Y.x) / Math.PI * 180);
+ const Y = calculateDeviceHeading().rotZ(evDev.webkitCompassHeading ?? 0);
const matVP = Mat4
.rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
@@ -27,4 +27,15 @@ rotZ(ang) {
});
const Mat4 = VanillaMat4;
+Mat4.rotZuvecYto = function(x, y) {
+ return this.getFactory().create([
+ y, -x, 0, 0,
+ x, y, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ ]);
+};
+Mat4.prototype.rotZuvecYto = function (x, y) {
+ return this.affect(Mat4.rotZuvecYto(x, y));
+};
Mat4.setToRadian(deg2rad);
Mat4.aspectMode = "min";
@@ -64,5 +75,4 @@ function setupDeviceCoordinateSystem(renderers) {
prepareAxisCube(gl)
);
- const line = prepareRoundCapLine(gl);
const grid = prepareSphericalGrid(gl);
const equator = prepareEquatorThickline(gl);
@@ -102,9 +112,7 @@ function setupDeviceCoordinateSystem(renderers) {
const Y = calculateDeviceHeading().rotZ(evDev.webkitCompassHeading ?? 0);
const matVP = Mat4
+ .rotZuvecYto(Y.x, Y.y)
.rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
.trans(0, 0, -1).affect(gl.pers(90, 0.01, 2.01));
- drawVector(progRLine, line, Y, matVP, {
- color: [ 1, 1, 1, 1 ], thickness: [ "min", 5 ]
- });
prog.use();
prog.setUniform("uMat:m4", matVP);
rotZuvecYto
すればよい
matVP
を適用すればデバイスの姿勢に応じて北指標が動くようになる
@@ -81,4 +81,5 @@ function setupDeviceCoordinateSystem(renderers) {
const matNorth = Mat4.scale(0.1, 0.5).trans(0, 0.5);
let evDev;
+ let magdec = 0;
function calculateDeviceHeading() {
const mat = Mat4.rotY(evDev.gamma).rotX(evDev.beta).rotZ(evDev.alpha);
@@ -110,5 +111,6 @@ function setupDeviceCoordinateSystem(renderers) {
return;
gl.initCanvas();
- const Y = calculateDeviceHeading().rotZ(evDev.webkitCompassHeading ?? 0);
+ const Y = calculateDeviceHeading()
+ .rotZ((evDev.webkitCompassHeading ?? 0) + magdec);
const matVP = Mat4
.rotZuvecYto(Y.x, Y.y)
@@ -133,5 +135,5 @@ function setupDeviceCoordinateSystem(renderers) {
});
return {
- updateDeviceOrientation: ev => { evDev = ev; }
+ updateDeviceOrientation: (ev, dec) => { evDev = ev; magdec = dec; }
}
}
@@ -325,5 +327,5 @@ $DCL(_ => {
});
evDev = ev;
- devco.updateDeviceOrientation(ev);
+ devco.updateDeviceOrientation(ev, magdec);
render?.request();
});
updateDeviceOrientation
に偏角も渡して、webkitCompassHeading
に足すだけ
@@ -80,6 +80,6 @@ function setupDeviceCoordinateSystem(renderers) {
const triangleFrame = prepareRoundCapLine(gl, triangleVertices, true);
const matNorth = Mat4.scale(0.1, 0.5).trans(0, 0.5);
+ let vecN = Vec4.create(0, 1, 0, 1);
let evDev;
- let magdec = 0;
function calculateDeviceHeading() {
const mat = Mat4.rotY(evDev.gamma).rotX(evDev.beta).rotZ(evDev.alpha);
@@ -111,8 +111,6 @@ function setupDeviceCoordinateSystem(renderers) {
return;
gl.initCanvas();
- const Y = calculateDeviceHeading()
- .rotZ((evDev.webkitCompassHeading ?? 0) + magdec);
const matVP = Mat4
- .rotZuvecYto(Y.x, Y.y)
+ .rotZuvecYto(vecN.x, vecN.y)
.rotZ(-evDev.alpha).rotX(-evDev.beta).rotY(-evDev.gamma)
.trans(0, 0, -1).affect(gl.pers(90, 0.01, 2.01));
@@ -135,5 +133,12 @@ function setupDeviceCoordinateSystem(renderers) {
});
return {
- updateDeviceOrientation: (ev, dec) => { evDev = ev; magdec = dec; }
+ updateDeviceOrientation: (ev, dec) => {
+ const oldHeading = evDev?.webkitCompassHeading;
+ evDev = ev;
+ if (ev.webkitCompassHeading != oldHeading) {
+ vecN = calculateDeviceHeading()
+ .rotZ((ev.webkitCompassHeading ?? 0) + dec);
+ }
+ }
}
}
webkitCompassHeading
が更新された時だけ北方向ベクトルvecN
を計算
matVP
はvecN
とイベントごとに更新されるデバイスの姿勢を適用
次回は多分実用編。
27 Sep 2024: 本編
09 Sep 2024: 予告編
ご意見・ご要望の送り先は あかもず仮店舗 の末尾をご覧ください。
Copyright (C) 2024 akamoz.jp