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}