- ベストアンサー
緩やかなカーブを表現する
今、座標(pos)と移動量(mov)を持った物体OBJがあります。 座標はx,y、移動量はspeed,theta(速度,角度)を持っています。 ここで、目的地GOAL(x,yを持つ)があり、OBJはそこに向かって移動します。 OBJがGOAL付近に到着すると、OBJはランダムな座標に設定されます。 ここで、私は飛行機や車のような緩やかなカーブを表現したいと思いました。そこで、 OBJの速度は一定で、目的地についたときに、OBJとその目的地までの角度を 変数OLDthetaに保存しておき、OLDthetaから新たな目的地までの角度(OBJ.mov.theta)を引き、 それが0でなければ、OLDthetaに0.05を加減させます。そうすると、 結果的にOLD.thetaはOBJ.mov.thetaと等しくなり、緩やかなカーブも表現できると思いました。 しかし、プログラムを実行すると、緩やかなカーブはするのですが、 遠回りなカーブをしたり、放って置くといきなり不規則な移動を起こして まったく関係のないところへ行ってしまいます。 どこらへんの考えが誤っているでしょうか?どなたか回答をお願いします。 ちなみに、移動量は OBJ.pos.x += cos(OLDtheta)*OBJ.mov.speed OBJ.pos.y += sin(OLDtheta)*OBJ.mov.speed で設定しています。 一応、関係のありそうな箇所のコードを書いておきます。 (角度を求めるコード) OBJ.mov.theta = atan2 (GOAL.y - OBJ.pos.y , GOAL.x - OBJ.pos.x); if(OLDtheta - OBJ.mov.theta != 0){ ______if(OLDtheta>OBJ.mov.theta){ ____________OLDtheta -= 0.05; ______}else{ ____________OLDtheta += 0.05; ______} } (構造体) struct VEC{ ______float x,y; }; struct VEC2{ ______float speed; //スピード ______float theta; //角度 }; struct CharaData{ ______VEC pos; //座標 ______VEC2 mov; //移動量 };
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
>ゴールまでの距離の二乗が速度の二乗未満ならゴール >になる理由がいまいちわかりません。 >なぜそうなるのでしょうか? 2点間の距離は、直角三角形の 底辺の2乗+高さの2乗=斜辺の2乗 の公式を使って求めます。 ですので、直角三角形の斜辺の長さは √(底辺の2乗+高さの2乗) です。 底辺3、高さ4の直角三角形なら、斜辺の長さは √(3×3+4×4)=√25=5 です。 この時「斜辺の長さ=2点間の距離」です。 「距離<速度」が成り立つ場合「現在地から1ステップで移動する長さを半径とした円内にゴールがある」と言う事です。 つまり、現時点か、次の1ステップ移動後の時点で「ゴールに最も近付く」と言う事です。 本来、ゴール判定は sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) で距離を求め「距離<1ステップで移動する長さ(つまり速度)」で判定しなければなりません。 ですが「平方根を求めるより、単純な掛け算の方が、処理が早い」ので「『距離<速度』が成り立つ場合『距離の2乗<速度の2乗』も成り立つ、と言うのを利用し、距離を求めるのにsqrt関数を使うのを避け、代わりに速度を2乗し「距離の2乗<速度の2乗」を判定する事で、ゴール判定しています。 底辺3、高さ4の直角三角形の斜辺の長さが5以上か判定するなら if (sqrt((3*3)+(4*4)) >= 5) と書かないで if ((3*3)+(4*4) >= (5*5)) と書いた方が、処理が早いですよね。 しかも (GOAL.x - OBJ.pos.x)*(GOAL.x - OBJ.pos.x) も (GOAL.y - OBJ.pos.y)*(GOAL.y - OBJ.pos.y) も、2点の位置関係に関係なく「2乗すれば必ずプラス」になるので、そのまま足し算すれば「距離の2乗」が求まります。 なお、現在地座標も、ゴール座標も「実数」で扱っているため「演算誤差で、ぴったり重なる事は絶対にない」と思って下さい。なので「1ステップで動く距離を半径とした円内にゴールがあるか?」と言う判定を行うしかありません。 また、ピッタリ重なるコースを通っていても「速度が2以上になると、ゴールを飛び越える可能性がある」ので、やはり「円内にゴールがあるか?」と言う判定をせざるを得ません。 「進行方向と現在速度から、ゴールの真上を通過し、かつ、ゴールを飛び越えたかどうかを判定する方法」もありますが、計算量が膨大になって遅くなるくせに、判定結果の精度が「ちょっと良くなるだけ」なので、余り意味がありません(偶然にゴールのすぐ真横を通り抜けた時に、ゴールしてないと判断するようになるだけ)
その他の回答 (6)
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
追記。 速度が一定で旋回角度に上限があると、最小旋回半径以上には旋回できません。最小旋回半径で円を描き、その内側にあるゴールには辿り付けません。 そこで、加速と減速の処理を施し、以下のソースコードのように、最小旋回半径が小さくなるようにしてあげる必要が出ます。 void ObjMove(void) { float NEWtheta,delta,Length; NEWtheta = atan2(GOAL.y - OBJ.pos.y , GOAL.x - OBJ.pos.x); /* 目標の方向 */ if (NEWtheta < 0.0) NEWtheta += 6.28318530717958; /* 目標の方向を0~360度に補正 */ delta = OBJ.mov.theta - NEWtheta; /* 自分から見た目標の方向を求める */ if ((delta >= -0.05) && (delta <= 0.05)){ OBJ.mov.theta = NEWtheta; /* 目標が殆ど正面にある */ }else{ if (delta < 0.0) delta += 6.28318530717958; /* 自分から見た目標の方向を0~360度に補正 */ if (delta <= 3.14159265358979){ /* 目標は右にある */ OBJ.mov.theta -= 0.05; if (OBJ.mov.theta < 0.0) OBJ.mov.theta += 6.28318530717958; /* 角度がマイナスになったらプラスに補正 */ }else{ /* 目標は左にある */ OBJ.mov.theta += 0.05; if (OBJ.mov.theta > 6.28318530717958) OBJ.mov.theta -= 6.28318530717958; /* 角度が360度を超えたら360度戻す */ } } OBJ.pos.x += cos(OBJ.mov.theta)*OBJ.mov.speed; /* 位置更新 */ OBJ.pos.y += sin(OBJ.mov.theta)*OBJ.mov.speed; /* 位置更新 */ if ((delta < 0.05) || (delta > 6.23318530717958)){ /* 目標が正面にある場合 */ OBJ.mov.speed *= 1.1; /* 10%加速する */ if (OBJ.mov.speed > 10.0) { OBJ.mov.speed = 10.0; /* 但し10.0以上には加速しない */ } }else if ((delta > 1.570796326794895) && (delta < 4.712388980384685)){ /* 目標が後方にある場合 */ OBJ.mov.speed /= 1.1; /* 10%減速する */ if (OBJ.mov.speed <= 0.01) { OBJ.mov.speed = 0.01; /* 但し0.01以下には加速しない */ } } Length = (GOAL.x - OBJ.pos.x)*(GOAL.x - OBJ.pos.x)+(GOAL.y - OBJ.pos.y)*(GOAL.y - OBJ.pos.y); /* ゴールまでの距離の2乗が */ if (Length < OBJ.mov.speed * OBJ.mov.speed) { /* 速度の2乗未満ならゴール */ GOAL.x = 新しいゴール; GOAL.y = 新しいゴール; } } このルーチンも「最低速度」と「旋回角度の上限」があるので、最小旋回半径の円の内側にゴールがあると、辿り付けません。 参考として、上記ルーチンでの「軌跡」を添付しておきます(画面上の「×」が「次々に出現するゴール」です)
お礼
具体的なコードを書いてくださったおかげで、 思うような動きを実現することが出来ました! 考え方は大体わかりましたが、最後の ゴールまでの距離の二乗が速度の二乗未満ならゴール になる理由がいまいちわかりません。 なぜそうなるのでしょうか?
- redfox63
- ベストアンサー率71% (1325/1856)
始点と終点を結んだ線分が円弧の弦になります 移動開始の角度がその時点での円弧の接線の角度ですから これらから円の中心を求めて 単位時間で移動する円弧上の点群を計算して移動させる といったアルゴリズムの方がいいように思います 開始時の角度と弦の角度から時計回り、反時計回りは決定できそうです 始点から単位時間移動した後の接線の角度の変位が 0.05radとは限らないと思いますが ・・・
お礼
そうですね、考え方は大体は理解できました。 しかし、曲線を描くように移動させる方法がまったく検討がつきません。 もう少しいろいろと数学を勉強するべきなのでしょうが、 趣味の範疇でやっているのでいまいちプログラミングと両立できません。 ご回答ありがとうございました。 因みに、0.05はいろいろと値を弄ってみて、適当だと思った値です。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
まず、 OBJ.mov.theta = atan2 (GOAL.y - OBJ.pos.y , GOAL.x - OBJ.pos.x); は「移動し、OBJ.posが変更されるたびに、再計算」すること。 次に if(OLDtheta - OBJ.mov.theta != 0){ if(OLDtheta>OBJ.mov.theta){ OLDtheta -= 0.05; }else{ OLDtheta += 0.05; } } のロジックではダメ。「方向」の概念が組み込まれてないので、反対方向に舵を切り続けます。 以下のようにしましょう。 float delta; delta = OBJ.mov.theta - OLDtheta; if ((delta >= -0.05) && (delta <= 0.05)){ OLDtheta = OBJ.mov.theta; /* 方向が目標の角度に近い */ }else{ if (delta < 0.0) delta += 6.28318530717958; /* 自分から見た目標の方向を(0~360度で)求める */ if (delta >= 3.14159265358979){ /* 目標は右にある */ OLDtheta -= 0.05; if (OLDtheta < 0.0) OLDtheta += 6.28318530717958; /* 角度がマイナスになったらプラスに補正 */ }else{ /* 目標は左にある */ OLDtheta += 0.05; if (OLDtheta > 6.28318530717958) OLDtheta -= 6.28318530717958; /* 角度が360度を超えたら360度戻す */ } } なお、これでも「近くに新しい目標が出ると、目標の真横をすり抜ける」ので「目標までの距離と、近寄っているか遠ざかっているかで、加速、減速する」必要があるでしょう。 また「目標への到達判断」も「座標の一致」ではなく「距離」で判定し、真上をすり抜けしないように判定しましょう(例えば、速度10で、残り5の距離まで近付いたら、次のステップでは目標の無効側の距離5の地点に移動してしまいますから、この場合は「到着した」と判定しないといけない)
× 速度20に加速したら、その時点での座標を始点として適当な中間点のY座標をプラスして再計算。 ○ 速度20に加速したら、その時点での座標を始点として適当な中間点のXY座標をプラスして再計算。
お礼
ごめんなさい プログラミングは初心者ですが、 数学に至っては、ベクトルや三角関数は完全に独学なので その理論をプログラムで表現できないです。 しかし、考え方自体は大体理解できました ベジェ曲線のようなものでしょうか。
補足: 速度と補完値の関係 速度10の時の中間点で全体の軌跡を計算。 速度20に加速したら、その時点での座標を始点として適当な中間点のY座標をプラスして再計算。 これで、らしくは見えると思いますよ。
プログラマではなくて単なるアパレル関係のデザイナです。 で、適当に聞き流してください。 始点と中間点と終点の3点があって、その間をスムーズな曲線を描くのが課題かと思います。 >1つの関数あるいはデータ列が与えられたとき、 >それを多項式などの関数で近似することを考える。 >補完法では与えられた点を通るような近似式を作り、 >それ以外の点の値を求めることになる。 で、始点と中間点と終点の3点の間に50個なら50個の点座標を発生。 で、後は、座標間を移動するタイムを設定。 つまり、最初から予定の軌道計算は終っていたが良いと思います。 ※上述の引用は、スプライン関数(図形描画ソフトでの自由定規)のそれです。 ※ですから、本当の軌道計算のそれとは違います。 ※本当の軌道計算になりますと、もっと、別の考えになろうかと思います。
お礼
なるほど、 わかりやすい解説ありがとうございます。 しっかりと理解することが出来ました。 問題は一応、解決することができましたので 回答は締め切らせていただきます。 この質問に回答してくださった全ての方 どうもありがとうございましたm(_ _)m