DirectX 3D_实践之DirectX3D中的物体拾取技术

                     今天我们谈一下,个人的核心竞争力,每个从事IT的人,都必须有自己的核心竞争力,有了这个,你才能有不可替代性,才可以在就业中,游刃有余,人的精力是有限的,不可能让自己在各个方面都有很深的研究和了解,但必须,在某一个方面,进行深入的研究,之前,我有讲过,我们每个人都在挖井,其实地下水在地下的深度是一致的,很多觉得当前的技术不好,没有进行深入,就重新去挖了一口井,过一段时间,又挖了一口井,但是他一直没有挖出地下水。我们应该在挖出水的情况下,再去,挖另外的井,因为现在的企业对人的素质,掌握的知识,都要求很全面,喜欢复合性人才,所以,我们必须在保证自己的核心竞争力的情况下,去学些其他的知识。触类旁通。对于自己,还是应该在驱动上面面再次往深入一些,尽量的多做几个复合性的驱动,多对几种 框架结构有所了解。

 

               今天来看一下,DirectX3D的拾取,拾取技术在许多游戏和3D程序中都得到了很广泛的应用。例如玩家需要通过单击鼠标与场景中各种各样的物体进行交互。玩家可以通过单击敌人而向其开火,也可以单击某些道具将其捡起。我们一般将拾取分解为以下4个步骤:

 

              给定所单击的屏幕点S,求出它在投影窗口中的对应点p.

              计算拾取射线。即自坐标原点发出且通过点p的那条射线。

             将拾取射线和物体模型变换至同一个坐标系中。

             进行物体/射线的相交判断。相交的物体即为用户所拾取的屏幕对象。

 

我们先来看DirectX3D.h的代码:

#ifndef __DirectX3DH__
#define __DirectX3DH__

#include 
#include 

namespace d3d
{
	bool InitD3D(
		HINSTANCE hInstance,       // [in] Application instance.
		int width, int height,     // [in] Backbuffer dimensions.
		bool windowed,             // [in] Windowed (true)or full screen (false).
		D3DDEVTYPE deviceType,     // [in] HAL or REF
		IDirect3DDevice9** device);// [out]The created device.
	
	int EnterMsgLoop( 
		bool (*ptr_display)(float timeDelta));
	
	LRESULT CALLBACK WndProc(
		HWND hwnd,
		UINT msg, 
		WPARAM wParam,
		LPARAM lParam);
	
	template void Release(T t)
	{
		if( t )
		{
			t->Release();
			t = 0;
		}
	}
	
	template void Delete(T t)
	{
		if( t )
		{
			delete t;
			t = 0;
		}
	}
	
	const D3DXCOLOR      WHITE( D3DCOLOR_XRGB(255, 255, 255) );
	const D3DXCOLOR      BLACK( D3DCOLOR_XRGB(  0,   0,   0) );
	const D3DXCOLOR        RED( D3DCOLOR_XRGB(255,   0,   0) );
	const D3DXCOLOR      GREEN( D3DCOLOR_XRGB(  0, 255,   0) );
	const D3DXCOLOR       BLUE( D3DCOLOR_XRGB(  0,   0, 255) );
	const D3DXCOLOR     YELLOW( D3DCOLOR_XRGB(255, 255,   0) );
	const D3DXCOLOR       CYAN( D3DCOLOR_XRGB(  0, 255, 255) );
	const D3DXCOLOR    MAGENTA( D3DCOLOR_XRGB(255,   0, 255) );
	
	//
	// Lights
	//
	
	D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
	D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
	D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);
	
	//
	// Materials
	//
	
	D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);
	
	const D3DMATERIAL9 WHITE_MTRL  = InitMtrl(WHITE, WHITE, WHITE, BLACK, 2.0f);
	const D3DMATERIAL9 RED_MTRL    = InitMtrl(RED, RED, RED, BLACK, 2.0f);
	const D3DMATERIAL9 GREEN_MTRL  = InitMtrl(GREEN, GREEN, GREEN, BLACK, 2.0f);
	const D3DMATERIAL9 BLUE_MTRL   = InitMtrl(BLUE, BLUE, BLUE, BLACK, 2.0f);
	const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 2.0f);	

	//
	// 外接体对象
	//
	
	struct BoundingBox
	{
		BoundingBox();
		
		bool isPointInside(D3DXVECTOR3& p);
		
		D3DXVECTOR3 _min;
		D3DXVECTOR3 _max;
	};
	
	struct BoundingSphere
	{
		BoundingSphere();
		
		D3DXVECTOR3 _center;
		float       _radius;
	};
	struct Ray
	{
		D3DXVECTOR3 _origin;
		D3DXVECTOR3 _direction;
	};
	
	//
	// 常量
	//
	/*
	常量INFINITY用来表示float类型所能存储的最大浮点数。由于我们不可能取得一个比FLT_MAX更大的浮点数,我们可将
	该值概念化为无穷大,这样可使表达了无穷大概念的代码更具可读性。常量EPSLION是我们定义的一个很小的数,如果某个
	数小于该值,我们就可认为该数为0.这样做是很有必要的,因为浮点数运算具有不精确性,一个本应为0的数在计算机中表示
	可能出现微小的偏差。这样,就会得出该数与0不相等的结果。这样,我们就将判断某个数是否等于0转化为判断某个数是否小于
	EPSILON.
	
	
	*/
	
	const float INFINITY = FLT_MAX;
	const float EPSILON  = 0.001f;

	bool DrawBasicScene(
		IDirect3DDevice9* device,// Pass in 0 for cleanup.
		float scale);            // uniform scale 
	
	//
	// Vertex Structures
	//
	
	struct Vertex
	{
		Vertex(){}
		Vertex(float x, float y, float z, 
			float nx, float ny, float nz,
			float u, float v)
		{
			_x  = x;  _y  = y;  _z  = z;
			_nx = nx; _ny = ny; _nz = nz;
			_u  = u;  _v  = v;
		}
		float _x, _y, _z;
		float _nx, _ny, _nz;
		float _u, _v;
		
		static const DWORD FVF;
	};

}




#endif 


再来看DirectX3D.cpp的代码:

#include "DirectX3D.h"


// vertex formats
const DWORD d3d::Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;



