Parsing mesh with more than 65k-vertices in Unity at runtime - c#

I'm trying to load OBJ models in runtime using Unity and C#. I'm using Unity's Wiki parser "FastOBJImporter" http://wiki.unity3d.com/index.php/FastObjImporter for parsing the OBJ files.
I can't load a mesh with more than 65,534 vertices since it's a Unity limitation ( http://answers.unity3d.com/questions/471639/mesh-with-more-than-65000-vertices.html )
My idea is passing a big mesh path to FastOBJImporter and generate multiple GameObjects with less than 65k vertices in order to load bigger models.
Does anybody know how can I modify FastOBJimporter safely in order to return a list of sub-meshes instead of a big mesh? Any other solutions/ideas are welcomed.

This script does what you want but with STL model files. It imports the model, no matter how large, by splitting the mesh into submeshes each time 65k verts is reached. STL files can be converted to OBJ files so, I imagine, using a simple converter, or altering the script, could do the trick.
Code below (I take no credit for code).
#pragma warning disable 0219
using UnityEngine;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Threading;
namespace Parabox.STL
{
/*Import methods for STL files*/
public class STL_ImportScript : MonoBehaviour
{
const int MAX_FACETS_PER_MESH = 65535 / 3;
class Facet
{
public Vector3 normal;
public Vector3 a, b, c;
public override string ToString()
{
return string.Format("{0:F2}: {1:F2}, {2:F2}, {3:F2}", normal, a, b, c);
}
}
/**
* Import an STL file at path.
*/
public static Mesh[] Import(string path)
{
try
{
return ImportBinary(path);
}
catch (System.Exception e)
{
UnityEngine.Debug.LogWarning(string.Format("Failed importing mesh at path {0}.\n{1}", path, e.ToString()));
return null;
}
}
private static Mesh[] ImportBinary(string path)
{
List<Facet> facets = new List<Facet>();
byte[] header;
uint facetCount;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
using (BinaryReader br = new BinaryReader(fs, new ASCIIEncoding()))
{
while (br.BaseStream.Position < br.BaseStream.Length)
{
// read header
header = br.ReadBytes(80);
facetCount = br.ReadUInt32();
for (uint i = 0; i < facetCount; i++)
{
try
{
Facet facet = new Facet();
facet.normal.x = br.ReadSingle();
facet.normal.y = br.ReadSingle();
facet.normal.z = br.ReadSingle();
facet.a.x = br.ReadSingle();
facet.a.y = br.ReadSingle();
facet.a.z = br.ReadSingle();
facet.b.x = br.ReadSingle();
facet.b.y = br.ReadSingle();
facet.b.z = br.ReadSingle();
facet.c.x = br.ReadSingle();
facet.c.y = br.ReadSingle();
facet.c.z = br.ReadSingle();
facets.Add(facet);
// padding
br.ReadUInt16();
}
catch (Exception e)
{
//Console.WriteLine(e.Message);
Debug.Log(e.Message);
}
}
}
}
}
return CreateMeshWithFacets(facets);
}
const int SOLID = 1;
const int FACET = 2;
const int OUTER = 3;
const int VERTEX = 4;
const int ENDLOOP = 5;
const int ENDFACET = 6;
const int ENDSOLID = 7;
const int EMPTY = 0;
private static int ReadState(string line)
{
if (line.StartsWith("solid"))
return SOLID;
else if (line.StartsWith("facet"))
return FACET;
else if (line.StartsWith("outer"))
return OUTER;
else if (line.StartsWith("vertex"))
return VERTEX;
else if (line.StartsWith("endloop"))
return ENDLOOP;
else if (line.StartsWith("endfacet"))
return ENDFACET;
else if (line.StartsWith("endsolid"))
return ENDSOLID;
else
return EMPTY;
}
private static Vector3 StringToVec3(string str)
{
string[] split = str.Trim().Split(null);
Vector3 v = new Vector3();
float.TryParse(split[0], out v.x);
float.TryParse(split[1], out v.y);
float.TryParse(split[2], out v.z);
return v;
}
private static Mesh[] CreateMeshWithFacets(IList<Facet> facets)
{
int fl = facets.Count, f = 0, mvc = MAX_FACETS_PER_MESH * 3;
Mesh[] meshes = new Mesh[fl / MAX_FACETS_PER_MESH + 1];
for (int i = 0; i < meshes.Length; i++)
{
int len = System.Math.Min(mvc, (fl - f) * 3);
Vector3[] v = new Vector3[len];
Vector3[] n = new Vector3[len];
int[] t = new int[len];
for (int it = 0; it < len; it += 3)
{
v[it] = facets[f].a;
v[it + 1] = facets[f].b;
v[it + 2] = facets[f].c;
n[it] = facets[f].normal;
n[it + 1] = facets[f].normal;
n[it + 2] = facets[f].normal;
t[it] = it;
t[it + 1] = it + 1;
t[it + 2] = it + 2;
f++;
}
meshes[i] = new Mesh();
meshes[i].vertices = v;
meshes[i].normals = n;
meshes[i].triangles = t;
}
return meshes;
}
}
}
Another solution could be to dynamically combine vertices that share the same space. When you bring in your models at run time you combine vertices that are within a certain threshold of each other to reduce total vertex count below the 65k limit.
Alternatively, use tools, such as Mesh Simplify to lower the level of detail, allowing you to optimize performance and reduce polygon counts below 65k to import your meshes without surpassing the 65k limit. There are different, and more affordable options, but this seemed to be one of the better ones available.

The current top answer here is nicely detailed, but the whole problem can be solved with 1 line of code:
myMeshFilter.mesh = new Mesh() { indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 }; // To support meshes > 65k verts
Correctly setting the index format for the mesh will allow for a bigger list of verts.

Related

Performing linear search on randomized array

I am trying to perform a linear search on some randomized array, but just cant see where the code gets stuck. Its a simple BigONotation algorithm test that I am currently learning and messed up somewhere. I am using a text editor for my coding. Can I get some help with this code?
using System;
using System.Collections.Generic;
using System.Data;
namespace TheBigONotations
{
public class BigONotations
{
///<properties>Class Properties</properties>
private int[] theArray;
private int arraySize;
private int itemsInArray = 0;
DateTime startTime;
DateTime endTime;
public BigONotations(int size)
{
arraySize = size;
theArray = new int[size];
}
///<order>O(1)</order>
public void addItemToArray(int newItem)
{
theArray[itemsInArray++] = newItem;
}
///<order>O(n)</order>
public void linearSearch(int value)
{
bool valueInArray = false;
string indexsWithValue = "";
startTime = DateTime.Now;
for (int i = 0; i < arraySize; i++)
{
if (theArray[i] == value)
{
valueInArray = true;
indexsWithValue += i + " ";
}
}
Console.WriteLine("Value found: " + valueInArray);
endTime = DateTime.Now;
Console.WriteLine("Linear Search took "+(endTime - startTime) );
}
///<order>O(n^2)</order>
public void BinarySearch()
{
}
///<order>O(n)</order>
///<order>O(n)</order>
///<order>O(n)</order>
public void generateRandomArray()
{
Random rnd = new Random();
for (int i = 0; i < arraySize; i++)
{
theArray[i] = (int)(rnd.Next() * 1000) + 10;
}
itemInArray = arraySize - 1;
}
public static void Main()
{
BigONotations algoTest1 = new BigONotations(100000);
algoTest1.generateRandomArray();
BigONotations algoTest2 = new BigONotations(200000);
algoTest2.generateRandomArray();
BigONotations algoTest3 = new BigONotations(300000);
algoTest3.generateRandomArray();
BigONotations algoTest4 = new BigONotations(400000);
algoTest4.generateRandomArray();
BigONotations algoTest5 = new BigONotations(500000);
algoTest5.generateRandomArray();
algoTest2.linearSearch(20);
algoTest3.linearSearch(20);
algoTest4.linearSearch(20);
}
}
}
I put all information in the code but had some error and cant see exactly where it is.
I think the error is because itemInArray inside generateRandomArray() function is not declared. Perhaps you meant itemsInArray?
String addition (with + and += operator) for too many results might be taking long time during linearSearch(). Consider using StringBuilder which is significantly faster and more memory efficient.
StringBuilder sb = new StringBuilder();
// ...
// Let's say "i" is an integer.
sb.Append(i.ToString());
sb.Append(" ");
// ...
Console.WriteLine("Indexes: " + sb.ToString());

Array.Contains() is always false

