public static Model ConvertFromAssimpScene(Ai.Scene aiScene, ModelConverterOptions options) { var scene = new Model(options.Version); // Convert assimp nodes to our nodes var nodeLookup = new Dictionary <string, NodeInfo>(); int nextNodeIndex = 0; scene.RootNode = ConvertAssimpNodeRecursively(aiScene, aiScene.RootNode, nodeLookup, ref nextNodeIndex, options); // Process the meshes attached to the assimp nodes var nodeToBoneIndices = new Dictionary <int, List <int> >(); int nextBoneIndex = 0; var boneInverseBindMatrices = new List <Matrix4x4>(); var transformedVertices = new List <Vector3>(); ProcessAssimpNodeMeshesRecursively(aiScene.RootNode, aiScene, nodeLookup, ref nextBoneIndex, nodeToBoneIndices, boneInverseBindMatrices, transformedVertices, options); // Don't build a bone palette if there are no skinned meshes if (boneInverseBindMatrices.Count > 0) { // Build bone palette for skinning scene.Bones = BuildBonePalette(boneInverseBindMatrices, nodeToBoneIndices); } // Build bounding box & sphere scene.BoundingBox = BoundingBox.Calculate(transformedVertices); scene.BoundingSphere = BoundingSphere.Calculate(scene.BoundingBox.Value, transformedVertices); return(scene); }
public static ModelPack ConvertFromAssimpScene(string filePath, ModelPackConverterOptions options) { // For importing textures string baseDirectoryPath = Path.GetDirectoryName(filePath); // Import scene var aiScene = AssimpSceneImporter.ImportFile(filePath); // Build textures & Materials var model = new ModelPack(options.Version); model.Textures = new TextureDictionary(options.Version); model.Materials = new MaterialDictionary(options.Version); foreach (var aiSceneMaterial in aiScene.Materials) { var material = ConvertMaterialAndTextures(aiSceneMaterial, options, baseDirectoryPath, model.Textures); model.Materials.Add(material); } // Create scene var sceneConverterOptions = new ModelConverterOptions() { Version = options.Version, ConvertSkinToZUp = options.ConvertSkinToZUp, GenerateVertexColors = options.GenerateVertexColors, MinimalVertexAttributes = options.MinimalVertexAttributes, }; model.Model = ModelConverter.ConvertFromAssimpScene(aiScene, sceneConverterOptions); return(model); }
private static Mesh ConvertAssimpMeshToGeometry(Ai.Mesh aiMesh, Ai.Material material, Dictionary <string, NodeInfo> nodeLookup, ref int nextBoneIndex, Dictionary <int, List <int> > nodeToBoneIndices, List <Matrix4x4> boneInverseBindMatrices, ref Matrix4x4 nodeWorldTransform, ref Matrix4x4 nodeInverseWorldTransform, List <Vector3> transformedVertices, ModelConverterOptions options) { if (!aiMesh.HasVertices) { throw new Exception("Assimp mesh has no vertices"); } var geometry = new Mesh(); var geometryTransformedVertices = new Vector3[aiMesh.VertexCount]; geometry.Vertices = aiMesh.Vertices .Select(x => new Vector3(x.X, x.Y, x.Z)) .ToArray(); for (int i = 0; i < geometry.Vertices.Length; i++) { geometryTransformedVertices[i] = Vector3.Transform(geometry.Vertices[i], nodeWorldTransform); } transformedVertices.AddRange(geometryTransformedVertices); if (aiMesh.HasNormals) { geometry.Normals = aiMesh.Normals .Select(x => new Vector3(x.X, x.Y, x.Z)) .ToArray(); } if (aiMesh.HasTextureCoords(0)) { geometry.TexCoordsChannel0 = aiMesh.TextureCoordinateChannels[0] .Select(x => new Vector2(x.X, x.Y)) .ToArray(); } if (aiMesh.HasTextureCoords(1)) { geometry.TexCoordsChannel1 = aiMesh.TextureCoordinateChannels[1] .Select(x => new Vector2(x.X, x.Y)) .ToArray(); } if (aiMesh.HasTextureCoords(2)) { geometry.TexCoordsChannel2 = aiMesh.TextureCoordinateChannels[2] .Select(x => new Vector2(x.X, x.Y)) .ToArray(); } if (aiMesh.HasVertexColors(0)) { geometry.ColorChannel0 = aiMesh.VertexColorChannels[0] .Select(x => ( uint )(( byte )(x.B * 255f) | ( byte )(x.G * 255f) << 8 | ( byte )(x.R * 255f) << 16 | ( byte )(x.A * 255f) << 24)) .ToArray(); } else if (options.GenerateVertexColors) { geometry.ColorChannel0 = new uint[geometry.VertexCount]; for (int i = 0; i < geometry.ColorChannel0.Length; i++) { geometry.ColorChannel0[i] = 0xFFFFFFFF; } } if (aiMesh.HasVertexColors(1)) { geometry.ColorChannel1 = aiMesh.VertexColorChannels[1] .Select(x => ( uint )(( byte )(x.B * 255f) | ( byte )(x.G * 255f) << 8 | ( byte )(x.R * 255f) << 16 | ( byte )(x.A * 255f) << 24)) .ToArray(); } if (aiMesh.HasFaces) { geometry.TriangleIndexFormat = aiMesh.VertexCount <= ushort.MaxValue ? TriangleIndexFormat.UInt16 : TriangleIndexFormat.UInt32; geometry.Triangles = aiMesh.Faces .Select(x => new Triangle(( uint )x.Indices[0], ( uint )x.Indices[1], ( uint )x.Indices[2])) .ToArray(); } if (aiMesh.HasBones) { geometry.VertexWeights = new VertexWeight[geometry.VertexCount]; for (int i = 0; i < geometry.VertexWeights.Length; i++) { geometry.VertexWeights[i].Indices = new byte[4]; geometry.VertexWeights[i].Weights = new float[4]; } var vertexWeightCounts = new int[geometry.VertexCount]; for (var i = 0; i < aiMesh.Bones.Count; i++) { var aiMeshBone = aiMesh.Bones[i]; // Find node index for the bone var boneLookupData = nodeLookup[AssimpConverterCommon.UnescapeName(aiMeshBone.Name)]; int nodeIndex = boneLookupData.Index; // Calculate inverse bind matrix var boneNode = boneLookupData.Node; var bindMatrix = boneNode.WorldTransform * nodeInverseWorldTransform; if (options.ConvertSkinToZUp) { bindMatrix *= YToZUpMatrix; } Matrix4x4.Invert(bindMatrix, out var inverseBindMatrix); // Get bone index int boneIndex; if (!nodeToBoneIndices.TryGetValue(nodeIndex, out var boneIndices)) { // No entry for the node was found, so we add a new one boneIndex = nextBoneIndex++; nodeToBoneIndices.Add(nodeIndex, new List <int>() { boneIndex }); boneInverseBindMatrices.Add(inverseBindMatrix); } else { // Entry for the node was found // Try to find the bone index based on whether the inverse bind matrix matches boneIndex = -1; foreach (int index in boneIndices) { if (boneInverseBindMatrices[index].Equals(inverseBindMatrix)) { boneIndex = index; } } if (boneIndex == -1) { // None matching inverse bind matrix was found, so we add a new entry boneIndex = nextBoneIndex++; nodeToBoneIndices[nodeIndex].Add(boneIndex); boneInverseBindMatrices.Add(inverseBindMatrix); } } foreach (var aiVertexWeight in aiMeshBone.VertexWeights) { int vertexWeightCount = vertexWeightCounts[aiVertexWeight.VertexID]++; geometry.VertexWeights[aiVertexWeight.VertexID].Indices[vertexWeightCount] = ( byte )boneIndex; geometry.VertexWeights[aiVertexWeight.VertexID].Weights[vertexWeightCount] = aiVertexWeight.Weight; } } } geometry.MaterialName = AssimpConverterCommon.UnescapeName(material.Name); geometry.BoundingBox = BoundingBox.Calculate(geometry.Vertices); geometry.BoundingSphere = BoundingSphere.Calculate(geometry.BoundingBox.Value, geometry.Vertices); geometry.Flags |= GeometryFlags.Flag80000000; return(geometry); }
private static void ProcessAssimpNodeMeshesRecursively(Ai.Node aiNode, Ai.Scene aiScene, Dictionary <string, NodeInfo> nodeLookup, ref int nextBoneIndex, Dictionary <int, List <int> > nodeToBoneIndices, List <Matrix4x4> boneInverseBindMatrices, List <Vector3> transformedVertices, ModelConverterOptions options) { if (aiNode.HasMeshes) { var nodeInfo = nodeLookup[AssimpConverterCommon.UnescapeName(aiNode.Name)]; var node = nodeInfo.Node; var nodeWorldTransform = node.WorldTransform; Matrix4x4.Invert(nodeWorldTransform, out var nodeInverseWorldTransform); foreach (var aiMeshIndex in aiNode.MeshIndices) { var aiMesh = aiScene.Meshes[aiMeshIndex]; var aiMaterial = aiScene.Materials[aiMesh.MaterialIndex]; var geometry = ConvertAssimpMeshToGeometry(aiMesh, aiMaterial, nodeLookup, ref nextBoneIndex, nodeToBoneIndices, boneInverseBindMatrices, ref nodeWorldTransform, ref nodeInverseWorldTransform, transformedVertices, options); if (!nodeInfo.IsMeshAttachment) { node.Attachments.Add(new NodeMeshAttachment(geometry)); } else { node.Parent.Attachments.Add(new NodeMeshAttachment(geometry)); node.Parent.RemoveChildNode(node); } } } foreach (var aiNodeChild in aiNode.Children) { ProcessAssimpNodeMeshesRecursively(aiNodeChild, aiScene, nodeLookup, ref nextBoneIndex, nodeToBoneIndices, boneInverseBindMatrices, transformedVertices, options); } }
public static Model ConvertFromAssimpScene(string filePath, ModelConverterOptions options) { var aiScene = AssimpSceneImporter.ImportFile(filePath); return(ConvertFromAssimpScene(aiScene, options)); }
private static Node ConvertAssimpNodeRecursively(Assimp.Scene aiScene, Ai.Node aiNode, Dictionary <string, NodeInfo> nodeLookup, ref int nextIndex, ModelConverterOptions options) { aiNode.Transform.Decompose(out var scale, out var rotation, out var translation); // Create node var node = new Node(AssimpConverterCommon.UnescapeName(aiNode.Name), new Vector3(translation.X, translation.Y, translation.Z), new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W), new Vector3(scale.X, scale.Y, scale.Z)); if (!IsMeshAttachmentNode(aiNode)) { // Convert properties ConvertAssimpMetadataToProperties(aiNode.Metadata, node); if (options.SetFullBodyNodeProperties) { if (node.Name == "See User Defined Properties") { TryAddProperty(node.Properties, new UserIntProperty("NiSortAdjustNode", 0)); } else if (node.Name.EndsWith("root") || node.Name == "Bip01") { TryAddProperty(node.Properties, new UserIntProperty("KFAccumRoot", 0)); } else if (sFullBodyObjectNames.Contains(node.Name)) { TryAddFullBodyObjectProperties(node.Properties, node.Name); } } if (!nodeLookup.ContainsKey(node.Name)) { // Add to lookup nodeLookup.Add(node.Name, new NodeInfo(aiNode, node, nextIndex++, false)); } else { throw new Exception($"Duplicate node name '{node.Name}'"); } // Is this a camera? var index = -1; if ((index = aiScene.Cameras.FindIndex(x => x.Name == node.Name)) != -1) { var aiCamera = aiScene.Cameras[index]; var camera = new Camera(-aiCamera.Direction.ToNumerics(), aiCamera.Up.ToNumerics(), aiCamera.Position.ToNumerics(), aiCamera.ClipPlaneNear, aiCamera.ClipPlaneFar, MathHelper.RadiansToDegrees(aiCamera.FieldOfview), aiCamera.AspectRatio, 0 ) { Version = options.Version }; node.Attachments.Add(new NodeCameraAttachment(camera)); } else if ((index = aiScene.Lights.FindIndex(x => x.Name == node.Name)) != -1) { var aiLight = aiScene.Lights[index]; var lightType = LightType.Point; switch (aiLight.LightType) { case Ai.LightSourceType.Directional: lightType = LightType.Type1; break; case Ai.LightSourceType.Point: case Ai.LightSourceType.Ambient: lightType = LightType.Point; break; case Ai.LightSourceType.Spot: lightType = LightType.Spot; break; } var light = new Light { Version = options.Version, AmbientColor = aiLight.ColorAmbient.ToNumerics(), DiffuseColor = aiLight.ColorDiffuse.ToNumerics(), SpecularColor = aiLight.ColorSpecular.ToNumerics(), AngleInnerCone = aiLight.AngleInnerCone, AngleOuterCone = aiLight.AngleOuterCone, Type = lightType, Flags = LightFlags.Bit1 | LightFlags.Bit2 }; node.Attachments.Add(new NodeLightAttachment(light)); } // Process children foreach (var aiNodeChild in aiNode.Children) { if (aiNodeChild.Name == "RootNode") { // For compatibility with old exports // Merge children of 'RootNode' node with actual root node foreach (var aiFakeRootNodeChild in aiNodeChild.Children) { var childNode = ConvertAssimpNodeRecursively(aiScene, aiFakeRootNodeChild, nodeLookup, ref nextIndex, options); node.AddChildNode(childNode); } } else { var childNode = ConvertAssimpNodeRecursively(aiScene, aiNodeChild, nodeLookup, ref nextIndex, options); node.AddChildNode(childNode); } } } else { nodeLookup.Add(node.Name, new NodeInfo(aiNode, node, -1, true)); } return(node); }