(Unity) vertex color importer for MeshLab's OBJ files
Forked from andrew-raphael-lukasik/.ObjFileFormatVertexColorImporter.cs.md
Created
April 24, 2024 10:13
-
-
Save unitycoder/76f842494d5ffa6643889351734c5187 to your computer and use it in GitHub Desktop.
OBJ file format vertex color importer for Unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment