Custom OBJ Importer UV trouble

I am totally scrambled by UV.

            mesh = new Mesh();
            for (int i = 0; i < VERTS.Count; i++)
            {
                VERTS[i] = VERTS[i] * 0.01f; // THIS IS THE SCALE;
                var X = VERTS[i].x;
                var Y = VERTS[i].y;
                var Z = VERTS[i].z;
                VERTS[i] = new Vector3(X, -Z, Y); // THIS IS THE ROTATION
                // WHAT IS THE SCIENCE HERE?
                if (i >= NORMALS.Count) // COMPLETE THE NORMALS
                    NORMALS.Add(NORMALS[FACES[i].z]); // Gives useable Normals
                if (i >= SORTEDUVS.Count)
                {
                    SORTEDUVS.Add(CUBEMAP(0, VERTS[i].y, VERTS[i].z) * 5); // RIGHT LEFT
                    //SORTEDUVS.Add(CUBEMAP(1, VERTS[i].x, VERTS[i].y) * 5); // FRONT BACK
                    //SORTEDUVS.Add(CUBEMAP(2, VERTS[i].x, VERTS[i].z) * 5); // TOP BOTTOM;
                }
            }
            for (int i = 0; i < TRIANGLES.Count; i++)
            {
                TRIANGLES[i] = TRIANGLES[i] - 1;
            }
            mesh.SetVertices(VERTS.ToArray());
            mesh.SetTriangles(TRIANGLES.ToArray(), 0);
            mesh.SetNormals(SORTEDNORMS.ToArray());
            mesh.SetUVs(0,SORTEDUVS.ToArray());
            FILTER.mesh = mesh;
    Vector2 CUBEMAP(int TEXTURE_SECTION, float X, float Y)
    {
        Vector2 UVOFFSET = Vector2.zero;
        if (TEXTURE_SECTION == 0)
        {
            if (X < 0)
                X = -X;
            if (Y < 0)
                Y = -Y;
           UVOFFSET = new Vector2(0 + X, 0 + Y);
        }
        if (TEXTURE_SECTION == 1)
        {
            if (X < 0)
                X = -X;
            if (Y < 0)
                Y = -Y;
            UVOFFSET = new Vector2(1 - X, 1 - Y);
        }
        if (TEXTURE_SECTION == 2)
        {
            if (X < 0)
                X = -X;
            if (Y < 0)
                Y = -Y;
            UVOFFSET = new Vector2(1 + X, 0 + Y);
        }
        return UVOFFSET;
    }

So I really only wanted to copy my UV from my OBJ file data. But i wound up not being able to figure out how, so I went down this crazy route of generating UV’s instead. And as you can see the result isn’t half bad, but its also not ideal either.

Ultimately I have reduced the verts from 75 if checked from project folder after Unity Import, to 21 if imported from desktop at runtime. But as you can see, I am heavily limited in UV coordinates, as truth be told, I look for a pattern in how to apply the UV list saved in the .OBJ data, and I search and search all day long, and i am just at this point where I am completely lost.

If any info you have, any logic, or guidance, expertise, any clue or suggestion, anything to drill into my head, or correct me on. Please you are quite welcome. If you need the long OBJ importing list code, i have a custom one not like the free version on the asset store, and unlike the github one online. I did analyze both files but i continue to fail to understand the way the UV Vert and Norm are assembled from the OBJ data. I am only able to do the side of the object which mirrors both sides.
8474183--1126292--Strange.png

How are these OBJ models getting modeled?

MeshRenderer and meshfilter.

but I think what you mean is how do they exist in unity without being in the project folder? The answer is system IO filestream data. They exist as text file data. This data is parsed and used to generate the model via the code you see above.

I think I have a theory for a solution.

Since 21 verts is a substantial reduction from 75 verts

it’s possible I can render the model twice, and texture it from only two directions left to right, and top to bottom.

Still I am flying blind. But I’ll see what I can achieve today.

i think unity (when importing meshes) will split (duplicate) vertices, to allow those uv “edges” or normals i guess, something to check, if its related to that issue.

Unity is doing exactly what the 3D modelling software wants it to do with the data. Producing the exact thing you modelled in the software. UV normals and all, I am just too baffled by the method of achieving this as applying the The .y vector from its face data to UV’s does not give 3D modelling software identical result. More so

the list of the actual vertices is smaller than the uv list and the normals data is even smaller than that, and so both normals and verts require bloating to the quality of the UV list, and some, but on file data the uv list is also substantially smaller than the intended final mesh. By a ratio of 21/75. And so applying uvs straight from file data is also incorrect.

The model needs to 3.6x larger than it needs to be for normals and verts, in order to accommodate uvs. The uv list also needs to be larger than both face data list aswell. But not so large that they are the same as triangles. My ratio is

21 verts to 75 verts
21 norms to 75 norms
21 uvs to 75 uvs
And
114 triangles but, let me double check the intended triangle number of the project folder import.

By the way I am happy to share the .obj importer code I am not trying to keep it hidden from you I just didn’t want to overly bulk the original post. A second file format .dae works similar to .obj and tells you the size of the arrays it has saved before listing the data of those arrays. However the list of array data does not match the 75 per finalised norm vert and uv that is assembled on project folder import and when opening the model in unity or VS. And it also requires a different data parse code.

            mesh = new Mesh();
            List<int> USEDFACES = new List<int>();
            for (int i = 0; i < UVS.Count; i++)
            {
                int FACEINDEX = 0;
                for (int F = 0; F < FACES.Count; F++)
                {
                    if (FACES[F].y == i && !USEDFACES.Contains(i))
                    {
                        FACEINDEX = i;
                        USEDFACES.Add(i);
                    }
                }
                if (i >= VERTS.Count)
                    VERTS.Add(VERTS[FACES[FACEINDEX].x]);
                VERTS[i] = VERTS[i] * 0.01f; // THIS IS THE SCALE;
                var X = VERTS[i].x;
                var Y = VERTS[i].y;
                var Z = VERTS[i].z;
                VERTS[i] = new Vector3(X, -Z, Y); // THIS IS THE ROTATION

                // WHAT IS THE SCIENCE HERE?
                if (i >= NORMALS.Count) // COMPLETE THE NORMALS
                    NORMALS.Add(NORMALS[FACES[FACEINDEX].z]); // Gives useable Normals
    
            }

Oh its a baffler alright, I’ll need alot of coffee for this.

the below image shows how Unity Imports the UV and apply from project folder, in comparison to what i am doing. The below has 75 UV’s the above has 21 UV’s

Hi Again, sorry to bang on about it. but maybe somebody will see this, now or in the future, and possibly explain in the correct way how a UV list is bloated.

Here is the Model in VS, these are called VERTEX NORMALS. I believe; these vertex normals, which differ from the FACE normals. are indeed, the representing directions of the UV Map.

Saving the Model IN VS appears to have changed the data in the list of the file, saving a full 21 list of normals as oppose to the original 14.

VS also gives me this image for the UVs

Now we can clearly see what i have done and what i aim to achieve

I haven’t read through all details of all posts here, but to me it seems there’s a confusion between two seperate concepts:

  • How a model / mesh is stored in a file
  • How a mesh is represented on the GPU

Specifically the OBJ file format defines “faces” / primitives by using several indices into the different and seperate vertex attribute arrays. So there’s a vertices array, a normals array and a UV array and a single primitive would provide a seperate index into those lists / arrays to actually form a vertex for a face.

On the GPU it works completely different. There are no seperate buffers for vertexposition, vertexnormals, vertextangents, vertexUVs, vertexcolors, … In reality one vertex is essentially a struct that contains all this information bundled together. The vertex buffer on the GPU is a single array that contains a list of this bundled data. We only have a single index buffer on the GPU which forms the primitives / faces of our mesh. The indices index into the vertex array, that’s it.

File formats are about storing the information in a compact way. So a cube may only have 8 positions and 6 normals and when the 12 triangles are constructed (12*3 == 36 indices) the file format uses seperate indices for those two lists and creates on the fly a unique combination of position and normal. This helps keeping the file size small.

On the GPU you actually need those unique vertices to be seperate. A cube on the GPU requires 24 unique vertices because the 6 faces require 4 unique combinations of position and normal. Therefore you have 6*4 == 24 vertices. It’s not 36 because each “face” which contains 2 triangle can actually share 2 vertices since all attributes at the seam between the two triangles are the same. However whenever a single vertex attribute needs to be different for one triangle that meets at a vertex from all the other triangles at that vertex, you need to have a seperate unique vertex.

So when loading OBJ data, you have to actually analyse the faces it contains and create a unique vertex for each unique combination of position, normal and uv indices. If a face / primitive uses the same combination of position, norma, and uv, that vertex can also be shared on the GPU.

It’s not clear if you wrote that OBJ loader yourself or if you’re using a third party library. Anyways it’s important to understand how meshes are represented on the GPU side and that it may be different to how file formats choose to store that information.

1 Like

Thanks Bunny, i wrote it myself.

    void GETFILEFULL()
    {
        if (File.Exists("C:/Users/SCHar/Desktop/FULLWACK3.obj"))
        {
            var MOVE = "C:/Users/SCHar/Desktop/FULLWACK3.obj";
            var MOVELINE = File.ReadAllLines(MOVE).Length;
            StreamReader r = new StreamReader(MOVE);
            for (int l = 0; l < MOVELINE; l++)
            {
                string line = r.ReadLine();
                DATA.Add(line);
                if (line.Contains("v ")) // GET VERT
                {
                    Vector3 VERT = Vector3.zero;
                    string PART = "";
                    int COORDINDEX = 0;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1)
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                            if (COORDINDEX == 0)
                                float.TryParse(PART, out VERT.x);
                            if (COORDINDEX == 1)
                                float.TryParse(PART, out VERT.y);
                            if (COORDINDEX == 2)
                            {
                                float.TryParse(PART, out VERT.z);
                                VERTS.Add(VERT);
                            }
                            COORDINDEX = COORDINDEX + 1;
                            PART = "";
                        }
                        else if (line[c] != 'v')
                            PART = PART + line[c];
                    }
                }
                if (line.Contains("f ")) // GET FACES
                {
                    Vector3Int TRIANGLE = Vector3Int.zero;
                    string PART = "";
                    int COORDINDEX = 0;
                    int TRIANGLEPART = 0;
                    Vector3Int TRI_UV_NORM = Vector3Int.zero;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1 || line[c] == '/')
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                            int INT = 0;
                            if (COORDINDEX == 0)
                            {
                                if (TRIANGLEPART == 0)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.x = INT;
                                    TRIANGLES.Add(INT);
                                }
                                if (TRIANGLEPART == 1)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.y = INT;
                                    TRIANGLES.Add(INT);
                                }
                                if (TRIANGLEPART == 2)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.z = INT;
                                    TRIANGLES.Add(INT);
                                }
                                int P = 0;
                                int.TryParse(PART, out P);
                                TRI_UV_NORM.x = P;
                            }
                            if (COORDINDEX == 1)
                            {
                                int.TryParse(PART, out INT);
                                UVINDEX.Add(INT);
                                TRI_UV_NORM.y = INT;
                            }
                            if (COORDINDEX == 2)
                            {
                                int.TryParse(PART, out INT);
                                NORMINDEX.Add(INT);
                                COORDINDEX = 0;
                                TRI_UV_NORM.z = INT;
                                FACES.Add(TRI_UV_NORM);
                                TRIANGLEPART = 0;
                            }
                            else
                            {
                                COORDINDEX = COORDINDEX + 1;
                                TRIANGLEPART = TRIANGLEPART + 1;
                            }
                            PART = "";
                        }
                        else
                            PART = PART + line[c];
                    }
                }
                if (line.Contains("vt ")) // GET UV TEX
                {
                    string PART = "";
                    int COORDINDEX = -1;
                    Vector2 ULTRAVIOLET = Vector2.zero;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1)
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];

                            if (COORDINDEX == 0)
                            {
                                float.TryParse(PART, out ULTRAVIOLET.x);
                            }
                            if (COORDINDEX == 1)
                            {
                                float.TryParse(PART, out ULTRAVIOLET.y);
                                UVS.Add(ULTRAVIOLET);
                            }
                            else
                                COORDINDEX = COORDINDEX + 1;
                            PART = "";
                        }
                        else
                        {
                            if (!Char.IsWhiteSpace(line[c]))
                            PART = PART + line[c];
                        }

                    }
                }
                if (line.Contains("vn ")) // GET NORMS
                {
                    Vector3 NORM = Vector3.zero;
                    string PART = "";
                    int COORDINDEX = -1;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1 || line[c] == '/')
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                           float FLT;
                            if (COORDINDEX == 0)
                            {
                                    float.TryParse(PART, out FLT);
                                    NORM.x = FLT;
                            }
                            if (COORDINDEX == 1)
                            {
                                float.TryParse(PART, out FLT);
                                NORM.y = FLT;
                            }
                            if (COORDINDEX == 2)
                            {
                                float.TryParse(PART, out FLT);
                                NORM.z = FLT;
                                NORMALS.Add(NORM);
                            }
                            else
                            {
                                COORDINDEX = COORDINDEX + 1;
                            }

                            PART = "";
                        }
                        else
                            PART = PART + line[c];
                    }
                }
            }
            r.Close();

            /* /// ITS ALL WRONG ANYWAY FROM HERE OUT
            mesh = new Mesh();
            List<int> USEDFACES = new List<int>();
            Debug.Log(NORMALS.Count);
            for (int i = 0; i < NORMALS.Count; i++)
            {
 
                int FACEINDEX = 0;
                for (int F = 0; F < FACES.Count; F++)
                {
                    for (int u = 0; u < UVS.Count; u++)
                    {
                        for (int v = 0; v < VERTS.Count; v++)
                        {
                            if (FACES[F].z == i && FACES[F].y == u && FACES[F].x == v)
                            {
                                FACEINDEX = i;
                            }
                        }
                    }
                }
    
         
                if (i >= VERTS.Count)
                    VERTS.Add(VERTS[FACES[FACEINDEX].x]);
                VERTS[i] = VERTS[i] * 0.01f; // THIS IS THE SCALE;
                var X = VERTS[i].x;
                var Y = VERTS[i].y;
                var Z = VERTS[i].z;
                VERTS[i] = new Vector3(X, -Z, Y); // THIS IS THE ROTATION

                // WHAT IS THE SCIENCE HERE?
                if (i >= UVS.Count) // COMPLETE THE NORMALS
                    UVS.Add(UVS[FACES[FACEINDEX].y]); // Gives useable Normals
 
            }
  

 
            for (int i = 0; i < TRIANGLES.Count; i++)
            {
                TRIANGLES[i] = TRIANGLES[i] - 1;
            }
            mesh.SetVertices(VERTS.ToArray());
            mesh.SetTriangles(TRIANGLES.ToArray(), 0);
            mesh.SetNormals(NORMALS.ToArray());
            mesh.SetUVs(0, UVS.ToArray());
            FILTER.mesh = mesh;
           */ 
        }
    

    }

Still experimenting. Searching. Looking for alternative scripts
Perhaps I will rewrite it, but i had confirmed the lists contain the correct data. But it is the assembly of the OBJ at the end which is the tough part.
If you remove all that ocurrs after the stream reader Close(). You will check all of the lists are the same as if you opened the OBJ file in notepad. Thereafter lies the unknown Science that i feel i must know.

Full script with some debug functions, and disfunctional obj assembly removed.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class FILEIMPORTER : MonoBehaviour
{
    public List<string> DATA;

    public List<Vector3> VERTS;
    public List<Vector3Int> FACES;
    public List<Vector2> UVS;
    public List<Vector3> NORMALS;
    public List<int> TRIANGLES;

    public List<Vector3> SORTEDTRIANGLES;
    public List<Vector3> SORTEDNORMS;
    public List<Vector2> SORTEDUVS;
    public List<Vector3> SORTEDVERTS;
    public List<int> UVINDEX;

    public List<Vector2> EXPECTEDUVS;
    public List<Vector3> EXPECTEDVERTS;
    public List<int> EXPECTEDTRIANGLES;

    public MeshFilter FILTER;
    public Mesh mesh;

    public float SCALE;

    public Vector3 DIRECTION;

    void GETEXPECTED() 
    {
// for Mesh in the project folder and compare it to our import.
        var EUVS = FILTER.mesh.uv;
        var VERTZ = FILTER.mesh.vertices;
        var TRIADS = FILTER.mesh.triangles;
        for (int i = 0; i < TRIADS.Length; i++)
        {
            EXPECTEDTRIANGLES.Add(TRIADS[i]);
        }
        for (int i = 0; i < EUVS.Length; i++)
        {
                EXPECTEDUVS.Add(EUVS[i]);
        }
        for (int i = 0; i < VERTZ.Length; i++)
        {
                EXPECTEDVERTS.Add(VERTZ[i]);
        }
    }
 

    void GETFILEFULL()
    {
        if (File.Exists("C:/Users/SCHar/Desktop/FULLWACK3.obj"))
        {
            var MOVE = "C:/Users/SCHar/Desktop/FULLWACK3.obj";
            var MOVELINE = File.ReadAllLines(MOVE).Length;
            StreamReader r = new StreamReader(MOVE);
            for (int l = 0; l < MOVELINE; l++)
            {
                string line = r.ReadLine();
                DATA.Add(line);
                if (line.Contains("v ")) // GET VERT
                {
                    Vector3 VERT = Vector3.zero;
                    string PART = "";
                    int COORDINDEX = 0;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1)
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                            if (COORDINDEX == 0)
                                float.TryParse(PART, out VERT.x);
                            if (COORDINDEX == 1)
                                float.TryParse(PART, out VERT.y);
                            if (COORDINDEX == 2)
                            {
                                float.TryParse(PART, out VERT.z);
                                VERTS.Add(VERT);
                            }
                            COORDINDEX = COORDINDEX + 1;
                            PART = "";
                        }
                        else if (line[c] != 'v')
                            PART = PART + line[c];
                    }
                }
                if (line.Contains("f ")) // GET FACES
                {
                    Vector3Int TRIANGLE = Vector3Int.zero;
                    string PART = "";
                    int COORDINDEX = 0;
                    int TRIANGLEPART = 0;
                    Vector3Int TRI_UV_NORM = Vector3Int.zero;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1 || line[c] == '/')
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                            int INT = 0;
                            if (COORDINDEX == 0)
                            {
                                if (TRIANGLEPART == 0)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.x = INT;
                                    TRIANGLES.Add(INT);
                                }
                                if (TRIANGLEPART == 1)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.y = INT;
                                    TRIANGLES.Add(INT);
                                }
                                if (TRIANGLEPART == 2)
                                {
                                    int.TryParse(PART, out INT);
                                    TRIANGLE.z = INT;
                                    TRIANGLES.Add(INT);
                                }
                                int P = 0;
                                int.TryParse(PART, out P);
                                TRI_UV_NORM.x = P;
                            }
                            if (COORDINDEX == 1)
                            {
                                int.TryParse(PART, out INT);
                                UVINDEX.Add(INT);
                                TRI_UV_NORM.y = INT;
                            }
                            if (COORDINDEX == 2)
                            {
                                int.TryParse(PART, out INT);
                                COORDINDEX = 0;
                                TRI_UV_NORM.z = INT;
                                FACES.Add(TRI_UV_NORM);
                                TRIANGLEPART = 0;
                            }
                            else
                            {
                                COORDINDEX = COORDINDEX + 1;
                                TRIANGLEPART = TRIANGLEPART + 1;
                            }
                            PART = "";
                        }
                        else
                            PART = PART + line[c];
                    }
                }
                if (line.Contains("vt ")) // GET UV TEX
                {
                    string PART = "";
                    int COORDINDEX = -1;
                    Vector2 ULTRAVIOLET = Vector2.zero;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1)
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];

                            if (COORDINDEX == 0)
                            {
                                float.TryParse(PART, out ULTRAVIOLET.x);
                            }
                            if (COORDINDEX == 1)
                            {
                                float.TryParse(PART, out ULTRAVIOLET.y);
                                UVS.Add(ULTRAVIOLET);
                            }
                            else
                                COORDINDEX = COORDINDEX + 1;
                            PART = "";
                        }
                        else
                        {
                            if (!Char.IsWhiteSpace(line[c]))
                            PART = PART + line[c];
                        }

                    }
                }
                if (line.Contains("vn ")) // GET NORMS
                {
                    Vector3 NORM = Vector3.zero;
                    string PART = "";
                    int COORDINDEX = -1;
                    for (int c = 2; c < line.Length; c++)
                    {
                        if (Char.IsWhiteSpace(line[c]) || c == line.Length - 1 || line[c] == '/')
                        {
                            if (c == line.Length - 1)
                                PART = PART + line[c];
                           float FLT;
                            if (COORDINDEX == 0)
                            {
                                    float.TryParse(PART, out FLT);
                                    NORM.x = FLT;
                            }
                            if (COORDINDEX == 1)
                            {
                                float.TryParse(PART, out FLT);
                                NORM.y = FLT;
                            }
                            if (COORDINDEX == 2)
                            {
                                float.TryParse(PART, out FLT);
                                NORM.z = FLT;
                                NORMALS.Add(NORM);
                            }
                            else
                            {
                                COORDINDEX = COORDINDEX + 1;
                            }

                            PART = "";
                        }
                        else
                            PART = PART + line[c];
                    }
                }
            }
            r.Close();
        }
    }
}

