曲線移動

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Rvyn

曲線移動

#1

投稿記事 by Rvyn » 13年前

こんにちは。

2点a(x1,y1),b(x2,y2)それぞれを表示位置(中心)とする箱が2つあり、
それら2つの箱それぞれを、外側を弧を描くように移動させて、
Tフレームかけて、位置を交換させたいのですが、
どう組めばよいか分からずにいます。

使用するかは分かりませんが、
座標以外にも箱の幅wと高さhは分かっています。

どなたかご教授頂けませんでしょうか。
よろしくお願い致します。

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前
住所: 東京
連絡を取る:

Re: 曲線移動

#2

投稿記事 by h2so5 » 13年前

「外側を弧を描くように移動」の意味がよくわかりません。
詳しく説明してください。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 曲線移動

#3

投稿記事 by beatle » 13年前

添付画像の感じでしょうか?
青い四角形と赤い四角形を矢印の向きに動かして、位置を交換する?
添付ファイル
曲線移動.png
曲線移動.png (5.77 KiB) 閲覧数: 8930 回

rvyn

Re: 曲線移動

#4

投稿記事 by rvyn » 13年前

ご回答ありがとうございます。

beatleさん、わざわざ画像まで用意してくださりありがとうございます。

おおよそ画像の通りですが、赤い□は下向きに移動させたいです。

また、綺麗な円の軌道ではなく
例えるならレモンの形のような軌道で移動させたいです。

引き続きよろしくお願い致します。

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前
住所: 東京
連絡を取る:

Re: 曲線移動

#5

投稿記事 by h2so5 » 13年前

綺麗な円軌道での移動については、円の中心から箱の各点に伸びるベクトルを回転行列を用いて回転していけば可能です。

レモンの形というのが良くわからないのですが、楕円とは違うのでしょうか?

Rvyn

Re: 曲線移動

#6

投稿記事 by Rvyn » 13年前

h2so5さん、ご回答ありがとうございます。

表現が悪くて申し訳ございません。
楕円軌道で結構です。

具体的なソースコード例でご教授頂けるとありがたいです。
よろしくお願い致します。

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 曲線移動

#7

投稿記事 by a5ua » 13年前

書いてみました。こんな感じでしょうか。
以下のコードでは、赤と青の円が30フレームかけて入れ替わります。
パラメータ a, b, T を変えると楕円の大きさや速度が変化します。

コード:

// VCでM_PIを使うため
#define _USE_MATH_DEFINES
#include <cmath>

// 周期Tフレームで1周する楕円軌道
namespace motion001
{
	// 中心の座標
	const int x0 = 200;
	const int y0 = 100;

	// 楕円の形のパラメータ
	// 横幅 = 2a、縦幅 = 2b
	const int a = 100;
	const int b = 30;
	
	// 周期 T
	// (T/2)フレームで半周、つまり入れ替わる
	const int T = 60;

	// tフレーム目の位置を計算する
	int GetX(int t)
	{
		return int(x0 + a * cos(2 * M_PI / T * t));
	}
	int GetY(int t)
	{
		return int(y0 + b * sin(2 * M_PI / T * t));
	}
}

#include "DxLib.h"

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPTSTR, int )
{
	ChangeWindowMode(TRUE);

	if (DxLib_Init() != 0) {
		return -1;
	}

	SetDrawScreen(DX_SCREEN_BACK);

	int count = 0;

	while (ProcessMessage() == 0) {
		ClearDrawScreen();
		if (CheckHitKey(KEY_INPUT_ESCAPE)) {
			break;
		}

		using motion001::T;
		using motion001::GetX;
		using motion001::GetY;

		// 一方の座標
		int x1 = GetX(count);
		int y1 = GetY(count);

		// もう一方の座標は半周期ぶんだけズレた位置にいる
		int x2 = GetX(count + T / 2);
		int y2 = GetY(count + T / 2);

		++count;

		if (count >= T / 2) {
			count = T / 2;
		}
		if (CheckHitKey(KEY_INPUT_SPACE)) {
			count = 0;
		}

		DrawFormatString(0, 0, GetColor(255, 255, 255), _T("%d"), count);
		DrawCircle(x1, y1, 10, GetColor(255, 0, 0));
		DrawCircle(x2, y2, 10, GetColor(0, 0, 255));

		ScreenFlip();
	}

	DxLib_End();

	return 0;
}

