HSP3.5 HGIMG4 で物理サイコロもどき

# getquat と setquat

HSP3.5 HGIMG4 では、getquat, setquat によりクォータニオンでのオブジェクト姿勢の取得または設定ができるようになりました。これによって物理演算の結果を他のオブジェクトの姿勢として適用するのが容易になりました。
今回はこの getquat, setquat を含めて物理サイコロもどきを作ってみます。
サンプルコードは以下のようになります。

```
#include "hgimg4.as"

#module
#deffunc qmul array a1, array a2, array a3, local l, local lre1, local lre2
// a1 と a2 のクォータニオン積を a3 に格納する
	lre1 = a1.3
	lre2 = a2.3
	l.0 = a1.0, a1.1, a1.2
	fvinner l, a2.0, a2.1, a2.2
	a3.3 = lre1 * lre2 - l.0

	l.0 = a1.0
	fvouter l, a2.0, a2.1, a2.2
	a3.0 = l.0 + lre2 * a1.0 + lre1 * a2.0
	a3.1 = l.1 + lre2 * a1.1 + lre1 * a2.1
	a3.2 = l.2 + lre2 * a1.2 + lre1 * a2.2
	return


#deffunc qrot array ap, array aq, array ar, local p, local f, local c
// 座標 ap を クォータニオン aq で回転して ar に格納する
	p.0 = ap.0, ap.1, ap.2, 0.0
	ddim f,4
	qmul aq, p, f
	c.0 = -aq.0, -aq.1, -aq.2, aq.3
	qmul f, c, ar
	return
#global

#const EG_CALC GPDRAW_OPT_OBJUPDATE
#const EG_DRAW2D (GPDRAW_OPT_DRAW2D | GPDRAW_OPT_DRAW2D_LATE)
#const EG_DRAW3D (GPDRAW_OPT_DRAWSCENE | GPDRAW_OPT_DRAWSCENE_LATE)

#const DELTA 0.0001

	randomize

	gpreset
	setcls CLSMODE_NONE

// 回転用障害物
	gpbox id, 2, 0x333333
	setpos id, 7.5, 5, -3.5 + double(rnd(100)) * 0.01
	gppbind id, 0
// アヒルと箱
	gpload id, "res/duck"
	setscale id, 0.8, 0.8, 0.8
	duck_id = id

	gpbox id, 2, 0xcccc00
	setpos id, 6, 9, -2
	gppbind id, 1.0, 0.5
	gppset id, GPPSET_DAMPING, 0.0, 0.0
	gppset id, GPPSET_FRICTION, 0.5, 0.75
	setalpha id, 128
	box_id = id
// 床
	gpfloor id, 40,40, 0x6666cc
	gppbind id, 0, 0.5
	gppset id, GPPSET_FRICTION, 0.5, 0.75

	setangr GPOBJ_LIGHT, -96, 32, 0

	setpos GPOBJ_CAMERA, 2, 21, 12
	gplookat GPOBJ_CAMERA, 0, 3, 0

	sdim faces,256,7
	faces.0 = "none"
	faces.1 = "head"
	faces.6 = "foot"
	faces.2 = "tail"
	faces.5 = "chest"
	faces.3 = "left"
	faces.4 = "right"
	ddim scores,7

// クォータニオン用変数
	ddim q,4
	ddim result,4

	fix = 0
	getreq prets, SYSREQ_TIMER
	pre.0 = -999.0, -999.0, -999.0

	repeat
		getreq ts, SYSREQ_TIMER
		getreq fps, SYSREQ_FPS
		redraw 0
		color 0,0,0x33
		boxf
		
		gpdraw EG_CALC

// 箱の状態をアヒルに反映
		getpos box_id, x,y,z
		setpos duck_id, x,y,z

		getquat box_id, q.0, q.1, q.2, q.3
		setquat duck_id, q.0, q.1, q.2, q.3


// 3つの基底を回転し (0,1,0) と内積を取る
		p.0 = 1.0, 0.0, 0.0
		qrot p, q, result
		scores.5 = result.1
		scores.2 = -result.1

		p.0 = 0.0, 1.0, 0.0
		qrot p, q, result
		scores.1 = result.1
		scores.6 = -result.1

		p.0 = 0.0, 0.0, 1.0
		qrot p, q, result
		scores.4 = result.1
		scores.3 = -result.1
		
		
		gpdraw EG_DRAW2D | EG_DRAW3D

		pos 16,16
		color 224,224,224
		font "",36
		mes strf("%2d [fps]", fps)

		max_score = -2.0
		max_fix = 0
		repeat 6,1
			mes strf("%d:",cnt) + faces.cnt + ": " + scores.cnt

			if scores.cnt > max_score {
				max_score = scores.cnt
				max_fix = cnt
			}
		loop

// 動きチェック
		if fix == 0 {
			diff = powf(x - pre.0, 2.0) + powf(y - pre.1, 2.0) + powf(z - pre.2, 2.0)
			if diff < DELTA {
				// 1.0秒止まったら確定とする
				if ts - prets >= 1000 {
					fix = 1
				}
			} else {
				prets = ts
			}
			// 前回の位置を覚えておく
			pre.0 = x,y,z
		} else {
			color 224,0,0
		}

// 現在の目
		gpcnvaxis x2,y2,z2, x,y,z
		pos x2 - 9, y2 - 72
		mes max_fix
		
		redraw 1
		await 1000/60
	loop
```

サイコロの目を貼るのが面倒だったので代わりにアヒルの向きでサイコロの目の代わりにしました。

|目|アヒルの向き|
|:-:|:-:|
|1|頭上から|
|2|しっぽ|
|3|左羽|
|4|右羽|
|5|正面|
|6|足|

|実行結果1|
|:-:|
|![z07dice1.png](https://qiita-image-store.s3.amazonaws.com/0/213145/8f991121-9505-cabd-a872-162c53d0df71.png)|
|転がる最中も出目候補を表示|

|実行結果2|
|:-:|
|![z07dice2.png](https://qiita-image-store.s3.amazonaws.com/0/213145/c08ba213-6bb4-aeec-d231-8cd3037df22f.png)|
|出目が確定したら赤色で表示|

なおサンプルコード内の qrot 関数は位置ベクトルをクォータニオン回転する関数なので、オブジェクトの向いている方へ延長するベクトルの計算などの他の目的にも応用して使えます。

# 注意

このサンプルでは物理サイコロの出目を判定していますが、初期状態やパラメータの都合で1～6までがきれいな乱数で出るわけではありませんのでそれを理解した上でご使用ください。このまま使用する場合はもっと派手に回転にするようにパラメータ設定値を調整して出目が適切にばらけるようにするなど対処するといいと思います。