But that’s the point ^^. You can not directly use any of the lists from the OBJ file in your mesh. You have to create vertices based on that data. One vertex per unique combination of position, normal and uv index. Since you already have your faces array of Vector3Int, you essentially need to completely rebuild all your vertex attribute lists for the mesh, based on the unique values in the faces list. So for each unique value in your faces list you need to generate a vertex for your mesh. A vertex consists of one position (vertices list), a normal and a UV value. Unity’s mesh class just splits the different vertex attributes into seperate arrays so it’s easier to construct different vertex layouts as Unity checks which arrays you have set and chooses a vertex layout that fits.

The mesh class now actually has advanced methods to manually setup a vertex layout and fill the actual VBO yourself. See SetVertexBufferParams and related methods. Though this does not change the fact that you have to actually create the vertices from the provided data. The way it’s stored in the file it can not be used in a Mesh. The most naive approach is to just add a new vertex for each index of a face. That way the mesh would be correct, but you may have split too many vertices.

I would probably use a dictionary with the Vector3Int as key and an “int” as value to map each unique vertex to an actual new vertex index. If it’s not yet in the list, create a new vertex by grabbing the position, normal and uv from the input lists and append it to the end of the actual vertices, normals and uvs lists and register this combination in the dictionary. So the next time you encounter the same vertex, you can directly grab the index from the dictionary. That’s how it’s done most of the time.

