虽然之前看了相关MD5的文件结构资料,自认为实现解析应该没问题,嘛~毕竟是停留在理论, 还是动手来实践一下。
之后看到某同学写了个MD5的解析Demo,于是研究了下发现,这实际上是个 Away3D移植出来的精简解析程序,只支持单个Mesh. 近期把它改进成支持多个Mesh吧!
网络上有很多人都发过MD5的文件格式讲解,嘛~我觉得按自己的思路整理一下也发一次。 just for myself;
numJoints 110 //总关节数
numMeshes 4 //总网格数
joints {} 关节节点,其数据顺序为
关节名称,关节父级ID,关节位置,关节旋转
关节在对无动画的纯md5Mesh模型来说没有意义,渲染时无需用到。
mesh {} 网格节点,其数据顺序为
vert token
顶点索引,顶点纹理U,顶点纹理V,顶点初始权重,顶点最大权重
tri token
三角形索引,构成三角形对应的三个顶点的索引
weight token
权重索引,权重对应的关节索引,权重值,权重位置
如果有多个网格,就是以上数据的重复。
之后是精简版解析器的源码,附带的中文的注释。 这几天看看把它扩展成多网格的版本。
[cc lang=”actionscript3″]
package C3.MD5
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Vector3D;
import flash.utils.ByteArray;
public class MD5MeshParser extends EventDispatcher
{
public function MD5MeshParser()
{
_rotationQuat = new Quaternion();
_rotationQuat.fromAxisAngle(Vector3D.X_AXIS, -Math.PI * .5);
}
public function load(data : ByteArray) : void
{
_textData = data.readUTFBytes(data.bytesAvailable);
handleData();
}
private function handleData() : void
{
var token : String;
while(true){
token = getNextToken();
switch(token)
{
case COMMENT_TOKEN:
ignoreLine();
break;
case VERSION_TOKEN:
_version = getNextInt();
if(_version != 10) throw new Error(“版本错误,支持的是10”);
break;
case COMMAND_LINE_TOKEN:
parseCMD();
break;
case NUM_JOINTS_TOKEN:
num_joints = getNextInt();
md5_joint = new Vector.
break;
case NUM_MESHES_TOKEN:
num_meshes = getNextInt();
md5_mesh = new MeshData();
break;
case JOINTS_TOKEN:
parseJoints();
break
case MESH_TOKEN:
parseMesh();
break;
default:
if(!_reachedEOF)
throw new Error(“关键词错误”);
}
if(_reachedEOF){
calculateMaxJointCount();
this.dispatchEvent(new Event(Event.COMPLETE));
break;
}
}
}
/**
* 计算总关节数
*/
private function calculateMaxJointCount() : void
{
maxJointCount = 0;
var vertexData : Vector.
var numVerts : int = vertexData.length;
for(var j : int = 0; j < numVerts; ++j)
{
var zeroWeights : int = countZeroWeightJoints(vertexData[j], md5_mesh.md5_weight);
var totalJoints : int = vertexData[j].weight_count - zeroWeights; //忽略0权重的关节
if(totalJoints > maxJointCount)
maxJointCount = totalJoints;
}
}
/**
* 计算0权重关节
*/
private function countZeroWeightJoints(vertex : MD5Vertex, weights : Vector.
{
var start : int = vertex.weight_index;
var end : int = vertex.weight_index + vertex.weight_count;
var count : int = 0;
var weight : Number;
for(var i : int = start; i < end; ++i){
weight = weights[i].bias;
if(weight == 0) ++count;
}
return count;
}
/**
* 解析网格几何体
*/
private function parseMesh() : void
{
var token : String = getNextToken();
var ch : String;
if(token != "{") throw new Error("关键字错误");
_shaders ||= new Vector.
while(ch != “}”) {
ch = getNextToken();
switch(ch){
case COMMENT_TOKEN:
ignoreLine();
break;
case MESH_SHADER_TOKEN:
_shaders.push(parseLiteralString());
break;
case MESH_NUM_VERTS_TOKEN:
md5_mesh.num_verts = getNextInt();
md5_mesh.md5_vertex = new Vector.
break;
case MESH_NUM_TRIS_TOKEN:
md5_mesh.num_tris = getNextInt();
md5_mesh.md5_triangle = new Vector.
break;
case MESH_VERT_TOKEN:
parseVertex(md5_mesh.md5_vertex);
break;
case MESH_TRI_TOKEN:
parseTri(md5_mesh.md5_triangle);
break;
case MESH_WEIGHT_TOKEN:
parseWeight(md5_mesh.md5_weight)
break;
}
}
}
/**
* 从数据流中读取下一个关节权重
*/
private function parseWeight(weights : Vector.
{
var weight : MD5Weight = new MD5Weight();
weight.id = getNextInt();
weight.jointID = getNextInt();
weight.bias = getNextNumber();
weight.pos = parseVector3D();
weights.push(weight);
}
/**
* 从数据流中读取下一个三角形,并存入列表中
*/
private function parseTri(indices : Vector.
{
var tri : MD5Triangle = new MD5Triangle();
tri.id = getNextInt();
tri.indexVec = new Vector.
tri.indexVec.push(getNextInt());
tri.indexVec.push(getNextInt());
tri.indexVec.push(getNextInt());
indices.push(tri);
}
/**
* 从数据流中取出下一个顶点,并存入列表中
*/
private function parseVertex(vertexData : Vector.
{
var vertex : MD5Vertex = new MD5Vertex();
vertex.id = getNextInt();
parseUV(vertex);
vertex.weight_index = getNextInt();
vertex.weight_count = getNextInt();
vertexData.push(vertex);
}
/**
* 从数据流中读取uv坐标,并存入顶点数据中
*/
private function parseUV(vertexData : MD5Vertex) : void
{
var ch : String = getNextToken();
if(ch != “(“) throw new Error(“解析错误,错误的(“);
vertexData.uv_x = getNextNumber();
vertexData.uv_y = getNextNumber();
if(getNextToken() != “)”) throw new Error(“解析错误,错误的)”);
}
/**
* 解析关节
*/
private function parseJoints() : void
{
var ch : String;
var i : int = 0;
var token : String = getNextToken();
var joint : MD5Joint;
if(token != “{“) throw new Error(“关键字错误”);
do{
if(_reachedEOF) throw new Error(“到达文件尾”);
joint = new MD5Joint();
joint.name = parseLiteralString();
joint.parentIndex = getNextInt();
var pos : Vector3D = parseVector3D();
// pos = _rotationQuat.rotatePoint(pos);
var quat : Quaternion = parseQuaternion();
joint.bindPose = quat.toMatrix3D();
joint.bindPose.appendTranslation(pos.x,pos.y,pos.z);
joint.inverseBindPose = joint.bindPose.clone();
joint.inverseBindPose.invert();
// var m1 : Matrix3D = joint.bindPose;
// var m2 : Matrix3D = joint.inverseBindPose;
//
// m1.append(m2);
// m2.transpose();
md5_joint.push(joint);
ch = getNextChar();
if(ch == “/”){
putBack();
ch = getNextToken();
if(ch == COMMENT_TOKEN) ignoreLine();
ch = getNextChar();
}
if(ch != “}”) putBack();
}while(ch != “}”);
}
/**
* 从数据流中解析下一个四元数
*/
private function parseQuaternion() : Quaternion
{
var quat : Quaternion = new Quaternion();
var ch : String = getNextToken();
if(ch != “(“) throw new Error(“解析错误,错误的(“);
quat.x = getNextNumber();
quat.y = getNextNumber();
quat.z = getNextNumber();
//四元数需要一个单位长度
var t : Number = 1 – quat.x * quat.x – quat.y * quat.y – quat.z * quat.z;
quat.w = t < 0 ? 0 : - Math.sqrt(t);
if (getNextToken() != ")") throw new Error("解析错误,错误的)");
return quat;
}
/**
* 获取Vector3d
*/
private function parseVector3D() : Vector3D
{
var vec : Vector3D = new Vector3D();
var ch : String = getNextToken();
if(ch != "(") throw new Error("解析错误,错误的(");
vec.x = getNextNumber();
vec.y = getNextNumber();
vec.z = getNextNumber();
if(getNextToken() != ")") throw new Error("解析错误,错误的)");
return vec;
}
/**
* 从数据流中解析下一个浮点型数值
*/
private function getNextNumber() : Number
{
var f : Number = parseFloat(getNextToken());
if(isNaN(f)) throw new Error("float type");
return f;
}
/**
* 解析指令行数据
*/
private function parseCMD() : void
{
//仅仅忽略指令行属性
parseLiteralString();
}
/**
* 解析数据流中的逐字符串,一个逐字符是一个序列字符,被两个引号包围
*/
private function parseLiteralString() : String
{
skipWhiteSpace();
var ch : String = getNextChar();
var str : String = "";
if(ch != """) throw new Error("引号解析错误");
do{
if(_reachedEOF) throw new Error("到达文件结尾");
ch = getNextChar();
if(ch != """) str += ch;
}while(ch != """);
return str;
}
private function getNextToken() : String
{
var ch : String;
var token : String = "";
while(!_reachedEOF){
ch = getNextChar();
if(ch == " " || ch == "r" || ch == "n" || ch == "t"){
//如果不为注释,跳过
if(token != COMMENT_TOKEN)
skipWhiteSpace();
//如果不为空白, 返回
if(token != "")
return token;
}else token += ch;
if(token == COMMENT_TOKEN) return token;
}
return token;
}
/**
* 读出数据流中的下一个整型
*/
private function getNextInt() : int
{
var i : Number = parseInt(getNextToken());
if(isNaN(i)) throw new Error("解析错误 int type");
return i;
}
/**
* 跳过下一行
*/
private function ignoreLine() : void
{
var ch : String;
while(!_reachedEOF && ch != "n")
ch = getNextChar();
}
/**
* 跳过数据流中的空白
*/
private function skipWhiteSpace() : void
{
var ch : String;
do {
ch = getNextChar();
}while(ch == "n" || ch == " " || ch == "r" || ch == "t");
putBack();
}
/**
* 将最后读出的字符放回数据流
*/
private function putBack() : void
{
_parseIndex--;
_chatLineIndex--;
_reachedEOF = _parseIndex >= _textData.length;
}
/**
* 从数据流中读取下一个字符
*/
private function getNextChar() : String
{
var ch : String = _textData.charAt(_parseIndex++);
//如果遇到换行符
if(ch == “n”){
++_line;
_chatLineIndex = 0;
}
//如果遇到回车符
else if(ch != “r”){
++_chatLineIndex;
}
if(_parseIndex >= _textData.length)
_reachedEOF = true;
return ch;
}
public var num_joints : int;
public var md5_joint : Vector.
public var num_meshes : int;
public var md5_mesh : MeshData;
public var maxJointCount : int;
private var _textData : String;
private var _reachedEOF : Boolean;
private var _parseIndex : int = 0;
private var _line : int = 0;
private var _chatLineIndex : int = 0;
private var _version : int;
private var _shaders : Vector.
private var _rotationQuat : Quaternion;
/**注释**/
private static const COMMENT_TOKEN : String = “//”;
/**版本**/
private static const VERSION_TOKEN : String = “MD5Version”;
/**指令**/
private static const COMMAND_LINE_TOKEN : String = “commandline”;
/**总骨骼数**/
private static const NUM_JOINTS_TOKEN : String = “numJoints”;
/**总网格数**/
private static const NUM_MESHES_TOKEN : String = “numMeshes”;
/**骨骼令牌**/
private static const JOINTS_TOKEN : String = “joints”;
/**网格令牌**/
private static const MESH_TOKEN : String = “mesh”;
/**网格shader令牌**/
private static const MESH_SHADER_TOKEN : String = “shader”;
/**网格总顶点令牌**/
private static const MESH_NUM_VERTS_TOKEN : String = “numverts”;
/**网格顶点令牌**/
private static const MESH_VERT_TOKEN : String = “vert”;
/**网格总三角形令牌**/
private static const MESH_NUM_TRIS_TOKEN : String = “numtris”;
/**网格三角形令牌**/
private static const MESH_TRI_TOKEN : String = “tri”;
/**网格总权重令牌**/
private static const MESH_NUM_WEIGHTS_TOKEN : String = “numweights”;
/**网格权重令牌**/
private static const MESH_WEIGHT_TOKEN : String = “weight”;
}
}
[/cc]
实际上,花了半个下午将多网格解析搞定了。
只需要将meshData 修改成为一个MeshData的数组,缓存所有的MeshData即可。
修改后的 parseMesh() 函数如下
[cc lang=”actionscript3″]
/**
* 解析网格几何体
*/
private function parseMesh() : void
{
var token : String = getNextToken();
var ch : String;
if(token != “{“) throw new Error(“关键字错误”);
_shaders ||= new Vector.
md5_mesh ||= new Vector.
var mesh : MeshData = new MeshData();
while(ch != “}”) {
ch = getNextToken();
switch(ch){
case COMMENT_TOKEN:
ignoreLine();
break;
case MESH_SHADER_TOKEN:
_shaders.push(parseLiteralString());
break;
case MESH_NUM_VERTS_TOKEN:
mesh.num_verts = getNextInt();
mesh.md5_vertex = new Vector.
break;
case MESH_NUM_TRIS_TOKEN:
mesh.num_tris = getNextInt();
mesh.md5_triangle = new Vector.
break;
case MESH_VERT_TOKEN:
parseVertex(mesh.md5_vertex);
break;
case MESH_TRI_TOKEN:
parseTri(mesh.md5_triangle);
break;
case MESH_WEIGHT_TOKEN:
parseWeight(mesh.md5_weight)
break;
}
}
md5_mesh.push(mesh);
}
[/cc]
最后的最后,对应每个MeshData都创建他们对应的 vetexBuffer,uvBuffer,indicesBuffer 即可。
在绘制的时候,只需要遍历各个MeshData进行绘制即可
just like this!
[cc lang=”actionscript3″]
for(var i : int = 0; i < md5Result.meshDataNum; i++){
var vertexBuffer : VertexBuffer3D = md5Result.vertexBufferList[i];
var uvBuffer : VertexBuffer3D = md5Result.uvBufferList[i];
var indexBuffer : IndexBuffer3D = md5Result.indexBufferList[i];
var texture : Texture = m_textureList[i];
m_context.setTextureAt(0, texture);
m_context.setVertexBufferAt(0, vertexBuffer,0, Context3DVertexBufferFormat.FLOAT_3);
m_context.setVertexBufferAt(1,uvBuffer,0,Context3DVertexBufferFormat.FLOAT_2);
m_context.drawTriangles(indexBuffer);
m_context.setTextureAt(0, null);
}
m_context.present();
[/cc]
接下来,将解析骨骼动画,这就丢给明天来做吧~