Пример #1
0
        public static Assimp.Scene CreateAssimpScene(this IGeometryModel model, Assimp.AssimpContext context, string formatId)
        {
            var scale = ModelViewerPlugin.Settings.GeometryScale;

            //either Assimp or collada has issues when there is a name conflict
            const string bonePrefix = "~";
            const string geomPrefix = "-";
            const string scenPrefix = "$";

            var scene = new Assimp.Scene();

            scene.RootNode = new Assimp.Node($"{scenPrefix}{model.Name}");

            //Assimp is Y-up in inches by default - this forces it to export as Z-up in meters
            scene.RootNode.Transform = (CoordinateSystem.HaloCEX * ModelViewerPlugin.Settings.AssimpScale).ToAssimp4x4();

            #region Nodes
            var allNodes = new List <Assimp.Node>();
            foreach (var node in model.Nodes)
            {
                var result = new Assimp.Node($"{bonePrefix}{node.Name}");

                var q   = new System.Numerics.Quaternion(node.Rotation.X, node.Rotation.Y, node.Rotation.Z, node.Rotation.W);
                var mat = System.Numerics.Matrix4x4.CreateFromQuaternion(q);
                mat.Translation  = new System.Numerics.Vector3(node.Position.X * scale, node.Position.Y * scale, node.Position.Z * scale);
                result.Transform = mat.ToAssimp4x4();

                allNodes.Add(result);
            }

            for (int i = 0; i < model.Nodes.Count; i++)
            {
                var node = model.Nodes[i];
                if (node.ParentIndex >= 0)
                {
                    allNodes[node.ParentIndex].Children.Add(allNodes[i]);
                }
                else
                {
                    scene.RootNode.Children.Add(allNodes[i]);
                }
            }
            #endregion

            var meshLookup = new List <int>();

            #region Meshes
            for (int i = 0; i < model.Meshes.Count; i++)
            {
                var geom = model.Meshes[i];
                if (geom.Submeshes.Count == 0)
                {
                    meshLookup.Add(-1);
                    continue;
                }

                meshLookup.Add(scene.MeshCount);

                foreach (var sub in geom.Submeshes)
                {
                    var m = new Assimp.Mesh($"mesh{i:D3}");

                    var indices = geom.Indicies.Skip(sub.IndexStart).Take(sub.IndexLength);

                    var minIndex  = indices.Min();
                    var maxIndex  = indices.Max();
                    var vertCount = maxIndex - minIndex + 1;

                    if (geom.IndexFormat == IndexFormat.TriangleStrip)
                    {
                        indices = indices.Unstrip();
                    }

                    indices = indices.Select(x => x - minIndex);
                    var vertices = geom.Vertices.Skip(minIndex).Take(vertCount);

                    if (geom.BoundsIndex >= 0)
                    {
                        vertices = vertices.Select(v => (IVertex) new CompressedVertex(v, model.Bounds[geom.BoundsIndex.Value]));
                    }

                    int vIndex     = -1;
                    var boneLookup = new Dictionary <int, Assimp.Bone>();
                    foreach (var v in vertices)
                    {
                        vIndex++;

                        if (v.Position.Count > 0)
                        {
                            m.Vertices.Add(v.Position[0].ToAssimp3D(scale));

                            //some Halo shaders use position W as the colour alpha - add it to a colour channel to preserve it
                            //also assimp appears to have issues exporting obj when a colour channel exists so only do this for collada
                            if (formatId == "collada" && v.Color.Count == 0 && !float.IsNaN(v.Position[0].W))
                            {
                                m.VertexColorChannels[0].Add(new Assimp.Color4D {
                                    R = v.Position[0].W
                                });
                            }
                        }

                        if (v.Normal.Count > 0)
                        {
                            m.Normals.Add(v.Normal[0].ToAssimp3D());
                        }

                        if (v.TexCoords.Count > 0)
                        {
                            m.TextureCoordinateChannels[0].Add(v.TexCoords[0].ToAssimpUV());
                        }

                        if (geom.VertexWeights == VertexWeights.None && !geom.NodeIndex.HasValue)
                        {
                            continue;
                        }

                        #region Vertex Weights
                        var weights = new List <Tuple <int, float> >(4);

                        if (geom.NodeIndex.HasValue)
                        {
                            weights.Add(Tuple.Create <int, float>(geom.NodeIndex.Value, 1));
                        }
                        else if (geom.VertexWeights == VertexWeights.Skinned)
                        {
                            var ind = v.BlendIndices[0];
                            var wt  = v.BlendWeight[0];

                            if (wt.X > 0)
                            {
                                weights.Add(Tuple.Create((int)ind.X, wt.X));
                            }
                            if (wt.Y > 0)
                            {
                                weights.Add(Tuple.Create((int)ind.Y, wt.Y));
                            }
                            if (wt.Z > 0)
                            {
                                weights.Add(Tuple.Create((int)ind.Z, wt.Z));
                            }
                            if (wt.W > 0)
                            {
                                weights.Add(Tuple.Create((int)ind.W, wt.W));
                            }
                        }

                        foreach (var val in weights)
                        {
                            Assimp.Bone b;
                            if (boneLookup.ContainsKey(val.Item1))
                            {
                                b = boneLookup[val.Item1];
                            }
                            else
                            {
                                var t = model.Nodes[val.Item1].OffsetTransform;
                                t.M41 *= scale;
                                t.M42 *= scale;
                                t.M43 *= scale;

                                b = new Assimp.Bone
                                {
                                    Name         = bonePrefix + model.Nodes[val.Item1].Name,
                                    OffsetMatrix = t.ToAssimp4x4()
                                };

                                m.Bones.Add(b);
                                boneLookup.Add(val.Item1, b);
                            }

                            b.VertexWeights.Add(new Assimp.VertexWeight(vIndex, val.Item2));
                        }
                        #endregion
                    }

                    m.SetIndices(indices.ToArray(), 3);
                    m.MaterialIndex = sub.MaterialIndex;

                    scene.Meshes.Add(m);
                }
            }
            #endregion

            #region Regions
            foreach (var reg in model.Regions)
            {
                var regNode = new Assimp.Node($"{geomPrefix}{reg.Name}");
                foreach (var perm in reg.Permutations)
                {
                    var meshStart = meshLookup[perm.MeshIndex];
                    if (meshStart < 0)
                    {
                        continue;
                    }

                    var permNode = new Assimp.Node($"{geomPrefix}{perm.Name}");
                    if (perm.TransformScale != 1 || !perm.Transform.IsIdentity)
                    {
                        permNode.Transform = Assimp.Matrix4x4.FromScaling(new Assimp.Vector3D(perm.TransformScale)) * perm.Transform.ToAssimp4x4(scale);
                    }

                    var meshCount = Enumerable.Range(perm.MeshIndex, perm.MeshCount).Sum(i => model.Meshes[i].Submeshes.Count);
                    permNode.MeshIndices.AddRange(Enumerable.Range(meshStart, meshCount));

                    regNode.Children.Add(permNode);
                }

                if (regNode.ChildCount > 0)
                {
                    scene.RootNode.Children.Add(regNode);
                }
            }
            #endregion

            #region Materials
            foreach (var mat in model.Materials)
            {
                var m = new Assimp.Material {
                    Name = mat?.Name ?? "unused"
                };

                //prevent max from making every material super shiny
                m.ColorEmissive = m.ColorReflective = m.ColorSpecular = new Assimp.Color4D(0, 0, 0, 1);
                m.ColorDiffuse  = m.ColorTransparent = new Assimp.Color4D(1);

                //max only seems to care about diffuse
                var dif = mat?.Submaterials.FirstOrDefault(s => s.Usage == MaterialUsage.Diffuse);
                if (dif != null)
                {
                    var suffix   = dif.Bitmap.SubmapCount > 1 ? "[0]" : string.Empty;
                    var filePath = $"{dif.Bitmap.Name}{suffix}.{ModelViewerPlugin.Settings.MaterialExtension}";

                    //collada spec says it requires URI formatting, and Assimp doesn't do it for us
                    //for some reason "new Uri(filePath, UriKind.Relative)" doesnt change the slashes, have to use absolute uri
                    if (formatId == FormatId.Collada)
                    {
                        filePath = new Uri("X:\\", UriKind.Absolute).MakeRelativeUri(new Uri(System.IO.Path.Combine("X:\\", filePath))).ToString();
                    }

                    m.TextureDiffuse = new Assimp.TextureSlot
                    {
                        BlendFactor = 1,
                        FilePath    = filePath,
                        TextureType = Assimp.TextureType.Diffuse
                    };
                }

                scene.Materials.Add(m);
            }
            #endregion

            return(scene);
        }
Пример #2
0
        public static Assimp.Scene ToAssimpScene(RWScene scene)
        {
            Assimp.Scene aiScene = new Assimp.Scene();

            int drawCallIdx = 0;
            int materialIdx = 0;
            int totalSplitIdx = 0;
            List<int> meshStartIndices = new List<int>();
            foreach (RWDrawCall drawCall in scene.DrawCalls)
            {
                meshStartIndices.Add(totalSplitIdx);
                var mesh = scene.Meshes[drawCall.MeshIndex];
                var node = scene.Nodes[drawCall.NodeIndex];

                int splitIdx = 0;
                foreach (RWMeshMaterialSplit split in mesh.MaterialSplitData.MaterialSplits)
                {
                    Assimp.Mesh aiMesh = new Assimp.Mesh(Assimp.PrimitiveType.Triangle);
                    aiMesh.Name = string.Format("DrawCall{0}_Split{1}", drawCallIdx.ToString("00"), splitIdx.ToString("00"));
                    aiMesh.MaterialIndex = split.MaterialIndex + materialIdx;

                    // get split indices
                    int[] indices = split.Indices;
                    if (mesh.MaterialSplitData.PrimitiveType == RWPrimitiveType.TriangleStrip)
                        indices = MeshUtilities.ToTriangleList(indices, true);

                    // pos & nrm
                    for (int i = 0; i < indices.Length; i++)
                    {
                        if (mesh.HasVertices)
                        {
                            var vert = Vector3.Transform(mesh.Vertices[indices[i]], node.WorldTransform);
                            aiMesh.Vertices.Add(vert.ToAssimpVector3D());
                        }
                        if (mesh.HasNormals)
                        {
                            var nrm = Vector3.TransformNormal(mesh.Normals[indices[i]], node.WorldTransform);
                            aiMesh.Normals.Add(nrm.ToAssimpVector3D());
                        }
                    }

                    // tex coords
                    if (mesh.HasTexCoords)
                    {
                        for (int i = 0; i < mesh.TextureCoordinateChannelCount; i++)
                        {
                            List<Assimp.Vector3D> texCoordChannel = new List<Assimp.Vector3D>();

                            for (int j = 0; j < indices.Length; j++)
                            {
                                texCoordChannel.Add(mesh.TextureCoordinateChannels[i][indices[j]].ToAssimpVector3D(0));
                            }

                            aiMesh.TextureCoordinateChannels[i] = texCoordChannel;
                        }
                    }

                    // colors
                    if (mesh.HasColors)
                    {
                        List<Assimp.Color4D> vertColorChannel = new List<Assimp.Color4D>();

                        for (int i = 0; i < indices.Length; i++)
                        {
                            var color = mesh.Colors[indices[i]];
                            vertColorChannel.Add(new Assimp.Color4D(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f));
                        }

                        aiMesh.VertexColorChannels[0] = vertColorChannel;
                    }

                    // generate temporary face indices
                    int[] tempIndices = new int[aiMesh.VertexCount];
                    for (int i = 0; i < aiMesh.VertexCount; i++)
                        tempIndices[i] = i;

                    aiMesh.SetIndices(tempIndices, 3);

                    // add the mesh to the list
                    aiScene.Meshes.Add(aiMesh);

                    splitIdx++;
                }

                totalSplitIdx += splitIdx;

                foreach (RWMaterial mat in mesh.Materials)
                {
                    Assimp.Material aiMaterial = new Assimp.Material();
                    aiMaterial.AddProperty(new Assimp.MaterialProperty(Assimp.Unmanaged.AiMatKeys.NAME, "Material" + (materialIdx++).ToString("00")));

                    if (mat.IsTextured)
                    {
                        aiMaterial.AddProperty(new Assimp.MaterialProperty(Assimp.Unmanaged.AiMatKeys.TEXTURE_BASE, mat.TextureReference.ReferencedTextureName + ".png", Assimp.TextureType.Diffuse, 0));
                    }

                    aiScene.Materials.Add(aiMaterial);
                }

                drawCallIdx++;
            }

            // store node lookup
            Dictionary<RWSceneNode, Assimp.Node> nodeLookup = new Dictionary<RWSceneNode, Assimp.Node>();

            // first create the root node
            var rootNode = new Assimp.Node("SceneRoot");
            rootNode.Transform = scene.Nodes[0].Transform.ToAssimpMatrix4x4();
            nodeLookup.Add(scene.Nodes[0], rootNode);

            for (int i = 1; i < scene.Nodes.Count - 1; i++)
            {
                var node = scene.Nodes[i];
                string name = node.BoneMetadata.BoneNameID.ToString();

                var aiNode = new Assimp.Node(name);
                aiNode.Transform = node.Transform.ToAssimpMatrix4x4();

                // get the associated meshes for this node
                var drawCalls = scene.DrawCalls.FindAll(dc => dc.NodeIndex == i);
                foreach (var drawCall in drawCalls)
                {
                    for (int j = 0; j < scene.Meshes[drawCall.MeshIndex].MaterialCount; j++)
                    {
                        aiNode.MeshIndices.Add(meshStartIndices[scene.DrawCalls.IndexOf(drawCall)] + j);
                    }
                }

                nodeLookup[node.Parent].Children.Add(aiNode);
                nodeLookup.Add(node, aiNode);
            }

            aiScene.RootNode = rootNode;

            return aiScene;
        }