本篇是创建游戏内核(12)【接口与实现分离版】的续篇,关于该内核的细节说明请参考创建游戏内核(13),这个版本主要是按照功能划分模块的思想,并严格按照接口与实现相分离的原则来写的,没有用面向对象的思想来写,没有继承没有多态。大家可以对比两个版本,比较优劣。
接口:
/*************************************************************************
PURPOSE:
Interface for D3D mesh and animation fucnton.
*************************************************************************/
#ifndef _CORE_MESH_ANIMATION_H_
#define _CORE_MESH_ANIMATION_H_
#include "core_common.h"
#include "core_graphics.h"
typedef void* MESH;
typedef MESH* MESH_PTR;
MESH create_mesh();
void destroy_mesh(MESH mesh);
BOOL load_mesh(MESH mesh, const char* filename, const char* texture_path);
BOOL draw_mesh(MESH mesh);
BOOL is_mesh_loaded(MESH mesh);
long get_num_frames(MESH mesh);
void get_mesh_bounds(MESH mesh,
float* min_x, float* min_y, float* min_z,
float* max_x, float* max_y, float* max_z,
float* radius);
#endif
实现:
/***********************************************************************************
PURPOSE:
Implement for D3D mesh and animation fucnton.
***********************************************************************************/
#include "core_common.h"
#include "core_mesh_animation.h"
#include "RmxfTmpl.h"
#include "RmxfGuid.h"
/////////////////////////////////// defines for mesh information ///////////////////////////////////
typedef struct _MESH_INFO
{
char* name; // name of mesh
ID3DXMesh* d3d_mesh; // D3D mesh object
ID3DXMesh* d3d_skin_mesh; // D3D skin mesh object
ID3DXSkinInfo* d3d_skin_info; // D3D skin mesh information
DWORD num_materials; // number of materials in mesh
D3DMATERIAL9* materials; // array of materials
LPDIRECT3DTEXTURE9* textures; // array of texture pointers
D3DXVECTOR3 min, max; // bounding box
float radius; // bounding sphere radius
_MESH_INFO* next; // pointer to next mesh information
} *_MESH_INFO_PTR;
//------------------------------------------------------------------------------------
// Create a new mesh informatio object.
//------------------------------------------------------------------------------------
static _MESH_INFO_PTR _create_mesh_info()
{
_MESH_INFO_PTR _mesh_info = (_MESH_INFO_PTR) malloc(sizeof(_MESH_INFO));
memset(_mesh_info, 0, sizeof(_MESH_INFO));
return _mesh_info;
}
//------------------------------------------------------------------------------------
// Destroy all mesh information object, recursively call.
//------------------------------------------------------------------------------------
static void _destroy_mesh_info(_MESH_INFO_PTR _mesh_info)
{
if(_mesh_info == NULL || _mesh_info->next == NULL)
return;
release_com(_mesh_info->d3d_mesh);
release_com(_mesh_info->d3d_skin_mesh);
release_com(_mesh_info->d3d_skin_info);
free(_mesh_info->name);
free(_mesh_info->materials);
if(_mesh_info->textures)
{
for(DWORD i = 0; i < _mesh_info->num_materials; i++)
release_com(_mesh_info->textures[i]);
free(_mesh_info->textures);
}
_destroy_mesh_info(_mesh_info->next);
}
//------------------------------------------------------------------------------------
// Find mesh information with specified name, call recusively.
//------------------------------------------------------------------------------------
static _MESH_INFO_PTR _find_mesh_info(const _MESH_INFO_PTR mesh_info, const char* name)
{
// return current mesh information if name is NULL
if(mesh_info == NULL || name == NULL)
return mesh_info;
// compare names and return if exact match
if(mesh_info->name && STREQ(mesh_info->name, name))
return mesh_info;
// search next in list
if(mesh_info->next)
{
_MESH_INFO_PTR _mesh_info;
// recursively call
if((_mesh_info = _find_mesh_info(mesh_info, name)) != NULL)
return _mesh_info;
}
return NULL;
}
/////////////////////////////////// defines for frame information ///////////////////////////////////
typedef struct _FRAME
{
char* name; // frame name
_MESH_INFO_PTR mesh_info; // list of mesh information attached to this frame
D3DXMATRIX mat_combined; // combined transformation matrix
D3DXMATRIX mat_transformed; // currently transformed matrix
D3DXMATRIX mat_original; // original .x file matrix
_FRAME* parent; // pointer to parent frame
_FRAME* child; // pointer to child frame
_FRAME* sibling; // pointer to silbing frame
} *_FRAME_PTR;
//------------------------------------------------------------------------------------
// Create a new frame.
//------------------------------------------------------------------------------------
static _FRAME_PTR _create_frame()
{
_FRAME_PTR _frame = (_FRAME_PTR) malloc(sizeof(_FRAME));
memset(_frame, 0, sizeof(_FRAME));
return _frame;
}
//------------------------------------------------------------------------------------
// Destroy frame object.
//------------------------------------------------------------------------------------
static void _destroy_frame(_FRAME_PTR frame)
{
free(frame->name);
free(frame->child);
free(frame->sibling);
}
//------------------------------------------------------------------------------------
// Find frame with specified name, call recursively.
//------------------------------------------------------------------------------------
static _FRAME_PTR _find_frame(const _FRAME_PTR frame, const char* name)
{
// return current frame if name is NULL
if(frame == NULL || name == NULL)
return frame;
// compare names and return if exact match
if(frame->name && STREQ(frame->name, name))
return frame;
_FRAME_PTR _frame;
// search child list, recursively call.
if(frame->child)
{
if((_frame = _find_frame(frame->child, name)) != NULL)
return _frame;
}
// search sibling list, recursively call.
if(frame->sibling)
{
if((_frame = _find_frame(frame->sibling, name)) != NULL)
return _frame;
}
return NULL;
}
//------------------------------------------------------------------------------------
// Reset frame transformed matrices to original matrices, recursively call.
//------------------------------------------------------------------------------------
static void _reset_frame_matrices(const _FRAME_PTR frame)
{
frame->mat_transformed = frame->mat_original;
if(frame->child)
_reset_frame_matrices(frame->child);
if(frame->sibling)
_reset_frame_matrices(frame->sibling);
}
//------------------------------------------------------------------------------------
// Add mesh to frame.
//------------------------------------------------------------------------------------
static void _add_mesh_info_to_frame(_FRAME_PTR frame, const _MESH_INFO_PTR mesh_info)
{
mesh_info->next = frame->mesh_info;
frame->mesh_info = mesh_info;
}
/////////////////////////////////// defines for mesh ////////////////////////////////
typedef struct _MESH
{
long num_mesh_info; // number of mesh information
_MESH_INFO_PTR mesh_info; // mesh information list
long num_frame; // number of frames
_FRAME_PTR frame; // frame list
D3DXVECTOR3 min; // the lower-left corner of the bounding box
D3DXVECTOR3 max; // the upper-right corner of the bounding box
float radius; // bounding box radius
} *_MESH_PTR;
//-------------------------------------------------------------------
// Return root mesh information.
//-------------------------------------------------------------------
static _MESH_INFO_PTR _get_root_mesh_info(_MESH_PTR mesh)
{
return mesh->mesh_info;
}
//-------------------------------------------------------------------
// Return mesh whih spefied name.
//-------------------------------------------------------------------
static _MESH_INFO_PTR _get_mesh_info(_MESH_PTR mesh, const char* name)
{
if(mesh->mesh_info == NULL)
return NULL;
return _find_mesh_info(mesh->mesh_info, name);
}
//-------------------------------------------------------------------
// Parse specified xfile data, recursive function.
//-------------------------------------------------------------------
static void _parse_xfile_data(_MESH_PTR mesh, ID3DXFileData* xfile_data,
_FRAME_PTR parent_frame, const char* texture_path)
{
// get the template type
GUID _type;
// retrieve the globally unique identifier (GUID) of the object's template
if(FAILED(xfile_data->GetType(&_type)))
return;
// get the template name (if any)
DWORD _size;
if(FAILED(xfile_data->GetName(NULL, &_size)))
return;
char* _name = NULL;
if(_size != 0)
{
if((_name = (char*) malloc(_size)) != NULL)
xfile_data->GetName(_name, &_size);
}
// give template a default name if none found
if(_name == NULL)
{
if((_name = (char*) malloc(9)) == NULL)
return;
strcpy(_name, "$NoName$");
}
// set current frame
_FRAME_PTR _current_frame = parent_frame;
// process the templates
if(_type == TID_D3DRMFrame) // it is a frame
{
// create a new frame
_FRAME_PTR _frame = _create_frame();
// store the name
_frame->name = _name;
_name = NULL;
// link to parent frame
_frame->parent = parent_frame;
_frame->sibling = parent_frame->child;
parent_frame->child = _frame;
// increase frame count
mesh->num_frame++;
// set current frame with new frame
_current_frame = _frame;
}
else if(_type == TID_D3DRMFrameTransformMatrix) // it is a frame transformation matrix
{
D3DXMATRIX* _frame_matrix = NULL;
// get frame transformation matrix
if(FAILED(xfile_data->Lock(&_size, (LPCVOID*) &_frame_matrix)))
return;
// set original matrix
parent_frame->mat_original = *_frame_matrix;
xfile_data->Unlock();
}
else if(_type == TID_D3DRMMesh) // it is a mesh
{
ID3DXBuffer* _material_buffer = NULL;
ID3DXBuffer* _adjacency = NULL;
_MESH_INFO_PTR _mesh_info = NULL;
// see if mesh already loaded
if(mesh->mesh_info == NULL || _find_mesh_info(mesh->mesh_info, _name) == NULL)
{
// create a new mesh structure
_mesh_info = _create_mesh_info();
// store the name
_mesh_info->name = _name;
_name = NULL;
// load mesh data
if(FAILED(D3DXLoadSkinMeshFromXof(xfile_data, 0, g_d3d_device, &_adjacency, &_material_buffer, NULL,
&_mesh_info->num_materials, &_mesh_info->d3d_skin_info, &_mesh_info->d3d_mesh)))
{
free(_name);
free(_mesh_info);
return;
}
BYTE* _ptr;
// calcualte the bounding box and sphere
if(SUCCEEDED(_mesh_info->d3d_mesh->LockVertexBuffer(D3DLOCK_READONLY, (void**) &_ptr)))
{
DWORD _num_vertices = _mesh_info->d3d_mesh->GetNumVertices();
DWORD _num_bytes_per_vertex = _mesh_info->d3d_mesh->GetNumBytesPerVertex();
// computes a coordinate-axis oriented bouding box
D3DXComputeBoundingBox((D3DXVECTOR3*) _ptr, _num_vertices, _num_bytes_per_vertex,
&_mesh_info->min, &_mesh_info->max);
// computes a bounding sphere for the mesh
D3DXComputeBoundingSphere((D3DXVECTOR3*) _ptr, _num_vertices, _num_bytes_per_vertex,
&D3DXVECTOR3(0.0f, 0.0f, 0.0f), &_mesh_info->radius);
_mesh_info->d3d_mesh->UnlockVertexBuffer();
}
// create a matching skinned mesh if bone exist
if(_mesh_info->d3d_skin_info && _mesh_info->d3d_skin_info->GetNumBones() != 0)
{
// clone a mesh using a flexible vertex format (FVF) code
if(FAILED(_mesh_info->d3d_mesh->CloneMeshFVF(0, _mesh_info->d3d_mesh->GetFVF(), g_d3d_device,
&_mesh_info->d3d_skin_mesh)))
{
release_com(_mesh_info->d3d_skin_info);
}
}
// load materials or create a default one if none
if(_mesh_info->num_materials == 0)
{
// create a default one
_mesh_info->materials = (D3DMATERIAL9*) malloc(sizeof(D3DMATERIAL9));
_mesh_info->textures = (LPDIRECT3DTEXTURE9*) malloc(sizeof(LPDIRECT3DTEXTURE9));
memset(_mesh_info->materials, 0, sizeof(D3DMATERIAL9));
_mesh_info->materials[0].Diffuse.r = 1.0f;
_mesh_info->materials[0].Diffuse.g = 1.0f;
_mesh_info->materials[0].Diffuse.b = 1.0f;
_mesh_info->materials[0].Diffuse.a = 1.0f;
_mesh_info->materials[0].Ambient = _mesh_info->materials[0].Diffuse;
_mesh_info->materials[0].Specular = _mesh_info->materials[0].Diffuse;
_mesh_info->textures[0] = NULL;
_mesh_info->num_materials = 1;
}
else
{
// load the materials
D3DXMATERIAL* _x_materials = (D3DXMATERIAL*) _material_buffer->GetBufferPointer();
_mesh_info->materials = (D3DMATERIAL9*) malloc(sizeof(D3DMATERIAL9) * _mesh_info->num_materials);
_mesh_info->textures = (LPDIRECT3DTEXTURE9*)
malloc(sizeof(LPDIRECT3DTEXTURE9) * _mesh_info->num_materials);
char _path[MAX_PATH];
for(DWORD i = 0; i < _mesh_info->num_materials; i++)
{
_mesh_info->materials[i] = _x_materials[i].MatD3D;
_mesh_info->materials[i].Ambient = _mesh_info->materials[i].Diffuse;
// build a texture path and load it
sprintf(_path, "%s%s", texture_path, _x_materials[i].pTextureFilename);
// create texture from file
if(FAILED(D3DXCreateTextureFromFile(g_d3d_device, _path, &_mesh_info->textures[i])))
_mesh_info->textures[i] = NULL;
}
}
release_com(_material_buffer);
release_com(_adjacency);
// link to mesh information list
_mesh_info->next = mesh->mesh_info;
mesh->mesh_info = _mesh_info;
mesh->num_mesh_info++;
}
else
// find mesh information in list
_mesh_info = _find_mesh_info(mesh->mesh_info, _name);
// add mesh information to current frame
if(_mesh_info)
_add_mesh_info_to_frame(_current_frame, _mesh_info);
} // end if(_type == TID_D3DRMMesh)
else if(_type == TID_D3DRMAnimationSet || _type == TID_D3DRMAnimation || _type == TID_D3DRMAnimationKey)
{
// skip animation sets and animations
free(_name);
return;
}
free(_name);
ID3DXFileData* _child_xfile_data = NULL;
SIZE_T _num_child;
xfile_data->GetChildren(&_num_child);
// scan for embedded templates
for(SIZE_T i = 0; i < _num_child; i++)
{
xfile_data->GetChild(i, &_child_xfile_data);
// parse child xfile data object
_parse_xfile_data(mesh, _child_xfile_data, _current_frame, texture_path);
release_com(_child_xfile_data);
}
}
//-------------------------------------------------------------------
// Draw frame, recursively call.
//-------------------------------------------------------------------
static BOOL _draw_frame(_FRAME_PTR frame)
{
D3DXMATRIX* _matrices = NULL;
ID3DXMesh* _d3d_mesh_to_draw;
_MESH_INFO_PTR _mesh_info;
// return if no frame
if(frame == NULL)
return FALSE;
if(frame->mesh_info)
{
// draw mesh if any in frame
if((_mesh_info = frame->mesh_info) != NULL)
{
// setup pointer to mesh to draw
_d3d_mesh_to_draw = _mesh_info->d3d_mesh;
// generate mesh from skinned mesh to draw with
if(_mesh_info->d3d_skin_mesh && _mesh_info->d3d_skin_info)
{
DWORD _num_bones = _mesh_info->d3d_skin_info->GetNumBones();
// allocate an array of matrices to orient bones
_matrices = (D3DXMATRIX *) malloc(sizeof(D3DXMATRIX) * _num_bones);
// set all bones to orientation to identity
for(DWORD i = 0; i < _num_bones; i++)
D3DXMatrixIdentity(&_matrices[i]);
// lock source and destination vertex buffers
void* _source = NULL;
void* _dest = NULL;
// locks a vertex buffer and obtains a pointer to the vertex buffer memory
_mesh_info->d3d_mesh->LockVertexBuffer(0, &_source);
_mesh_info->d3d_skin_mesh->LockVertexBuffer(0, &_dest);
// update skinned mesh, applies software skinning to the target vertices based on the current matrices.
_mesh_info->d3d_skin_info->UpdateSkinnedMesh(_matrices, NULL, _source, _dest);
// unlock buffers
_mesh_info->d3d_mesh->UnlockVertexBuffer();
_mesh_info->d3d_skin_mesh->UnlockVertexBuffer();
// pointer to skin mesh to draw
_d3d_mesh_to_draw = _mesh_info->d3d_skin_mesh;
}
// render the mesh
for(DWORD i = 0; i < _mesh_info->num_materials; i++)
{
// set the materials properties for the device
g_d3d_device->SetMaterial(&_mesh_info->materials[i]);
// assign a texture to a stage for a device
g_d3d_device->SetTexture(0, _mesh_info->textures[i]);
// draw a subset of a mesh
_d3d_mesh_to_draw->DrawSubset(i);
}
// free array of matrices
free(_matrices);
_matrices = NULL;
}
}
// draw child frames, recursively call.
_draw_frame(frame->child);
return TRUE;
}
//-------------------------------------------------------------------
// Create mesh object.
//-------------------------------------------------------------------
MESH create_mesh()
{
_MESH_PTR _mesh = (_MESH_PTR) malloc(sizeof(_MESH));
memset(_mesh, 0, sizeof(_MESH));
return (MESH) _mesh;
}
//-------------------------------------------------------------------
// Destroy all meshes and frames.
//-------------------------------------------------------------------
void destroy_mesh(MESH mesh)
{
_MESH_PTR _mesh = (_MESH_PTR) mesh;
_destroy_mesh_info(_mesh->mesh_info);
_destroy_frame(_mesh->frame);
}
//-------------------------------------------------------------------
// load .x file from specified filename.
//-------------------------------------------------------------------
BOOL load_mesh(MESH mesh, const char* filename, const char* texture_path)
{
// check condition
if(g_d3d_device == NULL || filename == NULL)
return FALSE;
_MESH_PTR _mesh = (_MESH_PTR) mesh;
ID3DXFile* _xfile = NULL;
// create the file object
if(FAILED(D3DXFileCreate(&_xfile)))
return FALSE;
// register the templates
if(FAILED(_xfile->RegisterTemplates((LPVOID) D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES)))
{
_xfile->Release();
return FALSE;
}
ID3DXFileEnumObject* _xfile_enum = NULL;
// create an enumeration object
if(FAILED(_xfile->CreateEnumObject((LPVOID) filename, DXFILELOAD_FROMFILE, &_xfile_enum)))
{
_xfile->Release();
return FALSE;
}
// create a frame
_FRAME_PTR _frame = _create_frame();
ID3DXFileData* _xfile_data = NULL;
SIZE_T _num_child;
// retrieve the number of children in this file data object
_xfile_enum->GetChildren(&_num_child);
// loop through all objects looking for the frames and meshes
for(SIZE_T i = 0; i < _num_child; i++)
{
if(FAILED(_xfile_enum->GetChild(i, &_xfile_data)))
return FALSE;
// parse xfile data
_parse_xfile_data(_mesh, _xfile_data, _frame, texture_path);
release_com(_xfile_data);
}
release_com(_xfile_enum);
release_com(_xfile);
// see if we should keep the frame as root
if(_frame->mesh_info)
{
_mesh->frame = _frame;
_mesh->frame->name = (char*) malloc(7);
strcpy(_mesh->frame->name, "%ROOT%");
}
else
{
// Ok, now there is no any mesh in this frame, assign child frame as the root frame and release this frame.
_mesh->frame = _frame->child;
_FRAME_PTR _frame_ptr = _mesh->frame;
// reset all child frames of this frame, only under one level.
while(_frame_ptr)
{
_frame_ptr->parent = NULL;
_frame_ptr = _frame_ptr->sibling;
}
_frame->child = NULL;
free(_frame);
}
_MESH_INFO* _mesh_info;
// calculate bounding box and sphere
if((_mesh_info = _mesh->mesh_info) != NULL)
{
while(_mesh_info)
{
// set the lower-left corner of the bounding box as the most lower-left corner
// of all meshes's bounding box
_mesh->min.x = min(_mesh->min.x, _mesh_info->min.x);
_mesh->min.y = min(_mesh->min.y, _mesh_info->min.y);
_mesh->min.z = min(_mesh->min.z, _mesh_info->min.z);
// set the upper-right corner of the bounding box as the most upper-right corner
// of all meshes's bounding box
_mesh->max.x = min(_mesh->max.x, _mesh_info->max.x);
_mesh->max.y = min(_mesh->max.y, _mesh_info->max.y);
_mesh->max.z = min(_mesh->max.z, _mesh_info->max.z);
// set bounding box radius as max radius of all meshes
_mesh->radius = max(_mesh->radius, _mesh_info->radius);
_mesh_info = _mesh_info->next;
}
}
return TRUE;
}
//-----------------------------------------------------------------------------
// Draw mesh, call recursively.
//-----------------------------------------------------------------------------
BOOL draw_mesh(MESH mesh)
{
return _draw_frame(((_MESH_PTR) mesh)->frame);
}
//-------------------------------------------------------------------
// Judge whether the mesh has been loaded successfully.
//-------------------------------------------------------------------
BOOL is_mesh_loaded(MESH mesh)
{
_MESH_PTR _mesh = (_MESH_PTR) mesh;
return (_mesh->mesh_info && _mesh->frame);
}
//-------------------------------------------------------------------
// Get number of frame.
//-------------------------------------------------------------------
long get_num_frames(MESH mesh)
{
return ((_MESH_PTR) mesh)->num_frame;
}
//-------------------------------------------------------------------
// Get bound box coordiante and radius.
//-------------------------------------------------------------------
void get_mesh_bounds(MESH mesh,
float* min_x, float* min_y, float* min_z,
float* max_x, float* max_y, float* max_z,
float* radius)
{
_MESH_PTR _mesh = (_MESH_PTR) mesh;
if(min_x != NULL) *min_x = _mesh->min.x;
if(min_y != NULL) *min_y = _mesh->min.y;
if(min_z != NULL) *min_z = _mesh->min.z;
if(max_x != NULL) *max_x = _mesh->max.x;
if(max_y != NULL) *max_y = _mesh->max.z;
if(max_z != NULL) *max_z = _mesh->max.z;
if(radius != NULL) *radius = _mesh->radius;
}
测试代码:
/***********************************************************************************
PURPOSE:
Test for mesh function.
***********************************************************************************/
#include <windows.h>
#include "core_framework.h"
#include "core_graphics.h"
#include "core_tool.h"
#include "core_mesh_animation.h"
MESH g_mesh;
//--------------------------------------------------------------------------------
// Initialize data for game.
//--------------------------------------------------------------------------------
BOOL game_init()
{
// Create Direct3D and Direct3DDevice object
if(! create_display(g_hwnd, get_client_width(g_hwnd), get_client_height(g_hwnd), 16, TRUE, FALSE))
return FALSE;
// set perspective projection transform matrix
set_perspective(D3DX_PI/4.0f, 1.33333f, 1.0f, 1000.0f);
D3DXMATRIX _mat_view;
// create and set the view matrix
D3DXMatrixLookAtLH(&_mat_view,
&D3DXVECTOR3(0.0, 50.0, -150.0),
&D3DXVECTOR3(0.0, 50.0, 0.0),
&D3DXVECTOR3(0.0, 1.0, 0.0));
g_d3d_device->SetTransform(D3DTS_VIEW, &_mat_view);
g_mesh = create_mesh();
if(! load_mesh(g_mesh, "warrior.x", ".\\"))
return FALSE;
return TRUE;
}
//--------------------------------------------------------------------------------
// Render every game frame.
//--------------------------------------------------------------------------------
BOOL game_frame()
{
clear_display_buffer(D3DCOLOR_RGBA(0, 0, 0, 255));
if(SUCCEEDED(g_d3d_device->BeginScene()))
{
D3DXMATRIX _mat_world;
// create and set the world transformation matrix
// rotate object along z-axis
D3DXMatrixRotationY(&_mat_world, (float) (timeGetTime() / 1000.0));
g_d3d_device->SetTransform(D3DTS_WORLD, &_mat_world);
// draw mesh
draw_mesh(g_mesh);
g_d3d_device->EndScene();
}
present_display();
return TRUE;
}
//--------------------------------------------------------------------------------
// Release all game resources.
//--------------------------------------------------------------------------------
BOOL game_shutdown()
{
destroy_mesh(g_mesh);
release_com(g_d3d_device);
release_com(g_d3d);
return TRUE;
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE pre_inst, LPSTR cmd_line, int cmd_show)
{
if(! build_window(inst, "MainClass", "MainWindow", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480))
return FALSE;
run_game(game_init, game_frame, game_shutdown);
return 0;
}
点击下载源码和工程
程序截图: