| name | add-mesh-import |
| description | Import 3D mesh files into CLOiSim via the Assimp pipeline, including collision generation and procedural meshes. Use when: adding a new mesh file format, debugging mesh import issues, generating convex hull colliders via VHACD, creating procedural geometry, fixing texture loading or UV mapping problems. |
Add Mesh Import
Reference and procedure for importing 3D mesh files through CLOiSim's Assimp-based pipeline, generating collision geometry via VHACD, and creating procedural meshes.
When to Use
- Importing a new 3D file format (OBJ, DAE, STL, FBX)
- Debugging mesh import failures (missing textures, wrong orientation, degenerate geometry)
- Generating convex hull colliders for complex meshes
- Creating procedural geometry (box, cylinder, sphere, heightmap)
- Fixing face winding, UV channels, or vertex color issues
- Understanding the mesh → material → texture loading pipeline
Architecture
MeshLoader.CreateMeshObject(meshPath)
│
├─ Assimp.Common: GetScene()
│ ├─ File validation (extension check)
│ ├─ Assimp post-processing flags
│ └─ Per-format rotation correction
│
├─ LoadEmbeddedTextures() (FBX)
│
├─ sceneMaterials.ToUnity()
│ ├─ Color: Diffuse → BaseColor, Emissive → Emission
│ ├─ Textures: Diffuse → _BaseMap, Normal → _BumpMap, etc.
│ └─ Transparency: Opacity → alpha blending
│
├─ sceneMeshes.ToUnity()
│ ├─ Vertex setup (UInt16/UInt32 index format)
│ ├─ UV channels (up to 8)
│ ├─ Face winding reversal (right→left hand)
│ └─ Normal/tangent calculation
│
└─ ToUnityMeshObject()
├─ Recursive node traversal
├─ MeshFilter + MeshRenderer per node
└─ Material assignment by submesh index
File Format Support
| Format | Extension | Rotation Correction | Notes |
|---|
| Collada | .dae | Euler(0, -90, 0) | Common for ROS/Gazebo models |
| Wavefront | .obj | Euler(90, -90, 0) | No animation support |
| STL | .stl | Euler(90, -90, 0) | Mesh-only, no materials |
| FBX | .fbx | None (identity) | Supports embedded textures |
Procedure
1. Loading a Mesh File
Entry point is MeshLoader.CreateMeshObject():
var meshObject = MeshLoader.CreateMeshObject(meshPath);
var meshObject = MeshLoader.CreateMeshObject(meshPath, subMeshName);
2. Assimp Scene Loading
GetScene() handles file validation, post-processing, and format-specific rotation:
3. Mesh Conversion to Unity
Key conversion details in sceneMeshes.ToUnity():
mesh.indexFormat = vertexCount >= 65536
? IndexFormat.UInt32 : IndexFormat.UInt16;
triangles[triIdx + 0] = face.Indices[2];
triangles[triIdx + 1] = face.Indices[1];
triangles[triIdx + 2] = face.Indices[0];
for (var ch = 0; ch < mesh.TextureCoordinateChannelCount; ch++)
{
}
if (mesh.VertexColorChannelCount > 0)
{
}
if (bounds.size.magnitude < 1e-4f)
continue;
4. Material Loading from Assimp
Materials are converted via sceneMaterials.ToUnity():
HasColorDiffuse → SetBaseColor() (alpha forced to 1.0)
HasOpacity → Alpha channel + SetTransparent()
HasColorEmissive → SetEmission()
HasColorSpecular → SetColor("_SpecColor")
HasShininess → Smoothness = 1.0 - Shininess
HasReflectivity → Smoothness = 1.0 - Reflectivity
HasTextureDiffuse → _BaseMap
HasTextureNormal → _BumpMap + _NORMALMAP keyword
HasTextureSpecular → _SpecGlossMap + _SPECGLOSSMAP keyword
HasTextureEmissive → _EmissionMap + _EMISSION keyword
5. Texture Search Path Resolution
Textures are resolved through a multi-path search:
""
"../"
"../../"
"textures/"
"../textures/"
"../materials/"
"materials/"
6. Embedded Texture Handling (FBX)
FBX files can embed textures directly:
LoadEmbeddedTextures(scene);
7. VHACD Convex Decomposition
For collision geometry on complex meshes:
VHACD.Apply(meshFilters);
8. Procedural Mesh Creation
For SDF primitive geometries (<box>, <cylinder>, <sphere>, etc.):
ProceduralMesh.CreateBox(size);
ProceduralMesh.CreateCylinder(radius, length);
ProceduralMesh.CreateSphere(radius);
ProceduralMesh.CreateEllipsoid(radii);
ProceduralMesh.CreatePlane(size);
ProceduralMesh.CreateCapsule(radius, length);
9. Heightmap Generation
For terrain from image files:
Adding a New File Format
- Add extension to
CheckFileSupport() validation
- Add rotation case in
GetRotationByFileExtension():
case ".myformat":
return Quaternion.Euler(rx, ry, rz);
- Verify Assimp supports the format (check
Assimp.AssimpContext.IsImportFormatSupported())
- Test face winding — ensure triangles render correctly (may need to adjust post-processing flags)
Common Issues
| Symptom | Cause | Fix |
|---|
| Mesh is rotated 90° | Wrong format rotation | Check GetRotationByFileExtension() for the format |
| Faces are inside-out | Winding order incorrect | Verify MakeLeftHanded flag or adjust triangle reversal |
| Missing textures | Search path doesn't find texture file | Add directory to search path list in TryLoadTexture() |
| Black/untextured model | Embedded textures not loaded | Check LoadEmbeddedTextures() runs before material setup |
| Mesh has holes | Degenerate faces filtered out | Lower the 1e-4 bounds threshold if needed |
| Collider doesn't match mesh | VHACD skipped degenerate submeshes | Check mesh bounds; may need finer decomposition params |
| Out of memory on large mesh | UInt16 index overflow | Verify indexFormat selection for meshes with 65536+ verts |
| TGA texture fails | Unsupported TGA variant | Check TextureUtil.LoadTGA() supports the specific TGA type |