Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save andrew-raphael-lukasik/3559728d022a4c96f491924f8285e1bf to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/3559728d022a4c96f491924f8285e1bf to your computer and use it in GitHub Desktop.
OBJ file format vertex color importer for Unity

(Unity) vertex color importer for MeshLab's OBJ files

/// src* https://gist.github.com/andrew-raphael-lukasik/3559728d022a4c96f491924f8285e1bf
///
/// Copyright (C) 2023 Andrzej Rafał Łukasik (also known as: Andrew Raphael Lukasik)
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU General Public License as published by
/// the Free Software Foundation, version 3 of the License.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
/// See the GNU General Public License for details https://www.gnu.org/licenses/
///
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using IO = System.IO;
using NumberStyles = System.Globalization.NumberStyles;
using CultureInfo = System.Globalization.CultureInfo;
public class ObjFileFormatVertexColorImporter : AssetPostprocessor
{
Dictionary<string,Color[]> _colorData = new Dictionary<string,Color[]>();
void OnPreprocessModel ()
{
if( !assetPath.EndsWith(".obj") ) return;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// read obj data
var current_sourceColor = new List<Color>();
var current_geometryIndices = new List<int>();
var current_sourceNormalHash = new Dictionary<int,int>();
var current_sourceUvHash = new Dictionary<int,int>();
var current_geometrySharedVerticesDetector = new HashSet<Vector3Int>();
var geometryIndices = new Dictionary<string,List<int>>() { { "default" , current_geometryIndices } };
var sourceColor = new Dictionary<string,List<Color>>() { { "default" , current_sourceColor } };
var sourceNormalHash = new Dictionary<string,Dictionary<int,int>>() { { "default" , current_sourceNormalHash } };
var sourceUvHash = new Dictionary<string,Dictionary<int,int>>() { { "default" , current_sourceUvHash } };
var geometrySharedVerticesDetector = new Dictionary<string,HashSet<Vector3Int>>() { { "default" , current_geometrySharedVerticesDetector } };
string line, g = null, o = null, meshName = "default";
int numVertexColorVertices = 0;
int v_index = 1, vn_index = 1, vt_index = 1;
var reader = IO.File.OpenText( Application.dataPath + assetPath.Replace("Assets","") );
while( (line = reader.ReadLine())!=null )
{
string[] words = line.Split(' ');
byte wordsLength = (byte)words.Length;
byte is_o = words[0]=="o" ? (byte)1 : (byte)0;
byte is_g = words[0]=="g" ? (byte)1 : (byte)0;
byte is_v = words[0]=="v" && wordsLength==7 ? (byte)1 : (byte)0;
byte is_vt = words[0]=="vt" && wordsLength==3 ? (byte)1 : (byte)0;
byte is_vn = words[0]=="vn" && wordsLength==4 ? (byte)1 : (byte)0;
byte is_f = words[0]=="f" && wordsLength>1 ? (byte)1 : (byte)0;
// parse o
if( is_o==1 )
{
o = line.Substring(1).Trim(' ').Replace(' ','_');
}
// parse g
if( is_g==1 )
{
g = line.Substring(1).Trim(' ').Replace(' ','_');
meshName = g;
current_sourceColor = new List<Color>();
current_geometryIndices = new List<int>();
current_sourceNormalHash = new Dictionary<int,int>();
current_sourceUvHash = new Dictionary<int,int>();
current_geometrySharedVerticesDetector = new HashSet<Vector3Int>();
sourceColor.Add( meshName , current_sourceColor );
geometryIndices.Add( meshName , current_geometryIndices );
sourceNormalHash.Add( meshName , current_sourceNormalHash );
sourceUvHash.Add( meshName , current_sourceUvHash );
geometrySharedVerticesDetector.Add( meshName , current_geometrySharedVerticesDetector );
v_index = 1;
vn_index = 1;
vt_index = 1;
}
// parse face data
if( is_f==1 )
{
for( int i=1 ; i<wordsLength ; i++ )
{
string[] v_vt_vn = words[i].Split('/');
if( v_vt_vn.Length==3 )
{
string v = v_vt_vn[0];
string vt = v_vt_vn[1];
string vn = v_vt_vn[2];
int vertexIndex = int.Parse( v , NumberStyles.Number , CultureInfo.InvariantCulture );
int normalHash;
if( vn.Length!=0 )
{
int normalIndex = int.Parse( vn , NumberStyles.Number , CultureInfo.InvariantCulture );
normalHash = current_sourceNormalHash[normalIndex];
}
else normalHash = -1;// vertex with no normal data
int uvHash;
if( vt.Length!=0 )
{
int uvIndex = int.Parse( vt , NumberStyles.Number , CultureInfo.InvariantCulture );
uvHash = current_sourceUvHash[uvIndex];
}
else uvHash = -1;// vertex with no uv data
if( current_geometrySharedVerticesDetector.Add(new Vector3Int(vertexIndex,uvHash,normalHash)) )
{
//Debug.Log($"new vertex index added: ( {vertexIndex} #{uvHash} , #{normalVectorHash} )");
current_geometryIndices.Add(vertexIndex);
}
//else Debug.Log($"ignoring vertex as shared: ( {vertexIndex} , #{normalVectorHash} )");
}
}
}
// parse vertex data (vertex color only)
if( is_v==1 )
{
float red = float.Parse( words[4] , NumberStyles.Number , CultureInfo.InvariantCulture );
float green = float.Parse( words[5] , NumberStyles.Number , CultureInfo.InvariantCulture );
float blue = float.Parse( words[6] , NumberStyles.Number , CultureInfo.InvariantCulture );
current_sourceColor.Add( new Color(red,green,blue) );
v_index++;
numVertexColorVertices++;
}
// parse vertex normal data
if( is_vn==1 )
{
int hash = 17;
unchecked
{
hash = hash * 23 + words[1].GetHashCode();
hash = hash * 23 + words[2].GetHashCode();
hash = hash * 23 + words[3].GetHashCode();
}
//Debug.Log($"vn #{vn_index} ( {words[1]} , {words[2]} , {words[3]} ) added as #{hash}");
current_sourceNormalHash.Add( vn_index++ , hash );
}
// parse texture UV data
if( is_vt==1 )
{
int hash = 17;
unchecked
{
hash = hash * 23 + words[1].GetHashCode();
hash = hash * 23 + words[2].GetHashCode();
}
//Debug.Log($"vt #{vn_index} ( {words[1]} , {words[2]} ) added as #{hash}");
current_sourceUvHash.Add( vt_index++ , hash );
}
}
reader.Dispose();
if( numVertexColorVertices!=0 )// was any vertex color data found ?
{
// change importer settings
var importer = (ModelImporter)assetImporter;
importer.optimizeMeshVertices = false;
importer.optimizeMeshPolygons = false;
importer.weldVertices = false;
importer.importNormals = ModelImporterNormals.Import;
importer.importTangents = ModelImporterTangents.CalculateMikk;
importer.importAnimation = false;
importer.animationType = ModelImporterAnimationType.None;
importer.materialImportMode = ModelImporterMaterialImportMode.None;
// prepare final color array
foreach( string nextMeshName in sourceColor.Keys )
{
List<int> nextMeshIndices = geometryIndices[nextMeshName];
int numIndices = nextMeshIndices.Count;
List<Color> unpreparedColorArray = sourceColor[nextMeshName];
Color[] finalColorArray = new Color[numIndices];
for( int i=0 ; i<numIndices ; i++ )
{
finalColorArray[i] = unpreparedColorArray[nextMeshIndices[i]-1];
}
_colorData.Add( nextMeshName , finalColorArray );
}
}
double totalSeconds = new System.TimeSpan(stopwatch.ElapsedTicks).TotalSeconds;
if( totalSeconds>0.1 ) Debug.LogWarning($"{GetType().Name}::{nameof(OnPreprocessModel)}() took {totalSeconds:0.00} seconds, `{assetPath}` asset");
}
void OnPostprocessModel ( GameObject gameObject )
{
if( !assetPath.EndsWith(".obj") ) return;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
if( _colorData.Count!=0 )
{
MeshFilter[] meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
//Debug.Log($"meshFilters:{meshFilters.Length}, _colorData.Count:{_colorData.Count}",gameObject);
foreach( var mf in meshFilters )
{
Mesh mesh = mf.sharedMesh;
var vertexColor = _colorData[ mesh.name ];
if( vertexColor.Length!=mesh.vertexCount ) Debug.LogError($"Invalid color data length! mesh.name:'{mesh.name}', vertexColor.Length:{vertexColor.Length}, mesh.vertexCount:{mesh.vertexCount}",gameObject);
mesh.SetColors( vertexColor );
}
}
double totalSeconds = new System.TimeSpan(stopwatch.ElapsedTicks).TotalSeconds;
if( totalSeconds>0.1 ) Debug.LogWarning($"{GetType().Name}::{nameof(OnPostprocessModel)}() took {totalSeconds:0.00} seconds, `{assetPath}` asset",gameObject);
}
}
#endif
@yong753
Copy link

yong753 commented Sep 25, 2023

Thank you @andrew-raphael-lukasik . I got a lot of help thanks to your code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment