計算機圖形學:虛擬服裝設計
要求:
(1)讀取給定的模型檔案並能夠分別使用線模式、點模式、填充模式進行渲染。
(2)對模型進行平移、縮放和旋轉。
(3)改變視點,對模型進行三維漫遊。
(4)為模型中的衣裙新增物理模型,要求能夠拖動某個節點或面片展示區域性形變效果。
(5)為模型中的衣裙新增碰撞檢測,使得衣裙能在重力作用下懸掛在衣架上,以及模擬風力作用下的衣裙飄動效果。
一、實驗環境
1. 作業系統:windows 8.1
2. 程式設計軟體:visual studio 2013
二、實驗內容
l 關鍵程式碼分析
(1)讀取obj模型
void ReadPIC()
{
int dianshu;
dianshu = 0;
ifstream ifs("dress.obj"); //用檔案輸入流讀入檔案
string s;
Mian *f;
POINT3 *v;
FaXiangLiang *vn;
WenLi *vt;
while (getline(ifs, s)) //從標準輸入流中讀取一個字串,儲存到字串string(物件)中
{
if (s.length()<2)continue
if (s[0] == 'v')
{
if (s[1] == 't') //vt 紋理 0.001992 0.001992
{
istringstream in(s); //定義一個字串輸入流的物件in,將s中所包含的字串放入in 物件中
vt = new WenLi();
string head;
in >> head >> vt->TU >> vt->TV;
m_pic.VT.push_back(*vt);
}
else if (s[1] == 'n') //vn 法向量 0.000000 -1.000000 0.000000
{
istringstream in(s);
vn = new FaXiangLiang();
string head;
in >> head >> vn->NX >> vn->NY >> vn->NZ;
m_pic.VN.push_back(*vn);
}
else //v 點 - 0.500000 - 0.500000 0.500000
{
istringstream in(s);
v = new POINT3();
string head;
in >> head >> v->X >> v->Y >> v->Z;
m_pic.V.push_back(*v);
dianshu++;
}
//printf("dianshu=%d",dianshu);
}
else if (s[0] == 'f')//f 面 1/1/1 2/2/2 3/3/3 這個面的頂點、紋理座標、法向量的索引 //f 2443//2656 2442//2656 2444//2656 面
{
for (int k = s.size() - 1; k >= 0; k--)
{
if (s[k] == '/')s[k] = ' ';
}
istringstream in(s);
f = new Mian();
string head;
in >> head;
int i = 0;
while (i<3)
{
if (m_pic.V.size() != 0)
{
in >> f->V[i];
f->V[i] -= 1;
}
if (m_pic.VT.size() != 0)
{
in >> f->T[i];
f->T[i] -= 1;
}
if (m_pic.VN.size() != 0)
{
in >> f->N[i];
f->N[i] -= 1;
}
i++;
}
m_pic.F.push_back(*f);
}
}
}
讀取檔案dress.obj,依據檔案中的索引將資料分別存入法向量m_pic.VN,紋理座標m_pic.VT,頂點m_pic.V,依據面的索引,法向量紋理座標頂點按所在三角形面分別存入m_pic.F中,方便後面繪製以及變換操作。由於資料量大,讀取檔案函式ReadPIC()設定了標誌位,只在檔案啟動時執行1次。
(2)繪製模型
void InitScene()
{
static GLint flag = 1;//設定標誌 位,第一次讀取obj檔案到陣列,後面直接顯示
if (flag == 1)
{
flag = 0;
ReadPIC();
}
glClearColor(1.000f, 1.000f, 1.000f, 1.0f); //Background color
// TODO: Replace the following sample code with your initialization code.
// Activate lighting and a light source
//用於啟用各種功能。具體功能由引數決定。與glDisable相對應。glDisable用以關閉各項功能。
glEnable(GL_LIGHT0);//啟用0號燈到7號燈(光源) 光源要求由函式glLight函式來完成
glEnable(GL_LIGHTING);//啟用燈源
glEnable(GL_DEPTH_TEST);//啟用深度測試。 根據座標的遠近自動隱藏被遮住的圖形(材料)
glEnable(GL_TEXTURE_2D); // 啟用二維紋理
// Define material parameters
static GLfloat glfMatAmbient[] = { 0.000f, 0.450f, 1.000f, 1.0f };
static GLfloat glfMatDiffuse[] = { 0.000f, 0.000f, 0.580f, 1.0f };
static GLfloat glfMatSpecular[] = { 1.000f, 1.000f, 1.000f, 1.0f };
static GLfloat glfMatEmission[] = { 0.000f, 0.000f, 0.000f, 1.0f };
static GLfloat fShininess = 128.000f;
// Set material parameters
//指定用於光照計算的當前材質屬性。引數face的取值可以是GL_FRONT、GL_BACK或GL_FRONT_AND_BACK,指出材質屬性將應用於物體的哪面。
//void glMaterial{if}(GLenum face, GLenum pname, TYPE param);
glMaterialfv(GL_FRONT, GL_AMBIENT, glfMatAmbient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, glfMatDiffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, glfMatSpecular);
glMaterialfv(GL_FRONT, GL_EMISSION, glfMatEmission);
glMaterialf(GL_FRONT, GL_SHININESS, fShininess);
}
void DrawScene()
{
// TODO: Replace the following sample code with your code to draw the scene.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除螢幕及深度快取
glLoadIdentity(); // 重置模型觀察矩陣
gluLookAt(viewer[0], viewer[1], viewer[2], 0.0f, -4.0f, -9.0f, 0.0, 1.0, 0.0);
glTranslatef(0.0f, -13.0f, -8.0f); // 移入螢幕 5.0
if (trackballMove&&translating&&!scaling&&!rotating)
{
glTranslatef(tran[0], tran[1], tran[2]);
//printf("tran[0]=%f, tran[1]=%f, tran[2]=%f\n", tran[0], tran[1], tran[2]);
}
if (trackballMove&&!translating&&scaling&&!rotating)
{
glScalef(scale[0], scale[1], scale[2]);
}
if (trackballMove&&!translating&&!scaling&&rotating)
{
glRotatef(angle0, axis[0], axis[1], axis[2]);//旋轉(角度,軸)
}
GLCube();// Draw a cube
glFlush();
}
void GLCube()
{
if (dian_model&&!xian_model&&!mian_model)
//if (0)
{
for (int i = 0; i < (m_pic.F.size()); i++)
//for (int i = 0; i<7959; i++)
{
glBegin(GL_POINTS);//點模式
if (qizhi0 == 1)
{
printf("dianmoshi");
qizhi0 = 0;
}
/*glVertex3f(m_pic.V[m_pic.F[3 * i].V[0]].X / YU, m_pic.V[m_pic.F[3 * i].V[0]].Y / YU, m_pic.V[m_pic.F[3 * i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[3 * i].V[1]].X / YU, m_pic.V[m_pic.F[3 * i].V[1]].Y / YU, m_pic.V[m_pic.F[3 * i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[3 * i].V[2]].X / YU, m_pic.V[m_pic.F[3 * i].V[2]].Y / YU, m_pic.V[m_pic.F[3 * i].V[2]].Z / YU);
*/
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
//glVertex3f(10*(model.vertices[3 * i]), 10*(model.vertices[3 * i + 1]), 10*(model.vertices[3 * i + 2]));
glEnd();
}
}
if (!dian_model&&xian_model&&!mian_model)
//if (0)
{
for (int i = 0; i<(m_pic.F.size()); i++)
{
glBegin(GL_LINES);//線模式
if (qizhi1 == 1)
{
printf("xianmoshi");
qizhi1 = 0;
}
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
//glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
glBegin(GL_LINES);
//glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
glBegin(GL_LINES);
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
//glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
}
}
if (!dian_model&&!xian_model&&mian_model)
//if (1)
{
for (int i = 0; i<(m_pic.F.size()); i++)
{
//printf("m_pic.F.size()=%d", m_pic.F.size());//16213
glBegin(GL_TRIANGLES);//面模式// 繪製三角形
if (qizhi2 == 1)
{
printf("mianmoshi");
qizhi2 = 0;
}
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[0]].TU, m_pic.VT[m_pic.F[i].T[0]].TV); //紋理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[0]].NX, m_pic.VN[m_pic.F[i].N[0]].NY, m_pic.VN[m_pic.F[i].N[0]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU); // 上頂點
//printf("dian:%f,%f,%f\n",m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[1]].TU, m_pic.VT[m_pic.F[i].T[1]].TV); //紋理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[1]].NX, m_pic.VN[m_pic.F[i].N[1]].NY, m_pic.VN[m_pic.F[i].N[1]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU); // 左下
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[2]].TU, m_pic.VT[m_pic.F[i].T[2]].TV); //紋理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[2]].NX, m_pic.VN[m_pic.F[i].N[2]].NY, m_pic.VN[m_pic.F[i].N[2]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU); // 右下
glEnd(); // 三角形繪製結束
}
}
}
InitScene()函式中設定了場景的光源和模型的材質。DrawScene()中繪製模型,呼叫函式GLCube()實現具體繪製,並由標誌位設定繪製模式:點模式,線模式和麵模式;同時使用庫函式實現模型旋轉縮放平移變換操作,具體引數由虛擬球設定;同時gluLookAt(viewer[0], viewer[1], viewer[2], 0.0f, -4.0f, -9.0f, 0.0, 1.0, 0.0);函式實現三維漫遊,viewer[0], viewer[1], viewer[2]數值由鍵盤設定,後面的視點方向等用於使模型位於世界座標系正中間。
(3)物理模型
Cloth::Cloth(GLMmodel *model)
{
//
SpringConstant = 1000;//彈力
DampingFactor = 10;//阻尼
density = 1.2;//密度
drag = 0.4;//阻力
//set vert
Myvertex vert;
verts_.push_back(vert);
for (int i = 1; i <= 7959; i++) // m_pic.V[m_pic.F[i].V[2]].X
{
//printf("%d\n", m_pic.V.size());//7959
vert.id_ = i;
vert.position_ = point(model->vertices[3 * i], model->vertices[3 * i + 1], model->vertices[3 * i + 2]);//point: vec
//printf("[1]=%lf\n", model->vertices[3 * i]);
//printf("[2]=%lf\n", model->vertices[3 * i+1]);
//printf("[3]=%lf\n", model->vertices[3 * i+2]);
//vert.texCoord_ = Vec2f(model->)
verts_.push_back(vert);
}
//calculate hanger_aabb 衣架aabb包圍盒
for (int i = 3381; i <= model->numvertices; i++)
{
if (model->vertices[3 * i] > hanger_aabb.max_x)
{
hanger_aabb.max_x = model->vertices[3 * i];
}
if (model->vertices[3 * i] < hanger_aabb.min_x)
{
hanger_aabb.min_x = model->vertices[3 * i];
}
if (model->vertices[3 * i + 1] > hanger_aabb.max_y)
{
hanger_aabb.max_y = model->vertices[3 * i + 1];
}
if (model->vertices[3 * i + 1] < hanger_aabb.min_y)
{
hanger_aabb.min_y = model->vertices[3 * i + 1];
}
if (model->vertices[3 * i + 2] > hanger_aabb.max_z)
{
hanger_aabb.max_z = model->vertices[3 * i + 2];
}
if (model->vertices[3 * i + 2] < hanger_aabb.min_z)
{
hanger_aabb.min_z = model->vertices[3 * i + 2];
}
}
update_aabb(); //calculate_cloth_aabb
for (int i = 1; i <= model->numvertices; i++) //if points in hanger_aabb,point is static
{
if (p_in_aabb(verts_[i].position_))
{
verts_[i].is_fixed = true;
}
else
verts_[i].is_fixed = false;
}
//set face
Myface face;
for (int i = 0; i < model->numtriangles; i++)
{
face.id_ = i;
int nid = model->triangles[i].findex;
face.verId = ivec3(model->triangles[i].vindices[0], model->triangles[i].vindices[1], model->triangles[i].vindices[2]);
face.normal_ = Vec3f(model->facetnorms[nid * 3], model->facetnorms[nid * 3 + 1], model->facetnorms[nid * 3 + 2]);
for (int j = 0; j < 3; j++)
{
int tid = model->triangles[i].tindices[j];
face.texCoord_[j] = Vec2f(model->texcoords[tid * 2], model->texcoords[2 * tid + 1]);
}
int id[3];
for (int j = 0; j < 3; j++)
{
id[j] = face.verId[j];
face.vertex3[j] = &verts_[id[j]];
}
face.s = fabs(((verts_[id[1]].position_ - verts_[id[0]].position_)CROSS(verts_[id[2]].position_ - verts_[id[0]].position_)).length() / 2.0);
faces_.push_back(face);
}
//delete some bad faces
for (int i = 0; i < 6546; i++)
{
if ((faces_[i].normal_[0] * faces_[i].vertex3[0]->position_[0] + faces_[i].normal_[2] * faces_[i].vertex3[0]->position_[2]) < -0.05)
{
faces_[i].badface = true;
}
}
//delete some bad verts,and find bound
for (int i = 0; i < 6546; i++)
{
if (faces_[i].badface)
{
for (int j = 0; j < 3; j++)
{
faces_[i].vertex3[j]->kengdie = true;
faces_[i].vertex3[j]->is_bound = true;
}
}
}
for (int i = 0; i < 6546; i++)
{
if (!faces_[i].badface)
{
for (int j = 0; j < 3; j++)
{
faces_[i].vertex3[j]->kengdie = false;
}
}
}
set_neighbor();
set_weight();
//Update(float deltatime);
}
}
服裝模型類,設定了服裝的物理模型,為服裝添加了SpringConstant彈力,DampingFactor阻尼,density密度,drag 阻力屬性;分別找到衣架和服裝的頂點取值範圍,確定衣架和服裝的包圍盒;形變是針對服裝模型的三角形面片作變化,將模型的基本資料儲存到face中;由於原模型中裙襬雙層布料,碰撞檢測的效果不好,故刪去裙襬內襯,即badface。
(4)碰撞檢測
static void idle() {
if (phy_){ if (loop < 16){ Update(); loop++; } else loop = 0; }
}
void keys(unsigned char key, int x, int y)
{
/* Use x, X, y, Y, z, and Z keys to move viewer */
if (key == 'x') viewer[0] -= 1.0;
if (key == 'X') viewer[0] += 1.0;
if (key == 'y') viewer[1] -= 1.0;
if (key == 'Y') viewer[1] += 1.0;
if (key == 'z') viewer[2] -= 1.0;
if (key == 'Z') viewer[2] += 1.0;
//display();
if (!phy_){
DrawScene();
}
if (key == 'p'){
phy_ = true; printf("phy_=true\n");
}
if (key == 'o'){
wind.x += 0.5;
wind.Print("New Wind:");
}
if (key == 'i'){
wind.y += 0.5;
wind.Print("New Wind:");
}
if (key == 'u'){
wind.z += 0.5;
wind.Print("New Wind:");
}
}
void Cloth::Update(float deltatime)
{
//set gravity
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].applyForce(Vec3f(0, -0.1, 0)*verts_[i].weight);
//verts_[i].applyForce(Vec3f(0, 0, 0.01));
//verts_[i].Update(deltatime);
}
change_wind();
//set wind
for (int i = 0; i < faces_.size(); i++)
{
faces_[i].updateface();
}
//set spring
for (int i = 0; i < springDampers.size(); i++)
{
springDampers[i].CalculateForces();
}
collision_detection();//碰撞檢測
//update verts
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].Update(deltatime);
}
//update normals
update_normal();
}
void Cloth::change_wind()
{
for (int i = 0; i < 6546; i++)
{
faces_[i].Changewind(wind);
}
}
void Cloth::update_normal()
{
for (int i = 0; i < faces_.size(); i++)
{
Vec3f p = verts_[faces_[i].verId[0]].position_;
Vec3f q = verts_[faces_[i].verId[1]].position_;
Vec3f r = verts_[faces_[i].verId[2]].position_;
Vec3f normal_update = ((p - r) CROSS(q - r));
normal_update.normalize();
//normal is continuous variable
if (normal_update DOT faces_[i].normal_>0)
{
faces_[i].normal_ = normal_update;
}
else
{
faces_[i].normal_ = -normal_update;
}
}
}
void Cloth::set_neighbor()
{
for (int i = 0; i < faces_.size(); i++)
{
if (!faces_[i].badface)
{
int vid1;
int vid2;
for (int j = 0; j < 3; j++)
{
vid1 = faces_[i].verId[j];
vid2 = faces_[i].verId[(j + 1) % 3];
verts_[vid1].neighborIdx.push_back(vid2);
float dis;
dis = (verts_[vid1].position_ - verts_[vid2].position_).length();
verts_[vid1].nei_dis.push_back(dis);
verts_[vid1].neighborfaceId.push_back(i);
if (verts_[vid1].is_bound&&verts_[vid2].is_bound)
{
verts_[vid2].neighborIdx.push_back(vid1);
verts_[vid2].nei_dis.push_back(dis);
}
}
}
}
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].degree_ = verts_[i].neighborIdx.size();
}
for (int i = 0; i < faces_.size(); i++)
{
if (!faces_[i].badface)
{
int vid1;
int vid2;
for (int j = 0; j < 3; j++)
{
vid1 = faces_[i].verId[j];
vid2 = faces_[i].verId[(j + 1) % 3];
springDampers.push_back(springDamper(&verts_[vid1], &verts_[vid2], SpringConstant, DampingFactor));//彈簧阻尼
}
}
}
}
void Cloth::collision_detection()
{
update_aabb();
for (int i = 0; i < 40; i++)
for (int j = 0; j < 40; j++)
for (int k = 0; k < 40; k++)
{
voxels[i][j][k].clear();
}
for (int i = 0; i < 6546; i++)
{
faces_[i].voxel_pos.clear();
}
for (int i = 0; i < 6546; i++)
{
if (!faces_[i].badface)
{
aabb ab_ = faces_[i].get_aabb();
int l = floor((ab_.min_x - cloth_aabb.min_x) / (cloth_aabb.max_x - cloth_aabb.min_x)*40.f);
int m = floor((ab_.min_y - cloth_aabb.min_y) / (cloth_aabb.max_y - cloth_aabb.min_y)*40.f);
int n = floor((ab_.min_z - cloth_aabb.min_z) / (cloth_aabb.max_z - cloth_aabb.min_z)*40.f);
if (l < 0)
l = 0;
if (m < 0)
m = 0;
if (n < 0)
n = 0;
voxels[l][m][n].push_back(i);
faces_[i].voxel_pos = ivec3(l, m, n);
}
}
for (int i = 0; i < 6546; i++)
{
ivec3 voxel = faces_[i].voxel_pos;
for (int j = 0; j < voxels[voxel[0]][voxel[1]][voxel[2]].size(); j++)
{
int face_id = voxels[voxel[0]][voxel[1]][voxel[2]][j];
bool b = tri_collision_detection(i, face_id);
}
}
/*for (int i = 0; i < 6546; i++)
for (int j = 0; j < 6546; j++)
{
tri_collision_detection(i, j);
}*/
}
bool Cloth::tri_collision_detection(int id1, int id2)
{
if ((faces_[id1].normal_ DOT faces_[id2].normal_)>0)
return false;
if (faces_[id1].badface || faces_[id2].badface)
return false;
Vec3f dis(0, 0, 0);
for (int i = 0; i < 3; i++)
{
dis += (faces_[id1].vertex3[i]->position_ - faces_[id2].vertex3[i]->position_) / 3.0f;
}
if (dis.length() > 0.001)
return false;
else
{
Vec3f n = Vec3f(0, 0, 0);
for (int i = 0; i < 3; i++)
{
n = (faces_[id2].normal_ DOT(faces_[id1].vertex3[i]->position_ - faces_[id2].vertex3[1]->position_))*faces_[id2].normal_;
if (n.length() < 0.01)
{
n.normalize();
if (n DOT faces_[id1].vertex3[i]->velocity < 0)
{
Vec3f vv = Vec3f(0, 0, 0);
vv = (n DOT faces_[id1].vertex3[i]->velocity)*-1.1f*n;
faces_[id1].vertex3[i]->velocity += vv;
/*for (int j = 0; j < 3; j++)
{
faces_[id2].vertex3[j]->velocity += vv / -1.1f / 3.0f;
}*/
//faces_[id1].vertex3[i]->velocity = Vec3f(0, faces_[id1].vertex3[i]->velocity[1], 0);
}
}
}
return true;
}
}
由鍵盤設定是否開啟碰撞檢測,按下’p’鍵即開啟碰撞檢測,風力大小也由鍵盤設定。碰撞檢測具體實現流程見上述程式碼。