using UnityEngine;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
using System;
namespace Nxr.Internal
{
public class ObjMesh
{
public struct IndexInfo
{
public Vector3Int indexVec;
public int index;
}
public struct Vector3Int
{
public Vector3Int(int ix, int iy, int iz)
{
x = ix;
y = iy;
z = iz;
}
public int x;
public int y;
public int z;
}
///
/// UV坐标列表
///
private List uvArrayList;
///
/// 法线列表
///
private List normalArrayList;
///
/// 顶点列表
///
private List vertexArrayList;
///
/// 面相关的顶点索引、UV索引列表、法线索引
///
private List faceVertexUVNormal;
///
/// UV坐标数组
///
public Vector2[] UVArray;
///
/// 法线数组
///
public Vector3[] NormalArray;
///
/// 顶点数组
///
public Vector3[] VertexArray;
///
/// 面数组
///
public int[] TriangleArray;
///
/// 构造函数 ///
public ObjMesh()
{
//初始化列表
uvArrayList = new List();
normalArrayList = new List();
vertexArrayList = new List();
faceVertexUVNormal = new List();
}
///
/// 从一个文本化后的.obj文件中加载模型
/// 格式 :f v/vt/vn v/vt/vn v/vt/vn(f 顶点索引 / 纹理坐标索引 / 顶点法向量索引)
///
public ObjMesh LoadFromObj(string objText)
{
uvArrayList.Clear();
normalArrayList.Clear();
vertexArrayList.Clear();
faceVertexUVNormal.Clear();
UVArray = null;
TriangleArray = null;
NormalArray = null;
VertexArray = null;
double startMS = new TimeSpan(DateTime.Now.Ticks).TotalMilliseconds;
if (objText.Length <= 0)
return null;
//v这一行在3dsMax中导出的.obj文件
// 前面是两个空格后面是一个空格
objText = objText.Replace(" ", " ");
//将文本化后的obj文件内容按行分割
string[] allLines = objText.Split('\n');
foreach (string line in allLines)
{
//将每一行按空格分割
string[] chars = line.Split(' ');
//根据第一个字符来判断数据的类型
switch (chars[0])
{
case "v":
//处理顶点
this.vertexArrayList.Add(new Vector3(
ConvertToFloat(chars[1]),
ConvertToFloat(chars[2]),
ConvertToFloat(chars[3]))
);
break;
case "vn":
//处理法线
this.normalArrayList.Add(new Vector3(
ConvertToFloat(chars[1]),
ConvertToFloat(chars[2]),
ConvertToFloat(chars[3]))
);
break;
case "vt":
//处理UV
this.uvArrayList.Add(new Vector2(
ConvertToFloat(chars[1]),
ConvertToFloat(chars[2]))
);
break;
case "f":
//处理面
GetTriangleList(chars);
break;
}
}
//合并三角面
Combine();
Debug.Log("ObjMesh Finish=" + (new TimeSpan(DateTime.Now.Ticks).TotalMilliseconds - startMS) + "MS");
return this;
}
private string GenerateKey(Vector3Int vector3)
{
return "key_" + (int)vector3.x + "_" + (int)vector3.y + "_" + (int)vector3.z;
}
///
/// 合并三角面
///
private void Combine()
{
Dictionary CacheDict = new Dictionary();
for (int i = 0, size = faceVertexUVNormal.Count; i < size; i++)
{
Vector3Int tmpVec = faceVertexUVNormal[i];
string key = GenerateKey(tmpVec);
IndexInfo mIndexInfo = new IndexInfo();
mIndexInfo.index = i;
mIndexInfo.indexVec = tmpVec;
if (CacheDict.ContainsKey(key))
{
CacheDict[key].Add(mIndexInfo);
}
else
{
CacheDict[key] = new ArrayList();
CacheDict[key].Add(mIndexInfo);
}
}
//使用一个字典来存储要合并的索引信息
Dictionary toCambineList = new Dictionary();
for (int i = 0, size = faceVertexUVNormal.Count; i < size; i++)
{
if (faceVertexUVNormal[i].x != 0 && faceVertexUVNormal[i].y != 0 && faceVertexUVNormal[i].z != 0)
{
Vector3Int iTemp = faceVertexUVNormal[i];
//相同索引的列表
ArrayList SameIndexList = new ArrayList();
SameIndexList.Add(i);
string key = GenerateKey(iTemp);
if (CacheDict.ContainsKey(key))
{
ArrayList mIdxInfoList = CacheDict[key];
foreach (IndexInfo IndexTtem in mIdxInfoList)
{
int j = IndexTtem.index;
if (j != i)
{
SameIndexList.Add(j);
faceVertexUVNormal[j] = new Vector3Int(0, 0, 0);
}
}
}
//用一个索引来作为字典的键名,这样它可以代替对应列表内所有索引
toCambineList.Add(i, SameIndexList);
}
}
//初始化各个数组
this.VertexArray = new Vector3[toCambineList.Count];
this.UVArray = new Vector2[toCambineList.Count];
this.NormalArray = new Vector3[toCambineList.Count];
this.TriangleArray = new int[faceVertexUVNormal.Count];
//定义遍历字典的计数器
int count = 0;
//遍历词典
foreach (KeyValuePair IndexTtem in toCambineList)
{
//根据索引给面数组赋值
foreach (int item in IndexTtem.Value)
{
TriangleArray[item] = count;
}
//当前的顶点、UV、法线索引信息
Vector3Int VectorTemp = faceVertexUVNormal[IndexTtem.Key];
//给顶点数组赋值
VertexArray[count] = vertexArrayList[VectorTemp.x - 1];
//给UV数组赋值
if (uvArrayList.Count > 0)
{
Vector2 tVec = uvArrayList[VectorTemp.y - 1];
UVArray[count] = new Vector2(tVec.x, tVec.y);
}
//给法线数组赋值
if (normalArrayList.Count > 0)
{
NormalArray[count] = normalArrayList[VectorTemp.z - 1];
}
count++;
}
}
///
/// 获取面列表.格式 :f v/vt/vn v/vt/vn v/vt/vn(f 顶点索引 / 纹理坐标索引 / 顶点法向量索引)
///
/// Chars.
private void GetTriangleList(string[] chars)
{
// f 960/1058/1195 961/1059/1196 962/1060/1197
// 顶点索引: 960/961/962
// 纹理索引:1058/1059/1060
// 法线索引:1195/1196/1197
List indexVectorList = new List();
for (int i = 1, size = chars.Length; i < size; ++i)
{
//将每一行按照空格分割后从第一个元素开始
//按照/继续分割可依次获得顶点索引、UV索引和法线索引
string[] indexs = chars[i].Split('/');
if (indexs.Length < 3) continue;
Vector3Int indexVector = new Vector3Int(0, 0, 0);
//顶点索引
indexVector.x = ConvertToInt(indexs[0]);
//UV索引
if (indexs.Length > 1)
{
if (indexs[1] != "")
indexVector.y = ConvertToInt(indexs[1]);
}
//法线索引
if (indexs.Length > 2)
{
if (indexs[2] != "")
indexVector.z = ConvertToInt(indexs[2]);
}
//将索引向量加入列表中
indexVectorList.Add(indexVector);
}
//这里需要研究研究
for (int j = 1; j < indexVectorList.Count - 1; ++j)
{
//按照0,1,2这样的方式来组成面
faceVertexUVNormal.Add(indexVectorList[0]);
faceVertexUVNormal.Add(indexVectorList[j]);
faceVertexUVNormal.Add(indexVectorList[j + 1]);
}
}
///
/// 将一个字符串转换为浮点类型
///
/// 待转换的字符串
///
private float ConvertToFloat(string s)
{
return FastFloatParse(s); //(float)System.Convert.ToDouble(s, CultureInfo.InvariantCulture);
}
///
/// 将一个字符串转化为整型 ///
/// 待转换的字符串
///
private int ConvertToInt(string s)
{
return FastIntParse(s); //System.Convert.ToInt32(s, CultureInfo.InvariantCulture);
}
///
/// Modified from https://codereview.stackexchange.com/a/76891. Faster than float.Parse
///
public static float FastFloatParse(string input)
{
input = cleanString(input);
if (input.Contains("e") || input.Contains("E"))
return float.Parse(input, CultureInfo.InvariantCulture);
float result = 0;
int pos = 0;
int len = input.Length;
if (len == 0) return float.NaN;
char c = input[0];
float sign = 1;
if (c == '-')
{
sign = -1;
++pos;
if (pos >= len) return float.NaN;
}
while (true) // breaks inside on pos >= len or non-digit character
{
if (pos >= len) return sign * result;
c = input[pos++];
if (c < '0' || c > '9') break;
result = (result * 10.0f) + (c - '0');
}
if (c != '.' && c != ',') return float.NaN;
float exp = 0.1f;
while (pos < len)
{
c = input[pos++];
if (c < '0' || c > '9') return float.NaN;
result += (c - '0') * exp;
exp *= 0.1f;
}
return sign * result;
}
///
/// Modified from http://cc.davelozinski.com/c-sharp/fastest-way-to-convert-a-string-to-an-int. Faster than int.Parse
///
public static int FastIntParse(string input)
{
input = cleanString(input);
int result = 0;
bool isNegative = (input[0] == '-');
for (int i = (isNegative) ? 1 : 0; i < input.Length; i++)
{
result = result * 10 + (input[i] - '0');
}
return (isNegative) ? -result : result;
}
private static string cleanString(string newStr)
{
string tempStr = newStr.Replace((char)13, ' ');
return tempStr.Replace((char)10, ' ').Trim();
}
}
}