[OpenGL] 场景管理四叉树demo


        所谓的四叉树场景管理就是先对场景进行预处理,用四叉树存储所有物体,运行中通过四叉树查询,只渲染当前区域的物体,以提高帧率,减少GPU的负担。


        整个demo,效果已经基本出来了,但在精度上有点问题,可能是因为邻域选的比较小,导致有时候视线里会突然冒出来一个物体。下图中红点代表人物。


       俯视:


        

        透视



包括三个类:

 

        场景管理类

        四叉树类

        物体类

 

物体类包含了物体的性质,目前以正方体代替物体,只包含边长,颜色和位置。实际中扩展的版本应该包含:

 

        顶点坐标

        下标索引

        纹理索引

        颜色数据

        ……

 

 

四叉树类包括以下几个部分:

 

性质:

        最小的地图砖块边长

        四叉树层数

        四叉树头节点

 

功能:

        插入节点(初始化)

        查找节点

 

场景管理包括以下几个部分:

 

场景创建:

 

        设计的四叉树比较简单,创建的方式用的是按层次建树,而不是递归方式,不允许一个物体同时处在两个区域,所以前期需要用特殊的地图编辑器(按理来说应该是一个桌面小软件,编辑好之后可以存成一个四叉树文件)。

        在这里,为了方便,写了一个简单的自动场景生成,详情见函数randBuildTree(),以迭代的方式一层层随机生成方块的位置。

 

数据结构:

 

std::set<Object*> set;//集合

std::list<Object*> list[MAX];//链表

 

        1. 物体的集合,包含了所有当前帧需要渲染的物体。(渲染内容)

        2. 当前位置邻域包含的物体,是一个链表的数组,数组的每个元素对应邻域的一块,每一块对应一个链表,包含了这一块的所有物体。(预备内容)

 

       之所以要把做一个邻域的链表,是因为不同的list查询的结果可能有重复的,所以最终渲染的时候要把物体放到集合中,保证唯一性。


         

    如果邻域选取3x3,那么数组大小为9,编号如上。

 

场景更新

 

           因为四叉树有很多层,不同层对应着不同尺度的物体,我们首先需要定义主角所在的层数,然后以这个层数为基础来搜索邻域。

         每一帧都调用一次更新函数,首先检查位置是否发生变化,可能发生的变化是:向右移动一格,向左移动一格,向上移动一格,向下移动一格。如果发生了,那么执行这样的操作:

 

 

 



 

        (1) 在预备链表中,移除红色区域的物体

        (2) 在预备链表中,加入黄色区域的物体

        (3) 更新显示的集合


         加入和移除是通过【四叉树的查找】来实现的。

         盗用了一下别人的图来显示一下这个过程:

         

代码

object.h

#pragma once
struct vec {
	float x;
	float y;
	float z;
};
class Object
{
public:
	float radius;
	float center_x;
	float center_y;
	vec color;
	Object(float _radius, float cx, float cy);
	void draw();
};

sceneManage.h

#pragma once
#include "tree.h"
#include "object.h"
#include <set>
#include <list>

class sceneManage
{
public:
	enum {
		MAX = 9,
		SIZE = 3,
		LAYER = 6,
		CURLAYER = 5,
		BLOCKSIZE = 1,
		LENGTH = 64,
	};
private:

	bool *isUsed;
	quadtree* tree;
	std::set<Object*> set;
	std::list<Object*> list[MAX];
	std::list<Object*> alllist;
	int cur_x;
	int cur_y;
	int n;
	int offset;
	void removeSet(int i, int j, int k);
	void removeSet(int i);
	void updateSet(int i, int j, int k);
	void updateSet(int i);
	void randBuildTree();
	void printMap();
public:

	std::list<Object*>& getAllObj();
	void init(float x, float y);
	std::set<Object*>& getObj();
	void moveTo(float x, float y);
	sceneManage();
};

tree.h

#pragma once
#include <list>
#include "object.h"

struct Box
{
	float xmin;
	float xmax;
	float ymin;
	float ymax;
	Box();
	Box(float _xmin, float _xmax, float _ymin, float _ymax);
};

struct treeNode {
	Box box;
	treeNode* child[4];
	std::list<Object*> List;
	bool hasChild;
	treeNode();
	void setBox(treeNode* parent, int i);
};

class quadtree
{
private:
	float blockSize;
	int layer;
	treeNode* head;
	std::list<Object*> allObj;
public:
	void print();
	std::list<Object*> findAll();
	std::list<Object*> find(float x, float y, int layer = -1);
	quadtree(float size, int l);
	void add(int layer, int x, int y, Object* obj);
};

object.cpp

#include "object.h"
#include <stdlib.h>
#include <stdio.h>
#include "gl/glut.h"

Object::Object(float r, float cx, float cy) :radius(r), center_x(cx), center_y(cy)
{
	color.x = (1.0*(rand() % 100) / 100);
	color.y = (1.0*(rand() % 100) / 100);
	color.z = (1.0*(rand() % 100) / 100);
	//printf("radius = %f \n", radius);
}

void Object::draw()
{
	glPushMatrix();
	glColor3f(color.x, color.y, color.z);
	//printf("%f %f %f\n", center_x - radius / 2, radius / 2, center_y - radius / 2);
	glTranslatef(center_x - radius / 2, radius / 2, center_y - radius / 2);
	glutSolidCube(radius);
	glPopMatrix();
}

sceneManage.cpp

#include "sceneManage.h"
#include <fstream>
#include <time.h>

sceneManage::sceneManage()
{
	srand(time(nullptr));
	n = pow(2.0, LAYER);
	offset = 2 * BLOCKSIZE;
	tree = new quadtree(BLOCKSIZE, LAYER);
	// block大小为1, 层数为6
	randBuildTree();
}

void sceneManage::init(float x, float y)
{
	list[0] = tree->find(x - offset, y + offset, LAYER);
	list[1] = tree->find(x - offset, y, LAYER);
	list[2] = tree->find(x - offset, y - offset, LAYER);
	list[3] = tree->find(x, y - offset, LAYER);
	list[4] = tree->find(x + offset, y - offset, LAYER);
	list[5] = tree->find(x + offset, y, LAYER);
	list[6] = tree->find(x + offset, y + offset, LAYER);
	list[7] = tree->find(x, y + offset, LAYER);
}

void sceneManage::printMap()
{
	std::ofstream out("map.txt");
	for (int i = 0; i < 64; i++) {
		for (int j = 0; j < 64; j++) {
			if (isUsed[64 * i + j]) {
				out << "X ";
			}
			else {
				out << ". ";
			}
		}
		out << "\n";
	}
	out << "\n";
}

void sceneManage::randBuildTree()
{
	isUsed = new bool[4096];
	for (int i = 0; i < 4096; i++) {
		isUsed[i] = false;
	}
	int num[6];
	num[0] = 0; // 4
	num[1] = 0; // 16
	num[2] = 2; // 64
	num[3] = 20; // 256
	num[4] = 40; // 1024
	num[5] = 200; // 4096

	int size = 64;
	int half = size >> 1;
	int nsize = 2;
	for (int k = 0; k < 6; k++) {
		//printf("size = %d half=%d\n", size,half);
		for (int i = 0; i < num[k]; i++) {
			int r;
			do {
				r = rand() % 4096;
			} while (isUsed[r]);
			int r1 = (r / 64) % nsize;//x
			int r2 = (r % 64) % nsize;//y
			float h = 1.0 * half / 2;
			//printf("h = %.1f\n", h);
			//printf("r1 = %f r2 = %f\n", -32 + h + r2 *half, -32 + h + r1 *half);
			//printf("h = %.0f r = %.0f cx = %.0f cy = %.0f ", h, half, -h + r2 *half, -h + r1 *half);
			Object* obj = new Object(half, -LENGTH / 2 * BLOCKSIZE + h + r2 *half, -LENGTH / 2 * BLOCKSIZE + h + r1 *half);
			tree->add(k + 1, r1, r2, obj);

			for (int k1 = r1 * half; k1 < (r1 + 1) * half; k1++) {
				for (int k2 = r2 * half; k2 < (r2 + 1) * half; k2++) {
					isUsed[k1 * 64 + k2] = true;
				}
			}
		}
		size = half;
		half = size >> 1;
		nsize = nsize << 1;
	}
	//tree->print();
	printMap();
}

void sceneManage::removeSet(int i)
{
	std::list<Object*>::iterator it;
	for (it = list[i].begin(); it != list[i].end(); it++) {
		set.erase(*it);
	}
}

void sceneManage::removeSet(int i, int j, int k)
{
	std::list<Object*>::iterator it;
	for (it = list[i].begin(); it != list[i].end(); it++) {
		set.erase(*it);
	}
	for (it = list[j].begin(); it != list[j].end(); it++) {
		set.erase(*it);
	}
	for (it = list[k].begin(); it != list[k].end(); it++) {
		set.erase(*it);
	}
}

void sceneManage::updateSet(int i)
{
	std::list<Object*>::iterator it;
	for (it = list[i].begin(); it != list[i].end(); it++) {
		set.insert(*it);
	}
}

void sceneManage::updateSet(int i, int j, int k)
{
	std::list<Object*>::iterator it;
	for (it = list[i].begin(); it != list[i].end(); it++) {
		set.insert(*it);
	}
	for (it = list[j].begin(); it != list[j].end(); it++) {
		set.insert(*it);
	}
	for (it = list[k].begin(); it != list[k].end(); it++) {
		set.insert(*it);
	}
}

std::list<Object*>& sceneManage::getAllObj()
{
	return tree->findAll();
}

std::set<Object*>& sceneManage::getObj()
{
	return set;
}

void sceneManage::moveTo(float x, float y)
{
	x /= BLOCKSIZE;
	y /= BLOCKSIZE;
	if (x == cur_x && y == cur_y) {
		return;
	}
	if (x == cur_x + 1) {
		printf("move right\n");
		removeSet(0, 1, 2);
		list[0] = tree->find(x - offset, y + offset, CURLAYER);
		list[1] = tree->find(x - offset, y, CURLAYER);
		list[2] = tree->find(x - offset, y - offset, CURLAYER);
		updateSet(0, 1, 2);
	}
	else if (y == cur_y + 1) {
		printf("move front\n");
		removeSet(2, 3, 4);
		list[2] = tree->find(x - offset, y - offset, CURLAYER);
		list[3] = tree->find(x, y - offset, CURLAYER);
		list[4] = tree->find(x + offset, y - offset, CURLAYER);
		updateSet(2, 3, 4);
	}
	else if (x == cur_x - 1) {
		printf("move left\n");
		removeSet(4, 5, 6);
		list[4] = tree->find(x + offset, y - offset, CURLAYER);
		list[5] = tree->find(x + offset, y, CURLAYER);
		list[6] = tree->find(x + offset, y + offset, CURLAYER);
		updateSet(4, 5, 6);
	}
	else if (y == cur_y - 1) {
		printf("move back\n");
		removeSet(0, 7, 6);
		list[0] = tree->find(x - offset, y + offset, CURLAYER);
		list[7] = tree->find(x, y + offset, CURLAYER);
		list[6] = tree->find(x + offset, y + offset, CURLAYER);
		updateSet(0, 7, 6);
	}
	list[8] = tree->find(x, y, CURLAYER);
	removeSet(8);
	updateSet(8);
	cur_x = x;
	cur_y = y;
}

tree.cpp

#include "tree.h"
#include <math.h>
#include <queue>
//#include <QTextStream>
//static QTextStream cout(stdout, QIODevice::WriteOnly);


Box::Box() {}
Box::Box(float _xmin, float _xmax, float _ymin, float _ymax) :
xmin(_xmin), xmax(_xmax), ymin(_ymin), ymax(_ymax)
{

}



treeNode::treeNode()
{
	for (int i = 0; i < 4; i++) {
		child[i] = nullptr;
	}
	hasChild = false;
}

void treeNode::setBox(treeNode* parent, int i)
{
	switch (i) {
	case 0: {
				box.xmax = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
				box.xmin = parent->box.xmin;
				box.ymax = parent->box.ymax;
				box.ymin = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
				break;
	}
	case 1: {
				box.xmax = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
				box.xmin = parent->box.xmin;
				box.ymax = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
				box.ymin = parent->box.ymin;
				break;
	}
	case 2: {
				box.xmax = parent->box.xmax;
				box.xmin = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
				box.ymax = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
				box.ymin = parent->box.ymin;
				break;
	}
	case 3: {
				box.xmax = parent->box.xmax;
				box.xmin = parent->box.xmin + (parent->box.xmax - parent->box.xmin) / 2;
				box.ymax = parent->box.ymax;
				box.ymin = parent->box.ymin + (parent->box.ymax - parent->box.ymin) / 2;
				break;
	}
	}
}

std::list<Object*> quadtree::findAll()
{
	return allObj;
}

quadtree::quadtree(float size, int l)
{
	int n = pow(2, l - 1);
	blockSize = size;
	layer = l;
	head = new treeNode();
	head->box.xmin = -n * size;
	head->box.xmax = n * size;
	head->box.ymin = -n * size;
	head->box.ymax = n * size;
}

std::list<Object*> quadtree::find(float x, float y, int layer)
{
	treeNode* node = head;
	treeNode* parent = nullptr;
	int count = 0;
	while (node != nullptr) {
		//	printf("count = %d\n", count);
		if (count == layer) {
			break;
		}
		int index;
		//	printf("%f %f\n", node->box.xmax - node->box.xmin, node->box.ymax - node->box.ymin);
		int i = 2 * (x + head->box.xmax) / (node->box.xmax - node->box.xmin);
		i = i % 2;
		int j = 2 * (y + head->box.ymax) / (node->box.ymax - node->box.ymin);
		j = j % 2;
		if (i == 0 && j == 0) {
			index = 1;
		}
		else if (i == 0 && j == 1) {
			index = 0;
		}
		else if (i == 1 && j == 0) {
			index = 2;
		}
		else if (i == 1 && j == 1) {
			index = 3;
		}
		parent = node;
		node = node->child[index];
		//	printf("index = %d\n", index);
		count++;
	}
	return parent->List;
}

void quadtree::add(int layer, int x, int y, Object* obj)
{
	allObj.push_back(obj);
	int n = pow(2, layer);
	int *index = new int[layer];
	int half = n >> 1;
	for (int i = layer - 1; i >= 0; i--) {
		if (x % 2 == 1) {
			if (y % 2 == 1) {
				index[i] = 2;
			}
			else {
				index[i] = 1;
			}
		}
		else {
			if (y % 2 == 1) {
				index[i] = 3;
			}
			else {
				index[i] = 0;
			}
		}
		x >>= 1;
		y >>= 1;
	}
	treeNode* node = head;
	for (int i = 0; i < layer - 1; i++) {
		if (!node->child[index[i]]) {
			node->child[index[i]] = new treeNode();
			node->child[index[i]]->setBox(node, index[i]);
		}
		if (node != head)node->List.push_back(obj);
		node->hasChild = true;
		node = node->child[index[i]];
	}
	treeNode* childNode = new treeNode();
	childNode->List.push_back(obj);
	if (node != head)node->List.push_back(obj);
	node->child[index[layer - 1]] = childNode;
	childNode->setBox(node, index[layer - 1]);
	node->hasChild = true;
}