bool d3d::InitD3D(
	HINSTANCE hInstance,
	int width, int height,
	bool windowed,
	D3DDEVTYPE deviceType,
	IDirect3DDevice9** device)
{
	//
	// Create the main application window.
	//

	WNDCLASS wc;

	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = (WNDPROC)d3d::WndProc; 
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = "Direct3D9App";

	if( !RegisterClass(&wc) ) 
	{
		::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
		return false;
	}
		
	HWND hwnd = 0;
	hwnd = ::CreateWindow("Direct3D9App", "Direct3D9App", 
		WS_EX_TOPMOST,
		0, 0, width, height,
		0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/); 

	if( !hwnd )
	{
		::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
		return false;
	}

	::ShowWindow(hwnd, SW_SHOW);
	::UpdateWindow(hwnd);

	//
	// Init D3D: 
	//

    //第一步
    //要初始化IDirect3D 首先必须获取IDirect3D9的指针,使用一个专门的Direct3D函数就可以很容易做到
     IDirect3D9 * _d3d9;
  
  //这个对象的主要有两个用途:设备枚举以及创建IDirect3DDevice9类型的对象。设备枚举是指获取系统中可用的的每块图形卡的
  //性能,显示模型,格式以及其他信息。这个函数调用失败会返回一个NULL指针。
  if(NULL == (_d3d9 = Direct3DCreate9(D3D_SDK_VERSION))){


    return FALSE;

  }

  //第二步
  //创建一个代表主显卡的IDirect3DDevice9类型对象时,必须指定使用该对象进行顶点运算的类型。如果可以,我们希望使用硬件顶点运算
  //但是由于并非所有的显卡都支持硬件顶点运算,我们必须首先检查图形卡是否支持该类型的运算。

  //要进行检查,必须先根据主显卡的性能参数初始化一个IDirect3DDevice9类型的对象。我们使用如下方法来完成初始化:
     /*
  HRESULT IDirect3D9:GetDeviceCaps(
      UINT Adapter,
      D3DDEVTYPE DeviceType,
      D3DCAPS9 * pCaps;
   
  )

        Adapter : 指定物理显卡的序号。
  DeviceType:指定设备类(例如硬件设备(D3DDEVTYPE_HAL)或软件设备(D3DDEVTYPE_REF));
  pCaps 返回已初始化的设备性能结构实例。
        */
         D3DCAPS9 caps; 
         int vp = 0; //代表顶点如何操作

        _d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);

          if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT){

                        vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
           }
          else
          {
                        vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
          }

          //第三步是填充D3DPRESENT_PARAMETER结构
          //该结构用于指定所要创建的IDirect3DDevice9类型对象的一些特性,该结构定义如下:
          /*
          typedef struct _D3DPRESENT_PARAMETERS_{
                        UINT BackBufferWidth;
                        UINT BackBufferHeight;
                        UINT BackBufferFormat;
                        UINT BackBufferCount;
                        D3DMULTISAMPLE_TYPE MultiSampleType;
                        DWORD MultiSampleQuality;
                        D3DSWAPEFFECT SwapEffect;
                        HWND  hDeviceWindow;
                        BOOL  Windowed;
                        BOOL  EnableAutoDepthStencil;
                        D3DFORMAT AutoDepthStencilFormat;
                        DWORD  Flags;
                        UINT   FullScreen_RefreshRateInHz;
                        UINT   PresentationInterval;
          }; 
         */
        
        /* 
         BackBufferWidth: 后台缓存中表面的宽度,单位为像素。
        BackBufferHeight:后台缓存中表面的高度,单位为像素。
        BackBufferFormat:后台缓存的像素格式(如32位像素格式:D3DFMT_A8R8G8B8);
        BackBufferCount: 所需使用的后台缓存的个数,通常指定为1,表明我们仅需要一个后台缓存。
        MultiSampleType: 后台缓存所使用的多重采样类型。
        MultiSampleQuality:多重采样的质量水平。
        SwapEffect:D3DSWAPEFFECT 枚举类型的一个成员。该枚举类型指定了交换链中的缓存的页面置换方式。指定D3DSWAPEFFECT_DISCARD时效率最高。
        hDeviceWindow:与设备相关的窗口句柄。指定了所要进行绘制的应用程序窗口。
        Windowed:为true时表示窗口模式,false时为全屏模式
        EnableAutoDepthStencil:设为true,则Direct3D自动创建并维护深度缓存或模板缓存。
        AutoDepthStencilFormat:深度缓存或模板缓存的像素格式(例如,用24位表示深度并将8位保留供模板缓存使用,D3DFMT_D24S8).
        Flags:一些附加的特性。可以指定为0,表示无标记,或D3DPRESENTFLAG集合中的一个成员,其中两个成员较常用。
                 D3DPRESENTFLAG_LOCKABLE_DEPTHBUFFER 指定为可锁定的后台缓存。注意,使用一个可锁定的后台缓存会降低性能。
                 D3DPRESENTFLAG_DISCARD_DEPTHBUFFER 指定当下一个后台缓存提交时,哪个深度或模块缓存将被丢弃。丢弃的意思是深度或模板缓存存储区
                                                    中的内容别丢弃或无效。这样可以提升性能。
        FullScreen_RefreshRateInHz: 刷新频率,如果想使用默认的刷新频率,则可将该参数指定为D3DPRESENT_RATE_DEFAULT;
        PresentationInterval:D3DPRESENT集合的一个成员,其中有两个比较常用。
                      D3DPRESENT_INTERVAL_IMMEDIATE 立即提交。
                      D3DPRESENT_INTERVAL_DEFAULT 由Direct3D来选择提交频率,通常该值等于刷新频率。
       */ 

                  D3DPRESENT_PARAMETERS d3dpp;  
                  ZeroMemory(&d3dpp, sizeof(d3dpp));  
                 d3dpp.BackBufferWidth            = 800;  
                 d3dpp.BackBufferHeight           = 600;  
                 d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;  
                 d3dpp.BackBufferCount            = 1;  
                 d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;  
                 d3dpp.MultiSampleQuality         = 0;  
                 d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD;   
                 d3dpp.hDeviceWindow              = hwnd;  
                 d3dpp.Windowed                   = true;  
                 d3dpp.EnableAutoDepthStencil     = true;   
                 d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;  
                 d3dpp.Flags                      = 0;  
                 d3dpp.FullScreen_RefreshRateInHz = 0;  
                 d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE; 

              //第四步 创建IDirectDevice9类型的对象
               /*  
                       HRESULT IDirect3D9::CreateDevice(
                               UINT Adapter,
                               D3DDEVTYPE DeviceType,
                               HWND hFocusWindow,
                               DWORD BehaviorFlags,
                               D3DPRESENT_PARAMETERS *pPresentationParameters,
                               IDirect3DDevice9 ** ppReturnedDeviceInterface
   
   
                       );

       Adapter:指定我们希望用已创建的IDirect3DDevice9对象代表哪块物理显卡。
       DeviceType:指定需要使用的设备类型()如,硬件设备用D3DDEVTYPE_HAL,或D3DDEVTYPE_REF代表软件设备。
       hFocusWindow:与设备相关的窗口句柄。通常情况下是指设备所要进行绘制的目标窗口。
       为了达到预期的目的,该句柄与D3DPRESENT_PARAMETER结构的数据成员hDeviceWindow应为同一个句柄。
       BehaviorFlags:该参数可为D3DCREATE_HARDWARE_VERTEXPROCESSING或D3DCREATE_SOFTWARE_VERTEXPROCESSING.
       pPresentationParameters:一个已经完成初始化的D3DPRESENT_PARAMETERS类型的实例,该实例定义了设备的一些特性。
       ppReturnedDeviceInterface:返回所创建的设备。
*/
  if(FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,   
   hwnd, vp, &d3dpp, device)))  
           return FALSE;  

      _d3d9->Release();

      return TRUE;


}

int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
	MSG msg;
	::ZeroMemory(&msg, sizeof(MSG));

	static float lastTime = (float)timeGetTime(); 

	while(msg.message != WM_QUIT)
	{
		if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
        {	
			float currTime  = (float)timeGetTime();
			float timeDelta = (currTime - lastTime)*0.001f;

			ptr_display(timeDelta);

			lastTime = currTime;
        }
    }
    return msg.wParam;
}


D3DLIGHT9 d3d::InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));
	
	light.Type      = D3DLIGHT_DIRECTIONAL;
	light.Ambient   = *color * 0.4f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Direction = *direction;
	
	return light;
}

D3DLIGHT9 d3d::InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));
	
	light.Type      = D3DLIGHT_POINT;
	light.Ambient   = *color * 0.4f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Position  = *position;
	light.Range        = 1000.0f;
	light.Falloff      = 1.0f;
	light.Attenuation0 = 1.0f;
	light.Attenuation1 = 0.0f;
	light.Attenuation2 = 0.0f;
	
	return light;
}

D3DLIGHT9 d3d::InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));
	
	light.Type      = D3DLIGHT_SPOT;
	light.Ambient   = *color * 0.4f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Position  = *position;
	light.Direction = *direction;
	light.Range        = 1000.0f;
	light.Falloff      = 1.0f;
	light.Attenuation0 = 1.0f;
	light.Attenuation1 = 0.0f;
	light.Attenuation2 = 0.0f;
	light.Theta        = 0.5f;
	light.Phi          = 0.7f;
	
	return light;
}

D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p)
{
	D3DMATERIAL9 mtrl;
	mtrl.Ambient  = a;
	mtrl.Diffuse  = d;
	mtrl.Specular = s;
	mtrl.Emissive = e;
	mtrl.Power    = p;
	return mtrl;
}

d3d::BoundingBox::BoundingBox()
{
	// infinite small 
	_min.x = d3d::INFINITY;
	_min.y = d3d::INFINITY;
	_min.z = d3d::INFINITY;
	
	_max.x = -d3d::INFINITY;
	_max.y = -d3d::INFINITY;
	_max.z = -d3d::INFINITY;
}

bool d3d::BoundingBox::isPointInside(D3DXVECTOR3& p)
{
	if( p.x >= _min.x && p.y >= _min.y && p.z >= _min.z &&
		p.x <= _max.x && p.y <= _max.y && p.z <= _max.z )
	{
		return true;
	}
	else
	{
		return false;
	}
}

d3d::BoundingSphere::BoundingSphere()
{
	_radius = 0.0f;
}


bool d3d::DrawBasicScene(IDirect3DDevice9* device, float scale)
{
	static IDirect3DVertexBuffer9* floor  = 0;
	static IDirect3DTexture9*      tex    = 0;
	static ID3DXMesh*              pillar = 0;

	HRESULT hr = 0;

	if( device == 0 )
	{
		if( floor && tex && pillar )
		{
			// they already exist, destroy them
			d3d::Release(floor);
			d3d::Release(tex);
			d3d::Release(pillar);
		}
	}
	else if( !floor && !tex && !pillar )
	{
		// they don't exist, create them
		device->CreateVertexBuffer(
			6 * sizeof(d3d::Vertex),
			0, 
			d3d::Vertex::FVF,
			D3DPOOL_MANAGED,
			&floor,
			0);

		Vertex* v = 0;
		floor->Lock(0, 0, (void**)&v, 0);

		v[0] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[1] = Vertex(-20.0f, -2.5f,  20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
		v[2] = Vertex( 20.0f, -2.5f,  20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);

		v[3] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[4] = Vertex( 20.0f, -2.5f,  20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
		v[5] = Vertex( 20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);

		floor->Unlock();

		D3DXCreateCylinder(device, 0.5f, 0.5f, 5.0f, 20, 20, &pillar, 0);

		D3DXCreateTextureFromFile(
			device,
			"desert.bmp",
			&tex);
	}
	else
	{
		//
		// Pre-Render Setup
		//
		device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

		D3DXVECTOR3 dir(0.707f, -0.707f, 0.707f);
		D3DXCOLOR col(1.0f, 1.0f, 1.0f, 1.0f);
		D3DLIGHT9 light = d3d::InitDirectionalLight(&dir, &col);

		device->SetLight(0, &light);
		device->LightEnable(0, true);
		device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
		device->SetRenderState(D3DRS_SPECULARENABLE, true);

		//
		// Render
		//

		D3DXMATRIX T, R, P, S;

		D3DXMatrixScaling(&S, scale, scale, scale);

		// used to rotate cylinders to be parallel with world's y-axis
		D3DXMatrixRotationX(&R, -D3DX_PI * 0.5f);

		// draw floor
		D3DXMatrixIdentity(&T);
		T = T * S;
		device->SetTransform(D3DTS_WORLD, &T);
		device->SetMaterial(&d3d::WHITE_MTRL);
		device->SetTexture(0, tex);
		device->SetStreamSource(0, floor, 0, sizeof(Vertex));
		device->SetFVF(Vertex::FVF);
		device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
		
		// draw pillars
		device->SetMaterial(&d3d::BLUE_MTRL);
		device->SetTexture(0, 0);
		for(int i = 0; i < 5; i++)
		{
			D3DXMatrixTranslation(&T, -5.0f, 0.0f, -15.0f + (i * 7.5f));
			P = R * T * S;
			device->SetTransform(D3DTS_WORLD, &P);
			pillar->DrawSubset(0);

			D3DXMatrixTranslation(&T, 5.0f, 0.0f, -15.0f + (i * 7.5f));
			P = R * T * S;
			device->SetTransform(D3DTS_WORLD, &P);
			pillar->DrawSubset(0);
		}
	}
	return true;
}





最后来看wmain.cpp的代码:

#include "DirectX3D.h"
#include 
#include 
//
// Globals
//

IDirect3DDevice9* Device = 0; 

const int Width  = 640;
const int Height = 480;

ID3DXMesh* Teapot = 0;
ID3DXMesh* Sphere = 0;

D3DXMATRIX World;
d3d::BoundingSphere BSphere;

//
// Framework Functions
//
//
// Functions
//
d3d::Ray CalcPickingRay(int x, int y)
{
	float px = 0.0f;
	float py = 0.0f;
	
	//得到视口的指针
	D3DVIEWPORT9 vp;
	Device->GetViewport(&vp);
	
	//进行投影变换
	D3DXMATRIX proj;
	Device->GetTransform(D3DTS_PROJECTION, &proj);
	
	px = ((( 2.0f*x) / vp.Width)  - 1.0f) / proj(0, 0);
	py = (((-2.0f*y) / vp.Height) + 1.0f) / proj(1, 1);
	
	d3d::Ray ray;
	ray._origin    = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	ray._direction = D3DXVECTOR3(px, py, 1.0f);
	
	return ray;
}

void TransformRay(d3d::Ray* ray, D3DXMATRIX* T)
{
	// transform the ray's origin, w = 1.
	/*函数D3DXVec3TransformCoord,和函数D3DXVec3TransformNormal均已3D向量作为其参数,但要注意,使用D3DXVec3TransformCoord
	时,向量参数的第4个分量应理解为1,D3DXVec3TransformNormal时,第4个参数应理解为0,所以我们用D3DXVec3TransformCoord来实现
	点的变换,D3DXVec3TransformNormal来实现向量的变换*/
	D3DXVec3TransformCoord(
		&ray->_origin,
		&ray->_origin,
		T);
	
	// transform the ray's direction, w = 0.
	D3DXVec3TransformNormal(
		&ray->_direction,
		&ray->_direction,
		T);
	
	// normalize the direction
	D3DXVec3Normalize(&ray->_direction, &ray->_direction);
}

bool RaySphereIntTest(d3d::Ray* ray, d3d::BoundingSphere* sphere)
{
	D3DXVECTOR3 v = ray->_origin - sphere->_center;
	
	float b = 2.0f * D3DXVec3Dot(&ray->_direction, &v);
	float c = D3DXVec3Dot(&v, &v) - (sphere->_radius * sphere->_radius);
	
	// find the discriminant
	float discriminant = (b * b) - (4.0f * c);
	
	// test for imaginary number
	if( discriminant < 0.0f )
		return false;
	
	discriminant = sqrtf(discriminant);
	
	float s0 = (-b + discriminant) / 2.0f;
	float s1 = (-b - discriminant) / 2.0f;
	
	// if a solution is >= 0, then we intersected the sphere
	if( s0 >= 0.0f || s1 >= 0.0f )
		return true;
	
	return false;
}


bool Setup()
{
	//
	// 创建茶壶
	//

	D3DXCreateTeapot(Device, &Teapot, 0);

	//
	//通过网格的顶点得到外接球的尺寸
	//

	BYTE* v = 0;
	Teapot->LockVertexBuffer(0, (void**)&v);
	
	D3DXComputeBoundingSphere(
		(D3DXVECTOR3*)v,
		Teapot->GetNumVertices(),
		D3DXGetFVFVertexSize(Teapot->GetFVF()),
		&BSphere._center,
		&BSphere._radius);
	
	Teapot->UnlockVertexBuffer();

	//
	// 创建外接球
	//
	
	D3DXCreateSphere(Device, BSphere._radius, 20, 20, &Sphere, 0);
	
	//
	// 设置光照
	//
	
	D3DXVECTOR3 dir(0.707f, -0.0f, 0.707f);
	D3DXCOLOR col(1.0f, 1.0f, 1.0f, 1.0f);
	D3DLIGHT9 light = d3d::InitDirectionalLight(&dir, &col);
	
	Device->SetLight(0, &light);
	Device->LightEnable(0, true);
	Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
	Device->SetRenderState(D3DRS_SPECULARENABLE, false);	
	
	//
	// 设置观察矩阵,进行取景变换
	//
	
	D3DXVECTOR3 pos(0.0f, 0.0f, -10.0f);
	D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
	
	D3DXMATRIX V;
	D3DXMatrixLookAtLH(&V, &pos, &target, &up);
	Device->SetTransform(D3DTS_VIEW, &V);
	
	//
	// 设置投影矩阵
	//
	
	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(
		&proj,
		D3DX_PI * 0.25f, // 45 - degree
		(float)Width / (float)Height,
		1.0f,
		1000.0f);
	Device->SetTransform(D3DTS_PROJECTION, &proj);
	return true;
}
//将之前分配的内存进行清理,也就是顶点缓存和索引缓存
void Cleanup()
{

d3d::Release(Teapot);
d3d::Release(Sphere);
}


bool Display(float timeDelta)
{
	if( Device )
	{
		

		//
		// Update: Update Teapot.
		//
		
		static float r     = 0.0f;
		static float v     = 1.0f;
		static float angle = 0.0f;
		
		D3DXMatrixTranslation(&World, cosf(angle) * r, sinf(angle) * r, 10.0f);
		
		// 不断的修改,外接球的球心位置
		// 
		BSphere._center = D3DXVECTOR3(cosf(angle)*r, sinf(angle)*r, 10.0f);
		
		r += v * timeDelta;
		
		if( r >= 8.0f )
			v = -v; // reverse direction
		
		if( r <= 0.0f )
			v = -v; // reverse direction
		
		angle += 1.0f * D3DX_PI * timeDelta;
		if( angle >= D3DX_PI * 2.0f )
			angle = 0.0f;
		
		//
		// Render
		//
		
		Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
		Device->BeginScene();
		
		//设置茶壶的材质,并绘制茶壶
		Device->SetTransform(D3DTS_WORLD, &World);
		Device->SetMaterial(&d3d::YELLOW_MTRL);
		Teapot->DrawSubset(0);
		
		// 将外接球和茶壶进行融合
		Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
		Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
		
		//设置融合后的物体的材质,并进行绘制
		D3DMATERIAL9 blue = d3d::BLUE_MTRL;
		blue.Diffuse.a = 0.25f; // 25% opacity
		Device->SetMaterial(&blue);
		Sphere->DrawSubset(0);
		
		Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
		
		Device->EndScene();
		Device->Present(0, 0, 0, 0);
	}
	return true;
}	

//
// WndProc
//
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch( msg )
	{
	case WM_DESTROY:
		::PostQuitMessage(0);
		break;
		
	case WM_KEYDOWN:
		if( wParam == VK_ESCAPE )
			::DestroyWindow(hwnd);
case WM_LBUTTONDOWN:
	
	// 通过鼠标按下时,点的坐标,计算出射线,我们用世界坐标系中的原点为射线的起点
	d3d::Ray ray = CalcPickingRay(LOWORD(lParam), HIWORD(lParam));
	
	// transform the ray to world space
	D3DXMATRIX view;
	Device->GetTransform(D3DTS_VIEW, &view);
	
	D3DXMATRIX viewInverse;
	D3DXMatrixInverse(&viewInverse,	0, &view);
	
	TransformRay(&ray, &viewInverse);
	
	// test for a hit
	if( RaySphereIntTest(&ray, &BSphere) )
		::MessageBox(0, "Hit!", "HIT", 0);
	
		break;
	}
	return ::DefWindowProc(hwnd, msg, wParam, lParam);
}

//
// WinMain
//
int WINAPI WinMain(HINSTANCE hinstance,
				   HINSTANCE prevInstance, 
				   PSTR cmdLine,
				   int showCmd)
{
	if(!d3d::InitD3D(hinstance,
		640, 480, true, D3DDEVTYPE_HAL, &Device))
	{
		::MessageBox(0, "InitD3D() - FAILED", 0, 0);
		return 0;
	}
	
	if(!Setup())
	{
		::MessageBox(0, "Setup() - FAILED", 0, 0);
		return 0;
	}
	
	d3d::EnterMsgLoop( Display );
	
	Cleanup();
	
	Device->Release();
	
	return 0;
}
bool ComputeBoundingSphere(ID3DXMesh* mesh, d3d::BoundingSphere* sphere)
{
	HRESULT hr = 0;
	
	BYTE* v = 0;
	mesh->LockVertexBuffer(0, (void**)&v);//得到网格的顶点缓存

	/*
	我们来看一下D3DX库提供的用来计算一个网格的外接球的函数
	HRESULT  D3DXComputeBoundingSphere(
	__in  const D3DXVECTOR3 *pFirstPosition,
	__in  DWORD NumVertices,
	__in  DWORD dwStride,
	__in  D3DXVECTOR3 *pCenter,
	__in  FLOAT *pRadius
	);
    
	pFirstPosition:指向顶点数组(该数组的每个元素都描述了对应顶点)中第一个顶点的位置向量的指针。我们可以通过网格对象得到顶点缓存的指针,最后
	                可转化为该值。
	NumVertices:该网格中顶点数组中顶点的个数。可通过网格mesh->GetNumVertices()得到
	dsStride:每个顶点的大小,单位为字节。该值很重要,因为一种顶点结构可能包含了许多该函数所不需要的附加信息,如法向量和纹理坐标等。这样该函数
	         就需要知道应跳过多少字节才能找到下一个顶点的位置。mesh->GetFVF()可以返回一个描述了顶点格式的DWORD类型值。D3DXGetFVFVertexSize这个
			 函数可以得到该顶点占用多少个字节。
			 
	pCenter:返回的外接球的球心位置。
	pRadius: 返回外接球的半径。
	
    */

	
	hr = D3DXComputeBoundingSphere(
		(D3DXVECTOR3*)v,
		mesh->GetNumVertices(),
		D3DXGetFVFVertexSize(mesh->GetFVF()),
		&sphere->_center,
		&sphere->_radius);
	
	mesh->UnlockVertexBuffer();
	
	if( FAILED(hr) )
		return false;
	
	return true;
}

bool ComputeBoundingBox(ID3DXMesh* mesh, d3d::BoundingBox* box)
{
	HRESULT hr = 0;
	
	BYTE* v = 0;
	mesh->LockVertexBuffer(0, (void**)&v);

	//这里是D3DX中计算一个网格外接体的函数,跟外接球的函数很类似,只是最后两个参数,返回外接体的最大点和最小点。
	
	hr = D3DXComputeBoundingBox(
		(D3DXVECTOR3*)v,
		mesh->GetNumVertices(),
		D3DXGetFVFVertexSize(mesh->GetFVF()),
		&box->_min,
		&box->_max);
	
	mesh->UnlockVertexBuffer();
	
	if( FAILED(hr) )
		return false;
	
	return true;
}


最后来看程序运行时的截图:

DirectX 3D_实践之DirectX3D中的物体拾取技术_第1张图片

 

每日总结:

拾取是一项根据用户在屏幕上单击的位置(3D物体在屏幕上的投影)来确定用户是否选中以及选中哪个3D物体的技术。

如果一条射线的起点与观察坐标系的原点重合,且经过用户所单击的屏幕点,则该射线即对应一条拾取射线。

要判断射线与某物体是否相交,可测试射线是否与构成物体的某一面片相交或射线是否与该物体的外接体相交。

你可能感兴趣的:(我的DirectX3D学习)