Rvyn

Re: 曲線移動

#8

投稿記事 by Rvyn » 13年前

a5uaさん

丁寧なサンプルをありがとうございます。

サンプル中の中心の座標というのは、
2点を直線で結んだ時の中心点、
つまり( (x2-x1)/2, (y2-y1)/2 )という解釈でよろしいでしょうか?

また、このサンプルは、2点が水平な直線上に無い場合や、
座標に負の値が入っている場合でも使用出来ますでしょうか?


さて、このサンプルは是非とも参考にさせて頂きたく思うのですが、
上級編?として、ベジェ曲線を使うと、等速での移動ではなく、
最初は勢いよく進み、徐々に緩やかに到達する
というようなことが出来るというのは事実でしょうか。
もしそうならば、こちらもご教授頂けないでしょうか。

こちらの場合もTフレームかけて移動させたいです。
よろしくお願い致します。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 曲線移動

#9

投稿記事 by beatle » 13年前

Rvyn さんが書きました:また、このサンプルは、2点が水平な直線上に無い場合や、
座標に負の値が入っている場合でも使用出来ますでしょうか?
2点が水平ではない場合は、GetX, GetYによって得られた座標を行列変換で好みの角度に調節すればいいのではと思います。
行列を用いた座標変換の原理、やり方はご存知ですか?

a5uaさんのプログラムを良く読んでいないので勘ですが、負の値も扱えると思います。
Rvyn さんが書きました:さて、このサンプルは是非とも参考にさせて頂きたく思うのですが、
上級編?として、ベジェ曲線を使うと、等速での移動ではなく、
最初は勢いよく進み、徐々に緩やかに到達する
というようなことが出来るというのは事実でしょうか。
もしそうならば、こちらもご教授頂けないでしょうか。

こちらの場合もTフレームかけて移動させたいです。
よろしくお願い致します。
GetX, GetYには時刻を渡します。渡す時刻を単なるループカウンタではなく、ループカウンタをベジェ曲線の関数に渡し、その戻り値にすればいいでしょう。

コード:

double Bezier(int count)
{
    // countに対応する値を計算する
    return 計算した値;
}
としておいて

コード:

double t = Bezier(count);
int x1 = GetX(t);
int y1 = GetY(t);
のようにすれば良いと思います。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 曲線移動

#10

投稿記事 by beatle » 13年前

僕も実装してみました

コード:

#include <cmath>
#include <DxLib.h>

int DrawBox(const VECTOR& center, float width, float height, int color, bool fill, const MATRIX& proj);
int Round(float x);
VECTOR Round(const VECTOR& x);
float Bezier(float angle);

// 位置計算インターフェース
class Move
{
public:
    Move() {}
    virtual ~Move() {}

    virtual VECTOR CalcPosition(float angle) const = 0;
};

// 楕円位置計算クラス
class EllipticMove : public Move
{
public:
    EllipticMove(float b_a)
        : b_a_(b_a)
    {}

    VECTOR CalcPosition(float angle) const
    {
        return VGet(
                cos(angle),
                b_a_ * sin(angle),
                0
                );
    }

private:
    const float b_a_;
};

// 位置変換フィルタークラス
class FilteredMove : public Move
{
public:
    FilteredMove(Move& move, const MATRIX& mat)
        : move_(move), mat_(mat)
    {}

    VECTOR CalcPosition(float angle) const
    {
        return VTransform(move_.CalcPosition(angle), mat_);
    }

private:
    Move& move_;
    const MATRIX mat_;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // ウィンドウの大きさ
    const float window_width = 800, window_height = 600;

    // DXライブラリ初期化
    if (ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK ||
            SetGraphMode(window_width, window_height, 32) != DX_CHANGESCREEN_OK ||
            DxLib_Init() != 0 ||
            SetDrawScreen(DX_SCREEN_BACK) != 0)
    {
        return -1;
    }

    // 投影行列
    const MATRIX projection_mat = MMult(
            MGetScale(VGet(window_width, -window_width, 0)),
            MGetTranslate(VGet(window_width / 2, window_height / 2, 0)));

    // 基本楕円(a = 1, b = 0.5)
    EllipticMove em(0.5);

    // 基本楕円を縦横0.3倍にした楕円1
    FilteredMove fm1(em, MGetScale(VGet(0.3, 0.3, 0)));

    // 楕円1を180度回転させた楕円2
    FilteredMove fm2(fm1, MGetRotZ(PHI_F));

    // 周期と論理的な現在時刻
    const int T = 2000; // msec
    int t = 0;

    // 実際の時刻
    const int start_time = GetNowCount();
    int prev_time, current_time = start_time;

    while (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0)
    {
        // 実際の時刻を更新
        prev_time = current_time;
        current_time = GetNowCount();

        // 論理時刻を更新
        t += current_time - prev_time;
        if (t >= T)
        {
            t -= T;
        }

        // 時刻tを変換する
        const float angle = Bezier(2 * PHI_F * t / T);
        VECTOR pos1 = fm1.CalcPosition(angle);
        VECTOR pos2 = fm2.CalcPosition(angle);

        DrawBox(pos1, 0.05, 0.05, GetColor(255, 0, 0), false, projection_mat);
        DrawBox(pos2, 0.05, 0.05, GetColor(0, 0, 255), false, projection_mat);
    }

    DxLib_End();
    return 0;
}

// 指定された中心座標と幅、高さを持つBoxを描画
int DrawBox(const VECTOR& center, float width, float height, int color, bool fill, const MATRIX& proj)
{
    VECTOR pos1 = VGet(center.x - width / 2, center.y + height / 2, 0); // 左上
    VECTOR pos2 = VGet(center.x + width / 2, center.y - height / 2, 0); // 右下

    pos1 = Round(VTransform(pos1, proj));
    pos2 = Round(VTransform(pos2, proj));

    return DrawBox(pos1.x, pos1.y, pos2.x + 1, pos2.y + 1, color, fill ? TRUE : FALSE);
}

// 四捨五入
int Round(float x)
{
    return static_cast<int>(x + 0.5f);
}

VECTOR Round(const VECTOR& x)
{
    return VGet(Round(x.x), Round(x.y), Round(x.z));
}

// 僕はベジェ曲線の式を知らないので、三角関数で適当に実装
float Bezier(float angle)
{
    if (angle <= PHI_F)
    {
        return PHI_F / 2 * (-cos(angle) + 1);
    }
    else
    {
        return PHI_F / 2 * (-cos(angle - PHI_F) + 3);
    }
}
a5uaさんのプログラムからの改良点
  • 座標を論理的な座標として扱う。この論理座標系ではウィンドウの中央が原点、右がX軸の+方向、上がY軸の+方向、ウィンドウの横幅が1.0である。縦幅はアスペクト比 * 横幅となる。
    つまり解像度が800 x 600なら、ウィンドウの左上=(-0.5, 0.375)、ウィンドウの右下=(0.5, -0.375)である。
    数学的な座標系で考えることにより分かりやすくなる。
  • 「図形の動き」をMoveインターフェースとして抽象化した。Move::CalcPositionは、角度angleに応じた図形の位置を計算する。周期は2π。
  • 現在時刻に比例する角度2πt/Tを元に、変換された角度を求める関数Bezierをかませることで、自由な図形移動ができる。
  • 座標変換に行列を用いることで、数学的になり、分かりやすい。

Rvyn

Re: 曲線移動

#11

投稿記事 by Rvyn » 13年前

beatleさん、丁寧なサンプルをありがとうございます。

>2点が水平ではない場合は、GetX, GetYによって得られた座標を行列変換で好みの角度に調節すればいいのではと思います。
>行列を用いた座標変換の原理、やり方はご存知ですか?

実装出来るほどの知識がないです、、。
ご教授願えますか?
(頂いたサンプルでは実現して頂けているのでしょうか...?)

また、頂いたサンプル中では、
論理座標系を使うようにご提案してくださってますが、
やはり、画面の中心を(0,0)としたワールド座標系で考えたいのですが、
そのように変更して頂くことは難しいでしょうか?
そして、サンプル中で、□の初期座標はどこでどのように反映されているのでしょうか?
読解力がなくて申し訳ありません。

よろしくお願い致します。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 曲線移動

#12

投稿記事 by beatle » 13年前

僕のプログラム中で用いている「論理座標系」は、画面の中心を原点、すなわち(0, 0)とした座標系です。X軸は画面右方向、Y軸は画面上方向となっています。
これはRvynさんがいう「ワールド座標系」とは異なるものでしょうか?

