I’ve created my own
but it uses the red vertex color channel to mark pivot regions
Here is the bake script
HierarchieBakerEditor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using Unity.Mathematics;
using System;
public class HierarchieBakerEditor : Editor {
[MenuItem("Tools/VegetationTools/Bake Hierarchy")]
public static void BakeHierarchy() {
GameObject gameObject = Selection.activeGameObject;
UnityEngine.Object assetObject = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
bool t = AssetDatabase.IsMainAsset(gameObject);
if (!AssetDatabase.IsMainAsset(gameObject) && assetObject != gameObject) {
EditorUtility.DisplayDialog("BakeHierarchy", "Please select the source asset!", "ok");
return;
}
Transform transform = gameObject.transform;
string gameObjectAssetPath = AssetDatabase.GetAssetPath(gameObject);
string[] splittedPath = gameObjectAssetPath.Split('/');
gameObjectAssetPath = string.Join("/", splittedPath.Take(splittedPath.Length - 1));
DummyScriptableObject dummyAsset = CreateInstance<DummyScriptableObject>();
string dummyAssetPath = gameObjectAssetPath + "/" + gameObject.name + ".asset";
AssetDatabase.CreateAsset(dummyAsset, dummyAssetPath);
AssetDatabase.ImportAsset(dummyAssetPath);
AssetDatabase.Refresh();
string prefabAssetPath = gameObjectAssetPath + "/" + gameObject.name + "_BakedHierarchy.prefab";
GameObject hierarchyAssets = PrefabUtility.CreatePrefab(prefabAssetPath, gameObject, ReplacePrefabOptions.Default);
MeshFilter[] sourceMeshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
MeshFilter[] hierarchyMeshFilters = hierarchyAssets.GetComponentsInChildren<MeshFilter>();
bool success = true;
for (int i = 0; i < sourceMeshFilters.Length; i++) {
Mesh bakeMesh = Instantiate(sourceMeshFilters[i].sharedMesh);
bakeMesh.name = sourceMeshFilters[i].sharedMesh.name + "_BakedHierarchy";
AssetDatabase.AddObjectToAsset(bakeMesh, dummyAsset);
try {
BakeHierarchy(sourceMeshFilters[i].sharedMesh, bakeMesh);
hierarchyMeshFilters[i].sharedMesh = bakeMesh;
EditorUtility.SetDirty(bakeMesh);
} catch(Exception ex) {
EditorUtility.DisplayDialog("BakeHierarchy", ex.Message + " \n" + ex.StackTrace, "ok");
success = false;
break;
}
}
if (!success) {
DestroyImmediate(dummyAsset, true);
DestroyImmediate(hierarchyAssets, true);
AssetDatabase.Refresh();
} else {
EditorUtility.SetDirty(hierarchyAssets);
EditorUtility.SetDirty(dummyAsset);
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(prefabAssetPath);
AssetDatabase.ImportAsset(dummyAssetPath);
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("BakeHierarchy","Bake complete", "ok");
}
}
private static void BakeHierarchy(Mesh mesh, Mesh bakeMesh) {
Color32[] colors = mesh.colors32;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
Dictionary<byte, Vector3> branchPivots = new Dictionary<byte, Vector3>();
Dictionary<int, Vector3> pivots = new Dictionary<int, Vector3>();
// build maps;
IEnumerable<IGrouping<byte, int>> hierarchyMap = colors.Select((color, index) => new { color, index }).GroupBy(c => c.color.g, c => c.index);
IEnumerable<IGrouping<byte, int>> pivotMap = colors.Select((color, index) => new { color, index }).Where(c => c.color.r == 255).GroupBy(c => c.color.g, c => c.index);
IGrouping<byte, int> trunc = hierarchyMap.Where(g => g.Key == 0).FirstOrDefault();
foreach (var vertex in trunc) {
pivots.Add(vertex, Vector3.zero);
}
IEnumerable<IGrouping<byte, int>> branches = hierarchyMap.Where(g => g.Key != 0 && g.Key != 255);
IEnumerable<IGrouping<byte, int>> branchePivots = pivotMap.Where(g => g.Key != 0 && g.Key != 255);
int numberOfBranchVerts = branches.Count();
int numberOfbranchPivots = branchePivots.Count();
if (numberOfBranchVerts != numberOfbranchPivots) {
throw new Exception(string.Format("Branches has/have no pivot: ({0})", string.Join(", ", branches.Select(x=>x.Key).Except(branchePivots.Select(x=>x.Key)))));
}
//build Branches
var branchGroups = branches.Join(branchePivots,
branchId => branchId.Key,
branchePivot => branchePivot.Key,
(verts, pivotsPoints) => new { verts.Key, Collections = new { Verts = verts.ToArray(), Pivots = pivotsPoints.ToArray() } });
foreach (var branch in branchGroups) {
Vector3 branchPivot = new Vector3(
branch.Collections.Pivots.Average(pivotId => vertices[pivotId].x),
branch.Collections.Pivots.Average(pivotId => vertices[pivotId].y),
branch.Collections.Pivots.Average(pivotId => vertices[pivotId].z));
int farthestAway = branch.Collections.Verts.OrderByDescending(vertId => Vector3.Distance(vertices[vertId], branchPivot)).First();
Vector3 pivotDir = (vertices[farthestAway] - branchPivot).normalized;
uint3 packedData = 0;
PackPivot0(branchPivot, pivotDir, ref packedData);
float3 pivotPoint = UIntToSingle32Bits(packedData);
branchPivots.Add(branch.Key, pivotPoint);
foreach (var branchVert in branch.Collections.Verts) {
pivots.Add(branchVert, pivotPoint);
}
}
//build Leaves
//var t2 = faces.Count();
//var brancheTuples = branches.SelectMany(pair =>
// Enumerable.Range(0, pair.Count())
// .Select(x => pair.Key)
// .Join(pair, key => key, value => value, (key, value) => new { key, value}));
var brancheTuples = branches.SelectMany(pair => pair.Select(value => new { key = pair.Key, value }));
//var t3 = branches.Count();
//var t4 = brancheTuples.Count();
IEnumerable<int> leafIndexes = hierarchyMap.Where(x => x.Key == 255).SelectMany(x=>x);
IEnumerable<int> leafPivotIndexes = pivotMap.Where(x => x.Key == 255).SelectMany(x=>x);
Dictionary<int, HashSet<int>> faces = triangles.Select((vert, index) => new { vert, index }).GroupBy(g => g.index / 3, i => i.vert).Where(g=> leafIndexes.Any(leaf=>g.Contains(leaf))).ToDictionary(g=>g.Key, g=>new HashSet<int>(g));
var t0 = faces.Count();
if (leafIndexes.Any() && leafPivotIndexes.Any()) {
var t1 = leafIndexes.Count();
var t2 = leafPivotIndexes.Count();
IEnumerable<IGrouping<int, int>> leaves = leafIndexes.AsParallel().GroupBy(g => {
var leafGroup = new HashSet<int>(faces.Where(f => f.Value.Contains(g)).SelectMany(f => f.Value).Distinct());
return leafPivotIndexes.FirstOrDefault(p => leafGroup.Contains(p));
}, i => i);
var t3 = leaves.Count();
//throw new NotImplementedException();
foreach (IGrouping<int, int> leaf in leaves) {
Vector3 leafPivot = vertices[leaf.Key];
int farthestAway = leaf.OrderByDescending(i => Vector3.Distance(vertices[i], leafPivot)).First();
Vector3 leafPivotDir = (vertices[farthestAway] - leafPivot).normalized;
var branch = brancheTuples.OrderBy(pair => Vector3.Distance(vertices[pair.value], leafPivot)).FirstOrDefault();
uint3 packedData = 0;
if (branch != null) {
byte branchKey = branch.key;
packedData = SingleToUInt32Bits(branchPivots[branchKey]);
PackPivot1(leafPivot, leafPivotDir, ref packedData);
}
float3 pivotPoint = UIntToSingle32Bits(packedData);
foreach (var leafVert in leaf) {
pivots.Add(leafVert, pivotPoint);
}
}
}
List<Vector3> uvPivots = new List<Vector3>();
for (int i = 0; i < vertices.Length; i++) {
if (!pivots.TryGetValue(i, out Vector3 pivot)) {
throw new Exception(string.Format("No pivot found for Vertex {0}", i));
}
uvPivots.Add(pivot);
}
bakeMesh.SetUVs(3, uvPivots);
}
static uint PackSFloatToFixed(float val, float range, int bits, int shift) {
uint BitMask = (1u << bits) - 1;
val = (val + range) / (2*range);
uint uval = (uint)(val * BitMask);
return (uval & BitMask) << shift;
}
static uint PackUFloatToFixed(float val, float range, int bits, int shift) {
uint BitMask = (1u << bits) - 1;
val /= range;
uint uval = (uint)(val * BitMask);
return (uval & BitMask) << shift;
}
// Needs to match shader packing in baking tool
static void PackPivot0(float3 pivotPos0, float3 pivotFwd0, ref uint3 packedData) {
float y = pivotFwd0.y;
pivotFwd0.y = 0;
pivotFwd0 = math.normalize(pivotFwd0);
pivotFwd0 *= math.sqrt(1 - y*y);
packedData.y |= (y < 0) ? 0u : 1u << 16;
packedData.x |= PackSFloatToFixed(pivotPos0.x, 8f, 10, 22);
packedData.x |= PackUFloatToFixed(pivotPos0.y, 32f, 12, 10);
packedData.x |= PackSFloatToFixed(pivotPos0.z, 8f, 10, 0);
packedData.y |= PackSFloatToFixed(pivotFwd0.x, 1, 8, 24);
packedData.y |= PackSFloatToFixed(pivotFwd0.z, 1, 7, 17);
}
// Needs to match shader packing in baking tool
static void PackPivot1(float3 pivotPos1, float3 pivotFwd1, ref uint3 packedData) {
float y = pivotFwd1.y;
pivotFwd1.y = 0;
pivotFwd1 = math.normalize(pivotFwd1);
pivotFwd1 *= math.sqrt(1 - y * y);
packedData.y |= (y < 0) ? 0u : 1u;
packedData.y |= PackSFloatToFixed(pivotFwd1.x, 1f, 8, 8);
packedData.y |= PackSFloatToFixed(pivotFwd1.z , 1f, 7, 1);
packedData.z |= PackSFloatToFixed(pivotPos1.x, 8f, 10, 22);
packedData.z |= PackUFloatToFixed(pivotPos1.y, 32f, 12, 10);
packedData.z |= PackSFloatToFixed(pivotPos1.z, 8f, 10, 0);
}
public static unsafe float3 UIntToSingle32Bits(uint3 value) {
return *(float3*)(&value);
}
//------------------------------
static float UnpackFixedToSFloat(uint val, float range, int bits, int shift) {
uint BitMask = (1u << bits) - 1;
val = (val >> shift) & BitMask;
float fval = val / (float)BitMask;
return (fval * 2f - 1f) * range;
}
static float UnpackFixedToUFloat(uint val, float range, int bits, int shift) {
uint BitMask = (1u << bits) - 1;
val = (val >> shift) & BitMask;
float fval = val / (float)BitMask;
return fval * range;
}
// Needs to match shader packing in baking tool
static bool UnpackPivot0(uint3 packedData, ref float3 pivotPos0, ref float3 pivotFwd0) {
if ((packedData.y & 0xFFFF0000) != 0) {
pivotPos0.x = UnpackFixedToSFloat(packedData.x, 8f, 10, 22);
pivotPos0.y = UnpackFixedToUFloat(packedData.x, 32f, 12, 10);
pivotPos0.z = UnpackFixedToSFloat(packedData.x, 8f, 10, 0);
pivotFwd0.x = UnpackFixedToSFloat(packedData.y, 1f, 8, 24);
pivotFwd0.z = UnpackFixedToSFloat(packedData.y, 1f, 7, 17);
pivotFwd0.y = math.sqrt(1f - math.saturate(math.dot(pivotFwd0.xz, pivotFwd0.xz))) * (((packedData.y >> 16) & 1) == 1 ? 1f : -1f);
pivotFwd0 = math.normalize(pivotFwd0);
return true;
}
return false;
}
// Needs to match shader packing in baking tool
static bool UnpackPivot1(uint3 packedData, ref float3 pivotPos1, ref float3 pivotFwd1) {
if ((packedData.y & 0x0000FFFF) != 0) {
pivotFwd1.x = UnpackFixedToSFloat(packedData.y, 1f, 8, 8);
pivotFwd1.z = UnpackFixedToSFloat(packedData.y, 1f, 7, 1);
pivotFwd1.y = math.sqrt(1f - math.saturate(math.dot(pivotFwd1.xz, pivotFwd1.xz))) * ((packedData.y & 1) == 1 ? 1f : -1f);
pivotFwd1 = math.normalize(pivotFwd1);
pivotPos1.x = UnpackFixedToSFloat(packedData.z, 8f, 10, 22);
pivotPos1.y = UnpackFixedToUFloat(packedData.z, 32f, 12, 10);
pivotPos1.z = UnpackFixedToSFloat(packedData.z, 8f, 10, 0);
return true;
}
return false;
}
public static unsafe uint3 SingleToUInt32Bits(float3 value) {
return *(uint3*)(&value);
}
}
DummyScriptableObject.cs
public class DummyScriptableObject : UnityEngine.ScriptableObject {}
Usage:
Create a tree object. Trunc, all branches and leaves in a single mesh with y axis is top. If you use blender… rotate you object so that the top of the modle faces along the y achis!!!
Vertex Colors:
Green Channel:
Trunc = 0
Branches: each branch group a different greenvalue between 0 and 255
Leaves = 255
Red Channel: marks the pivotregion (mean of all vertex in that group = pivot, R = 255 only)
Each group needs at least one red vertex
Trunc won’t need a pivot! The origin is the pivot of the trunk…
If you have any questions, just ask.
Have fun

