トップページ > 記事閲覧
3Dモデルのマウスによる移動
名前:north 日時: 2018/06/28 18:57

お世話になっております。 現在プログラムの勉強も兼ねてwindowsFormとDXライブラリのC#版を合わせてデスクトップマスコットを作成しているのですが デスクトップ上でマウスを左クリックした際にマウスの動きに合わせてモデルを動かせるようにしたいのですがここで詰まっております。 具体的には ・考えているやり方 マウスを左クリックしたときに線分(レイ)を飛ばして、その線分がモデルに当たればマウスポインタはモデルの上にあると判断 ・わからないところ やり方としては上記の方法で大丈夫でしょうか? またその際にマウスポインタののローカル座標とモデルのワールド座標を合わせないといけない(変換しないといけない)と思うのですが これはどのようにやればよいでしょうか? 環境は windows7 プロフェッショナル visual studio commyunity2017 DXライブラリC#版 上記のようになっております。 お忙しい中お手数おかけいたしますがどうかよろしくお願いいたします。
メンテ

Page: 1 |

Re: 3Dモデルのマウスによる移動 ( No.1 )
名前:管理人 日時:2018/06/30 23:11

> やり方としては上記の方法で大丈夫でしょうか? はい、大丈夫です > またその際にマウスポインタののローカル座標とモデルのワールド座標を合わせないといけない(変換しないといけない)と思うのですが > これはどのようにやればよいでしょうか? ConvScreenPosToWorldPos で画面上の座標をワールド座標に変換することができます // カメラの設定を行っておく 〜〜〜〜 カメラ設定処理 〜〜〜〜〜〜〜 VECTOR Start3DPos, End3DPos; VECTOR ScreenPos; int MouseX, MouseY; // マウスの画面座標を x, y にセット GetMousePoint( &MouseX, &MouseY ); ScreenPos.x = ( float )MouseX; ScreenPos.y = ( float )MouseY; // z をそれぞれ 0.0f と 1.0f にして2回 ConvScreenPosToWorldPos を呼ぶ ScreenPos.z = 0.0f; Start3DPos = ConvScreenPosToWorldPos( &ScreenPos ); ScreenPos.z = 1.0f; End3DPos = ConvScreenPosToWorldPos( &ScreenPos ); ConvScreenPosToWorldPos に渡す座標の z を 0.0f にした場合は画面の指定の座標のワールド座標での 一番手前( 画面に近い側 )の座標( カメラから SetCameraNearFar の Near で指定した分だけ離れた距離の位置 )を取得することができ、 z を 1.0f にした場合は画面の指定の座標のワールド座標での画面奥側の座標( カメラから SetCameraNearFar の Far で 指定した分だけ離れた距離の位置 )を取得することができます この二つの座標を繋ぐと丁度指定の画面座標を画面手前側から奥側に貫く線分になるので、これを当たり判定に使うことで 指定の画面座標に位置しているモデルを判定することができます よろしければお試しください m(_ _)m
メンテ
Re: 3Dモデルのマウスによる移動 ( No.2 )
名前:north 日時:2018/07/08 23:30

