Skip to content

Support loading FBX/OBJ via assimp#133

Open
nmfisher wants to merge 49 commits into
developfrom
feat/assimp
Open

Support loading FBX/OBJ via assimp#133
nmfisher wants to merge 49 commits into
developfrom
feat/assimp

Conversation

@nmfisher
Copy link
Copy Markdown
Owner

No description provided.

nmfisher and others added 30 commits March 6, 2026 19:52
This PR implements support for Linux/Desktop rendering in Flutter applications with EGL (Wayland + OpenGL) or DMA-BUF (Wayland + Vulkan or X11 + OpenGL).

Windows/Linux/Vulkan namespaces are now standardized:

- Base vulkan files: thermion::vulkan
- Windows files: thermion::vulkan::windows
- Linux files: thermion::vulkan::linux (changed from thermion::linux_platform::vulkan)
- Wrap VulkanUtils and linux_vulkan_utils in appropriate namespaces

Use block-linear DRM modifiers for VkImage creation when they support
COLOR_ATTACHMENT (NVIDIA GPUs). The driver picks the optimal modifier
from the list. If no COLOR_ATTACHMENT modifiers exist, fall back to a
two-image blit approach (render to TILING_OPTIMAL, blit to LINEAR
DMA-BUF export).

- Add queryColorAttachmentModifiers() to filter DRM modifiers
- createExportable() now uses block-linear modifiers with dedicated alloc
- Add createWithBlit() factory for render+export image pair
- Add BlitToExport() with command buffer on queue 1
- Pass actual DRM modifier to EGL (lo/hi split)
- Fix double-free: pass VK_NULL_HANDLE for memory to Filament
- Remove VK_KHR_swapchain (not needed for offscreen rendering)

NOTES ON VSYNC:

- drmWaitVBlank doesn't work on NVIDIA.
- we can't use Gdk events to synchronize with Flutter, these are completely disconnected.
- On Linux, we use a Flutter-synchronized render loop. Flutter's SchedulerBinding drives
frame timing via addPersistentFrameCallback, which calls the new
FrameScheduler_requestRender FFI to queue a non-blocking render on
the native render thread. After each render, a post-render callback
(FrameScheduler_setPostRenderCallback) marks textures as frame-available.

refactor: previously we needed to duplicate Filament headers (along with EGL, GLES2, GLES3, etc) for earlier implementations. For the former, we can now use Dart build hooks to propagate headers through from the Dart package to the Flutter package. For the latter, none of these are used any more, so they are safe to delete

refactor: use Dart build hooks to propagate headers through from the Dart package to the Flutter package
🤖 Generated with GitHub Actions
🤖 Generated with GitHub Actions
…t for tests on GitHub runners yet, so running tests with the Vulkan backend would only work with an actual Linux desktop
…s now extends NativeHandle<T> so we can use the T getNativeHandle() instead
Break the monolithic geometry.dart into individual files per shape
(camera, cube, cylinder, pyramid, quad, sphere) with shared utils.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace old wireframe implementation with applyWireframeBarycentrics on
GltfSceneAsset, which unwelds mesh vertices and assigns per-triangle
barycentric coordinates to CUSTOM0 for edge detection in the shader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update FFI bindings, Dart interfaces and implementations for asset
management, gizmo, view, and scene APIs. Add link hook and
analysis_options.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update existing tests for API changes and add new tests for cascade
validation, gizmo rotation, input pipeline and quad rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🤖 Generated with GitHub Actions
…sset

Instead of mutating existing glTF renderables in-place, extract mesh data
from GltfSceneAsset (with world transforms), unweld vertices, assign
barycentric coords, and create a new GeometrySceneAsset overlay entity.
This allows toggling wireframe visibility by adding/removing from scene.

Key changes:
- GeometrySceneAsset: add TransformManager component, disable culling/shadows
- GltfSceneAsset: add extractMeshData() and createWireframeOverlay() C++ methods
- C API: SceneAsset_getMeshDataSize, SceneAsset_getMeshData,
  SceneAsset_createWireframeOverlayRenderThread
- FFIWireframeAsset.createOverlayFromAsset() with color/width params
- wireframe.mat: fix alpha premultiplication (hardcode alpha=1.0 in output)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Disable face culling in silhouette material so the highlight outline
renders from both sides. Also fix plane geometry winding order to
match its declared normals, and add a test for both-sides visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BREAKING CHANGE: setGltfAnimationFrame(index, frame) is replaced by
setGltfAnimationTime(index, timeInSeconds). The old implementation
incorrectly computed the time offset passed to Filament's
applyAnimation (60 * frame * 1000 instead of frame / frameRate).
The new API accepts time in seconds, consistent with Filament's
gltfio Animator and getGltfAnimationDuration. Callers must convert
frames to seconds before calling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ace axis line width

