Skip to content

Instantly share code, notes, and snippets.

@revoconner
Last active December 22, 2025 02:54
Show Gist options
  • Select an option

  • Save revoconner/fd0c6da4c7bfd2de8ecd32a5ae485b46 to your computer and use it in GitHub Desktop.

Select an option

Save revoconner/fd0c6da4c7bfd2de8ecd32a5ae485b46 to your computer and use it in GitHub Desktop.
Create cube primitive colliders from imported meshes in Unity

FBX To Primitive Cubes

Converts cube meshes from imported FBX files into Unity Box Colliders.

image

Usage

  1. Build colliders as cubes in your DCC app (Maya, Blender, 3ds Max)
  2. Group them under an object named "Collider" or "Colliders"
  3. Export as FBX and import into Unity
  4. Drag the FBX into your scene
  5. Select it and run Tools > Colliders > FBX To Primitive Cubes

Features

  • Adds generated primitive box colliders under a parent empty called colliders
  • Deletes the imported mesh colliders in the fbx so you can put in a prefab easily.
  • Removes all mesh component from the generated colliders

Options

  • Use Selection: Automatically use the selected object in hierarchy
  • Remove Mesh Renderer: Strip renderer from generated colliders (recommended)
  • Remove Mesh Filter: Strip mesh filter from generated colliders (recommended)

Limitations

  • Only supports cuboid shapes (build colliders with cubes, then scale, move or rotate them by object or component)
  • Does not support shear transforms
/*
Build colliders in DCC application like maya and blender
Export and import it as fbx,
Run the tool to convert
************
Only supports cuboids shape,
so build your colliders with cubes.
***********
Full support of transform, rotation and scale in object space
Does not support shear
*/
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class FBXToPrimitiveCubes : EditorWindow
{
private GameObject sourceObject;
private bool removeMeshRenderer = true;
private bool removeMeshFilter = true;
private bool useSelection = true;
[MenuItem("Tools/Colliders/FBX To Primitive Cubes")]
public static void ShowWindow()
{
GetWindow<FBXToPrimitiveCubes>("FBX To Cubes");
}
private void OnGUI()
{
EditorGUILayout.LabelField("FBX to Primitive Cubes", EditorStyles.boldLabel);
EditorGUILayout.Space();
useSelection = EditorGUILayout.Toggle("Use Selection", useSelection);
if (useSelection)
sourceObject = Selection.activeGameObject;
else
sourceObject = (GameObject)EditorGUILayout.ObjectField("Source FBX", sourceObject, typeof(GameObject), true);
removeMeshRenderer = EditorGUILayout.Toggle("Remove Mesh Renderer", removeMeshRenderer);
removeMeshFilter = EditorGUILayout.Toggle("Remove Mesh Filter", removeMeshFilter);
EditorGUILayout.Space();
GUI.enabled = sourceObject != null;
if (GUILayout.Button("Generate Primitive Cubes"))
GenerateCubes();
GUI.enabled = true;
EditorGUILayout.Space();
EditorGUILayout.HelpBox(
"Instructions:\n" +
"1. Build colliders as cubes in DCC (Maya/Blender)\n" +
"2. Group them (Maya/3ds Max) or \n Put under an empty (Blender) named \n 'Collider' or 'Colliders'\n" +
"3. Export as FBX and import into Unity\n" +
"4. Bring the FBX into the scene, select it, and run this tool\n\n" +
"Note: Only cuboid shapes supported. No shear.",
MessageType.Info);
}
private void GenerateCubes()
{
// Find child named "collider" or "colliders" (case insensitive, whole word)
Transform colliderContainer = FindColliderContainer(sourceObject.transform);
if (colliderContainer == null)
{
Debug.LogWarning("No child named 'Collider' or 'Colliders' found.");
return;
}
MeshFilter[] meshFilters = colliderContainer.GetComponentsInChildren<MeshFilter>(true);
if (meshFilters.Length == 0)
{
Debug.LogWarning("No MeshFilters found under collider container.");
return;
}
GameObject container = new GameObject("Colliders");
container.transform.position = sourceObject.transform.position;
Undo.RegisterCreatedObjectUndo(container, "Create Cube Container");
int count = 0;
foreach (MeshFilter mf in meshFilters)
{
Mesh mesh = mf.sharedMesh;
if (mesh == null) continue;
if (!ExtractCubeTransform(mesh, out Vector3 localPos, out Quaternion localRot, out Vector3 localScale))
{
Debug.LogWarning($"Failed to extract transform from {mf.name}");
continue;
}
Transform src = mf.transform;
Vector3 worldPos = src.TransformPoint(localPos);
Quaternion worldRot = src.rotation * localRot;
Vector3 worldScale = Vector3.Scale(localScale, src.lossyScale);
count++;
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = $"Box_Collider_{count}";
cube.transform.SetParent(container.transform, false);
cube.transform.position = worldPos;
cube.transform.rotation = worldRot;
cube.transform.localScale = worldScale;
if (removeMeshRenderer)
DestroyImmediate(cube.GetComponent<MeshRenderer>());
if (removeMeshFilter)
DestroyImmediate(cube.GetComponent<MeshFilter>());
Undo.RegisterCreatedObjectUndo(cube, "Create Primitive Cube");
}
// Delete the collider container
Undo.DestroyObjectImmediate(colliderContainer.gameObject);
Debug.Log($"Generated {count} cubes.");
}
private Transform FindColliderContainer(Transform parent)
{
foreach (Transform child in parent)
{
string name = child.name.ToLowerInvariant();
if (name == "collider" || name == "colliders")
return child;
}
return null;
}
private bool ExtractCubeTransform(Mesh mesh, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = Vector3.zero;
rotation = Quaternion.identity;
scale = Vector3.one;
Vector3[] verts = mesh.vertices;
if (verts.Length == 0) return false;
// Cube mesh has 24 verts (4 per face), reduce to 8 unique corners
List<Vector3> unique = GetUniqueVertices(verts, 0.0001f);
if (unique.Count != 8)
{
Debug.LogWarning($"Expected 8 vertices, got {unique.Count}. Using bounds fallback.");
position = mesh.bounds.center;
scale = mesh.bounds.size;
return true;
}
// Center is average of all corners
foreach (var v in unique)
position += v;
position /= 8f;
Vector3 corner = unique[0];
List<Vector3> others = new List<Vector3>();
foreach (var v in unique)
{
if (v != corner)
others.Add(v);
}
others.Sort((a, b) => Vector3.Distance(corner, a).CompareTo(Vector3.Distance(corner, b)));
// Find 3 edges from corner using orthogonality (dot product ~0), not just distance
// Avoids picking face diagonal when it's shorter than longest edge
Vector3 e1 = others[0] - corner;
Vector3 e2 = Vector3.zero;
foreach (var v in others)
{
Vector3 candidate = v - corner;
if (candidate == e1) continue;
if (Mathf.Abs(Vector3.Dot(e1.normalized, candidate.normalized)) < 0.1f)
{
e2 = candidate;
break;
}
}
Vector3 e3 = Vector3.zero;
Vector3 expectedDir = Vector3.Cross(e1, e2).normalized;
foreach (var v in others)
{
Vector3 candidate = v - corner;
if (Mathf.Abs(Mathf.Abs(Vector3.Dot(expectedDir, candidate.normalized)) - 1f) < 0.1f)
{
e3 = candidate;
break;
}
}
if (e2 == Vector3.zero || e3 == Vector3.zero)
{
Debug.LogWarning("Could not find orthogonal edges. Using bounds fallback.");
position = mesh.bounds.center;
scale = mesh.bounds.size;
return true;
}
// Build rotation matrix from edge directions
float s1 = e1.magnitude;
float s2 = e2.magnitude;
float s3 = e3.magnitude;
Vector3 ax1 = e1 / s1;
Vector3 ax2 = e2 / s2;
Vector3 ax3 = e3 / s3;
if (Vector3.Dot(Vector3.Cross(ax1, ax2), ax3) < 0)
ax3 = -ax3;
Matrix4x4 rotMat = new Matrix4x4();
rotMat.SetColumn(0, new Vector4(ax1.x, ax1.y, ax1.z, 0));
rotMat.SetColumn(1, new Vector4(ax2.x, ax2.y, ax2.z, 0));
rotMat.SetColumn(2, new Vector4(ax3.x, ax3.y, ax3.z, 0));
rotMat.SetColumn(3, new Vector4(0, 0, 0, 1));
rotation = rotMat.rotation;
scale = new Vector3(s1, s2, s3);
return true;
}
private List<Vector3> GetUniqueVertices(Vector3[] verts, float tolerance)
{
List<Vector3> unique = new List<Vector3>();
foreach (var v in verts)
{
bool found = false;
foreach (var u in unique)
{
if (Vector3.Distance(v, u) < tolerance)
{
found = true;
break;
}
}
if (!found)
unique.Add(v);
}
return unique;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment