Skip to main content

russimp_ng/
scene.rs

1use crate::material::generate_materials;
2use crate::{
3    animation::Animation,
4    camera::Camera,
5    fs::{FileOperationsWrapper, FileSystem},
6    light::Light,
7    material::Material,
8    mesh::Mesh,
9    metadata::MetaData,
10    node::Node,
11    sys::*,
12    *,
13};
14use std::{
15    ffi::{CStr, CString},
16    rc::Rc,
17};
18
19use self::property::PropertyStore;
20
21#[derive(Derivative)]
22#[derivative(Debug)]
23pub struct Scene {
24    pub materials: Vec<Material>,
25    pub meshes: Vec<Mesh>,
26    pub metadata: Option<MetaData>,
27    pub animations: Vec<Animation>,
28    pub cameras: Vec<Camera>,
29    pub lights: Vec<Light>,
30    pub root: Option<Rc<Node>>,
31    pub flags: u32,
32}
33
34#[derive(Derivative)]
35#[derivative(Debug, Clone, Copy)]
36#[repr(u32)]
37pub enum PostProcess {
38    /// Calculates the tangents and bitangents for the imported meshes.
39    ///
40    /// Does nothing if a mesh does not have normals. You might want this post
41    /// processing step to be executed if you plan to use tangent space
42    /// calculations such as normal mapping applied to the meshes. There’s
43    /// a config setting, `AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE`, which
44    /// allows you to specify a maximum smoothing angle for the algorithm.
45    /// However, usually you’ll want to leave it at the default value.
46    CalculateTangentSpace = aiPostProcessSteps_aiProcess_CalcTangentSpace as _,
47    /// Identifies and joins identical vertex data sets within all imported
48    /// meshes.
49    ///
50    /// After this step is run, each mesh contains unique vertices, so a vertex
51    /// may be used by multiple faces. You usually want to use this post
52    /// processing step. If your application deals with indexed geometry, this
53    /// step is compulsory or you’ll just waste rendering time.
54    ///
55    /// **If this flag is *not* specified, no vertices are referenced by more
56    /// than one face and no index buffer is required for rendering.**
57    JoinIdenticalVertices = aiPostProcessSteps_aiProcess_JoinIdenticalVertices as _,
58    /// Converts all the imported data to a left-handed coordinate space.
59    ///
60    /// By default the data is returned in a right-handed coordinate space
61    /// (which OpenGL prefers). In this space, +X points to the right, +Z points
62    /// towards the viewer, and +Y points upwards. In the DirectX coordinate
63    /// space +X points to the right, +Y points upwards, and +Z points away
64    /// from the viewer.
65    ///
66    /// You'll probably want to consider this flag if you use Direct3D for
67    /// rendering. The #aiProcess_ConvertToLeftHanded flag supersedes this
68    /// setting and bundles all conversions typically required for D3D-based
69    /// applications.
70    MakeLeftHanded = aiPostProcessSteps_aiProcess_MakeLeftHanded as _,
71    /// Triangulates all faces of all meshes.
72    ///
73    /// By default the imported mesh data might contain faces with more than
74    /// three indices. For rendering you’ll usually want all faces to be
75    /// triangles. This post processing step splits up faces with more than
76    /// three indices into triangles. Line and point primitives are *not*
77    /// modified! If you want ‘triangles only’ with no other kinds of
78    /// primitives, try the following solution:
79    ///
80    /// * Specify both [`Triangulate`](PostProcess::Triangulate) and
81    ///   [`SortByPrimitiveType`](PostProcess::SortByPrimitiveType)
82    /// * Ignore all point and line meshes when you process assimp's output
83    Triangulate = aiPostProcessSteps_aiProcess_Triangulate as _,
84    /// Removes some parts of the data structure (animations, materials, light
85    /// sources, cameras, textures, vertex components).
86    ///
87    /// The components to be removed are specified in a separate configuration
88    /// option, `AI_CONFIG_PP_RVC_FLAGS`. This is quite useful if you don't need
89    /// all parts of the output structure. Vertex colors are rarely used today
90    /// for example... Calling this step to remove unneeded data from the
91    /// pipeline as early as possible results in increased performance and a
92    /// more optimized output data structure. This step is also useful if you
93    /// want to force Assimp to recompute normals or tangents. The corresponding
94    /// steps don't recompute them if they’re already there (loaded from the
95    /// source asset). By using this step you can make sure they are NOT there.
96    ///
97    /// This flag is a poor one, mainly because its purpose is usually
98    /// misunderstood. Consider the following case: a 3D model has been exported
99    /// from a CAD app, and it has per-face vertex colors. Vertex positions
100    /// can't be shared, thus the
101    /// [`JoinIdenticalVertices`](PostProcess::JoinIdenticalVertices) step
102    /// fails to optimize the data because of these nasty little vertex colors.
103    /// Most apps don't even process them, so it’s all for nothing. By using
104    /// this step, unneeded components are excluded as early as possible thus
105    /// opening more room for internal optimizations.
106    RemoveComponent = aiPostProcessSteps_aiProcess_RemoveComponent as _,
107    /// Generates normals for all faces of all meshes.
108    ///
109    /// This is ignored if normals are already there at the time this flag is
110    /// evaluated. Model importers try to load them from the source file, so
111    /// they’re usually already there. Face normals are shared between all
112    /// points of a single face, so a single point can have multiple
113    /// normals, which forces the library to duplicate vertices in some cases.
114    /// [`JoinIdenticalVertices`](PostProcess::JoinIdenticalVertices) is
115    /// *senseless* then.
116    ///
117    /// This flag may *not* be specified together with
118    /// [`GenerateSmoothNormals`](PostProcess::GenerateSmoothNormals).
119    GenerateNormals = aiPostProcessSteps_aiProcess_GenNormals as _,
120    /// Generates smooth normals for all vertices in the mesh.
121    ///
122    /// This is ignored if normals are already there at the time this flag is
123    /// evaluated. Model importers try to load them from the source file, so
124    /// they're usually already there.
125    ///
126    /// This flag may not be specified together with
127    /// [`GenerateNormals`](PostProcess::GenerateNormals)
128    ///
129    /// There’s a configuration option, `AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE`,
130    /// which allows you to specify an angle maximum for the normal smoothing
131    /// algorithm. Normals exceeding this limit are not smoothed, resulting in a
132    /// ’hard’ seam between two faces. Using a decent angle here (e.g. 80
133    /// degrees) results in very good visual appearance.
134    GenerateSmoothNormals = aiPostProcessSteps_aiProcess_GenSmoothNormals as _,
135    /// Splits large meshes into smaller sub-meshes.
136    ///
137    /// This is quite useful for real-time rendering, where the number of
138    /// triangles which can be maximally processed in a single draw-call is
139    /// limited by the video driver/hardware. The maximum vertex buffer is
140    /// usually limited too. Both requirements can be met with this step: you
141    /// may specify both a triangle and vertex limit for a single mesh.
142    ///
143    /// The split limits can (and should!) be set through the
144    /// `AI_CONFIG_PP_SLM_VERTEX_LIMIT` and `AI_CONFIG_PP_SLM_TRIANGLE_LIMIT`
145    /// settings. The default values are <tt>#AI_SLM_DEFAULT_MAX_VERTICES</tt>
146    /// and `AI_SLM_DEFAULT_MAX_TRIANGLES`.
147    ///
148    /// Note that splitting is generally a time-consuming task, but only if
149    /// there’s something to split. The use of this step is recommended for most
150    /// users.
151    SplitLargeMeshes = aiPostProcessSteps_aiProcess_SplitLargeMeshes as _,
152    /// Removes the node graph and pre-transforms all vertices with the local
153    /// transformation matrices of their nodes.
154    ///
155    /// The output scene still contains nodes, however there is only a root node
156    /// with children, each one referencing only one mesh, and each mesh
157    /// referencing one material. For rendering, you can simply render all
158    /// meshes in order - you don't need to pay attention to local
159    /// transformations and the node hierarchy. Animations are removed during
160    /// this step. This step is intended for applications without a scenegraph.
161    /// The step CAN cause some problems: if e.g. a mesh of the asset contains
162    /// normals and another, using the same material index, does not, they will
163    /// be brought together, but the first meshes's part of the normal list is
164    /// zeroed. However, these artifacts are rare.
165    ///
166    /// > The `AI_CONFIG_PP_PTV_NORMALIZE` configuration property can be
167    /// set to normalize the scene’s spatial dimension to the -1...1 range.
168    PreTransformVertices = aiPostProcessSteps_aiProcess_PreTransformVertices as _,
169    /// Limits the number of bones simultaneously affecting a single vertex to a
170    /// maximum value.
171    ///
172    /// If any vertex is affected by more than the maximum number of bones, the
173    /// least important vertex weights are removed and the remaining vertex
174    /// weights are renormalized so that the weights still sum up to 1. The
175    /// default bone weight limit is 4 (defined as `AI_LMW_MAX_WEIGHTS` in
176    /// config.h), but you can use the `AI_CONFIG_PP_LBW_MAX_WEIGHTS` setting to
177    /// supply your own limit to the post processing step.
178    ///
179    /// If you intend to perform the skinning in hardware, this post processing
180    /// step might be of interest to you.
181    LimitBoneWeights = aiPostProcessSteps_aiProcess_LimitBoneWeights as _,
182    /// Validates the imported scene data structure. This makes sure that all
183    /// indices are valid, all animations and bones are linked correctly, all
184    /// material references are correct, etc.
185    ///
186    /// It is recommended that you capture Assimp's log output if you use this
187    /// flag, so you can easily find out what's wrong if a file fails the
188    /// validation. The validator is quite strict and will find all
189    /// inconsistencies in the data structure... It is recommended that plugin
190    /// developers use it to debug their loaders. There are two types of
191    /// validation failures:
192    ///
193    /// * Error: There’s something wrong with the imported data. Further
194    ///   postprocessing is not possible and the data is not usable at all. The
195    ///   import fails.
196    /// * Warning: There are some minor issues (e.g. 1,000,000 animation
197    ///   keyframes with the same time), but further postprocessing and use of
198    ///   the data structure is still safe.
199    ///
200    /// This post-processing step is not time-consuming. Its use is not
201    /// compulsory, but recommended.
202    ValidateDataStructure = aiPostProcessSteps_aiProcess_ValidateDataStructure as _,
203    /// Reorders triangles for better vertex cache locality.
204    ///
205    /// The step tries to improve the ACMR (average post-transform vertex cache
206    /// miss ratio) for all meshes. The implementation runs in 𝖮(𝗇) and is
207    /// roughly based on the
208    /// [‘tipsify’ algorithm](http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf).
209    ///
210    /// If you intend to render huge models in hardware, this step might be of
211    /// interest to you. The `AI_CONFIG_PP_ICL_PTCACHE_SIZE` config setting can
212    /// be used to fine-tune the cache optimization.
213    ImproveCacheLocality = aiPostProcessSteps_aiProcess_ImproveCacheLocality as _,
214    /// Searches for redundant/unreferenced materials and removes them.
215    ///
216    /// This is especially useful in combination with the
217    /// [`PreTransformVertices`](PostProcess::PreTransformVertices) and
218    /// [`OptimizeMeshes`](PostProcess::OptimizeMeshes) flags. Both join
219    /// small meshes with equal characteristics, but they can't do their
220    /// work if two meshes have different materials. Because several
221    /// material settings are lost during Assimp's import filters, (and
222    /// because many exporters don't check for redundant materials), huge
223    /// models often have materials which are are defined several times with
224    /// exactly the same settings.
225    ///
226    /// Several material settings not contributing to the final appearance of a
227    /// surface are ignored in all comparisons (e.g. the material name). So, if
228    /// you’re passing additional information through the content pipeline
229    /// (probably using *magic* material names), don’t specify this flag.
230    /// Alternatively take a look at the `AI_CONFIG_PP_RRM_EXCLUDE_LIST`
231    /// setting.
232    RemoveRedundantMaterials = aiPostProcessSteps_aiProcess_RemoveRedundantMaterials as _,
233    /// Tries to determine which meshes have normal vectors that are
234    /// facing inwards and inverts them.
235    ///
236    /// The algorithm is simple but effective: the bounding box of all vertices
237    /// and their normals is compared against the volume of the bounding box of
238    /// all vertices without their normals. This works well for most objects,
239    /// problems might occur with planar surfaces. However, the step tries to
240    /// filter such cases. The step inverts all in-facing normals. Generally it
241    /// is recommended to enable this step, although the result is not always
242    /// correct.
243    FixInfacingNormals = aiPostProcessSteps_aiProcess_FixInfacingNormals as _,
244    /// Splits meshes with more than one primitive type in homogeneous
245    /// sub-meshes.
246    ///
247    /// The step is executed after the triangulation step. After the step
248    /// returns, just one bit is set in aiMesh::mPrimitiveTypes. This is
249    /// especially useful for real-time rendering where point and line
250    /// primitives are often ignored or rendered separately. You can use the
251    /// AI_CONFIG_PP_SBP_REMOVE option to specify which primitive types you
252    /// need. This can be used to easily exclude lines and points, which are
253    /// rarely used, from the import.
254    SortByPrimitiveType = aiPostProcessSteps_aiProcess_SortByPType as _,
255    /// Searches all meshes for degenerate primitives and converts
256    /// them to proper lines or points.
257    ///
258    /// A face is 'degenerate' if one or more of its points are identical. To
259    /// have the degenerate stuff not only detected and collapsed but removed,
260    /// try one of the following procedures: 1. (if you support lines and
261    /// points for rendering but don’t want the degenerates)
262    ///   * Specify the aiProcess_FindDegenerates flag.
263    ///   * Set the `AI_CONFIG_PP_FD_REMOVE` option to 1. This will cause the
264    ///     step to remove degenerate triangles from the import as soon as
265    ///     they're detected. They won't pass any further pipeline steps.
266    /// 2. (if you don't support lines and points at all)
267    ///   * Specify the aiProcess_FindDegenerates flag.
268    ///   * Specify the aiProcess_SortByPrimitiveType flag. This moves line and
269    ///     point primitives to separate meshes.
270    ///   * Set the `AI_CONFIG_PP_SBP_REMOVE` option to `aiPrimitiveType_POINTS
271    ///     | aiPrimitiveType_LINES` to cause SortByPrimitiveType to reject
272    ///     point and line meshes from the scene.
273    ///
274    /// > Degenerate polygons are not necessarily evil and that’s why they’re
275    /// not removed by default. There are several file formats which don't
276    /// support lines or points, and some exporters bypass the format
277    /// specification and write them as degenerate triangles instead.
278    FindDegenerates = aiPostProcessSteps_aiProcess_FindDegenerates as _,
279    /// Searches all meshes for invalid data, such as zeroed normal
280    /// vectors or invalid UV coords and removes/fixes them. This is intended to
281    /// get rid of some common exporter errors.
282    ///
283    /// This is especially useful for normals. If they are invalid, and the step
284    /// recognizes this, they will be removed and can later be recomputed, i.e.
285    /// by the aiProcess_GenSmoothNormals flag. The step will also remove
286    /// meshes that are infinitely small and reduce animation tracks consisting
287    /// of hundreds if redundant keys to a single key. The
288    /// `AI_CONFIG_PP_FID_ANIM_ACCURACY` config property decides the accuracy of
289    /// the check for duplicate animation tracks.
290    FixOrRemoveInvalidData = aiPostProcessSteps_aiProcess_FindInvalidData as _,
291    /// Converts non-UV mappings (such as spherical or cylindrical
292    /// mapping) to proper texture coordinate channels.
293    ///
294    /// Most applications will support UV mapping only, so you will probably
295    /// want to specify this step in every case. Note that Assimp is not always
296    /// able to match the original mapping implementation of the 3D app which
297    /// produced a model perfectly. It's always better to let the modelling app
298    /// compute the UV channels - 3ds max, Maya, Blender, LightWave, and Modo do
299    /// this for example.
300    ///
301    /// > If this step is not requested, you’ll need to process the
302    /// `AI_MATKEY_MAPPING` material property in order to display all assets
303    /// properly.
304    GenerateUVCoords = aiPostProcessSteps_aiProcess_GenUVCoords as _,
305    /// Applies per-texture UV transformations and bakes them into
306    /// stand-alone vtexture coordinate channels.
307    ///
308    /// UV transformations are specified per-texture — see the
309    /// `AI_MATKEY_UVTRANSFORM` material key for more information. This step
310    /// processes all textures with transformed input UV coordinates and
311    /// generates a new (pre-transformed) UV channel which replaces the old
312    /// channel. Most applications won't support UV transformations, so you will
313    /// probably want to specify this step.
314    ///
315    /// > UV transformations are usually implemented in real-time apps by
316    /// transforming texture coordinates at vertex shader stage with a 3x3
317    /// (homogenous) transformation matrix.
318    TransformUVCoords = aiPostProcessSteps_aiProcess_TransformUVCoords as _,
319    /// This step searches for duplicate meshes and replaces them with
320    /// references to the first mesh.
321    ///
322    /// This step takes a while, so don't use it if speed is a concern. Its main
323    /// purpose is to workaround the fact that many export file formats don't
324    /// support instanced meshes, so exporters need to duplicate meshes. This
325    /// step removes the duplicates again. Please note that Assimp does not
326    /// currently support per-node material assignment to meshes, which means
327    /// that identical meshes with different materials are currently not joined,
328    /// although this is planned for future versions.
329    FindInstances = aiPostProcessSteps_aiProcess_FindInstances as _,
330    /// Reduces the number of meshes.
331    ///
332    /// This will, in fact, reduce the number of draw calls.
333    ///
334    /// This is a very effective optimization and is recommended to be used
335    /// together with [`OptimizeGraph`](PostProcess::OptimizeGraph), if
336    /// possible. The flag is fully compatible with both
337    /// [`SplitLargeMeshes`](PostProcess::SplitLargeMeshes) and
338    /// [`SortByPrimitiveType`](PostProcess::SortByPrimitiveType).
339    OptimizeMeshes = aiPostProcessSteps_aiProcess_OptimizeMeshes as _,
340    /// Optimizes the scene hierarchy.
341    ///
342    /// Nodes without animations, bones, lights or cameras assigned are
343    /// collapsed and joined.
344    ///
345    /// Node names can be lost during this step. If you use special ‘tag nodes’
346    /// to pass additional information through your content pipeline, use the
347    /// `AI_CONFIG_PP_OG_EXCLUDE_LIST` setting to specify a list of node names
348    /// you want to be kept. Nodes matching one of the names in this list won’t
349    /// touched or modified.
350    ///
351    /// Use this flag with caution. Most simple files will be collapsed to a
352    /// single node, so complex hierarchies are usually completely lost. This is
353    /// not useful for editor environments, but probably a very effective
354    /// optimization if you just want to get the model data, convert it to your
355    /// own format, and render it as fast as possible.
356    ///
357    /// This flag is designed to be used with
358    /// [`OptimizeMeshes`](PostProcess::OptimizeMeshes) for best
359    /// results.
360    ///
361    /// > ‘Crappy’ scenes with thousands of extremely small meshes packed in
362    /// deeply nested nodes exist for almost all file formats.
363    /// [`OptimizeMeshes`](PostProcess::OptimizeMeshes) in combination with
364    /// [`OptimizeGraph`](PostProcess::OptimizeGraph) usually fixes
365    /// them all and makes them renderable.
366    OptimizeGraph = aiPostProcessSteps_aiProcess_OptimizeGraph as _,
367    /// This step flips all UV coordinates along the y-axis and adjusts material
368    /// settings and bitangents accordingly.
369    ///
370    /// You’ll probably want to consider this flag if you use Direct3D for
371    /// rendering. The
372    /// [`ConvertToLeftHanded`](PostProcess::ConvertToLeftHanded) flag
373    /// supersedes this setting and bundles all conversions typically
374    /// required for Direct3D-based applications.
375    FlipUVs = aiPostProcessSteps_aiProcess_FlipUVs as _,
376    /// Adjusts the output face winding order to be clockwise (CW).
377    ///
378    /// The default face winding order is counter clockwise (CCW).
379    FlipWindingOrder = aiPostProcessSteps_aiProcess_FlipWindingOrder as _,
380    /// Splits meshes with many bones into sub-meshes so that each su-bmesh has
381    /// fewer or as many bones as a given limit.
382    SplitByBoneCount = aiPostProcessSteps_aiProcess_SplitByBoneCount as _,
383    /// This step removes bones losslessly or according to some threshold.
384    ///
385    /// In some cases (i.e. formats that require it) exporters are forced to
386    /// assign dummy bone weights to otherwise static meshes assigned to
387    /// animated meshes. Full, weight-based skinning is expensive while
388    /// animating nodes is extremely cheap, so this step is offered to clean up
389    /// the data in that regard.
390    ///
391    /// Use `AI_CONFIG_PP_DB_THRESHOLD` to control this.
392    /// Use `AI_CONFIG_PP_DB_ALL_OR_NONE` if you want bones removed if and only
393    /// if all bones within the scene qualify for removal.
394    Debone = aiPostProcessSteps_aiProcess_Debone as _,
395    GlobalScale = aiPostProcessSteps_aiProcess_GlobalScale as _,
396    /// Force embedding of textures (using the `path = "*1"` convention).
397    ///
398    /// If a texture’s file does not exist at the specified path (due, for
399    /// instance, to an absolute path generated on another system),  it will
400    /// check if a file with the same name exists at the root folder of the
401    /// imported model. And if so, it uses that.
402    EmbedTextures = aiPostProcessSteps_aiProcess_EmbedTextures as _,
403    ForceGenerateNormals = aiPostProcessSteps_aiProcess_ForceGenNormals as _,
404    DropNormals = aiPostProcessSteps_aiProcess_DropNormals as _,
405    /// Calculate [axis-aligned bounding boxes](crate::AABB) for all meshes in
406    /// a scene.
407    GenerateBoundingBoxes = aiPostProcessSteps_aiProcess_GenBoundingBoxes as _,
408}
409
410pub type PostProcessSteps = Vec<PostProcess>;
411
412impl Scene {
413    fn new(scene: &aiScene) -> Russult<Self> {
414        let root = unsafe { scene.mRootNode.as_ref() };
415
416        Ok(Self {
417            materials: generate_materials(scene)?,
418            meshes: utils::get_vec_from_raw(scene.mMeshes, scene.mNumMeshes),
419            metadata: utils::get_raw(scene.mMetaData),
420            animations: utils::get_vec_from_raw(scene.mAnimations, scene.mNumAnimations),
421            cameras: utils::get_vec_from_raw(scene.mCameras, scene.mNumCameras),
422            lights: utils::get_vec_from_raw(scene.mLights, scene.mNumLights),
423            root: root.map(Node::new),
424            flags: scene.mFlags,
425        })
426    }
427
428    pub fn from_file(file_path: &str, flags: PostProcessSteps) -> Russult<Scene> {
429        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
430        let file_path = CString::new(file_path).unwrap();
431        match Scene::get_scene_from_file(file_path, bitwise_flag) {
432            Some(raw_scene) => {
433                let result = Scene::new(raw_scene);
434                Scene::drop_scene(raw_scene);
435                result
436            }
437            None => Err(Scene::get_error()),
438        }
439    }
440
441    pub fn from_file_with_props(
442        file_path: &str,
443        flags: PostProcessSteps,
444        props: &PropertyStore,
445    ) -> Russult<Scene> {
446        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
447        let file_path = CString::new(file_path).unwrap();
448        match Scene::get_scene_from_file_with_props(file_path, bitwise_flag, Some(props)) {
449            Some(raw_scene) => {
450                let result = Scene::new(raw_scene);
451                Scene::drop_scene(raw_scene);
452                result
453            }
454            None => Err(Scene::get_error()),
455        }
456    }
457
458    pub fn from_file_system<T: FileSystem>(
459        file_path: &str,
460        flags: PostProcessSteps,
461        file_io: &mut T,
462    ) -> Russult<Scene> {
463        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
464        let file_path = CString::new(file_path).unwrap();
465        match Scene::get_scene_from_filesystem(file_path, bitwise_flag, file_io) {
466            Some(raw_scene) => {
467                let result = Scene::new(raw_scene);
468                Scene::drop_scene(raw_scene);
469                result
470            }
471            None => Err(Scene::get_error()),
472        }
473    }
474
475    pub fn from_file_system_with_props<T: FileSystem>(
476        file_path: &str,
477        flags: PostProcessSteps,
478        file_io: &mut T,
479        props: &PropertyStore,
480    ) -> Russult<Scene> {
481        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
482        let file_path = CString::new(file_path).unwrap();
483        match Scene::get_scene_from_filesystem_with_props(
484            file_path,
485            bitwise_flag,
486            file_io,
487            Some(props),
488        ) {
489            Some(raw_scene) => {
490                let result = Scene::new(raw_scene);
491                Scene::drop_scene(raw_scene);
492                result
493            }
494            None => Err(Scene::get_error()),
495        }
496    }
497
498    pub fn from_buffer(buffer: &[u8], flags: PostProcessSteps, hint: &str) -> Russult<Scene> {
499        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
500        let hint = CString::new(hint).unwrap();
501        match Scene::get_scene_from_file_from_memory(buffer, bitwise_flag, hint) {
502            Some(raw_scene) => {
503                let result = Scene::new(raw_scene);
504                Scene::drop_scene(raw_scene);
505                result
506            }
507            None => Err(Scene::get_error()),
508        }
509    }
510
511    pub fn from_buffer_with_props(
512        buffer: &[u8],
513        flags: PostProcessSteps,
514        hint: &str,
515        props: &PropertyStore,
516    ) -> Russult<Scene> {
517        let bitwise_flag = flags.into_iter().fold(0, |acc, x| acc | (x as u32));
518        let hint = CString::new(hint).unwrap();
519        match Scene::get_scene_from_file_from_memory_with_props(
520            buffer,
521            bitwise_flag,
522            hint,
523            Some(props),
524        ) {
525            Some(raw_scene) => {
526                let result = Scene::new(raw_scene);
527                Scene::drop_scene(raw_scene);
528                result
529            }
530            None => Err(Scene::get_error()),
531        }
532    }
533
534    #[inline]
535    fn drop_scene(scene: &aiScene) {
536        unsafe {
537            aiReleaseImport(scene);
538        }
539    }
540
541    #[inline]
542    fn get_scene_from_file<'a>(string: CString, flags: u32) -> Option<&'a aiScene> {
543        Self::get_scene_from_file_with_props(string, flags, None)
544    }
545
546    #[inline]
547    fn get_scene_from_file_with_props<'a>(
548        string: CString,
549        flags: u32,
550        props: Option<&PropertyStore>,
551    ) -> Option<&'a aiScene> {
552        unsafe {
553            aiImportFileExWithProperties(
554                string.as_ptr(),
555                flags,
556                std::ptr::null_mut(),
557                props.map(|p| p.as_ptr()).unwrap_or(std::ptr::null_mut()),
558            )
559            .as_ref()
560        }
561    }
562
563    #[inline]
564    fn get_scene_from_filesystem<'a, T: FileSystem>(
565        string: CString,
566        flags: u32,
567        fs: &mut T,
568    ) -> Option<&'a aiScene> {
569        Self::get_scene_from_filesystem_with_props(string, flags, fs, None)
570    }
571
572    #[inline]
573    fn get_scene_from_filesystem_with_props<'a, T: FileSystem>(
574        string: CString,
575        flags: u32,
576        fs: &mut T,
577        props: Option<&PropertyStore>,
578    ) -> Option<&'a aiScene> {
579        let mut file_io = FileOperationsWrapper::new(fs);
580        unsafe {
581            aiImportFileExWithProperties(
582                string.as_ptr(),
583                flags,
584                file_io.ai_file(),
585                props.map(|p| p.as_ptr()).unwrap_or(std::ptr::null_mut()),
586            )
587            .as_ref()
588        }
589    }
590
591    #[inline]
592    fn get_scene_from_file_from_memory<'a>(
593        buffer: &[u8],
594        flags: u32,
595        hint: CString,
596    ) -> Option<&'a aiScene> {
597        Self::get_scene_from_file_from_memory_with_props(buffer, flags, hint, None)
598    }
599
600    #[inline]
601    fn get_scene_from_file_from_memory_with_props<'a>(
602        buffer: &[u8],
603        flags: u32,
604        hint: CString,
605        props: Option<&PropertyStore>,
606    ) -> Option<&'a aiScene> {
607        unsafe {
608            aiImportFileFromMemoryWithProperties(
609                buffer.as_ptr() as *const _,
610                buffer.len() as _,
611                flags,
612                hint.as_ptr(),
613                props.map(|p| p.as_ptr()).unwrap_or(std::ptr::null_mut()),
614            )
615            .as_ref()
616        }
617    }
618
619    fn get_error() -> RussimpError {
620        let error_buf = unsafe { aiGetErrorString() };
621        let error = unsafe { CStr::from_ptr(error_buf).to_string_lossy().into_owned() };
622        RussimpError::Import(error)
623    }
624}
625
626#[cfg(test)]
627mod test {
628    use crate::scene::{PostProcess, Scene};
629    use crate::utils;
630    use std::rc::Rc;
631
632    #[test]
633    fn importing_invalid_file_returns_error() {
634        let current_directory_buf = utils::get_model("models/box.blend");
635
636        let scene = Scene::from_file(
637            current_directory_buf.as_str(),
638            vec![
639                PostProcess::CalculateTangentSpace,
640                PostProcess::Triangulate,
641                PostProcess::JoinIdenticalVertices,
642                PostProcess::SortByPrimitiveType,
643            ],
644        );
645
646        assert!(scene.is_err())
647    }
648
649    #[test]
650    fn importing_valid_file_returns_scene() {
651        let current_directory_buf = utils::get_model("models/BLEND/box.blend");
652
653        let scene = Scene::from_file(
654            current_directory_buf.as_str(),
655            vec![
656                PostProcess::CalculateTangentSpace,
657                PostProcess::Triangulate,
658                PostProcess::JoinIdenticalVertices,
659                PostProcess::SortByPrimitiveType,
660            ],
661        )
662        .unwrap();
663
664        assert_eq!(8, scene.flags);
665    }
666
667    #[test]
668    fn debug_scene() {
669        let box_file_path = utils::get_model("models/BLEND/box.blend");
670
671        let scene = Scene::from_file(
672            box_file_path.as_str(),
673            vec![
674                PostProcess::CalculateTangentSpace,
675                PostProcess::Triangulate,
676                PostProcess::JoinIdenticalVertices,
677                PostProcess::SortByPrimitiveType,
678            ],
679        )
680        .unwrap();
681
682        dbg!(&scene);
683    }
684
685    #[test]
686    fn debug_scene_from_memory() {
687        let box_file_path = b"solid foo bar
688    facet normal 0.1 0.2 0.3
689        outer loop
690            vertex 1 2 3
691            vertex 4 5 6e-15
692            vertex 7 8 9.87654321
693        endloop
694    endfacet
695    endsolid foo bar";
696
697        let scene = Scene::from_buffer(
698            box_file_path,
699            vec![
700                PostProcess::CalculateTangentSpace,
701                PostProcess::Triangulate,
702                PostProcess::JoinIdenticalVertices,
703                PostProcess::SortByPrimitiveType,
704            ],
705            "stl",
706        )
707        .unwrap();
708
709        dbg!(&scene);
710    }
711
712    #[test]
713    fn memory_leak_test() {
714        let box_file_path = utils::get_model("models/BLEND/box.blend");
715
716        let scene = Scene::from_file(
717            box_file_path.as_str(),
718            vec![
719                PostProcess::CalculateTangentSpace,
720                PostProcess::Triangulate,
721                PostProcess::JoinIdenticalVertices,
722                PostProcess::SortByPrimitiveType,
723            ],
724        )
725        .unwrap();
726
727        let root = scene.root.as_ref().unwrap().clone();
728        assert_eq!(Rc::strong_count(&root), 2);
729
730        drop(scene);
731
732        // Strong refcount must be 1 here, otherwise we leak memory
733        assert_eq!(Rc::strong_count(&root), 1);
734    }
735}