public void TestImportFromStream() { String path = Path.Combine(TestHelper.RootPath, "TestFiles/duck.dae"); FileStream fs = File.OpenRead(path); AssimpContext importer = new AssimpContext(); LogStream.IsVerboseLoggingEnabled = true; LogStream logstream = new LogStream(delegate(String msg, String userData) { Console.WriteLine(msg); }); logstream.Attach(); Scene scene = importer.ImportFileFromStream(fs, ".dae"); fs.Close(); Assert.IsNotNull(scene); Assert.IsTrue((scene.SceneFlags & SceneFlags.Incomplete) != SceneFlags.Incomplete); }
public void TestImportFromStream() { String path = Path.Combine(TestHelper.RootPath, "TestFiles\\duck.dae"); FileStream fs = File.OpenRead(path); AssimpContext importer = new AssimpContext(); LogStream.IsVerboseLoggingEnabled = true; LogStream logstream = new LogStream(delegate(String msg, String userData) { Console.WriteLine(msg); }); logstream.Attach(); Scene scene = importer.ImportFileFromStream(fs, ".dae"); fs.Close(); Assert.IsNotNull(scene); Assert.IsTrue((scene.SceneFlags & SceneFlags.Incomplete) != SceneFlags.Incomplete); }
private void Init( GraphicsDevice gd, ResourceFactory factory, Stream stream, string extension, VertexElementSemantic[] elementSemantics, ModelCreateInfo?createInfo, PostProcessSteps flags = DefaultPostProcessSteps) { // Load file AssimpContext assimpContext = new AssimpContext(); Scene pScene = assimpContext.ImportFileFromStream(stream, DefaultPostProcessSteps, extension); parts.Clear(); parts.Count = (uint)pScene.Meshes.Count; Vector3 scale = new Vector3(1.0f); Vector2 uvscale = new Vector2(1.0f); Vector3 center = new Vector3(0.0f); if (createInfo != null) { scale = createInfo.Value.Scale; uvscale = createInfo.Value.UVScale; center = createInfo.Value.Center; } RawList <float> vertices = new RawList <float>(); RawList <uint> indices = new RawList <uint>(); VertexCount = 0; IndexCount = 0; // Load meshes for (int i = 0; i < pScene.Meshes.Count; i++) { var paiMesh = pScene.Meshes[i]; parts[i] = new ModelPart(); parts[i].vertexBase = VertexCount; parts[i].indexBase = IndexCount; VertexCount += (uint)paiMesh.VertexCount; var pColor = pScene.Materials[paiMesh.MaterialIndex].ColorDiffuse; Vector3D Zero3D = new Vector3D(0.0f, 0.0f, 0.0f); for (int j = 0; j < paiMesh.VertexCount; j++) { Vector3D pPos = paiMesh.Vertices[j]; Vector3D pNormal = paiMesh.Normals[j]; Vector3D pTexCoord = paiMesh.HasTextureCoords(0) ? paiMesh.TextureCoordinateChannels[0][j] : Zero3D; Vector3D pTangent = paiMesh.HasTangentBasis ? paiMesh.Tangents[j] : Zero3D; Vector3D pBiTangent = paiMesh.HasTangentBasis ? paiMesh.BiTangents[j] : Zero3D; foreach (VertexElementSemantic component in elementSemantics) { switch (component) { case VertexElementSemantic.Position: vertices.Add(pPos.X * scale.X + center.X); vertices.Add(-pPos.Y * scale.Y + center.Y); vertices.Add(pPos.Z * scale.Z + center.Z); break; case VertexElementSemantic.Normal: vertices.Add(pNormal.X); vertices.Add(-pNormal.Y); vertices.Add(pNormal.Z); break; case VertexElementSemantic.TextureCoordinate: vertices.Add(pTexCoord.X * uvscale.X); vertices.Add(pTexCoord.Y * uvscale.Y); break; case VertexElementSemantic.Color: vertices.Add(pColor.R); vertices.Add(pColor.G); vertices.Add(pColor.B); break; default: throw new System.NotImplementedException(); } ; } dim.Max.X = Math.Max(pPos.X, dim.Max.X); dim.Max.Y = Math.Max(pPos.Y, dim.Max.Y); dim.Max.Z = Math.Max(pPos.Z, dim.Max.Z); dim.Min.X = Math.Min(pPos.X, dim.Min.X); dim.Min.Y = Math.Min(pPos.Y, dim.Min.Y); dim.Min.Z = Math.Min(pPos.Z, dim.Min.Z); } dim.Size = dim.Max - dim.Min; parts[i].vertexCount = (uint)paiMesh.VertexCount; uint indexBase = indices.Count; for (uint j = 0; j < paiMesh.FaceCount; j++) { Face Face = paiMesh.Faces[(int)j]; if (Face.IndexCount != 3) { continue; } indices.Add(indexBase + (uint)Face.Indices[0]); indices.Add(indexBase + (uint)Face.Indices[1]); indices.Add(indexBase + (uint)Face.Indices[2]); parts[i].indexCount += 3; IndexCount += 3; } } uint vBufferSize = (vertices.Count) * sizeof(float); uint iBufferSize = (indices.Count) * sizeof(uint); VertexBuffer = factory.CreateBuffer(new BufferDescription(vBufferSize, BufferUsage.VertexBuffer)); IndexBuffer = factory.CreateBuffer(new BufferDescription(iBufferSize, BufferUsage.IndexBuffer)); gd.UpdateBuffer(VertexBuffer, 0, ref vertices[0], vBufferSize); gd.UpdateBuffer(IndexBuffer, 0, ref indices[0], iBufferSize); }
public override void LoadAndWriteToStream(FileInfo inputFile, IAssetMeta meta, Stream outputStream) { PostProcessSteps assimpFlags = PostProcessSteps.FlipWindingOrder | PostProcessSteps.Triangulate | PostProcessSteps.PreTransformVertices | PostProcessSteps.GenerateUVCoords | PostProcessSteps.GenerateSmoothNormals | PostProcessSteps.FlipUVs ; var context = new AssimpContext(); context.SetConfig(new FloatPropertyConfig("AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE", 80f)); string extension = inputFile.Extension; using FileStream fs = inputFile.OpenRead(); var scene = context.ImportFileFromStream(fs, assimpFlags, extension); // Generate vertex buffer from ASSIMP scene data float scale = 1.0f; MeshData newMesh = new MeshData(); newMesh.subMeshes = new SubMeshData[scene.MeshCount]; for (int m = 0; m < scene.MeshCount; m++) { Mesh mesh = scene.Meshes[m]; Vertex[] vertices = new Vertex[mesh.VertexCount]; Face[] faces = mesh.Faces.Where(x => x.IndexCount == 3).ToArray(); // Remove any degenerate faces UInt32[] indices = new UInt32[faces.Length * 3]; //DebugHelper.AssertThrow<OverflowException>(faces.Length * 3 <= UInt32.MaxValue); for (int v = 0; v < mesh.VertexCount; v++) { Vertex vertex; vertex.position = new vec3(mesh.Vertices[v].X, mesh.Vertices[v].Y, mesh.Vertices[v].Z) * scale; vertex.normal = new vec3(mesh.Normals[v].X, mesh.Normals[v].Y, mesh.Normals[v].Z); if (mesh.HasTextureCoords(0)) { vertex.uv0 = new vec2(mesh.TextureCoordinateChannels[0][v].X, mesh.TextureCoordinateChannels[0][v].Y); } else { vertex.uv0 = vec2.Zero; } vertices[v] = vertex; } for (int i = 0; i < faces.Length; i++) { indices[i * 3 + 0] = (UInt16)faces[i].Indices[0]; indices[i * 3 + 1] = (UInt16)faces[i].Indices[1]; indices[i * 3 + 2] = (UInt16)faces[i].Indices[2]; } newMesh.subMeshes[m] = new SubMeshData(vertices, indices); } //Write mesh to stream using BinaryWriter writer = new BinaryWriter(outputStream, Encoding.UTF8, true); writer.Write(newMesh.subMeshes.Length); foreach (SubMeshData subMesh in newMesh.subMeshes) { writer.Write(subMesh.vertices.Length); Span <byte> verts = new Span <Vertex>(subMesh.vertices).Cast <Vertex, byte>(); writer.Write(verts); writer.Write(subMesh.indices.Length); Span <uint> indsU = subMesh.indices; var inds = indsU.Cast <UInt32, byte>(); writer.Write(inds); } writer.Flush(); }
public unsafe override ProcessedModel ProcessT(Stream stream, string extension) { AssimpContext ac = new AssimpContext(); Scene scene = ac.ImportFileFromStream( stream, PostProcessSteps.FlipWindingOrder | PostProcessSteps.GenerateNormals | PostProcessSteps.FlipUVs, extension); aiMatrix4x4 rootNodeInverseTransform = scene.RootNode.Transform; rootNodeInverseTransform.Inverse(); List <ProcessedMeshPart> parts = new List <ProcessedMeshPart>(); List <ProcessedAnimation> animations = new List <ProcessedAnimation>(); HashSet <string> encounteredNames = new HashSet <string>(); for (int meshIndex = 0; meshIndex < scene.MeshCount; meshIndex++) { Mesh mesh = scene.Meshes[meshIndex]; string meshName = mesh.Name; if (string.IsNullOrEmpty(meshName)) { meshName = $"mesh_{meshIndex}"; } int counter = 1; while (!encounteredNames.Add(meshName)) { meshName = mesh.Name + "_" + counter.ToString(); counter += 1; } int vertexCount = mesh.VertexCount; int positionOffset = 0; int normalOffset = 12; int texCoordsOffset = -1; int boneWeightOffset = -1; int boneIndicesOffset = -1; List <VertexElementDescription> elementDescs = new List <VertexElementDescription>(); elementDescs.Add(new VertexElementDescription("Position", VertexElementSemantic.Position, VertexElementFormat.Float3)); elementDescs.Add(new VertexElementDescription("Normal", VertexElementSemantic.Normal, VertexElementFormat.Float3)); normalOffset = 12; int vertexSize = 24; bool hasTexCoords = mesh.HasTextureCoords(0); elementDescs.Add(new VertexElementDescription("TexCoords", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float2)); texCoordsOffset = vertexSize; vertexSize += 8; bool hasBones = mesh.HasBones; if (hasBones) { elementDescs.Add(new VertexElementDescription("BoneWeights", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float4)); elementDescs.Add(new VertexElementDescription("BoneIndices", VertexElementSemantic.TextureCoordinate, VertexElementFormat.UInt4)); boneWeightOffset = vertexSize; vertexSize += 16; boneIndicesOffset = vertexSize; vertexSize += 16; } byte[] vertexData = new byte[vertexCount * vertexSize]; VertexDataBuilder builder = new VertexDataBuilder(vertexData, vertexSize); Vector3 min = vertexCount > 0 ? mesh.Vertices[0].ToSystemVector3() : Vector3.Zero; Vector3 max = vertexCount > 0 ? mesh.Vertices[0].ToSystemVector3() : Vector3.Zero; for (int i = 0; i < vertexCount; i++) { Vector3 position = mesh.Vertices[i].ToSystemVector3(); min = Vector3.Min(min, position); max = Vector3.Max(max, position); builder.WriteVertexElement( i, positionOffset, position); Vector3 normal = mesh.Normals[i].ToSystemVector3(); builder.WriteVertexElement(i, normalOffset, normal); if (mesh.HasTextureCoords(0)) { builder.WriteVertexElement( i, texCoordsOffset, new Vector2(mesh.TextureCoordinateChannels[0][i].X, mesh.TextureCoordinateChannels[0][i].Y)); } else { builder.WriteVertexElement( i, texCoordsOffset, new Vector2()); } } List <int> indices = new List <int>(); foreach (Face face in mesh.Faces) { if (face.IndexCount == 3) { indices.Add(face.Indices[0]); indices.Add(face.Indices[1]); indices.Add(face.Indices[2]); } } Dictionary <string, uint> boneIDsByName = new Dictionary <string, uint>(); System.Numerics.Matrix4x4[] boneOffsets = new System.Numerics.Matrix4x4[mesh.BoneCount]; if (hasBones) { Dictionary <int, int> assignedBoneWeights = new Dictionary <int, int>(); for (uint boneID = 0; boneID < mesh.BoneCount; boneID++) { Bone bone = mesh.Bones[(int)boneID]; string boneName = bone.Name; int suffix = 1; while (boneIDsByName.ContainsKey(boneName)) { boneName = bone.Name + "_" + suffix.ToString(); suffix += 1; } boneIDsByName.Add(boneName, boneID); foreach (VertexWeight weight in bone.VertexWeights) { int relativeBoneIndex = GetAndIncrementRelativeBoneIndex(assignedBoneWeights, weight.VertexID); builder.WriteVertexElement(weight.VertexID, boneIndicesOffset + (relativeBoneIndex * sizeof(uint)), boneID); builder.WriteVertexElement(weight.VertexID, boneWeightOffset + (relativeBoneIndex * sizeof(float)), weight.Weight); } System.Numerics.Matrix4x4 offsetMat = bone.OffsetMatrix.ToSystemMatrixTransposed(); System.Numerics.Matrix4x4.Decompose(offsetMat, out var scale, out var rot, out var trans); offsetMat = System.Numerics.Matrix4x4.CreateScale(scale) * System.Numerics.Matrix4x4.CreateFromQuaternion(rot) * System.Numerics.Matrix4x4.CreateTranslation(trans); boneOffsets[boneID] = offsetMat; } } builder.FreeGCHandle(); uint indexCount = (uint)indices.Count; int[] int32Indices = indices.ToArray(); byte[] indexData = new byte[indices.Count * sizeof(uint)]; fixed(byte *indexDataPtr = indexData) { fixed(int *int32Ptr = int32Indices) { Buffer.MemoryCopy(int32Ptr, indexDataPtr, indexData.Length, indexData.Length); } } ProcessedMeshPart part = new ProcessedMeshPart( vertexData, elementDescs.ToArray(), indexData, IndexFormat.UInt32, (uint)indices.Count, boneIDsByName, boneOffsets); parts.Add(part); } // Nodes Node rootNode = scene.RootNode; List <ProcessedNode> processedNodes = new List <ProcessedNode>(); ConvertNode(rootNode, -1, processedNodes); ProcessedNodeSet nodes = new ProcessedNodeSet(processedNodes.ToArray(), 0, rootNodeInverseTransform.ToSystemMatrixTransposed()); for (int animIndex = 0; animIndex < scene.AnimationCount; animIndex++) { Animation animation = scene.Animations[animIndex]; Dictionary <string, ProcessedAnimationChannel> channels = new Dictionary <string, ProcessedAnimationChannel>(); for (int channelIndex = 0; channelIndex < animation.NodeAnimationChannelCount; channelIndex++) { NodeAnimationChannel nac = animation.NodeAnimationChannels[channelIndex]; channels[nac.NodeName] = ConvertChannel(nac); } string baseAnimName = animation.Name; if (string.IsNullOrEmpty(baseAnimName)) { baseAnimName = "anim_" + animIndex; } string animationName = baseAnimName; int counter = 1; while (!encounteredNames.Add(animationName)) { animationName = baseAnimName + "_" + counter.ToString(); counter += 1; } } return(new ProcessedModel() { MeshParts = parts.ToArray(), Animations = animations.ToArray(), Nodes = nodes }); }
protected override void CreateResources(ResourceFactory factory) { _projectionBuffer = factory.CreateBuffer(new BufferDescription(64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); _viewBuffer = factory.CreateBuffer(new BufferDescription(64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); _worldBuffer = factory.CreateBuffer(new BufferDescription(64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); Matrix4x4 worldMatrix = Matrix4x4.CreateTranslation(0, 15000, -5000) * Matrix4x4.CreateRotationX(3 * (float)Math.PI / 2) * Matrix4x4.CreateScale(0.05f); GraphicsDevice.UpdateBuffer(_worldBuffer, 0, ref worldMatrix); ResourceLayout layout = factory.CreateResourceLayout(new ResourceLayoutDescription( new ResourceLayoutElementDescription("Projection", ResourceKind.UniformBuffer, ShaderStages.Vertex), new ResourceLayoutElementDescription("View", ResourceKind.UniformBuffer, ShaderStages.Vertex), new ResourceLayoutElementDescription("World", ResourceKind.UniformBuffer, ShaderStages.Vertex), new ResourceLayoutElementDescription("Bones", ResourceKind.UniformBuffer, ShaderStages.Vertex), new ResourceLayoutElementDescription("SurfaceTex", ResourceKind.TextureReadOnly, ShaderStages.Fragment), new ResourceLayoutElementDescription("SurfaceSampler", ResourceKind.Sampler, ShaderStages.Fragment))); Texture texture; using (Stream ktxStream = OpenEmbeddedAssetStream("goblin_bc3_unorm.ktx")) { texture = KtxFile.LoadTexture( GraphicsDevice, factory, ktxStream, PixelFormat.BC3_UNorm); } _texView = ResourceFactory.CreateTextureView(texture); VertexLayoutDescription vertexLayouts = new VertexLayoutDescription( new[] { new VertexElementDescription("Position", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float3), new VertexElementDescription("UV", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float2), new VertexElementDescription("BoneWeights", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float4), new VertexElementDescription("BoneIndices", VertexElementSemantic.TextureCoordinate, VertexElementFormat.UInt4), }); GraphicsPipelineDescription gpd = new GraphicsPipelineDescription( BlendStateDescription.SingleOverrideBlend, DepthStencilStateDescription.DepthOnlyLessEqual, new RasterizerStateDescription(FaceCullMode.Back, PolygonFillMode.Solid, FrontFace.CounterClockwise, true, false), PrimitiveTopology.TriangleList, new ShaderSetDescription( new[] { vertexLayouts }, factory.CreateFromSpirv( new ShaderDescription(ShaderStages.Vertex, Encoding.UTF8.GetBytes(VertexCode), "main"), new ShaderDescription(ShaderStages.Fragment, Encoding.UTF8.GetBytes(FragmentCode), "main"))), layout, GraphicsDevice.SwapchainFramebuffer.OutputDescription); _pipeline = factory.CreateGraphicsPipeline(ref gpd); AssimpContext ac = new AssimpContext(); using (Stream modelStream = OpenEmbeddedAssetStream("goblin.dae")) { _scene = ac.ImportFileFromStream(modelStream, "dae"); } _rootNodeInverseTransform = _scene.RootNode.Transform; _rootNodeInverseTransform.Inverse(); _firstMesh = _scene.Meshes[0]; AnimatedVertex[] vertices = new AnimatedVertex[_firstMesh.VertexCount]; for (int i = 0; i < vertices.Length; i++) { vertices[i].Position = new Vector3(_firstMesh.Vertices[i].X, _firstMesh.Vertices[i].Y, _firstMesh.Vertices[i].Z); vertices[i].UV = new Vector2(_firstMesh.TextureCoordinateChannels[0][i].X, _firstMesh.TextureCoordinateChannels[0][i].Y); } _animation = _scene.Animations[0]; List <int> indices = new List <int>(); foreach (Face face in _firstMesh.Faces) { if (face.IndexCount == 3) { indices.Add(face.Indices[0]); indices.Add(face.Indices[1]); indices.Add(face.Indices[2]); } } for (uint boneID = 0; boneID < _firstMesh.BoneCount; boneID++) { Bone bone = _firstMesh.Bones[(int)boneID]; _boneIDsByName.Add(bone.Name, boneID); foreach (VertexWeight weight in bone.VertexWeights) { vertices[weight.VertexID].AddBone(boneID, weight.Weight); } } Array.Resize(ref _boneTransformations, _firstMesh.BoneCount); _bonesBuffer = ResourceFactory.CreateBuffer(new BufferDescription( 64 * 64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); _rs = factory.CreateResourceSet(new ResourceSetDescription(layout, _projectionBuffer, _viewBuffer, _worldBuffer, _bonesBuffer, _texView, GraphicsDevice.Aniso4xSampler)); _indexCount = (uint)indices.Count; _vertexBuffer = ResourceFactory.CreateBuffer(new BufferDescription( (uint)(vertices.Length * Unsafe.SizeOf <AnimatedVertex>()), BufferUsage.VertexBuffer)); GraphicsDevice.UpdateBuffer(_vertexBuffer, 0, vertices); _indexBuffer = ResourceFactory.CreateBuffer(new BufferDescription( _indexCount * 4, BufferUsage.IndexBuffer)); GraphicsDevice.UpdateBuffer(_indexBuffer, 0, indices.ToArray()); _cl = factory.CreateCommandList(); _camera.Position = new Vector3(110, -87, -532); _camera.Yaw = 0.45f; _camera.Pitch = -0.55f; _camera.MoveSpeed = 1000f; _camera.FarDistance = 100000; }
/// <summary> /// Generate LiveMesh instance from FBX data stream /// </summary> /// <param name="stream">FBX data to read</param> /// <param name="boneRegexs">Optional bone regex mappings</param> /// <param name="attachPointRegexs">Optional attach point regex mappings</param> /// <param name="debugLog">Debug log output function</param> /// <returns>LiveMesh instance</returns> public static LiveMesh GenerateLiveMesh(Stream stream, Dictionary <string, BoneType> boneRegexs = null, Dictionary <string, AttachPointType> attachPointRegexs = null, Action <string> debugLog = null) { // Relevant assimp documentation can be found here: // http://assimp.sourceforge.net/lib_html/data.html Scene scene; // API goes into native code to load FBX using (var ctx = new AssimpContext()) scene = ctx.ImportFileFromStream(stream, PostProcessSteps.Triangulate, "fbx"); var bones = new Dictionary <Bone, int>(); var mBones = new List <Modeling.Bone>(); var uCount = 0; var uACount = 0; var attachPoints = new List <AttachPoint>(); var subMeshes = new LiveSubMesh[scene.MeshCount]; debugLog?.Invoke($"Sub-meshes: {scene.MeshCount}"); // Load sub-meshes for (var iSubMesh = 0; iSubMesh < subMeshes.Length; iSubMesh++) { // Define sub-mesh var subMesh = scene.Meshes[iSubMesh]; debugLog?.Invoke($"-Sub-mesh [{subMesh.Name}]"); var vertices = new float[subMesh.VertexCount * 3]; var uvs = new float[subMesh.VertexCount * 2]; var normals = new float[subMesh.VertexCount * 3]; var boneIds = new int[subMesh.VertexCount * 4]; var boneWeights = new float[subMesh.VertexCount * 4]; var boneCounts = new int[subMesh.VertexCount]; var triangles = new int[subMesh.FaceCount * 3]; subMeshes[iSubMesh] = new LiveSubMesh { Vertices = vertices, UVs = uvs, Normals = normals, BoneIds = boneIds, BoneWeights = boneWeights, Triangles = triangles, MaterialIdx = subMesh.MaterialIndex, VertexCount = subMesh.VertexCount }; // Get vertices debugLog?.Invoke($"--Vertices: {subMesh.VertexCount}"); for (var iVertex = 0; iVertex < subMesh.Vertices.Count; iVertex++) { vertices[iVertex * 3] = subMesh.Vertices[iVertex].X; vertices[iVertex * 3 + 1] = subMesh.Vertices[iVertex].Y; vertices[iVertex * 3 + 2] = subMesh.Vertices[iVertex].Z; } // Get UVs for (var iUv = 0; iUv < subMesh.TextureCoordinateChannels[0].Count; iUv++) { uvs[iUv * 2] = subMesh.TextureCoordinateChannels[0][iUv].X; uvs[iUv * 2 + 1] = subMesh.TextureCoordinateChannels[0][iUv].X; } // Get normals for (var iNormal = 0; iNormal < subMesh.Normals.Count; iNormal++) { normals[iNormal * 3] = subMesh.Normals[iNormal].X; normals[iNormal * 3 + 1] = subMesh.Normals[iNormal].Y; normals[iNormal * 3 + 2] = subMesh.Normals[iNormal].Z; } // Get triangles debugLog?.Invoke($"--Triangles: {subMesh.FaceCount}"); for (var iTriangle = 0; iTriangle < subMesh.FaceCount; iTriangle++) { var x = subMesh.Faces[iTriangle]; for (var i = 0; i < 3; i++) { triangles[3 * iTriangle + i] = x.Indices[i]; } } // Get bones debugLog?.Invoke($"--Bones: {subMesh.BoneCount}"); foreach (var bone in subMesh.Bones) { foreach (var vWeight in bone.VertexWeights) { var count = boneCounts[vWeight.VertexID]; if (count == 4) { continue; } boneIds[4 * vWeight.VertexID + count] = bones.TryGetValue(bone, out var bId) ? bId : bones.Count; boneWeights[4 * vWeight.VertexID + count] = vWeight.Weight; boneCounts[vWeight.VertexID]++; } // Skip bone if already processed if (bones.ContainsKey(bone)) { continue; } debugLog?.Invoke($"{{Bone [{bone.Name}]}}"); // Add bone var mBone = new Modeling.Bone { BoneName = bone.Name, BindPose = bone.OffsetMatrix.ToMatrix4x4() }; if (boneRegexs != null) { foreach (var entry in boneRegexs) { if (Regex.IsMatch(bone.Name, entry.Key)) { mBone.Type = entry.Value; uCount++; break; } } } bones.Add(bone, bones.Count); mBones.Add(mBone); // Add attach point if applicable if (attachPointRegexs == null) { continue; } foreach (var entry in attachPointRegexs) { if (Regex.IsMatch(bone.Name, entry.Key)) { attachPoints.Add(new AttachPoint { BoneName = bone.Name, BindPose = bone.OffsetMatrix.ToMatrix4x4Array(), Type = entry.Value }); uACount++; break; } } } } debugLog?.Invoke($"Total stored bones: {mBones.Count}"); debugLog?.Invoke($"Total BoneType-matched bones: {uCount}"); debugLog?.Invoke($"Total AttachPointType-matched bones: {uACount}"); return(new LiveMesh { Bones = mBones.ToArray(), DefaultAttachPoints = attachPoints.ToArray(), SubMeshes = subMeshes }); }
/// <summary> /// Generate LiveAnim instance from FBX data stream /// </summary> /// <param name="stream">FBX data to read</param> /// <param name="boneRegexs">Optional bone regex mappings</param> /// <param name="debugLog">Debug log output function</param> /// <returns>LiveAnim instance</returns> public static LiveAnim GenerateLiveAnim(Stream stream, Dictionary <string, BoneType> boneRegexs = null, Action <string> debugLog = null) { // Relevant assimp documentation can be found here: // http://assimp.sourceforge.net/lib_html/data.html Scene scene; // API goes into native code to load FBX using (var ctx = new AssimpContext()) scene = ctx.ImportFileFromStream(stream, PostProcessSteps.Triangulate, "fbx"); var bones = new Dictionary <string, int>(); var mBones = new List <Modeling.Bone>(); var uCount = 0; var subAnims = new List <LiveSubAnim>(); debugLog?.Invoke($"Sub-meshes: {scene.MeshCount}"); // Get bones foreach (var subMesh in scene.Meshes) { debugLog?.Invoke($"-Sub-mesh [{subMesh.Name}]"); debugLog?.Invoke($"--Bones: {subMesh.BoneCount}"); foreach (var bone in subMesh.Bones) { // Skip bone if already processed if (bones.ContainsKey(bone.Name)) { continue; } debugLog?.Invoke($"{{Bone [{bone.Name}]}}"); // Add bone var mBone = new Modeling.Bone { BoneName = bone.Name, BindPose = bone.OffsetMatrix.ToMatrix4x4() }; if (boneRegexs != null) { foreach (var entry in boneRegexs) { if (Regex.IsMatch(bone.Name, entry.Key)) { mBone.Type = entry.Value; uCount++; break; } } } bones.Add(bone.Name, bones.Count); mBones.Add(mBone); } } debugLog?.Invoke($"Total stored bones: {mBones.Count}"); debugLog?.Invoke($"Total BoneType-matched bones: {uCount}"); // ?? Potential: Add internal structure for full mesh hierarchy with bone toggles (sort of like suggested) foreach (var anim in scene.Animations) { debugLog?.Invoke($"-Animation [{anim.Name}]"); foreach (var chan in anim.NodeAnimationChannels) { debugLog?.Invoke($"--Channel [{chan.NodeName}]"); // Hacky name matching var nodeName = chan.NodeName; var idxAssimp = nodeName.IndexOf("_$AssimpFbx$_", StringComparison.Ordinal); if (idxAssimp != -1) { nodeName = nodeName.Substring(0, idxAssimp); } var sub = new LiveSubAnim { BoneId = bones[nodeName] }; if (chan.HasPositionKeys) { debugLog?.Invoke($"---Position keys: {chan.PositionKeyCount}"); sub.PositionCount = chan.PositionKeyCount; sub.PositionTimes = new float[chan.PositionKeyCount]; sub.PositionX = new float[chan.PositionKeyCount]; sub.PositionY = new float[chan.PositionKeyCount]; sub.PositionZ = new float[chan.PositionKeyCount]; for (var i = 0; i < chan.PositionKeyCount; i++) { sub.PositionTimes[i] = (float)chan.PositionKeys[i].Time; var vec = chan.PositionKeys[i].Value; sub.PositionX[i] = vec.X; sub.PositionY[i] = vec.Y; sub.PositionZ[i] = vec.Z; } } if (chan.HasRotationKeys) { debugLog?.Invoke($"---Rotation keys: {chan.RotationKeyCount}"); sub.RotationCount = chan.RotationKeyCount; sub.RotationTimes = new float[chan.RotationKeyCount]; sub.RotationW = new float[chan.PositionKeyCount]; sub.RotationX = new float[chan.PositionKeyCount]; sub.RotationY = new float[chan.PositionKeyCount]; sub.RotationZ = new float[chan.PositionKeyCount]; for (var i = 0; i < chan.RotationKeyCount; i++) { sub.RotationTimes[i] = (float)chan.RotationKeys[i].Time; var qua = chan.RotationKeys[i].Value; sub.RotationW[i] = qua.W; sub.RotationX[i] = qua.X; sub.RotationY[i] = qua.Y; sub.RotationZ[i] = qua.Z; } } if (chan.HasScalingKeys) { debugLog?.Invoke($"---Scaling keys: {chan.ScalingKeyCount}"); sub.ScalingCount = chan.ScalingKeyCount; sub.ScalingTimes = new float[chan.ScalingKeyCount]; sub.ScalingX = new float[chan.ScalingKeyCount]; sub.ScalingY = new float[chan.ScalingKeyCount]; sub.ScalingZ = new float[chan.ScalingKeyCount]; for (var i = 0; i < chan.ScalingKeyCount; i++) { sub.ScalingTimes[i] = (float)chan.ScalingKeys[i].Time; var sca = chan.ScalingKeys[i].Value; sub.ScalingX[i] = sca.X; sub.ScalingY[i] = sca.Y; sub.ScalingZ[i] = sca.Z; } } subAnims.Add(sub); } } debugLog?.Invoke($"Total stored animation channels: {subAnims.Count}"); return(new LiveAnim { Bones = mBones.ToArray(), BoneSubAnims = subAnims.ToArray() }); }
public static CustomModel Load(string path, PostProcessSteps ppSteps, bool loadFromExe = false, params PropertyConfig[] configs) { Log.Info($"Loading model: {path}"); var model = new CustomModel(); if (!File.Exists(path) && !loadFromExe) { Log.Error($"Model {path} does not exist."); return(model); } var importer = new AssimpContext(); if (configs != null) { foreach (var config in configs) { importer.SetConfig(config); } } try { Scene scene; if (loadFromExe) { var assembly = Assembly.GetAssembly(typeof(ModelLoader)); Stream stream = assembly.GetManifestResourceStream(ModelsPath + path); scene = importer.ImportFileFromStream(stream, ppSteps, Path.GetExtension(path)); stream.Dispose(); } else { scene = importer.ImportFile(path, ppSteps); } if (scene == null) { Log.Error($"Error loading model {path}. Scene was null."); return(model); } if (scene.Meshes.Count == 0) { Log.Error($"Error loading model {path}. No meshes found."); return(model); } if (scene.Meshes.Count > 1) { Log.Warn($"Model {path} containing more than one mesh. Using first mesh."); } var mesh = new Mesh(new List <float>(), new List <float>(), new List <float>(), new List <int>()); for (int i = 0; i < scene.MeshCount; i++) { mesh += ProcessMesh(scene.Meshes[i]); } var material = ProcessMaterial(scene.Materials[scene.Meshes[0].MaterialIndex], loadFromExe ? "" : Path.GetDirectoryName(Path.GetFullPath(path))); model.Mesh = mesh; model.Material = material; return(model); } catch (AssimpException e) { Log.Error("Assimp has thrown an exception.", e); Console.WriteLine(e.Message); return(model); } }
internal static GeoModel LoadModel(string filename, bool flipTextureCoordinates = false, AssemblyMode am = AssemblyMode.Internal) { AssimpContext importer = new AssimpContext(); importer.SetConfig(new VertexBoneWeightLimitConfig(KWEngine.MAX_BONE_WEIGHTS)); importer.SetConfig(new MaxBoneCountConfig(KWEngine.MAX_BONES)); FileType type = CheckFileEnding(filename); Scene scene = null; if (am != AssemblyMode.File) { if (type == FileType.Invalid) { throw new Exception("Model file has invalid type."); } string resourceName; Assembly assembly; if (am == AssemblyMode.Internal) { assembly = Assembly.GetExecutingAssembly(); resourceName = "KWEngine2.Assets.Models." + filename; } else { assembly = Assembly.GetEntryAssembly(); resourceName = assembly.GetName().Name + "." + filename; } using (Stream s = assembly.GetManifestResourceStream(resourceName)) { PostProcessSteps steps = PostProcessSteps.LimitBoneWeights | PostProcessSteps.Triangulate | PostProcessSteps.ValidateDataStructure | PostProcessSteps.GenerateUVCoords | PostProcessSteps.CalculateTangentSpace; if (filename != "kwcube.obj" && filename != "kwcube6.obj") { steps |= PostProcessSteps.JoinIdenticalVertices; } if (type == FileType.DirectX) { steps |= PostProcessSteps.FlipWindingOrder; } if (flipTextureCoordinates) { steps |= PostProcessSteps.FlipUVs; } scene = importer.ImportFileFromStream(s, steps); } } else { if (type != FileType.Invalid) { PostProcessSteps steps = PostProcessSteps.LimitBoneWeights | PostProcessSteps.Triangulate //| PostProcessSteps.FixInFacingNormals | PostProcessSteps.ValidateDataStructure | PostProcessSteps.GenerateUVCoords | PostProcessSteps.CalculateTangentSpace | PostProcessSteps.JoinIdenticalVertices ; if (type == FileType.DirectX) { steps |= PostProcessSteps.FlipWindingOrder; } if (flipTextureCoordinates) { steps |= PostProcessSteps.FlipUVs; } scene = importer.ImportFile(filename, steps); } else { throw new Exception("Could not load model: only OBJ, DAE, FBX and X are supported (GLTF support coming soon)."); } } if (scene == null) { throw new Exception("Could not load or find model: " + filename); } GeoModel model = ProcessScene(scene, am == AssemblyMode.File ? filename.ToLower().Trim() : filename, am); return(model); }
protected override void CreateInternal(ReadOnlyMemory <byte> data) { var str = new ReadOnlyLinkedMemoryStream(); str.AddMemory(data); _assContext ??= new AssimpContext(); Scene scene = _assContext.ImportFileFromStream(str, PostProcessSteps.Triangulate | PostProcessSteps.FlipUVs | PostProcessSteps.OptimizeGraph | PostProcessSteps.OptimizeMeshes); var embeddedTextures = new List <Texture>(); for (var i = 0; i < scene.TextureCount; i++) { EmbeddedTexture assTexture = scene.Textures[i]; var embeddedTexture = new TextureAsset(); embeddedTexture.Create(assTexture.CompressedData); embeddedTextures.Add(embeddedTexture.Texture); } _materials = new List <MeshMaterial>(); for (var i = 0; i < scene.MaterialCount; i++) { Material material = scene.Materials[i]; Color4D diffColor = material.ColorDiffuse; bool embeddedTexture = material.HasTextureDiffuse && embeddedTextures.Count > material.TextureDiffuse.TextureIndex; var emotionMaterial = new MeshMaterial { Name = material.Name, DiffuseColor = new Color(new Vector4(diffColor.R, diffColor.G, diffColor.B, diffColor.A)), DiffuseTextureName = embeddedTexture ? $"EmbeddedTexture{material.TextureDiffuse.TextureIndex}" : null, DiffuseTexture = embeddedTexture ? embeddedTextures[material.TextureDiffuse.TextureIndex] : null }; _materials.Add(emotionMaterial); } _animations = new List <SkeletalAnimation>(); ProcessAnimations(scene); _meshes = new List <Mesh>(); Node rootNode = scene.RootNode; SkeletonAnimRigNode animRigRoot = ProcessNode(scene, rootNode); animRigRoot.LocalTransform *= Matrix4x4.CreateRotationX(-90 * Maths.DEG2_RAD); // Convert to right handed Z is up. Entity = new MeshEntity { Name = Name, Meshes = _meshes.ToArray(), Animations = _animations.ToArray(), AnimationRig = animRigRoot }; object scaleData = scene.RootNode.Metadata.GetValueOrDefault("UnitScaleFactor").Data; var scaleF = 1f; if (scaleData is float f) { scaleF = f; } Entity.Scale = scaleF; }