HSPポータル
サイトマップ お問い合わせ


HSPTV!掲示板


未解決 解決 停止 削除要請

2022
0826
youdaiHGIMG4で等速移動のeventを実装したい7解決


youdai

リンク

2022/8/26(Fri) 17:39:26|NO.97025

HGIMG4のイベント命令で等速移動を実装したいです。

連続した複数の座標x,y,zに等速で移動するイベントを作成することはできないでしょうか?

例えば、以下のようにすると連続する座標へ移動しますが、移動する座標の距離の違いで
移動速度がバラバラになってしまいます。
近い距離の時は速度が遅く、遠い距離の場合は速度が速くなってしまいます。


; frameは待機フレーム数 ; x,y,zの配列変数には同数の座標が入っていると思って下さい frame = 30 newevent ev foreach x event_pos ev, frame, x.cnt, y,cnt, z.cnt, 0 event_wait ev, frame loop

これを等速で移動するイベントを作成するには、どうしたらよいでしょうか?
アドバイスお願い致します。



この記事に返信する


usagi

リンク

2022/8/26(Fri) 20:30:46|NO.97026

良くある補間方法は距離を速さで割るとフレーム数が分かります。
一例ですかどうぞ


#include "hgimg4.as" chdir dir_exe+"\\sample\\hgimg4" x = 5, -5, 0, 0, 0, 0, 0 y = 0, 0, 3, -3, 0, 0, 0 z = 0, 0, 0, 0, 0, 5, 0 speed = 0.1 ;スピード変えてみてください。 newevent ev st = 0, 0, 0 ;開始点 foreach x ; 距離を求める _len = x.cnt - st.0, y.cnt - st.1, z.cnt - st.2 _dist = sqrt(_len.0*_len.0 + _len.1*_len.1 + _len.2*_len.2) st = x.cnt, y.cnt, z.cnt ; 開始点更新 frame = int(_dist/speed) ; 距離を速さで割るとフレームが出る event_pos ev, frame, x.cnt, y.cnt, z.cnt event_wait ev, frame loop setpos GPOBJ_CAMERA, 0,0,10 gpbox id, 1, 0xFF0000 setevent id, ev1 *MAIN redraw 0 : gpdraw : redraw 1 : await 1000/60 goto *MAIN



youdai

リンク

2022/8/26(Fri) 23:32:49|NO.97027

自己解決しました。
現在と次の座標との間の距離に乗算で補正して、round関数で四捨五入してint化すれば、それがそのまま等速のframe(速度)として使用できました。

> usagiさんへ
アドバイスありがとうございます。
自己解決の報告が間に合わなくて、申し訳ないです。
結果的にusagiさんのアドバイスと大筋同じスクリプトになりました。

違いとしては座標によっては距離が0.0の場合もあるので、除算だとエラーになってしまうので、距離には乗算系の方法で補正すると上手くいきました。
移動速度によってはint()単体ではなく、round(int())の方が等速感がある移動になるようです。数字上は小さい差でも、動かしてみると速度によってはスムーズさにかなり違う感覚がありました。

実はかなり悩んだ難問だったのですが、usagiさんはさすがに回答が早くて凄いなぁと思いました。



youdai

リンク

2022/8/26(Fri) 23:55:30|NO.97028

●sqrtかpowfかの違いについて
三次元の距離の出し方がusagiさんと自分のスクリプトでは異なることに気が付きました。
スクリプト上の違いは最終的に処理する関数が、sqrtかpowfかの違いでした。

youdai版の距離の出し方


/*  三次元距離関数 座標その1 x1,y1,z1 座標その2 x2,y2,z2 */ #module #defcfunc distance3d double x1, double y1, double z1, double x2, double y2, double z2 return powf( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1), 0.5 ); #global

C++言語の情報サイト調べですが、sqrtを使った方が処理が11倍早いという記述を見つけました。
powfではなく、sqrtを使った方がいいのでしょうか?



usagi

リンク

2022/8/27(Sat) 01:53:10|NO.97029

言語実装によると思うですが、おそらくsqrtの方が大体の言語で早くなるとは考えてます。

ちゃんと調べないと分からないですが、感覚としてはべき乗で0.5するよりは、
もともとルートをとる方がやることが決まってるので最適化されてそうくらいな気持ちです。
※x^0.5 = e^(0.5 ∗ log(x)) と考えただけでも重そう。。。

と、言うわけでHSPがどうなっているかわからないのですが、
私の環境では計ってみるとsqrtの方が早かったです。


;------------------------------------------------ ; 計測モジュール #module #uselib "kernel32" #func QueryPerformanceFrequency "QueryPerformanceFrequency" sptr #func QueryPerformanceCounter "QueryPerformanceCounter" sptr ; LONGLONGをDoubleに変換 #defcfunc longlong2double array _longlong longlong = 0.0 lpoke longlong, 0, _longlong.1 lpoke longlong, 4, _longlong.0 return double(strf("%I64u",longlong)) #deffunc performanceCounterInit dim lpFrequency, 2 dim lpPerformanceCount, 2 QueryPerformanceFrequency varptr(lpFrequency) freq = longlong2double(lpFrequency) QueryPerformanceCounter varptr(lpPerformanceCount) time_start = longlong2double(lpPerformanceCount) return #defcfunc performanceCounter QueryPerformanceCounter varptr(lpPerformanceCount) time_end = longlong2double(lpPerformanceCount) return (time_end-time_start)/freq ;秒 #global performanceCounterInit ;------------------------------------------------ a = 0.0 count1 = 8 count2 = 100000 repeat count1 mes ""+cnt+"回目" performanceCounterInit repeat count2 a = powf(1, 0.5) loop mes "powf :"+performanceCounter() performanceCounterInit repeat count2 a = sqrt(1) loop mes "sqrt :"+performanceCounter() loop



usagi

リンク

2022/8/27(Sat) 03:35:31|NO.97030

数式として改めて計ってみたらHSPではそこまで差は出ないみたいですね。

;------------------------------------------------ ; 計測モジュール #module #uselib "kernel32" #func QueryPerformanceFrequency "QueryPerformanceFrequency" sptr #func QueryPerformanceCounter "QueryPerformanceCounter" sptr ; LONGLONGをDoubleに変換 #defcfunc longlong2double array _longlong longlong = 0.0 lpoke longlong, 0, _longlong.1 lpoke longlong, 4, _longlong.0 return double(strf("%I64u",longlong)) #deffunc performanceCounterInit dim lpFrequency, 2 dim lpPerformanceCount1, 2 dim lpPerformanceCount2, 2 QueryPerformanceFrequency varptr(lpFrequency) freq = longlong2double(lpFrequency) QueryPerformanceCounter varptr(lpPerformanceCount1) return #defcfunc performanceCounter QueryPerformanceCounter varptr(lpPerformanceCount2) time_end = longlong2double(lpPerformanceCount2) time_start = longlong2double(lpPerformanceCount1) return (time_end-time_start)/freq ;秒 #global performanceCounterInit ;------------------------------------------------ #define ctype rndf(%1,%2) double(rnd(%1*powf(10,%2))) / (powf(10,%2)) randomize a = 0.0 count1 = -1 count2 = 100 sum1 = 0. : sum2 = 0. repeat count1 performanceCounterInit repeat count2 x1=rndf(10,2): y1=rndf(10,2): z1=rndf(10,2) x2=rndf(10,2): y2=rndf(10,2): z2=rndf(10,2) a = powf( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1), 0.5 ) loop c1 = performanceCounter() sum1 +=c1 performanceCounterInit repeat count2 x1=rndf(10,2): y1=rndf(10,2): z1=rndf(10,2) x2=rndf(10,2): y2=rndf(10,2): z2=rndf(10,2) a = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1) ) loop c2 = performanceCounter() sum2 +=c2 redraw 0 : color : boxf : color 255,255,255 : pos 0,0 mes ""+cnt+"回目" mes strf("dist(%f,%f,%f : %f,%f,%f ) = %f",x1,y1,z1, x2,y2,z2, a) mes "powf :"+c1 mes "sqrt :"+c2 if c1<c2 : wc1++ : else : wc2++ mes "powfの勝ち :"+wc1 mes "sqrtの勝ち :"+wc2 mes "合計時間" color 0,255 boxf 0,16*8,sum1*100,16*8+16 boxf 0,16*9,sum2*100,16*9+16 color 255,255,255 mes "powf :"+sum1,4 mes "sqrt :"+sum2,4 redraw 1 : wait 0 if sum1 > 6.4 || sum2>6.4 : break loop



usagi

リンク

2022/8/27(Sat) 21:38:43|NO.97032

>距離が0.0の場合もあるので、除算だとエラー
私の例だとspeedが0だとエラーはくので、
speed=0は移動しないとして使わない想定でした。

