端末から見た世界

 前回、磁北の偏角を補正して真北を求め、端末の外から眺める形で確認した。 今回は実際に端末から見た状態を描画する 簡単に言ってしまえば3Dコンパスを作る感じ。

表示ベクトルのW成分にゴミが入っていた

@@ -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

});

webkitCompassHeadingがなければ0として扱う

@@ -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列を指定
  • 比率は2:3
  • キャンバスにoverflow: hiddenを指定しているのは、何かの拍子にキャンバスから画面がはみ出すと、外側のdivが画面より大きくなってキャンバスがうまく分割されなくなる
    • 縦横切り替えたりするとどんどんキャンバスサイズが大きくなったりする
    • 新しい整形コンテキストを作る必要があり、clipだと新しい整形コンテキストができないのでうまくいかない
  • 境目に線を入れたいのでボーダーを指定するが、そうするとボーダーの分だけdivが大きくなってしまうのでbox-sizingborder-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;

  • 今回はキャンバスを分けているので、GLコンテキストが別々になる
  • ということは、GLのリソースもすべて別々に用意する必要がある
  • ごっちゃにならないように関数を分ける
  • とりあえずGLコンテキスト作ってキャンバスを初期化しておく
座標球表示

@@ -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から設定用の関数を返し、この関数をイベントハンドラから呼び出す
  • 良い子は真似をしてはいけない構造なので、この説明で分からなくても気にしない
  • てか、素直にクラスにしろよ、、、
  • これで座標球がデバイス姿勢に反応hしてグリグリ動く
赤道を描く

@@ -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は回転軸
  • 2回目のZの計算は回転軸をローカル座標系X方向に合わせた時の端末Z方向のローカル座標で、これが天頂になるまでX軸のまわりを回転する
  • 最終的には端末Y方向を示すベクトルをうにょうにょ回転したものを返すが、ここまではZYの両方を計算する必要があるので、行列で計算している
  • その先は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);

  • ベクトルを実際に描いて確認しておく
webkitCompassHeadingの適用

@@ -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);

  • ここが今回の本題
  • ローカル座標系とはある時点でブラウザが決めた座標系であり、Z軸は地平座標系(X軸が東、Y軸が北)と重なっている
  • 先ほど求めたベクトルはローカル座標系から見た北の方向なので、地平座標系のY軸がこのベクトルの方向に見えればよい
  • つまり、地平座標系のY軸単位ベクトルを、ローカル座標系の北ベクトルに重なるまで回せばよいので、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);

+ }

+ }

}

}

  • ローカル座標系は水平方向に(Z軸のまわりに)回転すれば地表座標系と重なるが、ローカル座標系決定後はこの回転角は一定になる
  • webkitCompassHeadingが更新された時だけ北方向ベクトルvecNを計算
  • matVPvecNとイベントごとに更新されるデバイスの姿勢を適用

次回は多分実用編


27 Sep 2024: 本編

09 Sep 2024: 予告編

ご意見・ご要望の送り先は あかもず仮店舗 の末尾をご覧ください。

Copyright (C) 2024 akamoz.jp