2
\$\begingroup\$

I am working on a game that requires meshes that only have edges and vertices but no faces. In blender, you can make a mesh with only edges and vertices, but if you export it to FBX or OBJ, once it is imported into Unity, the resulting prefab has no mesh data linked to it. The prefab has no MeshFilter. It's as if I imported an mesh with absolutely no mesh data.

For example, if I create a plane in Blender and then delete the face but leave the edges and vertices, and then export as OBJ, I can open the OBJ in a file editor. This is a square with no faces:

# Blender v2.78 (sub 0) OBJ File: ''
# www.blender.org
mtllib egdes.mtl
o Plane
v -1.000000 0.000000 1.000000
v 1.000000 0.000000 1.000000
v -1.000000 0.000000 -1.000000
v 1.000000 0.000000 -1.000000
l 3 1
l 1 2
l 2 4
l 4 3

So it appears that OBJ actually does export with the correct mesh data, however, it seems that Unity does not know how to import an OBJ if there are no faces.

Does anyone know if anyone has come up with a solution to this problem yet? If not, I guess I'll just have to write my own parser. If that's the case, does anyone know if there is a way to extend or override Unity's built-in OBJ parser?

\$\endgroup\$
3
\$\begingroup\$

First of all can I say I've never encountered by total chance someone encountering such an incredibly specific issue that I also encountered, so good job on that, universe.

So as you've already discovered, Unity's model importers discard all edges and points.

I haven't completely solved this problem myself for a similar project, but I have a partial workaround and an alternate solution you could try.

The stuff I was working on only applied to edges, but all of this can be extended to point meshes as well.

My workaround

My workaround I used in my experimental project recently: In Blender, before exporting my edges mesh to FBX, I add a Screw modifier and I enter 0 in all the text fields (screw, angle, steps, render step). Some of the values round up to 2. This creates two quads for each edge in the mesh, but they're all degenerate quads with two identical edges and two 0-length edges. I give the mesh object a name with "lines" in it, to identify it and tell it apart from normal meshes in the asset processing.

In Unity, I have a class LinesMesh that extends AssetPostProcessor. It disables welding vertices on the import settings to prevent the degenerate polygons from being removed by the model importer. After the mesh is processed, I recursively iterate over the transforms in the processed model to find any meshes with "lines" in their name. I change the topology to lines, and create new line indices based on the triangle indices.

I haven't done the work of collapsing all the duplicated verts down, so with this method you'll get 12 lines when all you needed/wanted was 1. This code is obviously really unfinished on that count. But, it preserves all per-vertex data like if you somehow managed to assign vertex colors and UVs to your edges in your 3D program.

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class LinesMesh : AssetPostprocessor
{
    void OnPreprocessModel()
    {
        ModelImporter modelImporter = (ModelImporter)assetImporter;
        modelImporter.weldVertices = false;
        //modelImporter.addCollider = false;
    }

    void OnPostprocessModel(GameObject g)
    {
        Apply(g.transform);
    }

    void Apply(Transform t)
    {
        if (t.name.ToLower().Contains("lines") && t.GetComponent<MeshFilter>())
        {
            Mesh mesh = t.GetComponent<MeshFilter>().sharedMesh;
            if (mesh)
            {
                // Toooons of duplicated and degenerate lines in this method. investigate optimizations.
                for (int submesh = 0; submesh < mesh.subMeshCount; submesh++)
                {
                    if (mesh.GetTopology(submesh) == MeshTopology.Triangles)
                    {
                        int[] triIndices = mesh.GetIndices(submesh);
                        int[] lineIndices = new int[triIndices.Length * 2];
                        for (int tri = 0; tri < triIndices.Length; tri += 3)
                        {
                            lineIndices[tri * 2] = triIndices[tri];
                            lineIndices[tri * 2 + 1] = triIndices[tri + 1];
                            lineIndices[tri * 2 + 2] = triIndices[tri + 1];
                            lineIndices[tri * 2 + 3] = triIndices[tri + 2];
                            lineIndices[tri * 2 + 4] = triIndices[tri + 2];
                            lineIndices[tri * 2 + 5] = triIndices[tri];
                        }

                        mesh.SetIndices(lineIndices, MeshTopology.Lines, submesh);
                    }
                    else
                    {
                        Debug.LogErrorFormat("Unhandled mesh topology ({0}) in mesh {1}", mesh.GetTopology(submesh), mesh.name);
                    }
                }
            }
            else
            {
                Debug.LogWarningFormat("Failed to get mesh for object {0}", t.name);
            }
        }

        // Recurse
        foreach (Transform child in t)
            Apply(child);
    }
}

The above code should work on any mesh, not just line-like polygonal meshes, and turn them into a wireframe mesh, with about 2x the optimal number of lines.

But maybe don't use that stuff

You could make it work with more tweaks on top of the above kludge to get rid of the duplicated edges, but the extra steps in Blender of adding the modifiers, naming objects and such adds a lot of work to each mesh you need to import. If you don't care about performance at all, you could even use the above code as-is, and just ignore that you're sending 2-3 times as much vertex data to the GPU and 6-12 times as many edges than you need.

What I would suggest instead is renaming the exported model's file extension to something that Unity won't process as a model, something like mymodel.linesobj. Then you have to produce a mesh by parsing the file yourself, since Unity doesn't give you the power to hook into the importer during the import process.

You can reference the above code for how to create a meshpart with lines topology, it should be way easier than what I did if you're parsing the obj file yourself.

The legacy way of writing custom importers:

In an asset postprocessor, you can use OnPostProcessAllAssets to detect when a .linesobj file finished importing, and write your own OBJ parser to produce a new model with meshes with Lines topology similar to above. Then you can call AssetDatabase.CreateAsset with the model to create a .asset file containing the model generated by your custom obj processor.

The currently experimental way to write custom importers in new versions of Unity

This is marked as experimental currently, but if this API gets completed it should be a much less janky way of custom asset processing: https://docs.unity3d.com/Manual/ScriptedImporters.html

If you do end up writing a custom importer or fixing up the mesh data, I would appreciate if you posted it here as its own answer, as I would like to revisit my project and fix the excessive geometry and tedious blender export process.

\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you for taking the time to right this all up. It's very Helpful! In my project, there can be no duplicates, so I think I will go down the route of implementing a custom importer using OnPostProcessAllAssets. I will definitely post the solution here when I get done with that. Thank you! \$\endgroup\$ – Croolsby Oct 13 '17 at 20:04
  • 1
    \$\begingroup\$ Hi Tim, I decided to go with the Scripted Importer. Despite being experimental, it's still a cleaner solution than OnPostProcessAllAssets. The code is available through the link on the accepted answer. Thanks again for your help! \$\endgroup\$ – Croolsby Oct 17 '17 at 8:44
1
\$\begingroup\$

With Tim R.'s help, I implemented a custom importer to address this problem. You can get the code here:

https://github.com/taylorgoolsby/lineobj-importer

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.