Replaces fwidth(abs(dist)) with sqrt(dFdx²+dFdy²) on the raw signed
offset to avoid the derivative discontinuity at the line center and
fwidth's L1 overestimation at oblique camera angles. Adds a guard
against near-zero division when the plane is viewed edge-on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create a separate solid-shaded overlay entity from a glTF asset using
Lambertian diffuse lighting with configurable base color, light direction
and intensity. Mirrors the wireframe overlay architecture but uses
POSITION + TANGENTS (normals converted to tangent quaternions via
SurfaceOrientation) instead of POSITION + CUSTOM0 (barycentrics).

- Add solid.mat material (unlit with manual N·L in fragment shader)
- Add extractMeshDataWithNormals() to extract positions + normals
- Add createSolidOverlay() C++ method with SurfaceOrientation pipeline
- Add FFISolidOverlayAsset Dart wrapper with createOverlayFromAsset()
- Add solid to build.sh and hook/build.dart material sources
- Fix wireframe.mat alpha premultiplication (hardcode alpha=1.0)
- Improve wireframe anti-aliasing (Euclidean gradient + transition band)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…servation

Replace the separate overlay entity approach (createWireframeOverlay,
createSolidOverlay, applyWireframeBarycentrics) with a load-time
preserveGeometry flag that rebuilds vertex buffers in-place with a superset
of attributes (POSITION, TANGENTS, UV0, CUSTOM0 barycentrics, and optional
BONE_INDICES/BONE_WEIGHTS). This preserves the full glTF feature set
(animations, skeleton, instancing) while enabling free material swapping.

Breaking changes:
- createWireframeOverlay, createSolidOverlay, applyWireframeBarycentrics removed
- extractMeshData, extractMeshDataWithNormals removed
- FFIWireframeAsset, FFISolidOverlayAsset removed
- loadGltf/loadGltfFromBuffer now accept preserveGeometry parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom solid.mat with gltfio's built-in ubershader material
(baseColorFactor, metallicFactor, roughnessFactor). This eliminates
a compiled material and its C API surface while getting IBL, shadows,
and proper specular for free.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nce wrappers

Adds convenience methods (createUbershaderMaterial, createWireframeMaterial)
on FilamentApp that return typed wrappers with named setters instead of
raw uniform names. Also adds geometrySource parameter to stencil highlight
for preserved-geometry instances, and implements the FFI wireframe material
factory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…port

- Pass UV coordinates to SurfaceOrientation for correct tangent generation
  (matching gltfio's TangentsJob Lengyel's method)
- Add dummy COLOR attribute (FLOAT4, all 1.0) so original materials don't
  read black vertex colors after vertex buffer rebuild
- Propagate material changes to all instances in setMaterialInstanceForAll
- Apply preserved geometry to freshly-allocated instances in createInstance
- Add preserved vertex/index buffer accessors for C API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nstructor

Move rebuildVertexBuffers() call into GltfSceneAsset constructor so geometry
rebuild happens before instance creation. Rename preserveGeometry to
rebuildVertices across C API, C++ implementation, Dart interface, and tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nmfisher and others added 18 commits April 6, 2026 16:35
…3 overflow

Three bugs in rebuildVertexBuffers:

1. Instance update loop overwrote correctly-matched geometry with wrong
   buffers. The primary loop iterated getRenderableEntities() (order A)
   but the instance update loop iterated getAssetInstances()[0]->getEntities()
   (order B). Different orders caused each entity to get the wrong mesh's
   vertex data — correct shapes but garbled textures. Fix: skip instance 0
   in the update loop (already handled by primary loop) and use name-based
   matching for instances 1+.

2. Original TANGENT vectors from glTF were ignored; tangent quaternions
   were recomputed from geometry, producing wrong tangent frames and
   broken normal mapping. Fix: read cgltf_attribute_type_tangent and
   pass to SurfaceOrientation::Builder::tangents().

3. Triangle indices for SurfaceOrientation used ushort3 (uint16), which
   overflows for meshes with >65535 unwelded vertices (FlightHelmet's
   RubberWood_low has 131,574). Fix: use uint3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace name-based entity matching with consistent use of
getEntities() across primary and instance update loops. This
fixes the nameless entity case and simplifies the code:

- Primary loop now iterates getAssetInstances()[0]->getEntities()
  instead of getRenderableEntities(), matching the instance loops
- Instance update uses sequential indexing (safe since all instances
  share the same scene graph and getEntities() order)
