| 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 |