>管理人様 サンプルも一緒にありがとうございます。 おかげさまであたり判定を取り移動させるところまでできました。 しかし、現在問題が起こっております。 再度モデルを移動させようとするとクリックするともとに位置から少し移動してスタートしてしまいます。 また、移動が平行移動ではなく、移動している最中にモデルが回転、Z方向にも移動してしまっております。 コードは下記のようになっております。 CheckMouseOnModel()関数内で処理をしております。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using DxLibDLL;//DxLibを使用 using CoreTweet; //CoreTweetを使用 using System.IO;//FileInfoを使用 namespace DesctopMascot { public partial class Form1 : Form { private int modelHandle; private int attachIndex; private float totalTime; private float playTime; private float playSpeed; private int motionNum; private const int maxNum = 2; private Tokens token; private DX.VECTOR StartPos, EndPos; private DX.VECTOR ScreenPos; private DX.VECTOR modelPos; private DX.VECTOR oldScreenPos; private int MouseX; private int MouseY; private DX.MV1_COLL_RESULT_POLY HitPoly; private bool firstFlg; public Form1() { InitializeComponent(); ClientSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);//画面サイズの設定 Text = "DesktopMascot";//ウインドウの名前を設定 AllowDrop = true;//ドラッグ&ドロップを許可 token = Tokens.Create(); //twitterアカウントの認証(ここが見せれないので消しています。) firstFlg = false; DX.SetOutApplicationLogValidFlag(DX.FALSE);//Log.txtを生成しないように設定 DX.SetUserWindow(Handle);//DxLibの親ウインドウをこのフォームに設定 DX.DxLib_Init();//DxLibの初期化処理 DX.SetDrawScreen(DX.DX_SCREEN_BACK);//描画先を裏画面に設定 System.Random rand = new System.Random(); motionNum = rand.Next(maxNum); modelHandle = DX.MV1LoadModel("Data/kaede/kaede.pmx");//3Dモデルの読み込み attachIndex = DX.MV1AttachAnim(modelHandle, motionNum, -1, DX.FALSE); totalTime = DX.MV1GetAttachAnimTotalTime(modelHandle, attachIndex); playTime = 0.0f; playSpeed = 0.2f; DX.SetCameraNearFar(0.1f, 1000.0f);//奥行0.1〜1000をカメラの描画範囲とする DX.SetCameraPositionAndTarget_UpVecY(DX.VGet(12.0f, 25.0f, -35.0f), DX.VGet(0.0f, 15.0f, 0.0f));//第1引数の位置から第2引数の位置を見る角度にカメラを設置 DX.MV1SetupCollInfo(modelHandle, -1, 8, 8, 8); } public void MainLoop() { DX.ClearDrawScreen();//裏画面を消す DX.DrawBox(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, DX.GetColor(1, 1, 1), DX.TRUE);//背景を設定(透過させる) playTime += playSpeed; //モーションの再生位置が終端まで来たら最初に戻す if (playTime >= totalTime) { playTime = 0.0f; } DX.MV1SetAttachAnimTime(modelHandle, attachIndex, playTime);//モーションの再生位置を設定 DX.MV1RefreshCollInfo(modelHandle, -1); DX.MV1DrawModel(modelHandle);//3Dモデルの描画 CheckMouseOnModel(); //ESCキーを押したら終了 if (DX.CheckHitKey(DX.KEY_INPUT_ESCAPE) != 0) { Close(); } DX.ScreenFlip();//裏画面を表画面にコピー } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { DX.DxLib_End();//DxLibの終了処理 } private void Form1_Load(object sender, EventArgs e) { FormBorderStyle = FormBorderStyle.None;//フォームの枠を非表示にする Bitmap bmp = new Bitmap(@"form.bmp"); //画像を読み込み Color transColor = bmp.GetPixel(0, 0); bmp.MakeTransparent(transColor); //画像を透明にする BackgroundImage = bmp; //背景画像を指定する //背景色を指定する BackColor = transColor; //透明を指定する TransparencyKey = transColor; } private void Form1_DragEnter(object sender, DragEventArgs e) { //ファイルがドラッグされた場合のみ受け付け if(e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Copy; } else { e.Effect = DragDropEffects.None; } } private void Form1_DragDrop(object sender, DragEventArgs e) { string[] path = (string[])e.Data.GetData(DataFormats.FileDrop, false); //ドロップされたファイルのパスを取得する var ids = new List<long>(); //各画像をアップロードしIDを取得 foreach(var p in path) { MediaUploadResult image = token.Media.Upload(media: new FileInfo(p)); ids.Add(image.MediaId); } //画像をツイートする Status s = token.Statuses.Update(status: "Upload Image", media_ids: ids); } private void CheckMouseOnModel() { //マウスのクリックが左クリックの時に実行 if ((DX.GetMouseInput() & DX.MOUSE_INPUT_LEFT) != 0) { DX.GetMousePoint(out MouseX, out MouseY); ScreenPos.x = (float)MouseX; ScreenPos.y = (float)MouseY; if(!firstFlg) { oldScreenPos.x = (float)MouseX; oldScreenPos.y = (float)MouseY; firstFlg = true; } //ローカル座標を画面の指定の座標のワールド座標での一番手前(画面に近い側)の座標に変換 ScreenPos.z = 0.0f; StartPos = DX.ConvScreenPosToWorldPos(ScreenPos); //ローカル座標を画面の指定の座標のワールド座標での画面の奥側の座標に変換 ScreenPos.z = 1.0f; EndPos = DX.ConvScreenPosToWorldPos(ScreenPos); //マウスポインタがモデル上にあるかチェック HitPoly = DX.MV1CollCheck_Line(modelHandle, -1, StartPos, EndPos); if (HitPoly.HitFlag == DX.TRUE) { modelPos = DX.MV1GetPosition(modelHandle); DX.DrawStringF(100, 500, ">>クリックされてるよ〜", 0xffffff); if (oldScreenPos.x != ScreenPos.x || oldScreenPos.y != ScreenPos.y) { DX.VECTOR modelPos2D = DX.ConvWorldPosToScreenPos(modelPos); modelPos2D.x += ScreenPos.x - oldScreenPos.x; modelPos2D.y += ScreenPos.y - oldScreenPos.y; if(modelPos2D.x > Screen.PrimaryScreen.Bounds.Width) { modelPos2D.x = Screen.PrimaryScreen.Bounds.Width; } if(modelPos2D.y > Screen.PrimaryScreen.Bounds.Height) { modelPos2D.y = Screen.PrimaryScreen.Bounds.Height; } DX.MV1SetPosition(modelHandle, DX.ConvScreenPosToWorldPos(modelPos2D)); } oldScreenPos = ScreenPos; } } } } } お手数をおかけして申し訳ありませんがどうかよろしくお願いいたします。
メンテ
Re: 3Dモデルのマウスによる移動 ( No.3 )
名前:管理人 日時:2018/07/11 01:18

私も3Dモデルをマウスで移動するプログラムを組んでみましたので、よろしければご覧ください m(_ _)m #include "DxLib.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { int ModelHandle ; int NowInput, EdgeInput, PrevInput ; int Catch ; int CatchMouseX, CatchMouseY ; VECTOR Catch3DModelPosition ; VECTOR Catch3DHitPosition ; VECTOR Catch2DHitPosition ; // ウインドウモードで起動 ChangeWindowMode( TRUE ) ; // 背景を灰色にする SetBackgroundColor( 128,128,128 ) ; // DXライブラリの初期化 if( DxLib_Init() < 0 ) return -1 ; // モデルの読み込み ModelHandle = MV1LoadModel( "DxChara.x" ) ; // 描画先を裏画面にする SetDrawScreen( DX_SCREEN_BACK ) ; // カメラの設定 SetCameraPositionAndTarget_UpVecY( VGet( -1000.0f, 1000.0f, -1000.0f ), VGet( 0.0f, 400.0f, 0.0f ) ); SetCameraNearFar( 15.0f, 3800.0f ); // 変数の初期化 NowInput = 0 ; EdgeInput = 0 ; PrevInput = 0 ; Catch = 0 ; // メインループ(何かキーが押されたらループを抜ける) while( ProcessMessage() == 0 ) { int MouseX, MouseY ; // マウスカーソルの座標を取得 GetMousePoint( &MouseX, &MouseY ) ; // マウスボタンの入力状態を更新 PrevInput = NowInput ; NowInput = GetMouseInput() ; EdgeInput = NowInput & ~PrevInput ; // 既にモデルを掴んでいるかどうかで処理を分岐 if( Catch == 0 ) { // 掴んでいない場合 // 左クリックされたらモデルをクリックしたかを調べる if( EdgeInput & MOUSE_INPUT_1 ) { VECTOR ScreenPos1 ; VECTOR ScreenPos2 ; VECTOR WorldPos1 ; VECTOR WorldPos2 ; // モデルとの当たり判定用の線分の2座標を作成 ScreenPos1.x = ( float )MouseX ; ScreenPos1.y = ( float )MouseY ; ScreenPos1.z = 0.0f ; ScreenPos2.x = ( float )MouseX ; ScreenPos2.y = ( float )MouseY ; ScreenPos2.z = 1.0f ; WorldPos1 = ConvScreenPosToWorldPos( ScreenPos1 ) ; WorldPos2 = ConvScreenPosToWorldPos( ScreenPos2 ) ; // モデルの当たり判定情報を更新 MV1RefreshCollInfo( ModelHandle, -1 ) ; // モデルと線分の当たり判定 MV1_COLL_RESULT_POLY Result = MV1CollCheck_Line( ModelHandle, -1, WorldPos1, WorldPos2 ) ; // 当たっていたら掴み状態にする if( Result.HitFlag ) { // 掴んでいるかどうかのフラグを立てる Catch = 1 ; // 掴んだときのスクリーン座標を保存 CatchMouseX = MouseX ; CatchMouseY = MouseY ; // 掴んだときのモデルのワールド座標を保存 Catch3DModelPosition = MV1GetPosition( ModelHandle ) ; // 掴んだときのモデルと線分が当たった座標を保存( 座標をスクリーン座標に変換したものも保存しておく ) Catch3DHitPosition = Result.HitPosition ; Catch2DHitPosition = ConvWorldPosToScreenPos( Catch3DHitPosition ) ; } } } else { // 掴んでいる場合 // マウスの左クリックが離されていたら掴み状態を解除 if( ( NowInput & MOUSE_INPUT_1 ) == 0 ) { Catch = 0 ; } else { // 掴み状態が継続していたらマウスカーソルの移動に合わせてモデルも移動 float MoveX, MoveY ; VECTOR NowCatch2DHitPosition ; VECTOR NowCatch3DHitPosition ; VECTOR Now3DModelPosition ; // 掴んだときのマウス座標から現在のマウス座標までの移動分を算出 MoveX = ( float )( MouseX - CatchMouseX ) ; MoveY = ( float )( MouseY - CatchMouseY ) ; // 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足す NowCatch2DHitPosition.x = Catch2DHitPosition.x + MoveX ; NowCatch2DHitPosition.y = Catch2DHitPosition.y + MoveY ; NowCatch2DHitPosition.z = Catch2DHitPosition.z ; // 掴んだときのモデルと線分が当たった座標をスクリーン座標に変換したものにマウスの移動分を足した座標をワールド座標に変換 NowCatch3DHitPosition = ConvScreenPosToWorldPos( NowCatch2DHitPosition ) ; // 掴んだときのモデルのワールド座標に『掴んだときのモデルと線分が当たった座標にマウスの移動分を足した座標をワールド座標に // 変換した座標』と、『掴んだときのモデルと線分が当たった座標』との差分を加算 Now3DModelPosition.x = Catch3DModelPosition.x + NowCatch3DHitPosition.x - Catch3DHitPosition.x ; Now3DModelPosition.y = Catch3DModelPosition.y + NowCatch3DHitPosition.y - Catch3DHitPosition.y ; Now3DModelPosition.z = Catch3DModelPosition.z + NowCatch3DHitPosition.z - Catch3DHitPosition.z ; // ↑の計算で求まった新しい座標をモデルの座標としてセット MV1SetPosition( ModelHandle, Now3DModelPosition ) ; } } // 画面のクリア ClearDrawScreen() ; // 3Dモデルの描画 MV1DrawModel( ModelHandle ) ; // 裏画面の内容を表画面に反映 ScreenFlip() ; } // DXライブラリの後始末 DxLib_End() ; // ソフトの終了 return 0 ; } northさんのプログラムと異なる点は ・モデルをクリックした際の座標を保存しておいて、左クリックが離されるまでその座標との差分で処理する  ( 毎フレームマウスの移動分だけ移動すると計算の精度の関係で徐々に誤差が大きくなるので・・・ ) ・モデルの移動値には『マウス座標の移動分』をそのままモデルの移動分とするのではなく、  『マウスがクリックした3D座標』に『マウス座標の移動分』を加味した3D座標の差分をモデルの移動値としている  ( 『マウスでクリックしたスクリーン座標』と『モデルの3D座標をスクリーン座標化したもの』が離れていればいるほど   マウスカーソルを移動した際の誤差が大きくなるので、『マウスでクリックした3D座標』の移動分を使用しています ) の2点です 概ね期待通りの動作をしていると思います よろしければお試しください m(_ _)m
メンテ
Re: 3Dモデルのマウスによる移動 ( No.4 )
名前:north 日時:2018/07/12 23:51

>管理人様 お忙しい中わざわざコードをありがとうございます。 お陰様でやりたいことが無事に実装できました。 マイフレームの移動分で計算するのではなく。クリックした際の座標と離される際の座標の差分で計算、 移動値もワールド座標で合わせないといけなかったのですね。 勉強になりました。 一つコードでわからないところがあるのですが EdgeInput = NowInput & ~PrevInput ; ここでビット演算をしているのはわかるのですがなぜこのような処理をしているのかがわからないです・・・ お手数ですが教えていただいてもよろしいでしょうか?
メンテ
Re: 3Dモデルのマウスによる移動 ( No.5 )
名前:yumetodo 日時:2018/07/15 01:54

オブジェクトではないところをクリックしてからオブジェクト上にマウスを動かしたときに、「クリックされた」と判定されるとまずいです。 そこで前回inputされたものをbit反転してand演算することで前回の入力されていたら無視となります。
メンテ
Re: 3Dモデルのマウスによる移動 ( No.6 )
名前:管理人 日時:2018/07/16 23:56

こちらの処理では PrevInput = NowInput ; NowInput = GetMouseInput() ; EdgeInput = NowInput & ~PrevInput ; PrevInput にひとつ前のフレーム( ループ )の押下状態、NowInput に今回のフレームの押下状態、 EdgeInput に前回のフレームでは押されていなくて今回のフレームでは押されているボタンのみの押下状態が 格納されるようになっています & や ~ はビット演算で全ビット( 全桁 )同じ処理が行われるので、1桁だけで考えてみます 例えば前回のフレームでは押されていなくて今回のフレームでは押されている場合は PrevInput は 0, NowInput は 1 になります、その状態で EdgeInput = NowInput & ~PrevInput ; を実行すると EdgeInput = 1 & ~0 ; となり、~ は not演算子で、値の 0 と 1 を反転するので EdgeInput = 1 & 1 ; となり、& は左右の値が 1 の場合のみ 1、それ以外では 0 になるので、結果 EdgeInput = 1 ; となり、『前回のフレームでは押されていなくて今回のフレームでは押されているボタンのみの押下状態』として 1(押された最初のフレーム) となります 例えば、押しっぱなしの場合では、PrevInput も NowInput も 1 となるので EdgeInput = NowInput & ~PrevInput ; は EdgeInput = 1 & ~1 ; となり、右側の 1 を not演算すると EdgeInput = 1 & 0 ; となるので、1 & 0 はどちらも 1 ではないので EdgeInput = 0 ; となり、『前回のフレームでは押されていなくて今回のフレームでは押されているボタンのみの押下状態』として 0(押された最初のフレームではない) となります なので、EdgeInput = NowInput & ~PrevInput ; の演算を行うことで『ボタンが押された最初のフレーム』を検出することができます No.3 のプログラムでは『モデルをクリックしたかどうか』で使用していて『ボタンが押された最初のフレーム』でのみモデルとの当たり判定を行っています ( 左クリックしたままマウスカーソルを移動してモデルにカーソルが当たった場合もモデルが動き出したら変なので・・・ )
メンテ
Re: 3Dモデルのマウスによる移動 ( No.7 )
名前:north(解決) 日時:2018/07/23 20:42

>yumetodo様、管理人様 詳しいご説明をありがとうございます。 理解が深まりました。
メンテ

Page: 1 |

題名
名前
コメント
パスワード (記事メンテ時に使用)

   クッキー保存