- Remove _entityBufferMap and name-based matching
- Fix dangling pointer: tris vector outlives orientBuilder.build()
- Remove debug logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nstance

Remove the separate createWireframeMaterialInstance (returning raw
MaterialInstance) and createWireframeMaterial (returning typed wrapper).
Now createWireframeMaterialInstance directly returns
WireframeMaterialInstance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete SurfaceOrientation* after extracting tangent quaternions
- Replace malloc/free with new uint8_t[]/delete[] for buffer data
- Consolidate wireframe test cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When flatShading is true (requires rebuildVertices: true), vertex
normals are replaced with face normals computed from triangle edge
cross products, giving a faceted/low-poly look with any material.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oggle

Remove the flatShading parameter from loadGltf/loadGltfFromBuffer and
rebuildVertexBuffers. Instead, always pre-compute both smooth and flat
tangent quaternion BufferObjects during rebuildVertexBuffers, and add
a setFlatShading(bool) method that swaps slot 1 (TANGENTS) on all
preserved VertexBuffers. This is a GPU pointer swap with near-zero
runtime cost.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add single-channel (R) support to pixelBufferToPng/pixelBufferToBmp and
thread numChannels through capture helpers so R32F depth textures render
correctly as grayscale images.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added obj_import.h C API header with FFI-compatible functions
- Added obj_import.cpp wrapper implementation using Filament's Assimp
- Copied Assimp headers from Filament third_party
- Supports loading OBJ files from memory buffers
- Extracts vertices, indices, normals, UVs, mesh names, and material names
- Triangulates polygons and generates normals automatically
- Uses Assimp post-processing: Triangulate, JoinIdenticalVertices,
  SortByPType, GenNormals, CalcTangentSpace, FlipUVs
- Added ffi_obj_importer.dart with ObjMesh and ObjImporter classes
- Added ObjImporter FFI bindings to thermion_dart_ffi.g.dart
- Exported ObjImporter and related classes from filament.dart
- ObjMesh holds parsed mesh data (vertices, normals, UVs, indices, names)
- ObjImporter.loadFromBuffer() loads OBJ files and returns list of meshes
- Includes toGeometry() method to convert to Thermion Geometry
- Handles UV flipping, dummy color/UV creation, and index type selection
- Safe UTF-8 string conversion for mesh/material names
- Added GeometryUtils.parseObjFromBuffer() for loading OBJ files
- Added ObjGeometryGroup class to hold mesh name, material name, and geometry
- Fixed bug in Geometry constructor where dummy UVs were created but not assigned
- Changed uvs handling to match pattern used for colors
- Removed premature uvs assignment in constructor
- Ensures dummy UVs are created when createDummyUvs is true and uvs is null
- parseObjFromBuffer supports flipUvs parameter for coordinate system conversion
- Added abstract loadObj() and loadObjFromBuffer() methods to ThermionViewerBase
- Implemented OBJ loading in ThermionViewerFFI
- Supports optional addToScene and flipUvs parameters
- loadObj() loads from URI (file path or asset)
- loadObjFromBuffer() loads from byte buffer
- Returns list of ThermionAsset objects (one per mesh in OBJ)
- Follows existing pattern used for glTF loading
- Added assimp to libs array in hook/build.dart
- Added Assimp include path to hook/build.dart includeDirs
- Linked assimp library in web/CMakeLists.txt
- Added Assimp header copying to build scripts (macOS, web)
- Copies Assimp headers from Filament third_party during build
- Enables OBJ loading across all platforms
- Added obj_import_tests.dart with 7 tests covering:
  - Basic OBJ parsing from buffer
  - Renderable asset creation
  - UV flipping (enabled/disabled)
  - OBJ files without normals/UVs (dummies created)
  - Multi-group OBJ files
  - Mesh and material name preservation
- Added test_cube.obj asset for testing
- All tests passing
- build_ios.sh: Added Assimp build and copy for iOS (arm64)
- build_web.sh: Added Assimp build for Emscripten/WebAssembly
  (library copying already handled by existing find command)
- build_linux.sh: Added Assimp build to build_third_party_libs function
  and copy steps for both release and debug
- build_windows.bat: Added Assimp build in separate cmake directories
  (cmake-release-assimp and cmake-debug-assimp) and copy steps

All platforms now build Assimp with minimal configuration:
- ASSIMP_BUILD_ASSIMP_TOOLS=OFF
- ASSIMP_BUILD_TESTS=OFF
- ASSIMP_BUILD_SAMPLES=OFF
- ASSIMP_WARNINGS_AS_ERRORS=OFF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant