実数サポート命令

 hgimg4ではX,Y,Z 3次元の実数値を扱うための命令群として「実数ベクトルサポート命令」というものがあります。
実数ベクトルサポート命令 - HGIMG4プログラミングガイド ( https://www.onionsoft.net/hsp/v36/doclib/hgimg4.html#VECTOR )

 この命令群は、基本的にHSP2のHGIMGから引き継いでいる機能です。 HSP2は、標準では実数を使用することができませんでした。 そこで当時のHGIMGでは、実数値(float値)を使用できるようにするため専用の命令群が実装されていました。

 HSP3(hgimg3)になってからは実数値(double値)が標準で使用できるようになったため、faddfmulなどいくつかの命令は削除されました。 一方で、実数が使用できる現在であっても実数ベクトル(x,y,zのセット)を一度に取り扱う命令(fv系命令)については使い勝手が良いため残されています。

サンプル

 使い方は、実際に動かしてみたほうがわかりやすいと思います。 HSP3本体にはfv系命令のサンプルが付属していないのでこちらを実行してみてください。


#include "hgimg4.as"

; ベクトル設定
fvset fv, 1, 2, 3
mes "fvset : (1, 2, 3)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル加算
fvadd fv, 1, 2, 3
mes "fvadd : fv + (1, 2, 3)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル減算
fvsub fv, 1, 2, 3
mes "fvsub : fv - (1, 2, 3)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル乗算
fvmul fv, 1, 2, 3
mes "fvmul : fv * (1, 2, 3)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル除算
fvdiv fv, 10, 20, 30
mes "fvdiv : fv / (10, 20, 30)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル回転
;
; fvdirの引数は、「回転角, 回転したいベクトル」で与えます。
;
; fvdirの回転角は、マイナス方向(左ねじ方向)に回転します。
; 指定した値は符号が逆になるので注意してください。
; 符号を反転して回転するので、使用には注意してください。
;
fvset fv, 0, 0, M_PI / 2	; 回転角
mes "fv = (0, 0, π/2)   回転角 [ラジアン]"
fvdir fv, 1.0, 0.0, 0.0
mes "fvdir : (1, 0, 0) を fv で回転"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル最大値
; 注意:命令名は「min」ですが、最大値
fvset fv, 1, 2, 3
mes "fv = (1, 2, 3)"
fvmin fv, 3, 2, 1
mes "fvmin : fv と (3, 2, 1)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトル最小値
; 注意:命令名は「max」ですが、最小値
fvset fv, 1, 2, 3
mes "fv = (1, 2, 3)"
fvmax fv, 3, 2, 1
mes "fvmax : fv と (3, 2, 1)"
mes "fv = " + fv(0) + ", " + fv(1) + ", " + fv(2)
mes "\n"

; ベクトルを文字列に変換
fv2str fv
mes "fv2str : fv"
mes "fv = 「" + refstr + "」"
mes "\n"

; 文字列をベクトルに変換
str2fv fv, "1, 2, 3"
mes "str2fv : \"1, 2, 3\""
fv2str fv
mes "fv = " + refstr

pos ginfo_winx/2, 0
; 文字列を小数値に変換
; fval = double("1.23")
; と同じ動作です。
str2f fval, "1.23"
mes "str2f : \"1.23\""
mes "fval = " + fval
mes "\n"

; 小数値を文字列に変換
; val = str(fval)
; と同じ動作です。
f2str val, fval
mes "f2str : 1.23"
mes "val = 「" + val + "」"
mes "\n"

; ベクトル外積
fvset   fv, 3, 4, 1
fvouter fv, 3, 7, 5
fv2str  fv
mes "fvouter : (3, 4, 1)×(3, 7, 5)"
mes "fv = " + refstr
mes "\n"

; ベクトル内積
; 結果はfv(0)に返されます。
; fv(1), fv(2) にはいっている値は使用しないでください。
fvset   fv, 3, 4, 1
fvinner fv, 3, 7, 5
fv2str  fv
mes "fvouter : (3, 4, 1)・(3, 7, 5)"
mes "fv = " + refstr
mes "\n"

; ベクトル正規化
;ベクトルの大きさ(矢印の長さ)を1にします。
fvset  fv, 3, 4, 1
fvunit fv
fv2str fv
mes "fvunit : (3, 4, 1)"
mes "fv   = " + refstr
; 長さを計算
fvmul fv, fv(0), fv(1), fv(2)
mes "|fv| = " + sqrt( fv(0) + fv(1) + fv(2) )
mes "\n"

; サインを求める
; sin( M_PI / 4 )
; と同じ。
fsin fval, -M_PI / 4
mes "fsin : -π/4"
mes "fval = " + fval
mes "\n"

; コサインを求める
; cos( M_PI / 4 )
; と同じ。
fcos fval, -M_PI / 4
mes "fcos : -π/4"
mes "fval = " + fval
mes "\n"

; 整数値角度を小数値に変換
;
; (hgimg3.txtより)
; > froti fval,prm	整数値角度を小数値に変換
; > 
; > 	fval    = float値が代入される変数名
; > 	prm     = 0~1023の角度値(整数)
; > 
; > 	prmで指定された整数値(0~1023)を角度を示すものとして、
; > 	fvalに-π~+πのラジアン角度値に変換して代入します。
;
; この命令はまだ未実装のようです。理由は次の通り。
; ・結果を40倍しないと説明通りの挙動にならない。
; ・計算精度が悪い。
; ・setangr 命令などが使用している整数角度と仕様が一致しない。
; 
froti fval, 1024.0
mes "froti : 1023"
mes "fval = " + fval + "rad (" + rad2deg(fval) + "deg)"
froti fval, 1023.0 / 2
mes "froti : 1023 / 2"
mes "fval = " + fval + "rad (" + rad2deg(fval) + "deg)"
mes "\n"

; 座標から角度を得る
; どの方向のベクトルを回転するのか不明
fvset  fv, 0, 0, 0
fvface fv, 1, 0, 0
fv2str fv
mes "fvface"
mes "(0, 0, 0)から(1, 0, 0)を見る角度"
mes "fv = " + rad2deg(fv(0)) + ", " + rad2deg(fv(1)) + ", " + rad2deg(fv(2))
mes "\n"
/*
fvset  fv, 0, 0, 0
fvface fv, 0, 1, 0
mes "fv = " + rad2deg(fv(0)) + ", " + rad2deg(fv(1)) + ", " + rad2deg(fv(2))

fvset  fv, 0, 0, 0
fvface fv, 0, 0, 1
mes "fv = " + rad2deg(fv(0)) + ", " + rad2deg(fv(1)) + ", " + rad2deg(fv(2))
*/

redraw 1

 最後のredraw 1がポイントです。これがないと表示されません。

四則演算

 fv系命令を使うと3方向の四則演算が1行で書けるので便利です。

fvset=fv値を設定
fvadd+加算
fvsub-減算
fvmul*乗算
fvdiv/除算

 ところで、fvsetでfv値を作ると、作成される配列変数の要素は、3つではなく4つのものが作成されます。 4つ目は、fvaddで数値が増えたりするのですが…これなんでしょうね。 内部で計算負荷を軽くするために使用されている領域だと思われるので、入っている数値は気にしなくて大丈夫です。 同次座標(3次元を使うときは1個増やして4次元で表現して計算する方法)では?という話もあるのですが、加算で増えるんだっけ…?

 fvsetの代わりにddimで3要素の配列を作っても自動拡張でカバーされるため問題はないようです。 ただし機会はないと思いますが、fv値にlength命令を使うと結果が4になるので注意してください。

回転角

 fvdirでベクトルを回転させることができます。回転角度はラジアンで指定します。単位で指定したい場合は、deg2radを使用してください。 例では、X軸方向のベクトルをZ軸中心に90度(π/2ラジアン)回転しています。

 HSP3.6(HSP3.7β1)では、引数の指定がマニュアルとは異なり、実際は、fv値に「回転角」、X,Y,Zに「回転したいベクトル」を指定して使います。 また、回転方向のプラスとマイナスが逆になっています。 バグなので使用を避けたいところですが、自分で実装となると大変ですよね。使用時は注意してください。マクロかなにかで置き換えて使ったほうがいいと思います。

最大値・最小値

 HSP3.6(HSP3.7β1)では、HGIMG4プログラミングガイドに記載の説明のとおりに動作しています。 fvminはベクトル最大値、fvmaxはベクトル最小値です。 本来はHDLの説明の通り、命令と動作が一致するはずなのでそのうち修正されると思われるバグです。

 使用する際は、マクロで適切な命令名に書き換えて使用するといいと思います。

ベクトルと文字列を相互変換

 fv2strstr2fvを使うと3次元の数値とカンマ区切りの文字列とで相互変換できます。 区切り文字は「半角英数のカンマ」です。デバッグのために現在を確認したい場合や、座標データをテキストファイルで作って読み込みたいなどの場合に便利です。 自分で実装することは可能ですが、使う機会を考えると面倒なのでとても助かります。

少数値と文字列を相互変換

 str2ff2strを使うと実数値と文字列とで相互変換できます。 自分で実装することは簡単…。

 double("1.23")str(1.23)と同じなので、標準命令で置き換えができます。 過去作品をhgimg4へ移植するために残されている命令だと思います。積極的に使用する理由はありません。

ベクトル外積・内積

 2つのベクトルの差を比較する際に使用します。 外積を使うと2つ目のベクトルが右にいるのか左にいるのかがわかります。また外積は、2ベクトルが作る面の法線方向(直角方向)もわかります。 内積を使うと2ベクトルの間の角度がcosで出てくるので、acosすると角度が算出できます。 高校数学で学習する内容なので、多少難しいですが使い方がわかると大変強力です。

 内積の結果は、fv値の0番目の要素に返されます。1,2番目の要素にも値が入っていますが、計算作業のバッファーとして使用した痕跡が残っているだけだと思います。 1,2番目の要素は使用しないようにしてください。

 外積と内積の計算式は驚くほど簡単なので自力実装も可能ですが、この命令を使うほうが圧倒的に楽なので活用しましょう。

正規化

 聞き馴染みのない名前かもしれませんが、ベクトル(x,y,z)を単位ベクトル…ベクトルの大きさ(長さ)を1(単位長さ)にしてくれる命令です。

 ベクトルの大きさを任意の値にしたい際、fvunitで大きさを1にしてから、fvmulで(x,y,z)に任意の同じ値を掛け算し、拡大して使います。 ベクトルの方向を変えずに大きさだけを変えたいときに使用する命令です。

 たとえば、敵キャラが自機に向けて弾丸を発射する時、弾丸の初速方向ベクトルを計算する場合などに使用します。

サイン・コサイン

 fsinfcosに指定する値は、ラジアンです。配列で指定することはできません。 sincosと動作はまったく同じなので、過去作品をhgimg4へ移植するために残されている命令だと思います。積極的に使用する理由はありません。 流石にもうサポートしなくてもいいのでは?と思うのですが、過去作品の移植などで役立っているのでしょうか…。

整数値角度を小数値に変換

 HSP3.6(HSP3.7β1)では、うまく動いていないようです。 使用方法についてもhgimg3.txtのみに記述されていて、hgimg4向けの記述は命令の紹介のみとなっています。 サンプル中にも記載している通り、動作結果に問題がありsetangrとも仕様に互換性がありません。 今後、何らかの形で修正(削除?)されると思いますので現在は使用しないほうがいいでしょう。

座標から角度を得る

 fvface命令の動作結果は、何を回転することを想定しているのか、どんな回転をしているのか今の私には理解できず使いこなせませんでした。 使い方がわかったら、ここに追記したいと思っています。

まとめ

 表にまとめてみました。右側にコメントがないものは、積極的に使ったほうがいい命令です。

fvset=, fv値を設定
fvadd+, ベクトル加算
fvsub-, ベクトル減算
fvmul*, ベクトル乗算
fvdiv/, ベクトル除算
fvdirベクトル回転要注意
fvminベクトル最大値要注意
fvmaxベクトル最小値要注意
fv2strベクトルを文字列に変換
str2fv文字列をベクトルに変換
str2f文字列を小数値に変換double("1.23")
f2str小数値を文字列に変換str(1.23)
fvouterベクトル外積
fvinnerベクトル内積
fvunitベクトル正規化
fsinサインsin
fcosコサインcos
froti整数値角度を小数値に変換今は使用禁止
fvface座標から角度を得る???

fvface

追記:2022/10/09(HSP3.7β3での調査結果)

 fvfaceについて検証してみたところ、どうやら -Zベクトルを回転するための角度のようです。(ようですとは言うものの、正解かどうかはわかりません。この理解でとりあえず矛盾なく使えるというだけです。)

 fvfaceは、「任意の基点座標にある -Zベクトル」を「指定された座標」に向けるための「X,Y,Z回転角度」を計算してくれるもののようです。 とりあえずは、動きを確認するためのサンプルです。


  #include "hgimg4.as"
  gpreset
  
  ;	ウィンドウの中心座標
  zCenter = ginfo_winx / 2
  yCenter = ginfo_winy / 2
  
  *main
    stick key, $180F
    if key&128 : end
    ; 目標座標
    mz = mousex
    my = mousey
    ; 目標座標(ウィンドウ中央基準)
    tx = 0
    ty = my - yCenter
    tz = mz - zCenter
  
    ; xとzを入れ替えた場合(計算結果のみ)
    ;tx = tz
    ;tz = 0
  
    redraw 0			; 描画開始
      pos 0, 0
      ;	説明
      color
      pos zCenter, yCenter
      mes "┌─→ +Z\n│\n↓ Xは、左回り正\n+Y"
      pos zCenter - 200, yCenter + 10
      mes "-Z"
  
      ;	元の向き
      ; 回転前の向きは、常に-Z方向。
      color 255, 0, 0
      line zCenter - 200, yCenter, zCenter, yCenter
  
      ;	回転後の向き
      ; -Zをこの方向に向けたい。
      color 0, 0, 255
      line mz, my, zCenter, yCenter
      
      ; 基点とする x,y,z 座標
      fvset fv, 0, 0, 0
      
      ; 基点を原点ではなく、任意の座標にする場合の例
      ; 計算結果は同じです。
      ; fvset fv, 0, yCenter, zCenter
      ; tx = 0 : ty = my : tz = mz
  
      ; 座標 fv で-Z方向を向いているものを、座標(tx, ty, tz)を向かせるために
      ; 必要な回転角度を計算してくれます。
      ; 実行後の fv の中身は、座標ではなく回転角度に変わります。
      fvface fv, tx, ty, tz
  
      pos 10, 10
      color
      mes "回転角度 [deg]"
      mes "" + strf("%5.1f, %5.1f, %5.1f", rad2deg(fv(0)), rad2deg(fv(1)), rad2deg(fv(2)))
    redraw 1			; 描画終了
    
    await 1000/60			; 待ち時間
    goto *main

 赤い線青い線の方向に向けるための回転角が取得できています。正しそうですね。

 しかし、このサンプルは確認できないのですが、取得できた角度値で -Zベクトルをsetangで回転すると「指定された座標」方向を向きません。 -Zベクトルを指定した座標方向に向けるには、setangyまたはsetangz命令を使い、Y軸の回転方向をマイナスにする必要があります。 つまり実際にノードオブジェクトを回転させる際は、次のように記述します。


setangy id_shadow, fv(0), -fv(1), fv(2)

 -Y → +X の順番に回転させると -Zベクトルが指定された座標方向を向きます。Z軸は0.0しか出てこないので気にする必要はありません。

 とりあえず挙動は把握できたのでこれで使用できます。 使えはしますが理解が難しい動きなので、マニュアルの説明不足による勘違いか、ただの実装ミス(バグ)では?と思っています。 適切な挙動になるように修正してほしいですね。