NullReferenceException when exporting scene

Issue #29 closed
Jax created an issue

When exporting a scene to file or blob, the exporting fails with a NullReferenceException. In particular, it appears like a pointer to some sort of native FileIO structure is null, and the aiExportSceneEx function fails because of it.

The scene was created programmaticaly from a format Assimp does not natively support (copying verts, etc), and contains a number of meshes. There are no bones, no textures, and no animations.

Stacktrace: https://gist.github.com/Nihlus/2b88703bb3f43fe9f06a6ee0587534a0 Exact line of code and input parameters: See screenshot

Runtime information: OS: Linux Mint 19.1 Mono: 5.18.0.247 64-bit Assimp.NET: 4.1.0 (from nuget) Assimp Native: Assimp.NET bundled

Comments (11)

  1. Nicholas Woodfield repo owner

    FileIO is an optional parameter, if it's null the native assimp default IO provider is used; most of the time there isn't a need to set any sort of custom IO handling.

    How do you setup the scene data structure? I suspect the problem lies with the scene data in one way or another...native debugging with the native assimp debug symbols will hopefully pinpoint the problem; otherwise I'd need to see the scene setup code.

  2. Jax reporter

    Sure, here's the code:

    //
    //  AssimpConverter.cs
    //
    //  Author:
    //       Jarl Gullberg <jarl.gullberg@gmail.com>
    //
    //  Copyright (c) 2017 Jarl Gullberg
    //
    //  This program is free software: you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License as published by
    //  the Free Software Foundation, either version 3 of the License, or
    //  (at your option) any later version.
    //
    //  This program is distributed in the hope that it will be useful,
    //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    //  GNU General Public License for more details.
    //
    //  You should have received a copy of the GNU General Public License
    //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    //
    
    using System.Collections.Generic;
    using System.Linq;
    using System.Numerics;
    using Assimp;
    using Warcraft.MDX;
    using Warcraft.MDX.Animation;
    using Matrix4x4 = Assimp.Matrix4x4;
    
    namespace Everlook.Export.Model
    {
        /// <summary>
        /// Converts models to Assimp representations.
        /// </summary>
        public static class AssimpConverter
        {
            /// <summary>
            /// Converts the given MDX model into an Assimp representation.
            /// </summary>
            /// <param name="model">The model.</param>
            /// <returns>The Assimp representation.</returns>
            public static Scene FromMDX(MDX model)
            {
                var scene = new Scene();
    
                foreach (var skin in model.Skins)
                {
                    foreach (var batch in skin.RenderBatches)
                    {
                        var sectionIndex = batch.SkinSectionIndex;
                        var section = skin.Sections[sectionIndex];
    
                        var mesh = new Mesh();
    
                        var globalVertexIndexes = skin.VertexIndices
                            .Skip(section.StartVertexIndex)
                            .Take(section.VertexCount);
    
                        var globalVertexes = model.Vertices
                            .Where((_, i) => globalVertexIndexes.Contains((ushort)i))
                            .ToList();
    
                        for (int i = 0; i < globalVertexes.Count; ++i)
                        {
                            var vertex = globalVertexes[i];
    
                            mesh.Vertices.Add(new Vector3D(vertex.Position.X, vertex.Position.Y, vertex.Position.Z));
                            mesh.Normals.Add(new Vector3D(vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z));
    
                            mesh.TextureCoordinateChannels[0].Add(new Vector3D(vertex.UV1.X, vertex.UV1.Y, 0));
                            mesh.TextureCoordinateChannels[1].Add(new Vector3D(vertex.UV2.X, vertex.UV2.Y, 0));
                        }
    
                        var globalTriangleIndexes = skin.Triangles
                            .Skip(section.StartTriangleIndex)
                            .Take(section.TriangleCount * 3)
                            .ToList();
    
                        for (int i = 0; i < section.TriangleCount * 3; i += 3)
                        {
                            var face = new Face();
                            face.Indices.AddRange(globalTriangleIndexes.Skip(i).Take(3).Select(index => (int)index));
    
                            mesh.Faces.Add(face);
                        }
    
                        scene.Meshes.Add(mesh);
                    }
                }
    
                return scene;
            }
        }
    }
    
  3. Nicholas Woodfield repo owner

    You need to specify a material and a node that uses the mesh. The data structure is a scenegraph, so just specifying the list of meshes isn't enough (potentially they can be referenced multiple times, using different world transforms). Either skin or render batch is probably going to have a 4x4 transform on it, and that'll correspond to the node you'll have to create.

    All objects in the data structure are referenced by an index in the lists that the scene object manages, so a mesh references a material by an index, a node references a mesh by an index, etc.

  4. Jax reporter

    I tried importing an obj file and reexporting it as GLTF2, and that worked fine, so it's very likely that there's something wrong with my scene setup. I've been comparing the imported scene and my generated one, and structurally I can't see much of a difference.

  5. Jax reporter

    I figured it out (mostly). My face indexes were incorrect, and didn't map properly. That that would result in a null reference exception is very surprising and unintuitive, but hey, them's the breaks. I still can't get past the exception, but now I'm sure it's in user code.

  6. Nicholas Woodfield repo owner

    Well, if you have bad indices, native assimp doesn't always do bounds checking...so you might be lucky that you're even getting a null reference exception.

    It might be worthwhile to have some functionality to validate the (managed) scene data structure, with helpful error output, so you can run that before passing it to native assimp.

  7. Nicholas Woodfield repo owner

    I added an enhancement to track scene validation, closing this now since it's user error.

  8. Log in to comment