MFC-PictureControl控件中使用OpenGL加载obj模型并实现鼠标控制旋转

在上一篇文章:opengl加载显示obj类型文件3D模型,已经能够实现OpenGL加载obj格式模型,现在目的就是在PictureControl控件中展示3D模型,并实现鼠标控制旋转。
参考博客:MFC+OPENGL配置+显示三维图形实现 旋转平移缩放+光照效果[对话框篇]

实现效果:

MFC-OpenGl鼠标控制旋转

写了一个类MyOpengl,类中实现了加载obj模型并鼠标控制旋转

MyOpengl.h

#pragma once
#include 
#ifdef __APPLE__
#include 
#else
#include 
#endif

#include 
#include 
#include 
class MyOpengl :
    public CWnd
{
public:
    MyOpengl(CWnd* wnd);
    ~MyOpengl();


public:
    DECLARE_MESSAGE_MAP()
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSize(UINT nType, int cx, int cy);

public:
    void Display(); //绘制图形
    //void Reflesh(); //刷新控件

    void updateMouseDownState();//改变鼠标按下状态

protected:
    bool mouseDown; //鼠标左键按下
    int xrot;       //x轴旋转值
    int yrot;       //y轴旋转值        
    int xdiff;      //x轴移动值,用于计算旋转
    int ydiff;      //y轴移动值,用于计算旋转

    GLuint scene_list;
    C_STRUCT aiVector3D scene_min, scene_max, scene_center;
    const C_STRUCT aiScene* scene;

    HDC hdc;        //hdc设备上下文
    
private:
    void print_error(const char* msg);
    void set_float4(float f[4], float a, float b, float c, float d);
    void color4_to_float4(const C_STRUCT aiColor4D* c, float f[4]);
    void apply_material(const C_STRUCT aiMaterial* mtl);
    void recursive_render(const C_STRUCT aiScene* sc, const C_STRUCT aiNode* nd);
    int loadasset(const char* path);
    void get_bounding_box(C_STRUCT aiVector3D* min, C_STRUCT aiVector3D* max);
    void get_bounding_box_for_node(const C_STRUCT aiNode* nd, C_STRUCT aiVector3D* min,
        C_STRUCT aiVector3D* max, C_STRUCT aiMatrix4x4* trafo);

    BOOL SetupPixelFormat(HDC hdc); //设置像素格式
public:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);  //左按键按下
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);    //左按键松开
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);    //鼠标移动
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
};


MyOpengl.cpp

#include "pch.h"
#include "MyOpengl.h"

#define aisgl_min(x,y) (xx?y:x)

BEGIN_MESSAGE_MAP(MyOpengl, CWnd)
	ON_WM_CREATE()
	ON_WM_SIZE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

MyOpengl::MyOpengl(CWnd* wnd)
{
	mouseDown = false;
	xrot = 0;
	yrot = 0;
	xdiff = 0;
	ydiff = 0;

	scene_list = 0;
	scene = NULL;

	hdc = ::GetDC(wnd->m_hWnd);
}

MyOpengl::~MyOpengl() 
{
	wglMakeCurrent(hdc, NULL);//释放设备上下文

	aiReleaseImport(scene);
	aiDetachAllLogStreams();
}

int MyOpengl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	// TODO:  在此添加您专用的创建代码
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	//hdc = ::GetDC(m_hWnd);//hdc设备上下文,m_hWnd窗口句柄
	SetupPixelFormat(hdc);//设置像素格式


	HGLRC hglrc = wglCreateContext(hdc);	//hglrc :opengl设备上下文
	wglMakeCurrent(hdc, hglrc);				//hglrc绑定hdc; 绘制到当前设备上下文


	if (!(hglrc = wglCreateContext(hdc)))	//创建一个新的OpenGL渲染描述表, 此描述表必须适用于绘制到由hdc返回的设备.这个渲染描述表将有和设备上下文(dc)一样的像素格式.
	{
		MessageBox(L"CreateContext failed!");
		return false;
	}
	if (!wglMakeCurrent(hdc, hglrc))	//设定OpenGL当前线程的渲染环境
	{
		MessageBox(L"MakeCurrent failed!");
		return false;
	}


	const char* model_file = NULL;
	C_STRUCT aiLogStream stream;
	model_file = "D:/Project/VS/OpenGLTest04/res/box.obj";
	const char* extension = strrchr(model_file, '.');		//strrchr:返回该字符以及其后面的字符
	if (!extension) {
		print_error("Please provide a file with a valid extension.");
		return EXIT_FAILURE;
	}
	if (AI_FALSE == aiIsExtensionSupported(extension)) {	//aiIsExtensionSupported:返回ASIMP是否支持给定的文件扩展名
		print_error("The specified model file extension is currently "
			"unsupported in Assimp 3.3.1");
		return EXIT_FAILURE;
	}

	/*
	* 获取一个预定义的日志流。这是解决问题的捷径,访问Assimp的日志系统。
	* 附加日志流可以稍微减少ASIMP的整体进口业绩。
	*
	* aiDefaultLogStream_FILE:指定要写入的文件
	*/
	stream = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL);
	aiAttachLogStream(&stream);
	stream = aiGetPredefinedLogStream(aiDefaultLogStream_FILE, "1.txt");
	aiAttachLogStream(&stream);

	if (0 != loadasset(model_file)) {
		print_error("Failed to load model. Please ensure that the specified file exists.");
		aiDetachAllLogStreams();
		return EXIT_FAILURE;
	}

	glClearColor(0.313f, 0.439f, 0.584f, 1.f);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	glEnable(GL_NORMALIZE);
	if (getenv("MODEL_IS_BROKEN"))
		glFrontFace(GL_CW);
	glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);



	return 0;
}

BOOL MyOpengl::OnEraseBkgnd(CDC* pDC)
{
	Display(); //展示模型
	return true;
}


void MyOpengl::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	GLdouble aspect_ratio;//窗口长宽比
	if (0 >= cx || 0 >= cy)//窗口长、宽必须大于0
		return;
	glViewport(0, 0, cx, cy);						//	根据窗口的实时变化重绘窗口
	aspect_ratio = (GLdouble)cx / (GLdouble)cy;		//	长宽比
	glMatrixMode(GL_PROJECTION);					//	对投影矩阵应用随后的矩阵操作
	glLoadIdentity();								//	重置当前投影矩阵指定的矩阵为单位矩阵	
	gluPerspective(45.0f, aspect_ratio, 0.1f, 100.0f);	//	谁知投影矩阵
	glMatrixMode(GL_MODELVIEW);						//	对模型视景矩阵堆栈应用随后的矩阵操作	
	glLoadIdentity();								//	重置当前模型矩阵为单位矩阵
}

//设置像素格式
BOOL MyOpengl::SetupPixelFormat(HDC hdc) 
{
	PIXELFORMATDESCRIPTOR pfd =
	{
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,	// 绘制到窗口|支持opengl|采用双缓冲
		PFD_TYPE_RGBA,		// 像素类型 RGBA
		24 ,				// 像素位数 4*8- 32
		0, 0, 0, 0, 0, 0,
		0,
		0,
		0,
		0, 0, 0, 0,
		32 ,				// 深度缓冲区位数
		0,					// 模板缓冲
		0,
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};


	int pixelformat;
	if (!(pixelformat = ChoosePixelFormat(hdc, &pfd)))	//在DC中选择合适的象素格式并返回索引号
	{
		MessageBox(L"选择像素格式失败!");
		return false;
	}
	if (!SetPixelFormat(hdc, pixelformat, &pfd))	//设置指定象素格式
	{
		MessageBox(L"设置像素格式失败!");
		return false;
	}

	return true;
}

void MyOpengl::Display() //绘制图形
{
	float tmp;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.f, 0.f, 3.f, 0.f, 0.f, -5.f, 0.f, 1.f, 0.f);

	glRotatef(xrot, 1.0f, 0.0f, 0.0f);
	glRotatef(yrot, 0.0f, 1.0f, 0.0f);

	tmp = scene_max.x - scene_min.x;
	tmp = aisgl_max(scene_max.y - scene_min.y, tmp);
	tmp = aisgl_max(scene_max.z - scene_min.z, tmp);
	tmp = 1.f / tmp;
	glScalef(tmp, tmp, tmp);
	glTranslatef(-scene_center.x, -scene_center.y, -scene_center.z);
	if (scene_list == 0) {
		scene_list = glGenLists(1);
		glNewList(scene_list, GL_COMPILE);
		recursive_render(scene, scene->mRootNode);
		glEndList();
	}

	glCallList(scene_list);

	SwapBuffers(hdc); //利用双缓冲
}

//void MyOpengl::Reflesh() //刷新控件
//{
//	Invalidate(false);//是控件无效
//	this->UpdateWindow();//更新控件
//}


#pragma region assimp 导入模型相关
void MyOpengl::set_float4(float f[4], float a, float b, float c, float d)
{
	f[0] = a;
	f[1] = b;
	f[2] = c;
	f[3] = d;
}

void MyOpengl::color4_to_float4(const C_STRUCT aiColor4D* c, float f[4])
{
	f[0] = c->r;
	f[1] = c->g;
	f[2] = c->b;
	f[3] = c->a;
}

void MyOpengl::apply_material(const C_STRUCT aiMaterial* mtl)
{
	float c[4];

	GLenum fill_mode;
	int ret1, ret2;
	C_STRUCT aiColor4D diffuse;
	C_STRUCT aiColor4D specular;
	C_STRUCT aiColor4D ambient;
	C_STRUCT aiColor4D emission;

	int two_sided;
	int wireframe;
	unsigned int max;
	float shininess, strength;

	set_float4(c, 0.8f, 0.8f, 0.8f, 1.0f);
	if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diffuse))
		color4_to_float4(&diffuse, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, c);

	set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &specular))
		color4_to_float4(&specular, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);

	set_float4(c, 0.2f, 0.2f, 0.2f, 1.0f);
	if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &ambient))
		color4_to_float4(&ambient, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, c);

	set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emission))
		color4_to_float4(&emission, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, c);

	shininess = 0;
	strength = 0;
	max = 1;
	ret1 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &shininess, &max);
	if (ret1 == AI_SUCCESS) {
		max = 1;
		ret2 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS_STRENGTH, &strength, &max);
		if (ret2 == AI_SUCCESS)
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);
		else
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
	}
	else {
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);
		set_float4(c, 0.0f, 0.0f, 0.0f, 0.0f);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);
	}

	max = 1;
	if (AI_SUCCESS == aiGetMaterialIntegerArray(mtl, AI_MATKEY_ENABLE_WIREFRAME, &wireframe, &max))
		fill_mode = wireframe ? GL_LINE : GL_FILL;
	else
		fill_mode = GL_FILL;
	glPolygonMode(GL_FRONT_AND_BACK, fill_mode);

	max = 1;
	if ((AI_SUCCESS == aiGetMaterialIntegerArray(mtl, AI_MATKEY_TWOSIDED, &two_sided, &max)) && two_sided)
		glDisable(GL_CULL_FACE);
	else
		glEnable(GL_CULL_FACE);
}

void MyOpengl::recursive_render(const C_STRUCT aiScene* sc, const C_STRUCT aiNode* nd)
{
	unsigned int i;
	unsigned int n = 0, t;
	C_STRUCT aiMatrix4x4 m = nd->mTransformation;
	aiTransposeMatrix4(&m);
	glPushMatrix();
	glMultMatrixf((float*)&m);
	for (; n < nd->mNumMeshes; ++n) {
		const C_STRUCT aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]];

		apply_material(sc->mMaterials[mesh->mMaterialIndex]);

		if (mesh->mNormals == NULL) {
			glDisable(GL_LIGHTING);
		}
		else {
			glEnable(GL_LIGHTING);
		}

		for (t = 0; t < mesh->mNumFaces; ++t) {
			const C_STRUCT aiFace* face = &mesh->mFaces[t];
			GLenum face_mode;

			switch (face->mNumIndices) {
			case 1: face_mode = GL_POINTS; break;
			case 2: face_mode = GL_LINES; break;
			case 3: face_mode = GL_TRIANGLES; break;
			default: face_mode = GL_POLYGON; break;
			}

			glBegin(face_mode);

			for (i = 0; i < face->mNumIndices; i++) {
				int index = face->mIndices[i];
				if (mesh->mColors[0] != NULL)
					glColor4fv((GLfloat*)&mesh->mColors[0][index]);
				if (mesh->mNormals != NULL)
					glNormal3fv(&mesh->mNormals[index].x);
				glVertex3fv(&mesh->mVertices[index].x);
			}

			glEnd();
		}

	}
	for (n = 0; n < nd->mNumChildren; ++n) {
		recursive_render(sc, nd->mChildren[n]);
	}

	glPopMatrix();
}

int MyOpengl::loadasset(const char* path)
{
	/**
	* aiImportFile:读取给定文件并返回其内容。
	* 如果调用成功,导入的数据将以Aisene结构返回。作为data的根。
	* 数据是只读的,它保留ASIMP库的属性,在调用aiReleaseImport()之前将保持稳定。
	* 完成后,调用aiReleaseImport()释放与此文件关联的资源。
	* 如果导入失败,则返回NULL。调用aiGetErrorString()检索人类可读的错误文本。
	*
	*/
	scene = aiImportFile(path, aiProcessPreset_TargetRealtime_MaxQuality);
	if (scene) {
		get_bounding_box(&scene_min, &scene_max);
		scene_center.x = (scene_min.x + scene_max.x) / 2.0f;
		scene_center.y = (scene_min.y + scene_max.y) / 2.0f;
		scene_center.z = (scene_min.z + scene_max.z) / 2.0f;
		return 0;
	}
	return 1;
}

void MyOpengl::get_bounding_box_for_node(const C_STRUCT aiNode* nd,
	C_STRUCT aiVector3D* min,
	C_STRUCT aiVector3D* max,
	C_STRUCT aiMatrix4x4* trafo
) {
	C_STRUCT aiMatrix4x4 prev;
	unsigned int n = 0, t;

	prev = *trafo;
	//将两个4x4矩阵相乘。第一个参数接收结果。
	aiMultiplyMatrix4(trafo, &nd->mTransformation);

	for (; n < nd->mNumMeshes; ++n) {
		const C_STRUCT aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]];
		for (t = 0; t < mesh->mNumVertices; ++t) {

			C_STRUCT aiVector3D tmp = mesh->mVertices[t];
			//用4x4矩阵变换向量
			aiTransformVecByMatrix4(&tmp, trafo);

			min->x = aisgl_min(min->x, tmp.x);
			min->y = aisgl_min(min->y, tmp.y);
			min->z = aisgl_min(min->z, tmp.z);

			max->x = aisgl_max(max->x, tmp.x);
			max->y = aisgl_max(max->y, tmp.y);
			max->z = aisgl_max(max->z, tmp.z);
		}
	}

	for (n = 0; n < nd->mNumChildren; ++n) {
		get_bounding_box_for_node(nd->mChildren[n], min, max, trafo);
	}
	*trafo = prev;
}

void MyOpengl::get_bounding_box(C_STRUCT aiVector3D* min, C_STRUCT aiVector3D* max)
{
	C_STRUCT aiMatrix4x4 trafo;
	aiIdentityMatrix4(&trafo);//获取4x4身份矩阵。

	min->x = min->y = min->z = 1e10f;
	max->x = max->y = max->z = -1e10f;
	get_bounding_box_for_node(scene->mRootNode, min, max, &trafo);
}

void MyOpengl::print_error(const char* msg) {
	printf("ERROR: %s\n", msg);
}
#pragma endregion

#pragma region 鼠标控制旋转
void MyOpengl::OnLButtonDown(UINT nFlags, CPoint point)
{
	mouseDown = true;
	xdiff = point.x - yrot;
	ydiff = -point.y + xrot;

	CWnd::OnLButtonDown(nFlags, point);
}


void MyOpengl::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	mouseDown = false;
	CWnd::OnLButtonUp(nFlags, point);
}


void MyOpengl::OnMouseMove(UINT nFlags, CPoint point)
{
	if (mouseDown) {
		yrot = point.x - xdiff;
		xrot = point.y + ydiff;
		if (yrot >= 360)
			yrot = yrot % 360;
		else if (yrot <= -360)
			yrot = yrot % 360;

		if (xrot >= 90)
			xrot = 90;
		else if (xrot <= -90)
			xrot = -90;

		Invalidate();//刷新界面
	}
	CWnd::OnMouseMove(nFlags, point);
}

void MyOpengl::updateMouseDownState() 
{
	mouseDown = false;
}
#pragma endregion

对话窗口类中调用自定义的MyOpengl类:

BOOL COpenGLTest04Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	CWnd* wnd = GetDlgItem(IDC_STATIC_PIC);
	myOpengl = new MyOpengl(wnd);    //先在.h中声明:MyOpengl* myOpengl;
	CRect rect; //在这个矩形中画图

	//获取控件相对于屏幕的位置
	wnd->GetWindowRect(&rect);
	
	myOpengl->Create(NULL,NULL,WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE,rect,this,0);

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

有个问题就是:当没有在控件内松开鼠标是不会触发OnLButtonUp事件的,我的解决办法就是在对话窗口类中也添加OnLButtonUp事件(主要是我的对话框是满屏的)

void COpenGLTest04Dlg::OnLButtonUp(UINT nFlags, CPoint point)
{
	myOpengl->updateMouseDownState();//修改鼠标按下状态
	CDialogEx::OnLButtonUp(nFlags, point);
}

你可能感兴趣的:(MFC,C++,C++,MFC,OpenGL)