void quadtree::print()
{
	treeNode* tmp;
	std::queue<treeNode*>q;
	q.push(head);
	int cur = 1;
	int curnum = 1;
	int nextnum = 0;
	while (!q.empty()) {
		tmp = q.front();
		q.pop();
		if (tmp->hasChild) {
			curnum--;
			//cout << "[";
			printf("[");
			for (int i = 0; i < 4; i++) {
				if (tmp->child[i]) {
					std::list<Object*>::iterator it;
					for (it = tmp->child[i]->List.begin(); it != tmp->child[i]->List.end(); it++) {
						printf("{%.0f %.0f %.0f}", (*it)->radius, (*it)->center_x, (*it)->center_y);
					}
					q.push(tmp->child[i]);
					nextnum++;
				}
				else printf("_");
				if (i != 3)printf(",");

			}
			printf("]");
		}
		if (curnum == 0) {
			printf("\n");
			cur++;
			curnum = nextnum;
			nextnum = 0;
		}
	}
}

main.cpp

#define _CRT_SECURE_NO_WARNINGS        
//simd
#include <stdlib.h>         
#include <time.h>             
#include "sceneManage.h"
#include"gl/glut.h" 

float cx = 0, cz = 0;
float center[] = { 0, 0, 0 };
float eye[] = { 0, 100,3};
float tx, ty = 0, ax, ay = 0, mx, my, zoom = 0;
bool isLine = false;
bool isDown = false;
bool isDrawAll = false;
sceneManage* scene;
float step = 1.0f;

void getFPS()
{
	static int frame = 0, time, timebase = 0;
	static char buffer[256]; //字符串缓冲区  

	frame++;
	time = glutGet(GLUT_ELAPSED_TIME);
	//返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒  
	if (time - timebase > 1000) { //时间间隔差大于1000ms时  
		sprintf(buffer, "FPS:%4.2f\n position(%.2f,%.2f)",
			frame*1000.0 / (time - timebase),cx,cz); //写入buffer中  
		//printf("%f\n", frame*1000.0 / (time - timebase));
		timebase = time; //上一次的时间间隔  
		frame = 0;
	}
	

	char *c;
	glColor3f(0,0,0);
	glDisable(GL_DEPTH_TEST);     // 禁止深度测试  
	glMatrixMode(GL_PROJECTION);  // 选择投影矩阵  
	glPushMatrix();               // 保存原矩阵  
	glLoadIdentity();             // 装入单位矩阵  
	glOrtho(0, 480, 0, 480, -1, 1);    // 位置正投影  
	glMatrixMode(GL_MODELVIEW);   // 选择Modelview矩阵  
	glPushMatrix();               // 保存原矩阵  
	glLoadIdentity();             // 装入单位矩阵  
	glRasterPos2f(10, 10);
	for (c = buffer; *c != '\0'; c++) {
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *c); //绘制字符  
	}
	glMatrixMode(GL_PROJECTION);  // 选择投影矩阵  
	glPopMatrix();                // 重置为原保存矩阵  
	glMatrixMode(GL_MODELVIEW);   // 选择Modelview矩阵  
	glPopMatrix();                // 重置为原保存矩阵  
	glEnable(GL_DEPTH_TEST);      // 开启深度测试  
}

void reshape(int width, int height)
{
	if (height == 0) height = 1;
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	float whRatio = (GLfloat)width / (GLfloat)height;
	gluPerspective(45, whRatio, 1, 500);
	glMatrixMode(GL_MODELVIEW);
}

void idle()
{
	glutPostRedisplay();
}

void init(void)
{
	//glClearColor(1.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);
	glColor4f(1.0, 1.0, 1.0, 1.0f);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

	scene = new sceneManage();
	scene->init(cx, -cz);
}