なるべく0は使いたくないですよね。。。悩みの種です。
(逆数求めるのにも割る必要がありますし。。。)


>round(int())の方が等速感
失礼いたしました。誤差が出るのでおっしゃる通りだと思います。

正確さを求めるならeventでは出来なくなってしまいますが、
線形補間して現在座標をしっかり出した方が良いかもですね。
(もしくは1F毎のイベントを無理やり登録)

と言う訳でどの程度の誤差なのか実験してみたのですが、
eventが切り替わるタイミングでズレてしまいますが、
(frameベースなのでそこでの四捨五入による誤差)
ゲーム的なパーティクルとか敵の動き、
車の経路などのちょっとしたナビゲーションであれば
余り気にならないし、見せ方次第で十分なクオリティーかと感じました。



;----------------------------------------------- ; 直線配列から位置を出すモジュール #module #defcfunc distance3d double x1, double y1, double z1, double x2, double y2, double z2 return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1) ) #defcfunc totalDistance3d array ax, array ay, array az if length(ax) < 2 || length(ay) < 2 || length(az) < 2 : return 0.0 sum = 0.0 repeat length(ax)-1 : cnt2 = cnt+1 sum += distance3d(ax.cnt, ay.cnt, az.cnt, ax.cnt2, ay.cnt2, az.cnt2) loop return sum #deffunc trimDistance3d array ax, array ay, array az, double atarget_dist, array apos sum_dist = totalDistance3d(ax, ay, az) ; 全体の距離 if sum_dist == 0.0 : return -1 ; エラー if atarget_dist >= sum_dist { ; 終点チェック n = length(ax)-1 apos.0 = ax.n : apos.1 = ay.n : apos.2 = az.n : return 1 } if atarget_dist <= 0.0 { ; 始点チェック apos.0 = ax.0 : apos.1 = ay.0 : apos.2 = az.0 : return 0 } ; トリム cur_dist = 0.0 ; 現在の距離 repeat length(ax)-1 : i = cnt : j = i+1 old_dist = cur_dist dist = distance3d(ax.i, ay.i, az.i, ax.j, ay.j, az.j) cur_dist += dist if atarget_dist < cur_dist { diff = atarget_dist - old_dist ; 差分 percent = diff / dist ; 割合 ; 線形補間 lerp ※イージングで代用 setease ax.i, ax.j, ease_linear : apos.0 = geteasef(percent) setease ay.i, ay.j, ease_linear : apos.1 = geteasef(percent) setease az.i, az.j, ease_linear : apos.2 = geteasef(percent) break } loop return 0 #global ;----------------------------------------------- #include "hspmath.as" #include "hgimg4.as" chdir dir_exe+"\\sample\\hgimg4" ; 設定 speed = 1.2345678 x = 0, 50, -50, 0, 0, 0, 0, 0, 0 y = 0, 0, 0, 30, -30, 0, 0, 0, 0 z = 0, 0, 0, 0, 0, 0, 50, -50, 0 newevent ev st = 0, 0, 0 ;開始点 foreach x ; 距離を求める _len = x.cnt - st.0, y.cnt - st.1, z.cnt - st.2 _dist = sqrt(_len.0*_len.0 + _len.1*_len.1 + _len.2*_len.2) st = x.cnt, y.cnt, z.cnt ;frame = int(_dist/speed) ; これは失敗 frame = int(round(_dist/speed)) ; 四捨五入で誤差減らす event_pos ev, frame, x.cnt, y.cnt, z.cnt event_wait ev, frame loop setpos GPOBJ_CAMERA, 0,30,100 gplookat GPOBJ_CAMERA, 0,0,0 gpbox id, 10, 0xFF0000 : setevent id, ev1 gpbox id, 10, 0x00FF00 ddim p, 3 : cur_dist = 0.0 *MAIN redraw 0 trimDistance3d x, y, z, cur_dist, p ; 配列から現在位置を求める setpos id, p.0, p.1, p.2 cur_dist += speed gpdraw : redraw 1 : await 1000/60 goto *MAIN



youdai

リンク

2022/8/27(Sat) 23:39:40|NO.97033

> usagiさんへ
sqrtについての詳細報告ありがとうございます。
sqrtが適用できる場合には積極的に使っていこうと思います。

イージング関数(線形補正代用)については使ったことがなかったので大変為になります。
これからじっくりスクリプトを研究してみようと思います。



ONION software Copyright 1997-2023(c) All rights reserved.