四角形の初期座標は、実は明示的には書かれておりません。
EllipticMoveは、原点を中心として、横幅が2.0に固定された楕円の軌道を表します。横幅が2.0なので、このままだと画面からはみ出します。
2.0というのは、a5uaさんのプログラムでパラメータaを1に固定した場合の楕円の横幅です。
縦幅はEllipticMoveのコンストラクタに渡した値で決まります。この値はa5uaさんのプログラムでいうところのパラメータbに相当します。

プログラム中では

コード:

EllipticMove em(0.5);
として、縦幅が横幅に比べて半分の楕円emを生成しています。

楕円emを、FilteredMoveと拡大行列(MGetScaleで生成している)の組み合わせによって、縦横0.3倍の大きさになるようにスケーリングした楕円をfm1としています。
さらに、FilteredMoveと回転行列(MGetRotZで生成している)の組み合わせによって、fm1をZ軸周り、つまりXY平面上で180度(PHI_Fラジアン)回転させた楕円をfm2としています。

fm1とfm2は丁度180度だけズレており、楕円の軌道は一致しますが、CalcPositionに同じ角度を渡した場合の戻り値は180度ずれていますので、真反対の座標になります。

そして、基本楕円emのCalcPositionで得られる初期座標は

コード:

    VECTOR CalcPosition(float angle) const
    {
        return VGet(
                cos(angle),
                b_a_ * sin(angle),
                0
                );
    }
この関数で計算されます。
初期角度として 0ラジアン を引数に渡せば
VGet(cos(0), b_a_ * sin(0), 0) → VGet(1, 0, 0)
となり、em.CalcPosition(0)は(1, 0, 0)となります。(僕のプログラムではZ座標は使っていませんから、常に0となります)

さて、この座標がfm1.CalcPositionによって変換された座標が赤い四角形の初期座標となります。
具体的な処理を説明します。
fm1.CalcPositionは次のような関数です。

コード:

    VECTOR CalcPosition(float angle) const
    {
        return VTransform(move_.CalcPosition(angle), mat_);
    }
この関数は、FilteredMoveのコンストラクタに渡した行列を用いて座標変換を行うだけです。
fm1.move_は先ほどのemへの参照ですから、move_.CalcPosition(0)は(1, 0, 0)となりますね。
そして、mat_(ここではMGetScale(VGet(0.3, 0.3, 0))によって生成した拡大行列)を用いて、(1, 0, 0)を縦横0.3倍します。
すると(0.3, 0, 0)となり、これが赤い四角形の初期座標となります。

青い四角形fm2はfm1を180度回転しただけのものですから、初期座標は(-0.3, 0, 0)となります。
Rvyn さんが書きました: >2点が水平ではない場合は、GetX, GetYによって得られた座標を行列変換で好みの角度に調節すればいいのではと思います。
>行列を用いた座標変換の原理、やり方はご存知ですか?

実装出来るほどの知識がないです、、。
ご教授願えますか?
(頂いたサンプルでは実現して頂けているのでしょうか...?)
サンプル中では実際に傾けてはいませんが、簡単に対応できます。
ただ単に、Z軸周りに少し回転させる回転行列とFilteredMoveを組み合わせるだけです。
これは、基本楕円を生成している行

コード:

    EllipticMove em(0.5);
を、次の2行のように改造すればできます。

コード:

    EllipticMove em0(0.5);
    FilteredMove em(em0, MGetRotZ(30 * PHI_F / 180));
これで、fm1とfm2の楕円軌道は、水平から30度傾きます。

Rvyn

Re: 曲線移動

#13

投稿記事 by Rvyn » 13年前

beatleさん

解説頂きありがとうございます。
私がいいたいワールド座標系とは、□の位置が画面の座標と一致している状態です。
例えば、ウィンドウの大きさが幅800高さ600で、□の幅と高さが20の正方形の時は、
□の表示位置が( -390, 290 )の時、左上隅にぴったりと表示されるという具合です。
そのほうが、私には直感的に理解できるのでありがたいです。

結局、私がご教授願いたいことは、
下記の画像のように任意の位置(□の位置は自由に設定できる)に4色の□があるときに、
画像
指定の2色の□の位置を、任意の楕円軌道(その都度設定可能にしたいですがおおよそ、beatleさんのサンプルのような軌道で問題ないです)
で入れ替えることができるプログラムです。
2つの□の移動経路は、これまで頂いたサンプルの通り逆側でお願いします。
また、一度移動した□も何度でも指定出来るようにしたいので、異動後の位置を保存する必要があると思います。

わがままなのは承知していますが、
今一度お力添えを願えませんでしょうか?
よろしくお願い致します。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 曲線移動

#14

投稿記事 by beatle » 13年前

Rvynさんが仰る「ワールド座標系」というのは、ピクセルの座標系なのですね。了解しました。
まあ確かに、ピクセルの座標系を使ったほうが、取っ掛かりやすいとは思いますので、僕のサンプルを改造していただいて結構です。

ピクセルの座標系を用いるデメリットとして、ウィンドウの大きさを変えるとプログラム各所の値を変える必要があるということですね。
ウィンドウの大きさを#defineなりconst変数に入れておいて、その変数を用いるようにすれば解決しますが。

サンプルは理解してこそのサンプルですから、是非理解していただいて、改造してください。
僕のサンプルにこだわらずに、a5uaさんのサンプルでもいいですし。

Rvyn

Re: 曲線移動

#15

投稿記事 by Rvyn » 13年前

beatleさん、ご助言ありがとうございます。

ですが、何卒どうか、最終的に私が聞きたいことを実現したサンプルをご教授頂けませんでしょうか?
というのも、a5uaさんやbeatleさんから頂いたサンプルを多少変更するくらいではどうにも実現出来なさそうに思うのです。

この最終的な質問内容を実現出来ているサンプルを頂けたならば、
今度は、そのサンプルを隅から隅まで理解して、
自己流に改造したいと思います。
甘えるのはこれを最後にします。

よろしくお願い致します。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: 曲線移動

#16

投稿記事 by softya(ソフト屋) » 13年前

ちゃんと理解するということであれば、まず今のものを理解されてからの方が良いと思います。
複雑なものは理解を妨げることはあっても理解しやすくなることは有りません。
それともお急ぎの理由がお有りですか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 曲線移動

#17

投稿記事 by ISLe » 13年前

全部同時に進めようとするから難しいのですよ。
弧を描くように動かす方法が分からないだけなら、直線上を動かして入れ替えるプログラムなら作れるでしょう。
そこから軌道を膨らませることは簡単ですからまずはできるところまでやって見せてもらえませんか?


要求仕様を十分満たすものはもはやサンプルではありませんね。
質問にかこつけた依頼ですよ。
それでもあくまでサンプルを要求しているだけとゴネるのでしょうか。

Rvyn

Re: 曲線移動

#18

投稿記事 by Rvyn » 13年前

softyaさん、ISLeさん

ご忠告ありがとうございます。
この掲示板の謳い文句が "疑問・宿題解決します" ということだったのでついつい甘えすぎてしまいました。
申し訳ございません。

> 弧を描くように動かす方法が分からないだけなら、直線上を動かして入れ替えるプログラムなら作れるでしょう。
> まずはできるところまでやって見せてもらえませんか?

お二方がおっしゃる通り、自分で出来るところまでは組もうと努力し、
なんとか、"直線上を動かして入れ替えるプログラム"までは作ることが出来ました。
ここから、"軌道を膨らませる処理"のところの実装例を示しては頂けないでしょうか。

ご質問頂いた、お急ぎの理由はありまして、
宿題の提出期限がもう間近なんです。

ご教授頂いた実装例は舐めるように読み返して、
理解して、自分なりに組み替えてから、提出するとお約束しますので、
どうか、お力添えをお願い致します。

何卒よろしくお願い致します。

コード:

 
#include "DxLib.h"

class Box
{
public:
	Box( float x, float y, int color ) : 
	  mX( x ),
	  mY( y ),
	  mTX( -1 ),
	  mTY( -1 ),
	  mSpeedX( 0 ),
	  mSpeedY( 0 ),
	  mColor( color ),
	  mTime( 0 )
	  {
		TIME = 100;
	  }

	float GetX( void ){ return mX; }
	float GetY( void ){ return mY; }

	void DrawBox( void ) const
	{
		switch( mColor )
		{
		case 0:
			DrawCircle( mX, mY, 10, GetColor(255, 0, 0));
			break;
		case 1:
			DrawCircle( mX, mY, 10, GetColor(0, 0, 255));
			break;
		case 2:
			DrawCircle( mX, mY, 10, GetColor(243, 152, 0));
			break;
		case 3:
			DrawCircle( mX, mY, 10, GetColor(185, 196, 47));
			break;
		}
	}

	void SetTarget( int tX, int tY )
	{
		mTX = tX;
		mTY = tY;
		mSpeedX = ( tX - mX ) / TIME;
		mSpeedY = ( tY - mY ) / TIME;
	}

	void Move( void )
	{
		mTime++;
		if( mTime < TIME )
		{
			mX += mSpeedX;
			mY += mSpeedY;
		}
		else
		{
			mTime = TIME;
		}
	}

private:
	int TIME;

	float mX;
	float mY;
	float mTX;
	float mTY;
	float mSpeedX;
	float mSpeedY;

	int mColor;
	int mTime;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPTSTR, int )
{
    // ウィンドウの大きさ
    const float window_width = 800, window_height = 600;
 
    // DXライブラリ初期化
    if (ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK ||
            SetGraphMode(window_width, window_height, 32) != DX_CHANGESCREEN_OK ||
            DxLib_Init() != 0 ||
            SetDrawScreen(DX_SCREEN_BACK) != 0)
    {
        return -1;
    }
 
    if (DxLib_Init() != 0) {
        return -1;
    }
 
    SetDrawScreen(DX_SCREEN_BACK);
 
	Box box[ 4 ] = {
		Box( 50, 250, 0 ),
		Box( 700, 100, 1 ),
		Box( 350, 580, 2 ),
		Box( 600, 400, 3 )
	};

    int count = 0;

	box[ 0 ].SetTarget( box[ 3 ].GetX(), box[ 3 ].GetY() );
	box[ 1 ].SetTarget( box[ 2 ].GetX(), box[ 2 ].GetY() );
	box[ 2 ].SetTarget( box[ 1 ].GetX(), box[ 1 ].GetY() );
	box[ 3 ].SetTarget( box[ 0 ].GetX(), box[ 0 ].GetY() );
 
    while (ProcessMessage() == 0) {
        ClearDrawScreen();
        if (CheckHitKey(KEY_INPUT_ESCAPE)) {
            break;
        }
		
		for( int i=0; i<4; i++ )
		{
			box[ i ].Move();
			box[ i ].DrawBox();
		}
 
        ScreenFlip();
    }
 
    DxLib_End();
 
    return 0;
}

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 曲線移動

#19

投稿記事 by ISLe » 13年前

複雑になるので単位時間あたりの移動距離を加算する方法から比率で座標を求める方法に変えました。

このコードでは単純なサインカーブを描きます。
楕円や二次曲線などにアレンジしたいときは、sin関数を使っているあたりの式をいじってください。

#整形ツールを使ったのでインデントが変化してます。

コード:

#include "DxLib.h"
#include <math.h>

class Box
{
public:
	Box( float x, float y, int color ) :
		mX( x ),
		mY( y ),
		mTX( -1 ),
		mTY( -1 ),
		mColor( color ),
		mTime( 0 )
	{
		TIME = 200;
	}

	float GetX( void ) { return mX; }
	float GetY( void ) { return mY; }

	void DrawBox( void ) const
	{
		DrawLine(mSX, mSY, mTX, mTY, GetColor(128, 128, 128));
		switch( mColor )
		{
		case 0:
			DrawCircle( mX, mY, 10, GetColor(255, 0, 0));
			break;
		case 1:
			DrawCircle( mX, mY, 10, GetColor(0, 0, 255));
			break;
		case 2:
			DrawCircle( mX, mY, 10, GetColor(243, 152, 0));
			break;
		case 3:
			DrawCircle( mX, mY, 10, GetColor(185, 196, 47));
			break;
		}
	}

	void SetTarget( int tX, int tY )
	{
		mSX = mX;
		mSY = mY;
		mTX = tX;
		mTY = tY;
#if 0
		float dist = sqrt(( tX - mX ) * ( tX - mX ) + ( tY - mY ) * ( tY - mY ));
		mVX = ( tY - mY ) / dist * dist / 4; // 距離の4分の1膨らませる
		mVY = ( tX - mX ) / dist * dist / 4; // 距離の4分の1膨らませる
#else
		mVX = ( tY - mY ) / 4; // 距離の4分の1膨らませる
		mVY = ( tX - mX ) / 4; // 距離の4分の1膨らませる
#endif
	}

	void Move( void )
	{
		mTime++;
		if( mTime >= TIME ) mTime = TIME;
		mX = (mTX - mSX) * mTime / TIME + mSX;
		mY = (mTY - mSY) * mTime / TIME + mSY;
		mX += mVX *  sin(PHI * mTime / TIME);
		mY += mVY * -sin(PHI * mTime / TIME);
	}

private:
	int TIME;

	float mX;
	float mY;
	float mSX; // 移動元X座標
	float mSY; // 移動元Y座標
	float mTX; // 移動先X座標
	float mTY; // 移動先Y座標
	float mVX; // 垂直X成分
	float mVY; // 垂直Y成分

	int mColor;
	int mTime;
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPTSTR, int )
{
	// ウィンドウの大きさ
	const float window_width = 800, window_height = 600;

	// DXライブラリ初期化
	if (ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK ||
			SetGraphMode(window_width, window_height, 32) != DX_CHANGESCREEN_OK ||
			DxLib_Init() != 0 ||
			SetDrawScreen(DX_SCREEN_BACK) != 0)
	{
		return -1;
	}

	if (DxLib_Init() != 0) {
		return -1;
	}

	SetDrawScreen(DX_SCREEN_BACK);

	Box box[ 4 ] = {
		Box( 50, 250, 0 ),
		Box( 700, 100, 1 ),
		Box( 350, 580, 2 ),
		Box( 600, 400, 3 )
	};

	int count = 0;

	box[ 0 ].SetTarget( box[ 3 ].GetX(), box[ 3 ].GetY() );
	box[ 1 ].SetTarget( box[ 2 ].GetX(), box[ 2 ].GetY() );
	box[ 2 ].SetTarget( box[ 1 ].GetX(), box[ 1 ].GetY() );
	box[ 3 ].SetTarget( box[ 0 ].GetX(), box[ 0 ].GetY() );

	while (ProcessMessage() == 0) {
		ClearDrawScreen();
		if (CheckHitKey(KEY_INPUT_ESCAPE)) {
			break;
		}

		for( int i=0; i<4; i++ )
		{
			box[ i ].Move();
			box[ i ].DrawBox();
		}

		ScreenFlip();
	}

	DxLib_End();

	return 0;
}

Rvyn

Re: 曲線移動

#20

投稿記事 by Rvyn » 13年前

ISLeさん

ご教授頂きましてありがとうございます。

慎重に読んで大方理解したのですが、

何故SetTarget関数の中で、
mVX = ( tY - mY ) / 4; // 距離の4分の1膨らませる
mVY = ( tX - mX ) / 4; // 距離の4分の1膨らませる
mVXにYの値を用いて導き出した数値を、
mVYにXの値を用いて導き出した数値を入れて上手くゆくのかがわかりません。

それと、Move関数の中での、
mX += mVX * sin(PHI * mTime / TIME);
mY += mVY * -sin(PHI * mTime / TIME);
の意味が、なんとなくは分かるのですが、きちんとは説明できません。

ご説明して頂くことは出来ませんでしょうか…。

また、楕円軌道に変更するにはどの程度の変更が必要なのでしょうか?
皆目見当も付かず、しかし宿題提出の期日は迫り大変焦っています。
引き続きご教授して頂けることは難しいでしょうか?

よろしくお願い致します。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 曲線移動

#21

投稿記事 by ISLe » 13年前

理解できてないですよね。
流し読みしただけですよね。

大方理解したとおっしゃってますが、理解できなかったとおっしゃるところを除いてみると、理解できたのはMoveメンバ関数のmX,mYに代入しているところだけですよね。
もともとRvynさんが書いたコードがあったところを書き換えた部分です。
弧を描くために追加した部分はまったく理解できていないということですよ。
そのことにご自身は気付いていますか?


なぜXとYの増分を入れ替えるとうまくいくのかはわたしにも分かりません。
mVX,mVYはコメントに書いてあるように進行方向に対して垂直方向を指す増分です。
垂直な増分はXとYの増分を入れ替えてどちらかに-1を掛けると求まるということしか知りません。
-1をどちらに掛けるかで進行方向に対してどちらに膨らむかが変わります。