void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(1, 1, 1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0);

	
	if (isLine)glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glPushMatrix();
	glColor3f(0.3f, 0, 0);
	glTranslatef(cx, 0.1f, cz);
	glutSolidSphere(0.3f, 20, 10);
	glPopMatrix();

	/*
	glPushMatrix();
	glTranslatef(cx, 0.1, cz);
	glColor3f(1,0, 0);
	glRotatef(90, 1, 0, 0);
	glRectd(-4, -4, 4, 4);
	glPopMatrix();*/

	glPushMatrix();
	glColor3f(0.2, 0.2, 0.2);
	glRotatef(90, 1, 0, 0);
	for (int i = 0; i < 64; i++){
		for (int j = 0; j < 64; j++){
			glPushMatrix();
			glTranslatef(-scene->LENGTH / 2 * scene->BLOCKSIZE + i*scene->BLOCKSIZE, -scene->LENGTH / 2 * scene->BLOCKSIZE + j*scene->BLOCKSIZE,0);
			glRectd(0, 0,scene->BLOCKSIZE, scene->BLOCKSIZE);
			glPopMatrix();
		}
	}
	//glRectd(-scene->LENGTH / 2 * scene->BLOCKSIZE, -scene->LENGTH / 2 * scene->BLOCKSIZE, scene->LENGTH / 2 * scene->BLOCKSIZE, scene->LENGTH / 2 * scene->BLOCKSIZE);
	glPopMatrix();

	if (isDrawAll) {
		std::list<Object*>::iterator it;
		std::list<Object*> list = scene->getAllObj();
		printf("size = %d\n", list.size());
		for (it = list.begin(); it != list.end(); it++) {
			(*it)->draw();
		}
	}
	else {
		scene->moveTo(cx, -cz);
		glColor3f(0, 0, 0);
		glPushMatrix();
		//glutSolidCube(3);
		std::set<Object*> set = scene->getObj();
		std::set<Object*>::iterator it;
		int count = 0;
		for (it = set.begin(); it != set.end(); it++) {
			count++;
			(*it)->draw();
		}
		//if(count!=0)printf("count = %d\n",count);
		glPopMatrix();
	}
	getFPS();
	glutSwapBuffers();
}

void myMouse(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON) {
		if (state == GLUT_DOWN) {
			isDown = true;
			mx = x;
			my = y;
		}
		else if (state == GLUT_UP) {
			isDown = false;
		}
	}
	glutPostRedisplay();
}

void mouseMotion(int x, int y)
{
	if (isDown) {
		ax += 1.0f*(y - my) / 10.0f;
		ay += 1.0f*(x - mx) / 10.0f;
		mx = x;
		my = y;
	}
	glutPostRedisplay();
}

void myKeyboard(unsigned char key, int x, int y)
{
	//printf("%d  \n",key);
	switch (key) {
	case 'a': { //左移 
				  cx -= step;
				  eye[0] -= step;
				  center[0] -= step;
				  if (cx < -31) {
					  cx = -31;
					  eye[0] = -31;
					  center[0] = -31;
				  }
				  break;
	}
	case 'd': { //右移    
				  cx += step;
				  eye[0] += step;
				  center[0] += step;
				  if (cx > 31) {
					  cx = 31;
					  eye[0] = 31;
					  center[0] = 31;
				  }
				  break;
	}
	case 'w': { //上移    
				  cz -= step;
				  eye[2] -= step;
				  center[2] -= step;
				  if (cz < -31) {
					  cz = -31;
					  eye[2] = -31;
					  center[2] = -31;
				  }
				  break;
	}
	case 's': { //下移    
				  cz += step;
				  eye[2] += step;
				  center[2] += step;
				  if (cz > 31) {
					  cz = 31;
					  eye[2] = 31;
					  center[2] = 31;
				  }
				  break;
	}
	case 'z': { //后移    
				  zoom += 1;
				  break;
	}
	case 'c': { //前移    
				  zoom -= 1;
				  break;
	}
	case 'p': {
				  // 切换绘制模式  
				  if (isLine) {
					  isLine = false;
				  }
				  else isLine = true;
				  break;
	}
	case 'm': {
				  if (isDrawAll) {
					  isDrawAll = false;
				  }
				  else isDrawAll = true;
	}
	}
	//glutPostRedisplay();
}

int main(int argc, char *argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
	glutInitWindowSize(800, 600);
	int windowHandle = glutCreateWindow("Simple GLUT App");
	glutDisplayFunc(redraw);
	glutReshapeFunc(reshape);
	glutMouseFunc(myMouse);
	glutMotionFunc(mouseMotion);
	glutKeyboardFunc(myKeyboard);
	glutIdleFunc(idle);
	init();
	glutMainLoop();
	//system("pause");
	return 0;
}


  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值