ps:
You do realise that this line:

File.ReadAllLines(MOVE).Length;

already reads in the whole file and splits the file into a string[ ] which you use just to get the number of lines and then you throw away the array just to use a stream reader to read in the whole file again? ^^

1 Like

:eyes:
line < Move.Length
Well spotted!

edit;::

            for (int l = 0; l < MOVELINE.Length; l++)
            {
                 string line = MOVELINE[l];
             }

uh

as of the main issue. I understand what you’re saying but I have no idea how to do this. As you can see all the above images actually just used the Verts, and the normals can be made = to this list count and then assuming you only wanted an object that was coloured, then this would produce a 3D object with correct normals, scale, direction and faces. But no ability to uv map it.

i really need to see a clearly coded example of how these datas go on to assemble an object. VS and Unity obviously assemble the object using the same data practically instantaneously. So whatever the behind the scenes trick is, it’s pretty quick and second nature to these programs.

GitHub - stefangordon/ObjParser: A WaveFront Obj Parser and Writer in C#
^^ the above OBJ Parser provides useful resource, but ultimately; he has programmed it in an overly complex way, containing 10 individual scripts. And has converted the final data into custom types I assume needlessly. However analysis of the code written in this way is unable to teach me anything except that the parts of the OBJ I am retrieving are the correct parts to talk about. It doesn’t appear to assemble the obj in accordance to the data it retrieves. But since we have here already all of the required Data in a useable format, the logic behind final steps of the assembly will exist somewhere.
It appears to the catch with most online obj parser scripts, is that they are only parsing the same data i have already retrieved. Hence the reference to the terminology —> parser. Obtaining the value of the strings. Gooden Greifen.

For performance reasons, it also makes sense to make an intermediate Vector3 wrapper, one that implements IEquatable and provides good hash codes. A couple of years ago I’ve designed so-called point maps exactly for this kind of work. It was insanely fast. But with a mesh in this example a plain ol’ Dictionary<Vector3, int> will suffice.

Whatever happened to the old Unity3D community wiki? I know it’s been shut down, but it had an OBJ importer that worked the last time I checked. Has anyone moved that code to github?

@AnimalMan
maybe you can check for the actual file format instead, learn more about the actual data being stored
for example OBJ File Format
and this GitHub - PhalanxHead/UnityRuntimeOBJLoaderDocs: More Extensive Documentation for the Free Unity Runtime OBJ Loader by Aaro4130. (this leads to free importer on the asset store)

1 Like

I have something interesting here

obj-importer/ModelLoader.cs at master · srcnalt/obj-importer · GitHub

Thanks Orion i’ll keep at this one until i get it, and hopefully script it in a way that is easy to see how it works.

Looks like I am nearing an understanding

           if (words[0] == "f") {
            ArrayList temp = new ArrayList();
            ArrayList triangleList = new ArrayList();
            for (int j = 1; j < words.Length; ++j)
            {
                Vector3 indexVector = new Vector3(0,0);
                string[] indices = words[j].Split("/"[0]);
                indexVector.x = System.Convert.ToInt32(indices[0], CultureInfo.InvariantCulture);
                if (indices.Length > 1) {
                    if (indices[1] != "")
                        indexVector.y = System.Convert.ToInt32(indices[1], CultureInfo.InvariantCulture);
                }
                if (indices.Length > 2) {
                    if (indices[2] != "")
                        indexVector.z = System.Convert.ToInt32(indices[2], CultureInfo.InvariantCulture);
                }
                temp.Add(indexVector);
            }
            for (int i = 1; i < temp.Count - 1; ++i) {
                triangleList.Add(temp[0]);
                triangleList.Add(temp[i]);
                triangleList.Add(temp[i+1]);
            }

            foreach (Vector3 item in triangleList) {
                _facesVertNormUV.Add(item);
            }
        }

from here [UPDATE] runtime obj importer - Unity Forum

A parser is supposed to tokenize and lexically analyze the structure and grammar of some encoded information. Textual file format parsers are practically glorified deserializers, but their behavior pretty much depends on the schematics of the actual file format.

When you compile your script, a parser runs through it to arrange and decorate your text (that we call source code) in a way that helps the compiler to meaningfully translate everything into lower symbols (so-called common intermediate language) similar to virtualized assembly instructions but with some semantic quirks on top of it. Parsers are everywhere.

Just as Bunny said, I remember OBJ being very odd in certain respects, compared to what Unity (and GPU) digests as a standard mesh object, so in this case, it’s the job for a parser to serve as an intermediate “scribe” between two completely different standards, and allow you to access the “parsed” information in a way that likely suits you better.

Thus, by analyzing how any such parser works, this can reveal a lot of meta information about what to expect from a particular file format. And given that you already know what to expect on Unity side (or at least I assume you do), the work before you is more of investigative nature.

However, I’m still having a hard time understanding why are you reinventing the wheel, when there are so many free OBJ importers already on the internet. OBJ is likely very complicated in how it was standardized and not every OBJ has to follow the identical structure, although most of them are probably simple. What I’m trying to say is that whatever you’re doing right now has to be imperfect because you’re not willing to address the foundation of it with more dedication and patience, and if that’s the case, why do it at all?

I’ve almost got it anyways, rewriting in my own version. Il post when it’s finished. I almost have the 75 result. I know what you’re saying but doing these things and learning is how I feel proud of the project. And will add value to the project in terms of my own judgement. Of which that value will likely encourage me to stick with this project. 99% of our projects I am sure this applies to everyone, 99% of our projects are abandoned as the result of boredom, confusion, complexity, or lack of motivation, and sometimes a dependency on others work.

1 Like

I tried a bunch of scripts online seems everybody has a very elaborate run around but ultimately nobody can successfully import the mesh.

So the closest I’ve seen to it being achieved is the mesh uvs and norms I generated before I wasted my time hoping that peoples examples and claims were truth.

turns out they indicate my triangles list is wrong but all the codes I tested were unable to create my mesh most of the time they just create a garble of triangles that isn’t even matching the same shape as my mesh. So the original code I have I guess still stands as testament to the best possible way to do it. Seeming that nobody actually knows how to do it. They just lie and post code projects that cannot achieve the goal. Maybe for a portfolio that they know won’t be checked by the employer but maybe only skimmed over before deciding whether they believe it or not.
Ah well

I’ll have to just keep developing my own way since it’s the closest I’ve got so far.

Some of these guys you get deep in to the code and you realize they are making a joke out of you doing something like taking a fixed value and slowly creating it as they go it’s insanity.
I gotta take a break. Il see if anyone at Microsoft or Vs might be able to shed light how that these imports are properly assembled.

What I probably need to do is create an export script from 3D modelling software. So it actually exports the full data in order that it is assembled and not missing data. So all arrays match. On export.

I am actually going to go for this solution ———-

I will have a second project where I will put my models in and then save their mesh data correctly in order as was rendered and imported by Unity. Aka mesh.vertice, mesh.uvs. mesh.normals, then I will make text file of all the data for the models in that project: Then use those text files to rebuild them in my other project from another directory without having to fill the project folder with 3D assets :slight_smile: this is currently how I save and load a crafted map from my map editor.

1 Like