I've been smashing my head against this problem for days and have tried a tons of different things. I've been all over the forums, tried everything I've seen with no luck. My issue could be that I don't have an override, but I can't figure out how to get that to work.
I want to check if an array of 5,000+ elements contains a user-entered word. The word gets entered character by character and combined into a string(guessString). And then I use .Contains() to see if that word is in an array.
***EDIT please see screenshots for debug logs WordArray Elements -- Debug Output -- Debug With whitespace detection -- Code that doesnt work
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Linq;
public class WordAction : MonoBehaviour
{
TMP_Text m_textComponent;
TMP_Text m_currentSquare;
public TMP_Text[] squareArray;
List<string> dupKey = new List<string>();
public string[] WordArray;
public List<string> DictionaryList = new List<string>();
public TextAsset file;
[SerializeField]
Color correctColor, wrongColor, maybeColor;
[SerializeField]
float colorFadeSpeed, colorFadeTime; // 2f, 1f
public float waitTime;
public string levelID;
public string key;
public AudioSource revealSFX;
bool guess;
string guessString;
int guessRegulator;
int guessCount = 1;
int lessGuessCount; // Starts variable at value of first current row element
int maxGuessCount;
string[] guessStringArray = new string[1];
void Start()
{
for (int i = 0; i < 5; i++) // Duplicate key
{
dupKey.Add(key[i].ToString());
}
var content = file.text;
string[] AllWords = content.Split('\n');
WordArray = AllWords;
}
public void Magic()
{
StartCoroutine(CompareKey());
}
IEnumerator CompareKey()
{
guessRegulator++;
GuessRegulatorFunction();
lessGuessCount = (guessCount * 5) - 5; // Starts variable at value of first current row element
maxGuessCount = guessCount * 5;
guessCount++; // Moves to next row
int k = 0; // Indexer for key[]
int cW = 0; // Indexer for CombineWord()
GameObject keyGO; // Keyboard GO
for (int i = lessGuessCount; i < maxGuessCount; i++)
{
if (cW < 1)
{
CombineWord(i);
cW++;
}
bool match = WordArray.Contains(guessString); // not working
Debug.Log(match);
if (match)
{
//do stuff
}
//compare stuff
string guessStr = squareArray[i].text.ToString();
string keyStr = key[k].ToString();
bool result = guessStr == keyStr;
if (!result && !dupKey.Contains(guessStr))
{
//wrong stuff
GameObject parentGO = squareArray[i].transform.parent.gameObject; // Gets parent of SquareArray element
Image parentImage = parentGO.GetComponent<Image>(); // Gets Image component of parent game object
keyGO = GameObject.Find(squareArray[i].text); // Keyboard
Image keyParentImage = keyGO.GetComponent<Image>(); // Keyboard
wrongColor.a = 255;
keyParentImage.color = wrongColor;
parentImage.color = wrongColor;
yield return null;
}
if (result)
{
//correct stuff
dupKey[k] = "";
GameObject parentGO = squareArray[i].transform.parent.gameObject; // Gets parent of SquareArray element
Image parentImage = parentGO.GetComponent<Image>(); // Gets Image component of parent game object
keyGO = GameObject.Find(squareArray[i].text); // Keyboard
Image keyParentImage = keyGO.GetComponent<Image>(); // Keyboard
correctColor.a = 255;
keyParentImage.color = correctColor;
parentImage.color = correctColor;
yield return null;
}
if (!result && dupKey.Contains(guessStr))
{
//yellow stuff
for (int x = 0; x < 5; x++) // Duplicate key
{
if (guessStr == dupKey[x])
{
dupKey[x] = "";
}
}
GameObject parentGO = squareArray[i].transform.parent.gameObject; // Gets parent of SquareArray element
Image parentImage = parentGO.GetComponent<Image>(); // Gets Image component of parent game object
keyGO = GameObject.Find(squareArray[i].text); // Keyboard
Image keyParentImage = keyGO.GetComponent<Image>(); // Keyboard
maybeColor.a = 255;
keyParentImage.color = maybeColor;
parentImage.color = maybeColor;
yield return null;
}
revealSFX.Play();
k++;
yield return new WaitForSeconds(waitTime);
}
dupKey.Clear();
for (int i = 0; i < 5; i++) // Duplicate key
{
dupKey.Add(key[i].ToString());
}
}
void GuessRegulatorFunction()
{
guessRegulator++; // Stops multiple guess attempts
for (int i = 0; i < (guessCount * 5); i++) // Checks if row is blank when guessing
{
if (squareArray[i].text == "")
{
guess = false;
guessRegulator = 0; // Resets guess regulator
break;
}
else
{
guess = true;
}
}
if (guessRegulator > 1 || guess == false) // Stops multiple guess attempts
{
return;
}
}
public void BackSpace()
{
for (int i = guessCount * 5; i > (guessCount * 5) - 6; i--)
{
if (squareArray[i].text != "")
{
squareArray[i].text = "";
break;
}
}
}
public void InputLetter()
{
guessRegulator = 0;
for (int i = 0; i < guessCount * 5; i++)
{
if (squareArray[i].text == "")
{
squareArray[i].text = EventSystem.current.currentSelectedGameObject.name.ToString();
break;
}
}
}
void CombineWord(int i)
{
var string1 = squareArray[i].text.ToString();
var string2 = squareArray[i + 1].text.ToString();
var string3 = squareArray[i + 2].text.ToString();
var string4 = squareArray[i + 3].text.ToString();
var string5 = squareArray[i + 4].text.ToString();
guessString = string1 + string2 + string3 + string4 + string5;
//Debug.Log(guessString);
}
}
I've taken your line of code that isn't working and copied it verbatim. I've then taken the data that you say is in the WordArray and guessString variables and set those up. Then I ran this:
var WordArray = new [] { "WHICH", "THERE", "THEIR", "ABOUT" };
var guessString= "THERE";
bool match = WordArray.Contains(guessString);
Console.WriteLine(match);
match comes out True.
Your variables do not contain the data you think they do.
It's likely that the content that you call .Split('\n') on actually contains Windows end of line markers, so a combination of "\r\n". Since you only split on '\n' it's likely that the '\r' remains in your strings and hence "THERE" does not match "THERE\r".
Try this instead:
.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
If your code is run on either Windows or on Linux the above line works. Just watch out for files that mix the endings.
Well, your WordArray is always empty. You put your file content into a local variable called AllWords.
Your word will never be found in an empty array.