ところで、画面の座標系と三角関数の対応とごっちゃになってました。
結果は変わらないのですが意味合いとして正しくないので訂正します。
mVX = ( tY - mY ) / 4; // 距離の4分の1膨らませる
mVY = ( tX - mX ) / 4; // 距離の4分の1膨らませる

mVX = ( tY - mY ) / 4; // 距離の4分の1膨らませる
mVY = ( mX - tX ) / 4; // 距離の4分の1膨らませる
と変更(-1を掛けるかわりに入れ替え)、
そして
mX += mVX * sin(PHI * mTime / TIME);
mY += mVY * -sin(PHI * mTime / TIME);
はマイナス符号を取って
mX += mVX * sin(PHI * mTime / TIME);
mY += mVY * sin(PHI * mTime / TIME);
とすることで全体で意味が通ります。
Rvyn さんが書きました:mX += mVX * sin(PHI * mTime / TIME);
mY += mVY * -sin(PHI * mTime / TIME);
の意味が、なんとなくは分かるのですが、きちんとは説明できません。
ここまでの説明で分かると思いますが、進行方向に対して垂直方向に膨らましているところです。
sin関数は膨らます幅を求めるために使っていますがsin関数自体が何を意味するかは分かりますよね。
それが分からないのであればこのコードをRvynさんが提出するのは無理がありますよ。
Rvyn さんが書きました:また、楕円軌道に変更するにはどの程度の変更が必要なのでしょうか?
皆目見当も付かず、しかし宿題提出の期日は迫り大変焦っています。
当初Rvynさんは「綺麗な円の軌道ではなく例えるならレモンの形のような軌道で移動させたいです」とおっしゃってます。
もともとレモン型をお望みかと思ってサインカーブにしたんですが。
それよりもsin関数の意味するところがなんとなくしか分からないのに楕円の式が理解できるのでしょうか。

コード:

		/*  サインカーブ  */
		//mX += mVX * sin(PHI * mTime / TIME);
		//mY += mVY * sin(PHI * mTime / TIME);
		/* 楕円 */
		mX += mVX * sin(acos(mTime * 2.0f / TIME - 1.0f));
		mY += mVY * sin(acos(mTime * 2.0f / TIME - 1.0f));

Rvyn

Re: 曲線移動

#22

投稿記事 by Rvyn » 13年前

ISLeさん

ご解説頂きありがとうございました。

お陰様で大分理解が深まりました。
楕円軌道の計算部分は自分で努力して理解しようと思います。

皆様のお陰で、なんとか課題をこなせそうです。
質問して良かったです!誠にありがとうございました。

Rvyn

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 曲線移動

#23

投稿記事 by ISLe » 13年前

角度を基準にしないと気持ちの良い動きではないですけど良いのでしょうかね。
進行方向と垂直方向を分けたのでa5uaさんのサンプルから座標を求める式をサクッと当てはめるだけですけど。


ところで楕円やベジェというと大学入試レベル以上ですが回転が分からないとか知識がまるで釣り合ってないと思います。
実はわたしのサンプルプログラムも中学で習う知識でもって回転させているのが主旨ですし。
こんな課題を出すのはいったいどんな学校なんでしょうね。

Rvyn

Re: 曲線移動

#24

投稿記事 by Rvyn » 13年前

ISLeさん

ご心配頂きありがとうございます。
a5uaさんのサンプルを参考にすると、
角度を基準にした気持ちの良い楕円軌道の動きをさせることが出来るのですか?

もしまだご教授して頂けるならば、詳しくお話を伺いたいです。

また、私の在籍している学校の話ですが、
大学で、プログラミングを専攻しています。
知識が釣り合っていないと感じられるのは単に私の知識が乏しいためだと思います。

ベジェ曲線を使ってというのは、
プラス課題でして、とりあえずは楕円軌道で動かせば良いのです。
やはり、ベジェ曲線というのは複雑で難解な物なのでしょうか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: 曲線移動

#25

投稿記事 by softya(ソフト屋) » 13年前

今更なのですが大学の課題でDXライブラリはOKなのですか?
Win32APIで組まなくて良いのかと気になりました。

あと現状でISLeさんのコードが課題的にOKならa5uaさんのコードをご自分で理解されてはどうでしょう。
それほど難易度が高いコードではありません。ISLeさんのコードの本質を理解しているならちょっとした応用です。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

閉鎖

“C言語何でも質問掲示板” へ戻る