SKLReader
using System;
using System.Collections.Generic;
using System.Linq;using
System.Text;using System.IO;
namespace SknImporter
{
class SklReader
{
public struct SklHeader
{
public byte[] version;
public int numObjects;
public int skeletonHash;
public int numElements;
}
public struct SklBone
{
//length :32
public int index;
public string name;
public int parent;
public float scale;
public float[,] matrix;
}
public List<SklBone> Bones = new List<SklBone>();
public SklReader(string file)
{
SklHeader header = new SklHeader(); FileStream fs = File.Open(file, FileMode.Open); BinaryReader reader = new BinaryReader(fs);
header.version = reader.ReadBytes(8); header.numObjects = reader.ReadInt32(); header.skeletonHash = reader.ReadInt32(); header.numElements = reader.ReadInt32();
for (int b = 0; b < header.numElements; b++)
{
SklBone bone = new SklBone(); bone.index = b; bone.name = Encoding.ASCII.GetString(reader.ReadBytes(32)).Replace("\0", ""); bone.parent = reader.ReadInt32(); bone.scale = reader.ReadSingle(); bone.matrix = new float[3, 4]; for (int y = 0; y < 3; y++) { for (int x = 0; x < 4; x++) { bone.matrix[y, x] = reader.ReadSingle(); } }
Bones.Add(bone);
} fs.Close();
}
}
}
SknImporter
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using System.IO;
// TODO: 将这些项替换为处理器输入和输出类型。
namespace SknImporter
{
/// <summary>
/// 此类将由 XNA Framework 内容管道实例化,
/// 以便将自定义处理应用于内容数据,将对象转换用于类型 TInput 到
/// 类型 TOnput 的改变。如果处理器希望改变数据但不更改其类型,
/// 输入和输出类型可以相同。
///
/// 这应当属于内容管道扩展库项目的一部分。
///
/// TODO: 更改 ContentProcessor 属性以为此处理器指定
/// 正确的显示名称。
/// </summary>
[ContentImporter(".skn", CacheImportedData = true, DefaultProcessor = "SKN Processor")]
public class SknImporter : ContentImporter<NodeContent>
{
ContentImporterContext importerContext;
// The root NodeContent of our model
private NodeContent rootNode;
// All vertex data in the file
private List<Vector3> positions;
private List<Vector2> texCoords;
private List<Vector3> normals;
// The current mesh being constructed
private MeshBuilder meshBuilder;
private int textureCoordinateDataIndex;
private int normalDataIndex;
private int[] positionMap;
public override NodeContent Import(string filename, ContentImporterContext context)
{
//这句话需要被注释掉,实际使用的话
System.Diagnostics.Debugger.Launch();
importerContext = context;
string sklPath = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".skl";
importerContext.AddDependency(sklPath);
rootNode = new NodeContent();
rootNode.Identity = new ContentIdentity(filename);
SklReader sklReader = new SklReader(sklPath);
SknReader sknReader = new SknReader(filename);
positions = new List<Vector3>();
texCoords = new List<Vector2>();
normals = new List<Vector3>();
importerContext.Logger.LogWarning(null, rootNode.Identity, sklReader.Bones.Count.ToString("X2"));
foreach (var item in sknReader.materialList)
{
for (int i = item.startVertex; i < item.numVertices; i++)
{
SkinModelVertex vertex = sknReader.modelData.verteces;
positions.Add(new Vector3(sknReader.modelData.verteces.position[0],
sknReader.modelData.verteces.position[1],
sknReader.modelData.verteces.position[2]));
texCoords.Add(new Vector2(sknReader.modelData.verteces.texcoords[0],
sknReader.modelData.verteces.texcoords[1]));
normals.Add(new Vector3(sknReader.modelData.verteces.normal[0],
sknReader.modelData.verteces.normal[1],
sknReader.modelData.verteces.normal[2]));
}
StartMesh(item.name);
BuildIndex(sknReader);
MeshContent mc = FinishMesh();
positions.Clear();
texCoords.Clear();
normals.Clear();
mc.Children.Add(BuildBone(sklReader));
rootNode.Children.Add(mc);
}
#region Testing Build Mesh with Bones
//foreach (var item in sknReader.materialList)
//{
// for (int i = item.startVertex; i < item.numVertices; i++)
// {
// SkinModelVertex vertex = sknReader.modelData.verteces;
// texCoords.Add(new Vector2(vertex.texcoords[0],
// vertex.texcoords[1]));
// normals.Add(new Vector3(vertex.normal[0],
// vertex.normal[1],
// vertex.normal[2]));
// }
//}
//foreach (var bone in sklReader.Bones)
//{
// List<SkinModelVertex> exsitVexter = new List<SkinModelVertex>();
// List<int> addedVertex = new List<int>();
// foreach (var vertex in sknReader.modelData.verteces)
// {
// for (int i = 0; i < 4; i++)
// {
// if(vertex.boneIndex == bone.index)
// {
// exsitVexter.Add(vertex);
// positions.Add(new Vector3(
// vertex.position[0],
// vertex.position[1],
// vertex.position[2]));
// }
// }
// }
// StartMesh(bone.name);
// BuildIndexByBone(sknReader,exsitVexter);
// MeshContent mc = FinishMesh();
// rootNode.Children.Add(mc);
// positions.Clear();
//}
#endregion
return rootNode;
}
private BoneContent BuildBone(SklReader sklReader)
{
List<BoneContent> bone = new List<BoneContent>();
foreach (var item in sklReader.Bones)
{
BoneContent bc = new BoneContent();
bc.Transform = new Matrix(
item.matrix[0, 0], item.matrix[0, 1], item.matrix[0, 2], item.matrix[0, 3],
item.matrix[1, 0], item.matrix[1, 1], item.matrix[1, 2], item.matrix[1, 3],
item.matrix[2, 0], item.matrix[2, 1], item.matrix[2, 2], item.matrix[2, 3],
0f, 0f, 0f, 0f);
bc.Name = item.name;
bone.Add(bc);
}
BoneContent rootBone = new BoneContent();
BoneContent tmpBone;
int max = bone.Count - 1;
while (max > 0)
{
if (sklReader.Bones[max].parent >= 0)
tmpBone = bone[sklReader.Bones[max].parent];
else
tmpBone = rootBone;
tmpBone.Children.Add(bone[max]);
bone.RemoveAt(max);
max--;
}
return rootBone;
}
private void AddTriangleVertex(int index)
{
Vector2 texCoord = Vector2.Zero;
texCoord = texCoords[index];
meshBuilder.SetVertexChannelData(textureCoordinateDataIndex,
texCoord);
Vector3 normal = Vector3.Zero;
normal = normals[index];
meshBuilder.SetVertexChannelData(normalDataIndex,
normal);
meshBuilder.AddTriangleVertex(index);
}
private void BuildIndex(SknReader sknReader)
{
for (int i = 0; i < sknReader.modelData.numIndices / 3; i++)
{
int a = sknReader.modelData.indices[i * 3];
int b = sknReader.modelData.indices[i * 3 + 1];
int c = sknReader.modelData.indices[i * 3 + 2];
AddTriangleVertex(a);
AddTriangleVertex(b);
AddTriangleVertex(c);
}
}
private void BuildIndexByBone(SknReader sknReader,List<SkinModelVertex> vertes)
{
int i = 0;
foreach (var item in vertes)
{
Vector2 texCoord = Vector2.Zero;
texCoord = texCoords[item.index];
meshBuilder.SetVertexChannelData(textureCoordinateDataIndex,
texCoord);
Vector3 normal = Vector3.Zero;
normal = normals[item.index];
meshBuilder.SetVertexChannelData(normalDataIndex,
normal);
meshBuilder.AddTriangleVertex(positionMap);
i++;
}
}
private void StartMesh(string name)
{
meshBuilder = MeshBuilder.StartMesh(name);
// Obj files need their winding orders swapped
meshBuilder.SwapWindingOrder = true;
meshBuilder.MergeDuplicatePositions = true;
// Add additional vertex channels for texture coordinates and normals
textureCoordinateDataIndex = meshBuilder.CreateVertexChannel<Vector2>(
VertexChannelNames.TextureCoordinate(0));
normalDataIndex =
meshBuilder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal());
// Add each position to this mesh with CreatePosition
positionMap = new int[positions.Count];
for (int i = 0; i < positions.Count; i++)
{
// positionsMap redirects from the original positions in the order
// they were read from file to indices returned from CreatePosition
positionMap = meshBuilder.CreatePosition(positions);
}
}
private MeshContent FinishMesh()
{
MeshContent meshContent = meshBuilder.FinishMesh();
Groups without any geometry are just for transform
//if (meshContent.Geometry.Count > 0)
//{
// // Add the mesh to the model
// // rootNode.Children.Add(meshContent);
//}
else
{
// Convert to a general NodeContent
NodeContent nodeContent = new NodeContent();
nodeContent.Name = meshContent.Name;
// Add the transform-only node to the model
rootNode.Children.Add(nodeContent);
}
meshBuilder = null;
return meshContent;
}
}
}
SknReader
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
namespace SknImporter
{
[StructLayout(LayoutKind.Sequential)]
public struct SkinModelHeader
{
//Structure for the Header in skn files
public int magic;
public short numMaterials;
public short numObjects;
};
[StructLayout(LayoutKind.Sequential)]
public struct SkinModelMaterial
{
//Structure for a material block in skn files
public int matIndex;
public string name;
public int startVertex;
public int numVertices;
public int startIndex;
public int numIndices;
};
[StructLayout(LayoutKind.Sequential)]
public struct SkinModelVertex
{
//Vertex block in skn files
public float[] position;
public char[] boneIndex;
public float[] weights;
public float[] normal;
public float[] texcoords;
public int index;
};
public struct SkinModelData
{
//data block in skn files
public int numIndices;
public int numVertices;
public List<short> indices;
public List<SkinModelVertex> verteces;
};
class SknReader
{
public SkinModelData modelData = new SkinModelData();
public List<SkinModelMaterial> materialList = new List<SkinModelMaterial>();
FileStream fileStream;
string fpath;
public SknReader(string path)
{
fpath = path;
fileStream = System.IO.File.Open(path, FileMode.Open);
BinaryReader reader = new BinaryReader(fileStream);
int headerSize = Marshal.SizeOf(typeof(SkinModelHeader));
//SkinModelHeader header =(SkinModelHeader)BytesToStruct(reader.ReadBytes(headerSize),typeof(SkinModelHeader));
SkinModelHeader header = new SkinModelHeader();
header.magic = reader.ReadInt32();
header.numObjects = reader.ReadInt16();
header.numMaterials = reader.ReadInt16();
if (header.numMaterials == 1)
{
int matCount = reader.ReadInt32();
if (matCount > 0)
{
SkinModelMaterial mat = new SkinModelMaterial();
for (int mc = 0; mc < matCount; mc++)
{
mat.matIndex = mc;
mat.name = ASCIIEncoding.ASCII.GetString(reader.ReadBytes(64)).Replace("\0", "");
mat.startVertex = reader.ReadInt32();
mat.numVertices = reader.ReadInt32();
mat.startIndex = reader.ReadInt32();
mat.numIndices = reader.ReadInt32();
materialList.Add(mat);
}
}
}
modelData.numIndices = reader.ReadInt32();
modelData.numVertices = reader.ReadInt32();
modelData.indices = new List<short>();
modelData.verteces = new List<SkinModelVertex>();
for (int i = 0; i < modelData.numIndices; i++)
{
short idx = 0;
idx = reader.ReadInt16();
modelData.indices.Add(idx);
}
for (int i = 0; i < modelData.numVertices; i++)
{
SkinModelVertex vertex = new SkinModelVertex();
vertex.index = i;
vertex.position = new float[3];
vertex.boneIndex = new char[4];
vertex.weights = new float[4];
vertex.normal = new float[3];
vertex.texcoords = new float[2];
vertex.position[0] = reader.ReadSingle();
vertex.position[1] = reader.ReadSingle();
vertex.position[2] = reader.ReadSingle();
// vertex.boneIndex = reader.ReadInt32();
vertex.boneIndex[0] = reader.ReadChar();
vertex.boneIndex[1] = reader.ReadChar();
vertex.boneIndex[2] = reader.ReadChar();
vertex.boneIndex[3] = reader.ReadChar();
vertex.weights[0] = reader.ReadSingle();
vertex.weights[1] = reader.ReadSingle();
vertex.weights[2] = reader.ReadSingle();
vertex.weights[3] = reader.ReadSingle();
vertex.normal[0] = reader.ReadSingle();
vertex.normal[1] = reader.ReadSingle();
vertex.normal[2] = reader.ReadSingle();
vertex.texcoords[0] = reader.ReadSingle();
vertex.texcoords[1] = reader.ReadSingle();
modelData.verteces.Add(vertex);
}
reader.Close();
}
}
}
DEMO CODE
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace LOL_MOD_DISPLAY
{
/// <summary>
/// 这是游戏的主类型
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Camera camera;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 600;
}
/// <summary>
/// 允许游戏在开始运行之前执行其所需的任何初始化。
/// 游戏能够在此时查询任何所需服务并加载任何非图形
/// 相关的内容。调用 base.Initialize 将枚举所有组件
/// 并对其进行初始化。
/// </summary>
protected override void Initialize()
{
// TODO: 在此处添加初始化逻辑
base.Initialize();
}
/// <summary>
/// 对于每个游戏会调用一次 LoadContent,
/// 用于加载所有内容。
/// </summary>
///
Model teemo;
Texture2D texture;
string name = "Ryze";
protected override void LoadContent()
{
// 创建新的 SpriteBatch,可将其用于绘制纹理。
spriteBatch = new SpriteBatch(GraphicsDevice);
teemo = Content.Load<Model>(name);
texture = Content.Load<Texture2D>(name+"_texture");
camera = new Camera(this);
this.Components.Add(camera);
// TODO: 在此处使用 this.Content 加载游戏内容
}
/// <summary>
/// 对于每个游戏会调用一次 UnloadContent,
/// 用于取消加载所有内容。
/// </summary>
///
protected override void UnloadContent()
{
// TODO: 在此处取消加载任何非 ContentManager 内容
}
/// <summary>
/// 允许游戏运行逻辑,例如更新全部内容、
/// 检查冲突、收集输入信息以及播放音频。
/// </summary>
/// <param name="gameTime">提供计时值的快照。</param>
protected override void Update(GameTime gameTime)
{
// 允许游戏退出
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: 在此处添加更新逻辑
base.Update(gameTime);
}
/// <summary>
/// 当游戏该进行自我绘制时调用此项。
/// </summary>
/// <param name="gameTime">提供计时值的快照。</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix world = Matrix.CreateWorld(new Vector3(-0, -20, -0), Vector3.UnitZ, Vector3.Up) ;
Model model = teemo;
Matrix[] modelTransforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(modelTransforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.TextureEnabled = true;
effect.Texture = texture;
effect.World =Matrix.CreateTranslation(new Vector3 (0,-20,0))* Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalMilliseconds / 1000f) * modelTransforms[mesh.ParentBone.Index] * world ;
effect.View = camera.View;
effect.Projection = camera.Projection;
}
mesh.Draw();
}
// TODO: 在此处添加绘图代码
base.Draw(gameTime);
}
}
}