Having issue creating recursive constructor

I'm trying to build Quad-tree which receive array of points and split them into 4 cells. those 4 cells(cell1...cell4) are suppose to be created recursively but it doesn't works.
What I'm doing wrong? wasted all day trying fix it.
I erased some basic things not to overload the script here.
class QuadTreeNode
{
private QuadTreeNode cell1;
private QuadTreeNode cell2;
private QuadTreeNode cell3;
private QuadTreeNode cell4;
private int maxppc;
private double leftminX, leftminY, rightmaxX, rightmaxY;
public QuadTreeNode(Point2D leftMin, Point2D rightMax, int maxPPC)
{
this.leftminX = leftMin.East;
this.leftminY = leftMin.North;
this.rightmaxX = rightMax.East;
this.rightmaxY = rightMax.North;
this.maxppc = maxPPC;
}
public QuadTreeNode(Point2D[] pointArray, Point2D leftMin, Point2D rightMax, int maxPPC)
{
for (int i=0; i <4; i++)
{
cellList[i] = new List<Point2D>();
}
allPointList = pointArray.ToList();
this.leftminX = leftMin.East;
this.leftminY = leftMin.North;
this.rightmaxX = rightMax.East;
this.rightmaxY = rightMax.North;
this.maxppc = maxPPC;
if (allPointList.Count > maxPPC)
{
Split(allPointList);
}
}
public void Split(List<Point2D> Array)
{
if (Array.Count > maxppc)
{
double centerE = (this.leftminX + this.rightmaxX) / 2;
double centerN = (this.leftminY + this.rightmaxY) / 2;
double deltaE = (this.rightmaxX - this.leftminX) / 2;
double deltaN = (this.rightmaxY - this.leftminY) / 2;
Point2D Center = new Point2D(centerE, centerN);
this.cell1 = new QuadTreeNode(cellList[0].ToArray(),new Point2D((Center.East - deltaE), (Center.North - deltaN)), Center, maxppc);
this.cell2 = new QuadTreeNode(cellList[1].ToArray(), new Point2D(Center.East, Center.North - deltaN), new Point2D((Center.East + deltaE), Center.North), maxppc);
this.cell3 = new QuadTreeNode(cellList[2].ToArray(), new Point2D((Center.East - deltaE), (Center.North)), new Point2D(Center.East, (Center.North + deltaN)), maxppc);
this.cell4 = new QuadTreeNode(cellList[3].ToArray(), Center, new Point2D((Center.East + deltaE), (Center.North + deltaN)), maxppc);
for (pntIndex = 0; pntIndex < Array.Count; pntIndex++)
{
CellIndex(Array[pntIndex]);
}
pntIndex = 0;
Array.Clear();
for (int c=0; c < 4; c++)
{
Array = cellList[c].ToList();
cellList[0].Clear();
cellList[1].Clear();
cellList[2].Clear();
cellList[3].Clear();
Split(Array);
}
return;
}
else
{
return;
}
}
public void CellIndex(Point2D point)
{
//locates points up to East,North
}
The problem may be with this part:
Array = cellList[c].ToList();
cellList[0].Clear();
cellList[1].Clear();
cellList[2].Clear();
cellList[3].Clear();
Split(Array);
Here, you're copying the list at location c but you're clearing each list at every iteration anyway, so you're likely losing some of your points.

Having trouble getting past NullReferenceException [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 8 years ago.
I'm a newbie in programming, especially in c#. I have written some code but I keep getting an error when running it and I can't move on until I get that fixed.
The error in question is a NullReferenceException. It also tells me "Object reference not set to an instance of an object".
It seems like a pretty clear error message indicating that an object hasn't been instantiated yet. However I thought I had done that. I hope someone can explain to me what I'm doing wrong. Here's my code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace EvenHelemaalOvernieuw
{
class Globals
{
public static int size = 50;
public static int factor = 3;
public static int puzzleNumber = 1;
public static Square[,] allSquares = new Square[Globals.factor * Globals.factor, Globals.factor * Globals.factor];
public static String path = #"" + factor.ToString() + "\\" + puzzleNumber.ToString() + ".txt";
public static int[,][,] values = new int[factor, factor][,];
public Globals() { }
public void setSize(int s)
{
size = s;
if (size > 100)
{
size = 100;
}
if (size < 20)
{
size = 20;
}
}
public void setFactor(int f)
{
factor = f;
if (factor > 5)
{
factor = 5;
}
if (factor < 2)
{
factor = 2;
}
}
public Square getSquare(int x, int y)
{
return allSquares[x, y];
}
public static void readPuzzle()
{
List<int> conversion = new List<int>();
int count = 0;
using (StreamReader codeString = new StreamReader(path))
{
String line = codeString.ReadToEnd();
Array characters = line.ToCharArray();
foreach (char a in characters)
{
if (a.ToString() != ",")
{
conversion.Add(Convert.ToInt32(a));
}
}
for (int panelX = 0; panelX < factor; panelX++)
{
for (int panelY = 0; panelY < factor; panelY++)
{
for (int squareX = 0; squareX < factor; squareX++)
{
for (int squareY = 0; squareY < factor; squareY++)
{
values[panelX, panelY][squareX, squareY] = conversion[count];
count++;
}
}
}
}
}
}
}
}
The line that is indicated by the error message is near the bottom and reads values[panelX, panelY][squareX, squareY] = conversion[count];.
The problem is the following line
public static int[,][,] values = new int[factor, factor][,];
This is an array of arrays but this code only creates the outer array. The inner array is uninitialized and will be null. Hence when the following code runs it will throw a NullReferenceException trying to access the inner array
values[panelX, panelY][squareX, squareY] = conversion[count];
To fix this just initialize the array elements right before the 3rd nested loop
values[panelX, panelY] = new int[factor, factor];
for (int squareX = 0; squareX < factor; squareX++)

C# - A faster alternative to Convert.ToSingle()

I'm working on a program which reads millions of floating point numbers from a text file. This program runs inside of a game that I'm designing, so I need it to be fast (I'm loading an obj file). So far, loading a relatively small file takes about a minute (without precompilation) because of the slow speed of Convert.ToSingle(). Is there a faster way to do this?
EDIT: Here's the code I use to parse the Obj file
http://pastebin.com/TfgEge9J
using System;
using System.IO;
using System.Collections.Generic;
using OpenTK.Math;
using System.Drawing;
using PlatformLib;
public class ObjMeshLoader
{
public static StreamReader[] LoadMeshes(string fileName)
{
StreamReader mreader = new StreamReader(PlatformLib.Platform.openFile(fileName));
MemoryStream current = null;
List<MemoryStream> mstreams = new List<MemoryStream>();
StreamWriter mwriter = null;
if (!mreader.ReadLine().Contains("#"))
{
mreader.BaseStream.Close();
throw new Exception("Invalid header");
}
while (!mreader.EndOfStream)
{
string cmd = mreader.ReadLine();
string line = cmd;
line = line.Trim(splitCharacters);
line = line.Replace(" ", " ");
string[] parameters = line.Split(splitCharacters);
if (parameters[0] == "mtllib")
{
loadMaterials(parameters[1]);
}
if (parameters[0] == "o")
{
if (mwriter != null)
{
mwriter.Flush();
current.Position = 0;
}
current = new MemoryStream();
mwriter = new StreamWriter(current);
mwriter.WriteLine(parameters[1]);
mstreams.Add(current);
}
else
{
if (mwriter != null)
{
mwriter.WriteLine(cmd);
mwriter.Flush();
}
}
}
mwriter.Flush();
current.Position = 0;
List<StreamReader> readers = new List<StreamReader>();
foreach (MemoryStream e in mstreams)
{
e.Position = 0;
StreamReader sreader = new StreamReader(e);
readers.Add(sreader);
}
return readers.ToArray();
}
public static bool Load(ObjMesh mesh, string fileName)
{
try
{
using (StreamReader streamReader = new StreamReader(Platform.openFile(fileName)))
{
Load(mesh, streamReader);
streamReader.Close();
return true;
}
}
catch { return false; }
}
public static bool Load2(ObjMesh mesh, StreamReader streamReader, ObjMesh prevmesh)
{
if (prevmesh != null)
{
//mesh.Vertices = prevmesh.Vertices;
}
try
{
//streamReader.BaseStream.Position = 0;
Load(mesh, streamReader);
streamReader.Close();
#if DEBUG
Console.WriteLine("Loaded "+mesh.Triangles.Length.ToString()+" triangles and"+mesh.Quads.Length.ToString()+" quadrilaterals parsed, with a grand total of "+mesh.Vertices.Length.ToString()+" vertices.");
#endif
return true;
}
catch (Exception er) { Console.WriteLine(er); return false; }
}
static char[] splitCharacters = new char[] { ' ' };
static List<Vector3> vertices;
static List<Vector3> normals;
static List<Vector2> texCoords;
static Dictionary<ObjMesh.ObjVertex, int> objVerticesIndexDictionary;
static List<ObjMesh.ObjVertex> objVertices;
static List<ObjMesh.ObjTriangle> objTriangles;
static List<ObjMesh.ObjQuad> objQuads;
static Dictionary<string, Bitmap> materials = new Dictionary<string, Bitmap>();
static void loadMaterials(string path)
{
StreamReader mreader = new StreamReader(Platform.openFile(path));
string current = "";
bool isfound = false;
while (!mreader.EndOfStream)
{
string line = mreader.ReadLine();
line = line.Trim(splitCharacters);
line = line.Replace(" ", " ");
string[] parameters = line.Split(splitCharacters);
if (parameters[0] == "newmtl")
{
if (materials.ContainsKey(parameters[1]))
{
isfound = true;
}
else
{
current = parameters[1];
}
}
if (parameters[0] == "map_Kd")
{
if (!isfound)
{
string filename = "";
for (int i = 1; i < parameters.Length; i++)
{
filename += parameters[i];
}
string searcher = "\\" + "\\";
filename.Replace(searcher, "\\");
Bitmap mymap = new Bitmap(filename);
materials.Add(current, mymap);
isfound = false;
}
}
}
}
static float parsefloat(string val)
{
return Convert.ToSingle(val);
}
int remaining = 0;
static string GetLine(string text, ref int pos)
{
string retval = text.Substring(pos, text.IndexOf(Environment.NewLine, pos));
pos = text.IndexOf(Environment.NewLine, pos);
return retval;
}
static void Load(ObjMesh mesh, StreamReader textReader)
{
//try {
//vertices = null;
//objVertices = null;
if (vertices == null)
{
vertices = new List<Vector3>();
}
if (normals == null)
{
normals = new List<Vector3>();
}
if (texCoords == null)
{
texCoords = new List<Vector2>();
}
if (objVerticesIndexDictionary == null)
{
objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>();
}
if (objVertices == null)
{
objVertices = new List<ObjMesh.ObjVertex>();
}
objTriangles = new List<ObjMesh.ObjTriangle>();
objQuads = new List<ObjMesh.ObjQuad>();
mesh.vertexPositionOffset = vertices.Count;
string line;
string alltext = textReader.ReadToEnd();
int pos = 0;
while ((line = GetLine(alltext, pos)) != null)
{
if (line.Length < 2)
{
break;
}
//line = line.Trim(splitCharacters);
//line = line.Replace(" ", " ");
string[] parameters = line.Split(splitCharacters);
switch (parameters[0])
{
case "usemtl":
//Material specification
try
{
mesh.Material = materials[parameters[1]];
}
catch (KeyNotFoundException)
{
Console.WriteLine("WARNING: Texture parse failure: " + parameters[1]);
}
break;
case "p": // Point
break;
case "v": // Vertex
float x = parsefloat(parameters[1]);
float y = parsefloat(parameters[2]);
float z = parsefloat(parameters[3]);
vertices.Add(new Vector3(x, y, z));
break;
case "vt": // TexCoord
float u = parsefloat(parameters[1]);
float v = parsefloat(parameters[2]);
texCoords.Add(new Vector2(u, v));
break;
case "vn": // Normal
float nx = parsefloat(parameters[1]);
float ny = parsefloat(parameters[2]);
float nz = parsefloat(parameters[3]);
normals.Add(new Vector3(nx, ny, nz));
break;
case "f":
switch (parameters.Length)
{
case 4:
ObjMesh.ObjTriangle objTriangle = new ObjMesh.ObjTriangle();
objTriangle.Index0 = ParseFaceParameter(parameters[1]);
objTriangle.Index1 = ParseFaceParameter(parameters[2]);
objTriangle.Index2 = ParseFaceParameter(parameters[3]);
objTriangles.Add(objTriangle);
break;
case 5:
ObjMesh.ObjQuad objQuad = new ObjMesh.ObjQuad();
objQuad.Index0 = ParseFaceParameter(parameters[1]);
objQuad.Index1 = ParseFaceParameter(parameters[2]);
objQuad.Index2 = ParseFaceParameter(parameters[3]);
objQuad.Index3 = ParseFaceParameter(parameters[4]);
objQuads.Add(objQuad);
break;
}
break;
}
}
//}catch(Exception er) {
// Console.WriteLine(er);
// Console.WriteLine("Successfully recovered. Bounds/Collision checking may fail though");
//}
mesh.Vertices = objVertices.ToArray();
mesh.Triangles = objTriangles.ToArray();
mesh.Quads = objQuads.ToArray();
textReader.BaseStream.Close();
}
public static void Clear()
{
objVerticesIndexDictionary = null;
vertices = null;
normals = null;
texCoords = null;
objVertices = null;
objTriangles = null;
objQuads = null;
}
static char[] faceParamaterSplitter = new char[] { '/' };
static int ParseFaceParameter(string faceParameter)
{
Vector3 vertex = new Vector3();
Vector2 texCoord = new Vector2();
Vector3 normal = new Vector3();
string[] parameters = faceParameter.Split(faceParamaterSplitter);
int vertexIndex = Convert.ToInt32(parameters[0]);
if (vertexIndex < 0) vertexIndex = vertices.Count + vertexIndex;
else vertexIndex = vertexIndex - 1;
//Hmm. This seems to be broken.
try
{
vertex = vertices[vertexIndex];
}
catch (Exception)
{
throw new Exception("Vertex recognition failure at " + vertexIndex.ToString());
}
if (parameters.Length > 1)
{
int texCoordIndex = Convert.ToInt32(parameters[1]);
if (texCoordIndex < 0) texCoordIndex = texCoords.Count + texCoordIndex;
else texCoordIndex = texCoordIndex - 1;
try
{
texCoord = texCoords[texCoordIndex];
}
catch (Exception)
{
Console.WriteLine("ERR: Vertex " + vertexIndex + " not found. ");
throw new DllNotFoundException(vertexIndex.ToString());
}
}
if (parameters.Length > 2)
{
int normalIndex = Convert.ToInt32(parameters[2]);
if (normalIndex < 0) normalIndex = normals.Count + normalIndex;
else normalIndex = normalIndex - 1;
normal = normals[normalIndex];
}
return FindOrAddObjVertex(ref vertex, ref texCoord, ref normal);
}
static int FindOrAddObjVertex(ref Vector3 vertex, ref Vector2 texCoord, ref Vector3 normal)
{
ObjMesh.ObjVertex newObjVertex = new ObjMesh.ObjVertex();
newObjVertex.Vertex = vertex;
newObjVertex.TexCoord = texCoord;
newObjVertex.Normal = normal;
int index;
if (objVerticesIndexDictionary.TryGetValue(newObjVertex, out index))
{
return index;
}
else
{
objVertices.Add(newObjVertex);
objVerticesIndexDictionary[newObjVertex] = objVertices.Count - 1;
return objVertices.Count - 1;
}
}
}
Based on your description and the code you've posted, I'm going to bet that your problem isn't with the reading, the parsing, or the way you're adding things to your collections. The most likely problem is that your ObjMesh.Objvertex structure doesn't override GetHashCode. (I'm assuming that you're using code similar to http://www.opentk.com/files/ObjMesh.cs.
If you're not overriding GetHashCode, then your objVerticesIndexDictionary is going to perform very much like a linear list. That would account for the performance problem that you're experiencing.
I suggest that you look into providing a good GetHashCode method for your ObjMesh.Objvertex class.
See Why is ValueType.GetHashCode() implemented like it is? for information about the default GetHashCode implementation for value types and why it's not suitable for use in a hash table or dictionary.
Edit 3: The problem is NOT with the parsing.
It's with how you read the file. If you read it properly, it would be faster; however, it seems like your reading is unusually slow. My original suspicion was that it was because of excess allocations, but it seems like there might be other problems with your code too, since that doesn't explain the entire slowdown.
Nevertheless, here's a piece of code I made that completely avoids all object allocations:
static void Main(string[] args)
{
long counter = 0;
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
var text = File.ReadAllText("spacestation.obj");
for (int i = 0; i < text.Length; i++)
{
int start = i;
while (i < text.Length &&
(char.IsDigit(text[i]) || text[i] == '-' || text[i] == '.'))
{ i++; }
if (i > start)
{
sb.Append(text, start, i - start); //Copy data to the buffer
float value = Parse(sb); //Parse the data
sb.Remove(0, sb.Length); //Clear the buffer
counter++;
}
}
sw.Stop();
Console.WriteLine("{0:N0}", sw.Elapsed.TotalSeconds); //Only a few ms
}
with this parser:
const int MIN_POW_10 = -16, int MAX_POW_10 = 16,
NUM_POWS_10 = MAX_POW_10 - MIN_POW_10 + 1;
static readonly float[] pow10 = GenerateLookupTable();
static float[] GenerateLookupTable()
{
var result = new float[(-MIN_POW_10 + MAX_POW_10) * 10];
for (int i = 0; i < result.Length; i++)
result[i] = (float)((i / NUM_POWS_10) *
Math.Pow(10, i % NUM_POWS_10 + MIN_POW_10));
return result;
}
static float Parse(StringBuilder str)
{
float result = 0;
bool negate = false;
int len = str.Length;
int decimalIndex = str.Length;
for (int i = len - 1; i >= 0; i--)
if (str[i] == '.')
{ decimalIndex = i; break; }
int offset = -MIN_POW_10 + decimalIndex;
for (int i = 0; i < decimalIndex; i++)
if (i != decimalIndex && str[i] != '-')
result += pow10[(str[i] - '0') * NUM_POWS_10 + offset - i - 1];
else if (str[i] == '-')
negate = true;
for (int i = decimalIndex + 1; i < len; i++)
if (i != decimalIndex)
result += pow10[(str[i] - '0') * NUM_POWS_10 + offset - i];
if (negate)
result = -result;
return result;
}
it happens in a small fraction of a second.
Of course, this parser is poorly tested and has these current restrictions (and more):
Don't try parsing more digits (decimal and whole) than provided for in the array.
No error handling whatsoever.
Only parses decimals, not exponents! i.e. it can parse 1234.56 but not 1.23456E3.
Doesn't care about globalization/localization. Your file is only in a single format, so there's no point caring about that kind of stuff because you're probably using English to store it anyway.
It seems like you won't necessarily need this much overkill, but take a look at your code and try to figure out the bottleneck. It seems to be neither the reading nor the parsing.
Have you measured that the speed problem is really caused by Convert.ToSingle?
In the code you included, I see you create lists and dictionaries like this:
normals = new List<Vector3>();
texCoords = new List<Vector2>();
objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>();
And then when you read the file, you add in the collection one item at a time.
One of the possible optimizations would be to save total number of normals, texCoords, indexes and everything at the start of the file, and then initialize these collections by these numbers. This will pre-allocate the buffers used by collections, so adding items to the them will be pretty fast.
So the collection creation should look like this:
// These values should be stored at the beginning of the file
int totalNormals = Convert.ToInt32(textReader.ReadLine());
int totalTexCoords = Convert.ToInt32(textReader.ReadLine());
int totalIndexes = Convert.ToInt32(textReader.ReadLine());
normals = new List<Vector3>(totalNormals);
texCoords = new List<Vector2>(totalTexCoords);
objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>(totalIndexes);
See List<T> Constructor (Int32) and Dictionary<TKey, TValue> Constructor (Int32).
This related question is for C++, but is definitely worth a read.
For reading as fast as possible, you're probably going to want to map the file into memory and then parse using some custom floating point parser, especially if you know the numbers are always in a specific format (i.e. you're the one generating the input files in the first place).
I tested .Net string parsing once and the fastest function to parse text was the old VB Val() function. You could pull the relevant parts out of Microsoft.VisualBasic.Conversion Val(string)
Converting String to numbers
Comparison of relative test times (ms / 100000 conversions)
Double Single Integer Int(w/ decimal point)
14 13 6 16 Val(Str)
14 14 6 16 Cxx(Val(Str)) e.g., CSng(Val(str))
22 21 17 e! Convert.To(str)
23 21 16 e! XX.Parse(str) e.g. Single.Parse()
30 31 31 32 Cxx(str)
Val: fastest, part of VisualBasic dll, skips non-numeric,
ConvertTo and Parse: slower, part of core, exception on bad format (including decimal point)
Cxx: slowest (for strings), part of core, consistent times across formats

Categories