Skip to content

Instantly share code, notes, and snippets.

@runevision
Created September 15, 2019 13:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runevision/f081c6e322a7da4152e40c9a5bddeb05 to your computer and use it in GitHub Desktop.
Save runevision/f081c6e322a7da4152e40c9a5bddeb05 to your computer and use it in GitHub Desktop.
Redistribute UVs PostProcessor for Unity
// This can be used to redistribute the UV coordinates
// (either, U, V, or both) on an imported mesh which represents an
// extruded surface, such as ropes, or other shapes extruded along a
// spline. It's important for this method to work, that all vertices
// of each edge loop have the same U or V coordinate (depending on
// which is chosen for redistribution).
//
// Usage:
//
// Meshes that should be redistributed should contain "Redistribute"
// in their name, followed by "U", "V" or both.
// Example: "MyMesh_RedistributeU".
//
// The name may also contain "Flip", in which case U and V are flipped.
// This is done prior to redistributing.
// (This will flip mesh tangents and binormals as well so the tangents
// are aligned with the new U directions in the mesh.)
// Example: "MyMesh_Flip_RedistributeV".
//
// The model should have a label "RedistributeUVs" added in Unity.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
public class RedistributeUVsPostProcessor : AssetPostprocessor {
void OnPostprocessModel (GameObject g) {
var labels = AssetDatabase.GetLabels (assetImporter);
bool found = false;
foreach (var label in labels) {
Debug.Log ("Label: "+label);
if (label == "RedistributeUVs") {
found = true;
break;
}
}
if (!found)
return;
Apply(g.transform);
}
void Apply (Transform t) {
var invariant = System.StringComparison.InvariantCulture;
MeshFilter filter = t.GetComponent<MeshFilter> ();
if (filter != null && filter.sharedMesh != null) {
if (filter.sharedMesh.name.Contains ("Flip"))
FlipUVs (filter.sharedMesh);
int index = filter.sharedMesh.name.IndexOf ("Redistribute", invariant);
if (index >= 0) {
if (filter.sharedMesh.name.IndexOf ("U", index + 12, invariant) >= 0)
Redistribute (filter.sharedMesh, 0);
if (filter.sharedMesh.name.IndexOf ("V", index + 12, invariant) >= 0)
Redistribute (filter.sharedMesh, 1);
if (filter.sharedMesh.name.IndexOf ("Flip", index + 12, invariant) >= 0)
Redistribute (filter.sharedMesh, 1);
}
}
// Recurse
foreach (Transform child in t)
Apply(child);
}
void FlipUVs (Mesh mesh) {
Vector2[] uvs = mesh.uv;
Vector3[] normals = mesh.normals;
Vector4[] tangents = mesh.tangents;
for (int i = uvs.Length - 1; i >= 0; i--) {
uvs[i] = new Vector2 (uvs[i].y, uvs[i].x);
Vector3 tangent = (Vector3)tangents[i];
Vector3 binormal = Vector3.Cross (normals[i], tangent) * tangents[i].w;
tangents[i] = new Vector4 (binormal.x, binormal.y, binormal.z, -tangents[i].w);
}
mesh.uv = uvs;
mesh.tangents = tangents;
}
const int tolerance = 100000;
class VertexGroup {
public readonly float texCoord;
List<Vector3> vertices = new List<Vector3> ();
public Vector3 avgPos;
public float newTexCoord;
public VertexGroup (float texCoord) {
this.texCoord = texCoord;
}
public void AddVertex (Vector3 vertex) {
avgPos += vertex;
vertices.Add (vertex);
}
public void CalcAvgPos () {
avgPos /= vertices.Count;
}
}
void Redistribute (Mesh mesh, int uvAxis) {
Dictionary<int, VertexGroup> groupDict = new Dictionary<int, VertexGroup> ();
Vector3[] vertices = mesh.vertices;
Vector2[] uvs = mesh.uv;
// Create vertex groups and add vertices to them.
for (int i = vertices.Length - 1; i >= 0; i--) {
Vector3 vertex = vertices[i];
Vector2 uv = uvs[i];
float texCoord = uv[uvAxis];
int key = Mathf.RoundToInt (texCoord * tolerance);
VertexGroup group;
if (!groupDict.TryGetValue (key, out group)) {
group = new VertexGroup (texCoord);
groupDict[key] = group;
}
group.AddVertex (vertex);
}
Debug.Log ("Created " + groupDict.Count + " vertex groups from " + vertices.Length + " vertices.");
// Sort vertex groups.
List<VertexGroup> groupList = groupDict.Values.OrderBy (e => e.texCoord).ToList ();
// Calculate average positions and cumulative lengths for vertex groups.
float cumulativeLength = 0;
groupList[0].CalcAvgPos ();
for (int i = 1; i < groupList.Count; i++) {
groupList[i].CalcAvgPos ();
float length = Vector3.Distance (groupList[i].avgPos, groupList[i - 1].avgPos);
cumulativeLength += length;
groupList[i].newTexCoord = cumulativeLength;
}
// Redistribute texCoord based on lengths but in same range as originally.
float lowestTexCoord = groupList.First ().texCoord;
float highestTexCoord = groupList.Last ().texCoord;
for (int i = 0; i < groupList.Count; i++) {
groupList[i].newTexCoord = Mathf.LerpUnclamped (
lowestTexCoord,
highestTexCoord,
groupList[i].newTexCoord / cumulativeLength);
}
// Assign texcoords back to mesh
for (int i = uvs.Length - 1; i >= 0; i--) {
Vector2 uv = uvs[i];
float texCoord = uv[uvAxis];
int key = Mathf.RoundToInt (texCoord * tolerance);
VertexGroup group = groupDict[key];
uv[uvAxis] = group.newTexCoord;
uvs[i] = uv;
}
mesh.uv = uvs;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment