// // コンピュータグラフィックス特論U // ピッキング サンプルプログラム // // GLUTヘッダファイルのインクルード #include // ベクトル・行列の表現・計算に vecmath を使用 #include #include #include #include #include #include "vecmath_gl.h" // 幾何形状オブジェクト、及び、読み込み・描画関数 #include "Obj.h" // 標準算術関数・定数の定義 #define _USE_MATH_DEFINES #include // // カメラ・GLUTの入力処理に関するグローバル変数 // // カメラの回転のための変数 float camera_yaw = 15.0f; // Y軸を中心とする回転角度 float camera_pitch = -20.0f; // X軸を中心とする回転角度 float camera_distance = 15.0f; // 中心からカメラの距離 // マウスのドラッグのための変数 int drag_mouse_r = 0; // 右ボタンがドラッグ中かどうかのフラグ(1:ドラッグ中, 0:非ドラッグ中) int drag_mouse_l = 0; // 左ボタンがドラッグ中かどうかのフラグ(1:ドラッグ中, 0:非ドラッグ中) int drag_mouse_m = 0; // 中ボタンがドラッグ中かどうかのフラグ(1:ドラッグ中, 0:非ドラッグ中) int last_mouse_x, last_mouse_y; // 最後に記録されたマウスカーソルの座標 // ウィンドウのサイズ int win_width, win_height; // // オブジェクトの配置・表示に関するグローバル変数 // // 表示用の幾何形状モデル Obj * object; Obj * object_selected; Vector3f object_size; // 点光源の位置(影の投影方向) Vector3f light_pos( 0.0f, 10.0f, 0.0f ); // 影の色 Color4f shadow_color( 0.2f, 0.2f, 0.2f, 0.5f ); // オブジェクトの配置情報 struct ObjectInfo { // 位置・向き Point3f pos; Matrix3f ori; // 変換行列(位置・向きから描画用に計算) Matrix4f frame; // 画面上での位置(ピッキングのために計算) Point2f screen_pos; }; // 全オブジェクトの配列 int num_objects = 0; ObjectInfo * objects = NULL; // // ピッキング処理に関するグローバル変数 // // ピッキング判定方法を表す列挙型 enum PickModeEnum { PICK_SCREEN, PICK_WORLD, }; // ピッキング判定方法 PickModeEnum pick_mode = PICK_SCREEN; // オブジェクトの選択情報(選択中のオブジェクト番号) int selected_object_no = -1; // オブジェクトの選択情報(選択された点の位置)(ワールド座標系での判定時のみ有効) bool enable_slected_point = false; Point3f selected_point; // 最後にピッキングを行ったときの視線ベクトル(ワールド座標系での判定時のみ) bool enable_eye_line = false; Point3f eye_line_org; Vector3f eye_line_vec; // 視線ベクトルを描画するかどうかの設定(ワールド座標系での判定時のみ有効) bool draw_eye_line = false; // // ピッキング処理 // // // 全オブジェクトの画面上の位置を更新 // void UpdateObjectProjection() { // ワールド座標系からカメラ座標系への変換行列が設定されているものとする // 計算用変数 Point3d projected_pos; // OpenGL の変換行列を取得 double model_view_matrix[ 16 ]; double projection_matrix[ 16 ]; int viewport_param[ 4 ]; glGetDoublev( GL_MODELVIEW_MATRIX, model_view_matrix ); glGetDoublev( GL_PROJECTION_MATRIX, projection_matrix ); glGetIntegerv( GL_VIEWPORT, viewport_param ); // 各オブジェクトの画面上の位置を計算 for ( int i=0; iscreen_pos.x = ???; // obj->screen_pos.y = ???; } } // // ピッキング処理(スクリーン座標系) // int PickObjectScreen( int mouse_x, int mouxe_y ) { // 選択されたかどうかを判定するための、画面上での距離の閾値 // オブジェクトの中心位置とマウス位置の距離が閾値以下であれば、選択されたと判定する const float threshold = 20.0f; // 全オブジェクトの画面上の位置を更新 //(本来は、前回の計算時から視点が更新されていなければ再計算の必要はないが、毎回計算を行っている) UpdateObjectProjection(); // ※レポート課題 // 各オブジェクトの画面上の位置(objects[ i ].screen_pos)とマウス位置の間の距離を計算して、 // 距離が閾値以下で、最もマウス位置に近いオブジェクトを探索 // 選択されたオブジェクトの番号を返す // マウス座標の近くにオブジェクトがない場合は、-1 を返す return -1; } // // 三角形と半直線の交差判定 // bool CheckCross( const Point3f & p0, const Point3f & p1, const Point3f & p2, const Point3f & seg_org, const Vector3f & seg_vec, Point3f & cross_point ) { // ※レポート課題 // 三角形と直線が交差する場合は、戻り値として true を返す // 交差しない場合は、false を返す // また、交差する場合は、交点の座標を cross_point に格納して返す return false; } // // ピッキング処理(ワールド座標系) // int PickObjectWorld( int mouse_x, int mouse_y ) { // OpenGL の変換行列を取得 double model_view_matrix[ 16 ]; double projection_matrix[ 16 ]; int viewport_param[ 4 ]; glGetDoublev( GL_MODELVIEW_MATRIX, model_view_matrix ); glGetDoublev( GL_PROJECTION_MATRIX, projection_matrix ); glGetIntegerv( GL_VIEWPORT, viewport_param ); // マウス位置に対応する3次元空間の半直線 double wx, wy, wz, dx, dy, dz; // ※レポート課題 // 視点位置(wx,wy,wz)と視線ベクトル(dx,dy,dz)を計算 wx = 0.0f; wy = 0.0f; wz = 0.0f; dx = 0.0f; dy = 0.0f; dz = 0.0f; // 半直線の始点(視点位置)と方向ベクトル(視線ベクトル)を設定 Point3f line_org; Vector3f line_vec; line_org.set( wx, wy, wz ); line_vec.set( dx, dy, dz ); // ピッキングを行ったときの視線ベクトルを記録(表示用) enable_eye_line = true; eye_line_org = line_org; eye_line_vec = line_vec; // 計算用変数 Point3f p0, p1, p2; Point3f cross_point, closest_cross_point; Vector3f vec; bool cross; int dist; // 最も視点に近い交点のオブジェクト番号と距離(最初は -1 で初期化) int closeset_object_no = -1; float closest_dist = -1.0f; // 各オブジェクトと直線の交差判定 for ( int i=0; inum_triangles; j++ ) { // 三角面の頂点座標を取得(モデル座標系) p0.set( &object->vertices[ object->tri_v_no[ j*3 + 0 ] ].x ); p1.set( &object->vertices[ object->tri_v_no[ j*3 + 1 ] ].x ); p2.set( &object->vertices[ object->tri_v_no[ j*3 + 2 ] ].x ); // 三角面の頂点座標を計算(ワールド座標系) obj.frame.transform( &p0 ); obj.frame.transform( &p1 ); obj.frame.transform( &p2 ); // 半直線と三角面の交差判定 cross = CheckCross( p0, p1, p2, line_org, line_vec, cross_point ); // 交差する場合の処理 if ( cross ) { // 交点と視点の距離を計算 vec.sub( cross_point, line_org ); dist = vec.length(); // 最も視点に近い交点とそのオブジェクト番号を記録 if ( ( closeset_object_no == -1 ) || ( dist < closest_dist ) ) { closeset_object_no = i; closest_dist = dist; // 交点の位置を記録 enable_slected_point = true; selected_point = cross_point; } } } } // オブジェクトが選択されなかった場合は、交点の位置は無効とする if ( closeset_object_no == -1 ) enable_slected_point = false; // 選択されたオブジェクトの番号を返す return closeset_object_no; } // // ピッキング処理 // int PickObject( int mouse_x, int mouse_y ) { // 選択された点の位置の情報、視線ベクトルの情報をクリア enable_slected_point = false; enable_eye_line = false; // スクリーン座標系で判定 if ( pick_mode == PICK_SCREEN ) return PickObjectScreen( mouse_x, mouse_y ); // ワールド座標系で判定 else if ( pick_mode == PICK_WORLD ) return PickObjectWorld( mouse_x, mouse_y ); // オブジェクトが選択されなかった場合は、-1 を返す return -1; } // // 以下、プログラムのメイン処理 // // // シーン初期化(オブジェクトをランダムに配置) // void InitScene( int n ) { // 全オブジェクトの情報を格納する配列を初期化 num_objects = n; if ( !objects ) delete[] objects; objects = new ObjectInfo[ num_objects ]; // 全オブジェクトの位置・向きをランダムに設定 ObjectInfo * obj = NULL; float yaw, pitch, roll; Matrix3f rot; for ( int i=1; ipos.x = ( (float) rand() / RAND_MAX ) * 10.0f - 5.0f; obj->pos.z = ( (float)rand() / RAND_MAX ) * 10.0f - 5.0f; obj->pos.y = ( (float)rand() / RAND_MAX ) * 5.0f + 0.2f; pitch = ( (float) rand() / RAND_MAX ) * 0.5f * M_PI - 0.25f * M_PI; yaw = ( (float) rand() / RAND_MAX ) * 2.0f * M_PI; roll = ( (float) rand() / RAND_MAX ) * 0.5f * M_PI - 0.25f * M_PI; obj->ori.setIdentity(); rot.rotZ( roll ); obj->ori.mul( obj->ori, rot ); rot.rotX( pitch ); obj->ori.mul( obj->ori, rot ); rot.rotY( yaw ); obj->ori.mul( obj->ori, rot ); obj->frame.set( obj->ori, obj->pos, 1.0f ); } // 1つ目のオブジェクトは、必ず原点に配置する。 objects[ 0 ].pos.set( 0.0f, 0.3f, 0.0f ); objects[ 0 ].ori.setIdentity(); objects[ 0 ].frame.set( objects[ 0 ].ori, objects[ 0 ].pos, 1.0f ); } // // 格子模様の床を描画 // void DrawFloor( float tile_size, int num_x, int num_z, float r0, float g0, float b0, float r1, float g1, float b1 ) { int x, z; float ox, oz; glBegin( GL_QUADS ); glNormal3d( 0.0, 1.0, 0.0 ); ox = - ( num_x * tile_size ) / 2; for ( x=0; x 360.0 ) camera_yaw -= 360.0; // マウスの縦移動に応じてX軸を中心に回転 camera_pitch -= ( my - last_mouse_y ) * 1.0; if ( camera_pitch < -90.0 ) camera_pitch = -90.0; else if ( camera_pitch > 90.0 ) camera_pitch = 90.0; } // 中ボタンのドラッグ中は視点とカメラの距離を変更する if ( drag_mouse_m ) { // 前回のマウス座標と今回のマウス座標の差に応じて視点を回転 // マウスの縦移動に応じて距離を移動 camera_distance += ( my - last_mouse_y ) * 0.2; if ( camera_distance < 2.0 ) camera_distance = 2.0; } // 今回のマウス座標を記録 last_mouse_x = mx; last_mouse_y = my; // 再描画 glutPostRedisplay(); } // // キーボードのキーが押されたときに呼ばれるコールバック関数 // void KeyboardCallback( unsigned char key, int mx, int my ) { // ピッキング判定方法の変更 if ( key == 'p' ) { if ( pick_mode == PICK_SCREEN ) pick_mode = PICK_WORLD; else pick_mode = PICK_SCREEN; } // 視線ベクトルを描画するかの設定を変更 if ( key == 'd' ) draw_eye_line = !draw_eye_line; glutPostRedisplay(); } // // 環境初期化関数 // void initEnvironment( void ) { // 光源を作成する float light0_position[] = { 0.0, 10.0, 0.0, 1.0 }; float light0_diffuse[] = { 0.8, 0.8, 0.8, 1.0 }; float light0_specular[] = { 1.0, 1.0, 1.0, 1.0 }; float light0_ambient[] = { 0.1, 0.1, 0.1, 1.0 }; glLightfv( GL_LIGHT0, GL_POSITION, light0_position ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light0_diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light0_specular ); glLightfv( GL_LIGHT0, GL_AMBIENT, light0_ambient ); glEnable( GL_LIGHT0 ); // 光源計算を有効にする glEnable( GL_LIGHTING ); // 物体の色情報を有効にする glEnable( GL_COLOR_MATERIAL ); // Zテストを有効にする glEnable( GL_DEPTH_TEST ); // 背面除去を有効にする glCullFace( GL_BACK ); glEnable( GL_CULL_FACE ); // 背景色を設定 glClearColor( 0.5, 0.5, 0.8, 0.0 ); // オブジェクトの幾何形状モデルの読み込み object = LoadObj( "car.obj" ); if ( !object || ( object->num_triangles == 0 ) ) { // 読み込みに失敗したら終了 printf( "Failed to load the object file." ); exit( -1 ); } ScaleObj( object, 1.0f, &object_size.x, &object_size.y, &object_size.z ); // 選択表示用のオブジェクトの幾何形状モデルの作成 //(同じオブジェクトを読み込んで、色を変更) object_selected = LoadObj( "car.obj" ); ScaleObj( object_selected, 1.0f, &object_size.x, &object_size.y, &object_size.z ); for ( int i=0; inum_materials; i++ ) { Mtl * mtl = object_selected->materials[ i ]; mtl->kd.r = 1.0f - mtl->kd.r; mtl->kd.g = 1.0f - mtl->kd.g; mtl->kd.b = 1.0f - mtl->kd.b; } // シーンの初期化(オブジェクトをランダムに配置) InitScene( 30 ); } // // メイン関数(プログラムはここから開始) // int main( int argc, char ** argv ) { // GLUTの初期化 glutInit( &argc, argv ); glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL ); glutInitWindowSize( 480, 480 ); glutInitWindowPosition( 0, 0 ); glutCreateWindow("Picking"); // コールバック関数の登録 glutDisplayFunc( DisplayCallback ); glutReshapeFunc( ReshapeCallback ); glutMouseFunc( MouseClickCallback ); glutMotionFunc( MouseDragCallback ); glutKeyboardFunc( KeyboardCallback ); // 環境初期化 initEnvironment(); // GLUTのメインループに処理を移す glutMainLoop(); return 0; }