|
|
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
これを等速で移動するイベントを作成するには、どうしたらよいでしょうか?
アドバイスお願い致します。
|
|
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
|
|
2022/8/26(Fri) 23:32:49|NO.97027
自己解決しました。
現在と次の座標との間の距離に乗算で補正して、round関数で四捨五入してint化すれば、それがそのまま等速のframe(速度)として使用できました。
> usagiさんへ
アドバイスありがとうございます。
自己解決の報告が間に合わなくて、申し訳ないです。
結果的にusagiさんのアドバイスと大筋同じスクリプトになりました。
違いとしては座標によっては距離が0.0の場合もあるので、除算だとエラーになってしまうので、距離には乗算系の方法で補正すると上手くいきました。
移動速度によってはint()単体ではなく、round(int())の方が等速感がある移動になるようです。数字上は小さい差でも、動かしてみると速度によってはスムーズさにかなり違う感覚がありました。
実はかなり悩んだ難問だったのですが、usagiさんはさすがに回答が早くて凄いなぁと思いました。
|
|
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を使った方がいいのでしょうか?
|
|
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
| |
|
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
| |
|
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
| |
|
2022/8/27(Sat) 23:39:40|NO.97033
> usagiさんへ
sqrtについての詳細報告ありがとうございます。
sqrtが適用できる場合には積極的に使っていこうと思います。
イージング関数(線形補正代用)については使ったことがなかったので大変為になります。
これからじっくりスクリプトを研究してみようと思います。
|
|