NullReferenceException when exporting scene
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)
-
repo owner -
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; } } }
-
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.
-
reporter I'm afraid that didn't help. Maybe I'm missing something? This is the updated code.
https://gist.github.com/Nihlus/c46018c4358e366bf157517b544b0d0c
-
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.
-
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.
-
reporter - changed status to resolved
-
reporter - changed status to open
-
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.
-
repo owner - changed status to closed
I added an enhancement to track scene validation, closing this now since it's user error.
-
reporter Awesome, thanks :)
- Log in to comment
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.