protected override void OnLoad(System.EventArgs e) { VSync = VSyncMode.On; GL.Enable(EnableCap.Texture2D); GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.CullFace); GL.CullFace(CullFaceMode.Back); GL.FrontFace(FrontFaceDirection.Cw); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); GL.ClearColor(Color.FromArgb(255, 24, 24, 24)); multisampling = new Multisampling(Width, Height, 8); defaultShader = new DefaultShader(); model = ColladaLoader.Load(modelName); model.CreateVBOs(); model.LoadTextures(); model.Bind(defaultShader.ShaderProgram, defaultShader.Texture, defaultShader.HaveTexture, defaultShader.Ambient, defaultShader.Diffuse, defaultShader.Specular, defaultShader.Shininess); }
public static void TestRenderBones() { TestXNAGame main = null; SkinnedModel model = null; ColladaModel col = null; TestXNAGame.Start("TestShowBones", delegate { main = TestXNAGame.Instance; //col = ColladaModel.FromFile( new GameFile( main.RootDirectory + "/Content/Goblin.DAE" ) ); //col = ColladaModel.LoadSimpleCharacterAnim001(); col = ColladaModel.LoadSimpleBones001(); model = SkinnedModelBuilder.LoadSkinnedModel(main, col); //model.WorldMatrix = Matrix.CreateFromYawPitchRoll( -MathHelper.PiOver2, -MathHelper.PiOver2, 0 ); }, delegate { model.UpdateAnimation(main.Elapsed); model.RenderBonesLines(); }); }
public void ColladaModelConstructorTest() { string filename = TestDataLoader.GetTestFilePath("APC_animation.DAE"); Assert.IsTrue(File.Exists(filename), (new FileInfo(filename).FullName) + " does not exist"); ColladaModel target = new ColladaModel(filename); }
public void Import(XmlNode xmlRoot, ColladaModel model) { _model = model; XmlNode xmlScene = xmlRoot.SelectSingleNode("scene"); model.MeshInstances = ImportMeshInstances(xmlScene, model); }
public static ColladaModel Load(string name) { using (var xml = SourceLoader.AsStream($"models.{name}.dae")) { var xRoot = XDocument.Load(xml); var model = new ColladaModel(); // Parse Geometries var xMeshes = xRoot.Descendants($"{ns}mesh"); if (!xMeshes.Any()) { throw new ApplicationException("Failed to find geometries!"); } foreach (var xMesh in xMeshes) { var geoLoader = new GeometryLoader(xMesh); var geometry = geoLoader.Load(); model.Geometries.Add(geometry); } // Parse Materials var xMaterials = xRoot.Descendants($"{ns}material"); foreach (var xMaterial in xMaterials) { var materialLoader = new MaterialLoader(xRoot, xMaterial); var material = materialLoader.Load(); model.Materials.Add(material); } return(model); } }
public static SkinnedModel LoadSkinnedModel(IXNAGame game, Stream colladaStream) { ColladaModel colladaModel = ColladaModel.FromStream(colladaStream); SkinnedModelBuilder builder = new SkinnedModelBuilder(); return(builder.CreateSkinnedModel(game, colladaModel)); }
public ColladaScene(ColladaModel nModel) { model = nModel; Type = NodeType.Scene; Matrix = Matrix.Identity; scene = this; parent = null; Instance_Geometry = null; }
public void ImportTest() { AnimationImporter target = new AnimationImporter(); // TODO: Initialize to an appropriate value XmlNode xmlRoot = null; // TODO: Initialize to an appropriate value ColladaModel model = null; // TODO: Initialize to an appropriate value target.Import(xmlRoot, model); Assert.Inconclusive("A method that does not return a value cannot be verified."); }
/// <summary> /// Retrieves all the joints referenced by the given skin. The bones are only /// references to the bones contained by the given model! While fetching /// the bones (by following references in XML) this method updates the /// inverse bind-pose matrix of each bone according to the XML INV_BIND_MATRIX /// input as found in the XML skin definition. /// </summary> /// <param name="xmlSkin">XML skin node</param> /// <param name="model">Model instance</param> /// <returns>Used bones with updated inverse bind-pose matrices</returns> static List <Joint> GetUpdatedJointsFromSkin(XmlNode xmlSkin, ColladaModel model) { if (model.Joints.Count <= 1) { throw new ArgumentException("Model does not contain any joints"); } // Inverse Bind-Pose Matrices XmlNode xmlMatInput = xmlSkin.SelectSingleNode("joints/input[@semantic='INV_BIND_MATRIX']"); Source matrixSource = Source.FromInput(xmlMatInput, xmlSkin); List <Matrix> invBindMatrices = matrixSource.GetData <Matrix>(); // Joints (as referenced by this skin) XmlNode xmlJointInput = xmlSkin.SelectSingleNode("joints/input[@semantic='JOINT']"); Source jointSource = Source.FromInput(xmlJointInput, xmlSkin); List <string> names = jointSource.GetData <string>(); List <Joint> joints = new List <Joint>(names.Count); int i = 0; // Dictionary of joints in model by their name Dictionary <string, Joint> modelJoints = model.Joints.ToDictionary(j => j.GetAddressPart(jointSource.ColladaType)); // Check if the names actually refer to the joints in the dictionary if (modelJoints.ContainsKey(names[0]) == false) { // As of COLLADA 1.4 "name" can refer to name OR sid attribute! // If the former didn't work try the latter if (jointSource.ColladaType == "name") { // Only elements that actually have a sid can be part of the dictionary. // Elements which don't have a SID usually aren't referenced, so they are // not needed in the dictionary anyway modelJoints = model.Joints.Where(j => j.GetAddressPart("sid") != null). ToDictionary(j => j.GetAddressPart("sid")); } // If that still didn't help it's hopeless if (modelJoints.ContainsKey(names[0]) == false) { throw new ApplicationException("Invalid joint references in skin definition"); } } // Look up bones within the model by name from JOINT input) foreach (Joint joint in names.Select(name => modelJoints[name])) { Debug.Assert(joint != null); // while we're at it update inverse bind-pose matrix joint.InvBindPose = invBindMatrices[i++]; joints.Add(joint); } return(joints); }
public static void Main(string[] args) { ColladaModel model = new ColladaModel(); model.Libraries.Add(new ColladaLibraryGeometries()); Console.Write(ColladaExporter.Export(model)); Console.ReadLine(); }
private void ImportAnimationClips(XmlNode xmlNode, ColladaModel model) { if (xmlNode == null || model.JointAnimations == null || !model.JointAnimations.Any()) { return; } XmlNodeList xmlClips = xmlNode.SelectNodes("animation_clip"); if (xmlClips == null) { return; } foreach (XmlNode xmlClip in xmlClips) { // Reference name of the animation clip: name > id > sid String name = xmlClip.GetAttributeString("name") ?? (xmlClip.GetAttributeString("id") ?? xmlClip.GetAttributeString("sid")); // Start and end times float start = float.Parse(xmlClip.Attributes["start"].Value, CultureInfo.InvariantCulture); float end = float.Parse(xmlClip.Attributes["end"].Value, CultureInfo.InvariantCulture); // Animations to be played XmlNodeList xmlInstances = xmlClip.SelectNodes("instance_animation"); if (xmlInstances == null) { return; } List <JointAnimation> animations = new List <JointAnimation>(); foreach (XmlNode xmlInstance in xmlInstances) { // Assume url="#id" String id = xmlInstance.GetAttributeString("url").Substring(1); // Find animation with given id var temp = from a in model.JointAnimations where a.GlobalID.Equals("id") select a; animations.AddRange(temp); } JointAnimationClip clip = new JointAnimationClip(animations.ToArray(), TimeSpan.FromSeconds(start), TimeSpan.FromSeconds(end)); model.JointAnimationClips.Add(clip); } }
public void Import(XmlNode xmlRoot, ColladaModel model) { XmlNodeList xmlLightInstances = xmlRoot.SelectNodes(".//instance_light"); if (xmlLightInstances == null || xmlLightInstances.Count == 0) { // silently quit (lights are not vital) return; } model.Lights = (from XmlNode xmlInstance in xmlLightInstances where CanImport(xmlInstance) select ImportLight(xmlInstance)).ToList(); }
/// <summary> /// Imports skin information /// </summary> /// <remarks>Joint weights and indices defined by skins are imported /// by GeometryImporter since these values have to be added to the /// vertex container of the meshes.</remarks> /// <param name="xmlSkin"></param> /// <param name="model"></param> static void ImportSkin(XmlNode xmlSkin, ColladaModel model) { Mesh mesh = GetMeshFromSkin(xmlSkin, model); if (mesh == null) { throw new Exception("Mesh referenced by skin not found"); } // TODO: Take bind_shape_matrix into consideration for animation // Read inverse bind-pose matrices and save them in the corresponding joints GetUpdatedJointsFromSkin(xmlSkin, model); }
/// <summary> /// Returns the mesh referenced by given skin XML node if that mesh exists /// in the given model. Otherwise null is returned. /// </summary> /// <param name="xmlSkin">XML skin node</param> /// <param name="model">Model instance</param> /// <returns>instance of Mesh or null</returns> static Mesh GetMeshFromSkin(XmlNode xmlSkin, ColladaModel model) { string meshId = xmlSkin.Attributes["source"].Value.Substring(1); XmlNode xmlGeom = xmlSkin.OwnerDocument.DocumentElement. SelectSingleNode(".//geometry[@id='" + meshId + "']"); string name = xmlGeom.Attributes["name"] != null ? xmlGeom.Attributes["name"].Value : meshId; return((from mesh in model.Meshes where mesh.Name.Equals(name) select mesh).SingleOrDefault()); }
public void TestCreateObjectAndSaveWorkingCopy() { WizardsEditor wizardsEditor = new WizardsEditor(); EntityWizardsEditorExtension extension = new EntityWizardsEditorExtension(wizardsEditor); EditorObject obj = new EditorObject(); obj.FullData = new ObjectFullData(); extension.EditorObjects.Add(obj); ObjectEditor objectEditor = new ObjectEditor(obj, wizardsEditor); objectEditor.AddColladaModel(ColladaModel.LoadWall001()); wizardsEditor.SaveToWorkingCopy(); }
public void Import(XmlNode xmlRoot, ColladaModel model) { this.model = model; // Find material nodes in library XmlNodeList xmlMaterials = xmlRoot.SelectNodes("library_materials/material"); if (xmlMaterials == null || xmlMaterials.Count == 0) { // No Materials found return; } model.Materials = (from XmlNode xmlMaterial in xmlMaterials select ImportMaterial(xmlMaterial)).ToList(); }
Joint GetParentJoint(XmlNode xmlNode, ColladaModel model) { XmlNode xmlParent = xmlNode.ParentNode; if (xmlParent == null) { return(null); } if (xmlParent.Attributes == null || xmlParent.Attributes.Count == 0) { return(null); } string name = xmlParent.Attributes["name"] != null ? xmlParent.Attributes["name"].Value : xmlParent.Attributes["id"].Value; return(model.Joints.Where(j => j.Name.Equals(name)).FirstOrDefault()); }
static void ImportAnimations(XmlNodeList xmlAnimations, ColladaModel model) { // Joint dictionary Dictionary <string, Joint> joints = model.Joints.ToDictionary(joint => joint.GlobalID); // Import single animations List <JointAnimation> animations = (from XmlNode xmlAnim in xmlAnimations where DoesAnimationAffectJoints(joints, xmlAnim) select ImportAnimation(xmlAnim, joints)).ToList(); // Combine those affecting the same joint to one animation var combined = from c in (from anim in animations where anim.Channels.Length == 1 group anim by anim.Channels[0].Target) select CombineAnimations(c); // List of those who cannot be joined (because they have multiple channels) var rest = from anim in animations where anim.Channels.Length > 1 select anim; animations = combined.Union(rest).ToList(); // TODO: Implement Animation Clips (missing example so far) // Try to merge all animations (must be extended to consider animation clips) // NOTE: Didn't work for Maya animations, something's still of with them if (animations.Count > 1 && false) { try { var animation = MergeAnimations(animations); animations.Clear(); animations.Add(animation); } catch (Exception) { // didn't work, ignore } } model.JointAnimations = new JointAnimationList(animations); }
/// <summary> /// Imports all joint nodes referenced by <skeleton> nodes and their /// children. Stores the found joints in the given model's joint collection. /// </summary> /// <param name="xmlRoot">XML root node</param> /// <param name="model">model to store joints in</param> public void Import(XmlNode xmlRoot, ColladaModel model) { knownJoints.Clear(); // Add root joint that can be used to transform the model alltogether Joint root = new Joint("__root") { Parent = null, Transform = Matrix.Identity, Index = 0, ScopedID = "__root", GlobalID = "__root" }; ReadJoints(root, FindJointNodes(xmlRoot), model); // Last joint is the root model.Joints.Add(root); root.Index = model.Joints.Count - 1; }
public void Import(XmlNode xmlRoot, ColladaModel model) { _model = model; // Find skin definitions. Multiple skin definitions are supported // but they have to work on disjoint sets (relate to different joints) XmlNodeList xmlSkins = xmlRoot.SelectNodes(".//skin"); foreach (XmlNode xmlSkin in xmlSkins) { ImportSkin(xmlSkin, model); } // Next import animations from library_animations XmlNodeList xmlAnimations = xmlRoot.SelectNodes("/COLLADA/library_animations/animation"); ImportAnimations(xmlAnimations, model); // Import the animation clips library XmlNode xmlNode = xmlRoot.SelectSingleNode("/COLLADA/library_animation_clips"); ImportAnimationClips(xmlNode, model); }
public SkinnedModel CreateSkinnedModel(IXNAGame game, ColladaModel _colladaModel) { SkinnedModel ret; colladaModel = _colladaModel; skinnedModel = new SkinnedModel(game); ret = skinnedModel; skinnedModel.FrameRate = colladaModel.frameRate; skinnedModel.numOfAnimations = colladaModel.numOfAnimations; LoadMeshes(); LoadBones(); // Clean up for possible next creation colladaModel = null; skinnedModel = null; return(ret); }
public void LoadNode(XmlNode nodeNode) { ID = XmlHelper.GetXmlAttribute(nodeNode, "id"); Name = XmlHelper.GetXmlAttribute(nodeNode, "name"); SetNodeType(XmlHelper.GetXmlAttribute(nodeNode, "type")); XmlNode matrixNode = XmlHelper.GetChildNode(nodeNode, "matrix"); if (matrixNode != null) { Matrix = ColladaModel.LoadColladaMatrix(matrixNode); } foreach (XmlNode instance_geometryNode in nodeNode) { if (instance_geometryNode.Name == "instance_geometry") { if (Instance_Geometry != null) { throw new Exception( "More then one Instance_Geometry node found, which is not currently implemented!"); } string url = XmlHelper.GetXmlAttribute(instance_geometryNode, "url").Substring(1); Instance_Geometry = scene.Model.GetMesh(url); } } foreach (XmlNode childNode in nodeNode) { if (childNode.Name == "node") { ColladaSceneNode node = new ColladaSceneNode(this); Nodes.Add(node); node.LoadNode(childNode); } } }
public static void TestRenderAnimated() { TestXNAGame main = null; SkinnedModel model = null; ColladaModel col = null; TestXNAGame.Start("TestRenderAnimated", delegate { main = TestXNAGame.Instance; //col = ColladaModel.FromFile( new GameFile( main.RootDirectory + "/Content/Goblin.DAE" ) ); //col = ColladaModel.FromFile( new GameFile( main.RootDirectory + "/DebugFiles/VerySimpleBones001(disabled).DAE" ) ); //col = ColladaModel.FromFile( new GameFile( main.RootDirectory + "/Content/TriangleBones002.DAE" ) ); col = ColladaModel.LoadSimpleCharacterAnim001(); //col = ColladaModel.LoadSimpleBones001(); //col = ColladaModel.LoadTriangleBones001(); model = SkinnedModelBuilder.LoadSkinnedModel(main, col); model.WorldMatrix = Matrix.CreateFromYawPitchRoll(-MathHelper.PiOver2, -MathHelper.PiOver2, 0); //model.WorldMatrix = Matrix.Identity; }, delegate { main.GraphicsDevice.RenderState.CullMode = CullMode.None; if (main.Keyboard.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.R)) { model = SkinnedModelBuilder.LoadSkinnedModel(main, col); model.WorldMatrix = Matrix.Identity; // Matrix.CreateFromYawPitchRoll( -MathHelper.PiOver2, -MathHelper.PiOver2, 0 ); } model.UpdateAnimation(main.Elapsed); model.Render(); model.RenderBonesLines(); }); }
/// <summary> /// Imports the COLLADA Model from the given .DAE file into the XNA Content Model. /// </summary> /// <param name="filename">Path to .DAE file</param> /// <param name="context">Context (is not used)</param> /// <returns></returns> public override NodeContent Import(string filename, ContentImporterContext context) { boneIndex = 0; importerContext = context; // Load the complete collada model which is to be converted / imported collada = new ColladaModel(filename); //Debugger.Launch(); //Debugger.Break(); rootNode = new NodeContent(); rootNode.Name = Path.GetFileNameWithoutExtension(filename); rootNode.Identity = new ContentIdentity(filename); // The default XNA processor only supports up to 255 joints / bones if (collada.Joints.Count < 255) { CreateBones(CreateAnimations()); } else { String helpLink = ""; ContentIdentity cid = rootNode.Identity; String msg = String.Format("Model contains {0} bones. Maximum is 255. " + "Skinning Data (Bones and Vertex Channels) will not be imported!", collada.Joints.Count); importerContext.Logger.LogWarning(helpLink, cid, msg); excludeBlendWeights = true; } CreateMaterials(); CreateMeshes(); return(rootNode); }
public void Import(XmlNode xmlRoot, ColladaModel model) { // Find geometry nodes XmlNodeList xmlGeometries = xmlRoot.SelectNodes("library_geometries/geometry"); if (xmlGeometries == null || xmlGeometries.Count == 0) { throw new ApplicationException("No geometry found"); } ColladaModel existingInstance = model; model.Meshes = (from XmlNode xmlGeom in xmlGeometries where xmlGeom.SelectNodes(".//triangles|.//polygons|.//polylist").Count > 0 select ImportGeometry(xmlGeom, existingInstance)).ToList(); // Check for unsupported geometry, silently ignore lines if ((from XmlNode xmlGeom in xmlGeometries where xmlGeom.SelectNodes(".//trifangs|.//tristrips").Count > 0 select xmlGeom).Any()) { throw new ApplicationException("Only triangles are supported"); } }
List <MeshInstance> ImportMeshInstances(XmlNode xmlScene, ColladaModel model) { if (model.Meshes.Any() == false) { throw new Exception("No meshes found"); } List <MeshInstance> meshes = new List <MeshInstance>(); XmlNode xmlVisualScene = GetVisualScene(xmlScene); // Look for geometry instances and determine transformation from bottom up XmlNodeList xmlGeometries = xmlVisualScene.SelectNodes(".//instance_geometry"); foreach (XmlNode xmlGeom in xmlGeometries) { string geometryId = xmlGeom.Attributes["url"].Value.Substring(1); string key = GetMeshLibKey(xmlGeom, geometryId); // Check whether the referenced mesh exists if (!model.Meshes.Any(m => m.Name == key)) { continue; } MeshInstance mesh = new MeshInstance(); mesh.MeshName = key; mesh.AbsoluteTransform = CreateAbsoluteTransform(xmlGeom); mesh.ParentJoint = GetParentJoint(xmlGeom, model); meshes.Add(mesh); } // Look for controller instances // TODO: Load controller instances and joints correctly! XmlNodeList xmlControllerInstances = xmlVisualScene.SelectNodes(".//instance_controller"); foreach (XmlNode xmlControllerInstance in xmlControllerInstances) { string meshId = GetMeshId(xmlControllerInstance); string key = GetMeshLibKey(xmlControllerInstance, meshId); // Check whether the referenced mesh exists if (!model.Meshes.Any(m => m.Name == key)) { continue; } MeshInstance mesh = new MeshInstance(); mesh.MeshName = key; mesh.AbsoluteTransform = CreateAbsoluteTransformFromSkeleton(xmlControllerInstance); mesh.ParentJoint = GetParentJoint(xmlControllerInstance, model); meshes.Add(mesh); } // Check if any meshes have been found if (meshes.Count == 0) { throw new Exception("No mesh instances have been found"); } return(meshes); }
List<MeshInstance> ImportMeshInstances(XmlNode xmlScene, ColladaModel model) { if (model.Meshes.Any() == false) { throw new Exception("No meshes found"); } List<MeshInstance> meshes = new List<MeshInstance>(); XmlNode xmlVisualScene = GetVisualScene(xmlScene); // Look for geometry instances and determine transformation from bottom up XmlNodeList xmlGeometries = xmlVisualScene.SelectNodes(".//instance_geometry"); foreach (XmlNode xmlGeom in xmlGeometries) { string geometryId = xmlGeom.Attributes["url"].Value.Substring(1); string key = GetMeshLibKey(xmlGeom, geometryId); // Check whether the referenced mesh exists if (!model.Meshes.Any(m => m.Name == key)) continue; MeshInstance mesh = new MeshInstance(); mesh.MeshName = key; mesh.AbsoluteTransform = CreateAbsoluteTransform(xmlGeom); mesh.ParentJoint = GetParentJoint(xmlGeom, model); meshes.Add(mesh); } // Look for controller instances // TODO: Load controller instances and joints correctly! XmlNodeList xmlControllerInstances = xmlVisualScene.SelectNodes(".//instance_controller"); foreach (XmlNode xmlControllerInstance in xmlControllerInstances) { string meshId = GetMeshId(xmlControllerInstance); string key = GetMeshLibKey(xmlControllerInstance, meshId); // Check whether the referenced mesh exists if (!model.Meshes.Any(m => m.Name == key)) continue; MeshInstance mesh = new MeshInstance(); mesh.MeshName = key; mesh.AbsoluteTransform = CreateAbsoluteTransformFromSkeleton(xmlControllerInstance); mesh.ParentJoint = GetParentJoint(xmlControllerInstance, model); meshes.Add(mesh); } // Check if any meshes have been found if (meshes.Count == 0) { throw new Exception("No mesh instances have been found"); } return meshes; }
Joint GetParentJoint(XmlNode xmlNode, ColladaModel model) { XmlNode xmlParent = xmlNode.ParentNode; if (xmlParent == null) return null; if (xmlParent.Attributes == null || xmlParent.Attributes.Count == 0) return null; string name = xmlParent.Attributes["name"] != null ? xmlParent.Attributes["name"].Value : xmlParent.Attributes["id"].Value; return model.Joints.Where(j => j.Name.Equals(name)).FirstOrDefault(); }
/// <summary> /// This method extacts skinning information from a skin definition that /// refers to the given mesh node. If no skin definition is found jointWeights /// and jointIndices are assigned empty arrays. If there is a valid skin /// definition this method will output four joint indices per base vertex (Vector4) /// and three weights (Vector3). If there are more than four joints assigned to a /// vertex only the four most influencial are selected. The indices in the resulting /// jointIndices array are referring to positions within the given model's joint /// collection. 0 refers to the joint at model.Joints[0], 1 refers to the joint at /// model.Joints[1] and so on. If the model contains no joints this method outputs /// two empty arrays, just as if no skin defintion existed. /// </summary> /// <remarks>The outputted Vector3s for jointWeights represent four normalized weights /// in three components. The sum of all four weights is 1. The fourth weight is implicitly /// defined as (1 - X - Y -Z).</remarks> /// <param name="xmlMeshNode">XML mesh node</param> /// <param name="model">Model instance with non-empty joint collection</param> /// <param name="jointIndices">Array of 4-d vectors representing joint indices</param> /// <param name="jointWeights">Array of 3-d vectors representing their respective weights</param> protected static void GetJointWeightsAndIndices(XmlNode xmlMeshNode, ColladaModel model, out Vector4[] jointIndices, out Vector3[] jointWeights) { // Look for a skin definition that references this mesh XmlNode xmlSkin = xmlMeshNode.SelectSingleNode("/COLLADA/library_controllers/" + "controller/skin[@source='#" + xmlMeshNode.ParentNode.Attributes["id"].Value + "']"); if (xmlSkin == null || model.Joints == null || model.Joints.Count == 0) { // no skinning information found jointIndices = new Vector4[0]; jointWeights = new Vector3[0]; return; } // Read number of vertex weight assignments (this is equivalent to the number of base vertices) XmlNode xmlVertexWeights = xmlSkin.SelectSingleNode("vertex_weights"); int count = int.Parse(xmlVertexWeights.Attributes["count"].Value); // Read weight source XmlNode xmlWeightInput = xmlSkin.SelectSingleNode("vertex_weights/input[@semantic='WEIGHT']"); Source weightSource = Source.FromInput(xmlWeightInput, xmlSkin); var weights = weightSource.GetData <float>(); // Read assignments XmlNode xmlVertexCount = xmlSkin.SelectSingleNode("vertex_weights/vcount"); XmlNode xmlVertices = xmlSkin.SelectSingleNode("vertex_weights/v"); int[] vcount = XmlUtil.ParseInts(xmlVertexCount.InnerText); ContentAssert.AreEqual(vcount.Length, count, "vcount.Length"); int[] data = XmlUtil.ParseInts(xmlVertices.InnerText); // How many items per vertex (this corresponds to the maximum offset of // all inputs within vertex_weights) int stride = (from XmlNode node in xmlVertexWeights.SelectNodes("input/@offset") select int.Parse(node.Value)).Max() + 1; ContentAssert.IsTrue(stride >= 2, "Invalid weight data"); // It is assumed that joint indices are at offset 0 and their weights at offset 1 // For each base vertex there is one block of joint-weight assigments jointIndices = new Vector4[count]; jointWeights = new Vector3[count]; bool reachedEnd = false; for (int i = 0, k = 0; i < count; i++) { // There may be more than 4 weights defined List <JointWeightPair> pairs = new List <JointWeightPair>(); // Add all defined joint-weight pairs for (int j = 0; j < vcount[i]; j++) { int jointIndex = data[k + 0]; int weightIndex = data[k + 1]; pairs.Add(new JointWeightPair(jointIndex, weights[weightIndex])); k += stride; } // Take the four vertices with greatest influence JointWeightPair[] best = (from pair in pairs orderby pair.Weight descending select pair).Take(4).ToArray(); Vector4 curIndices = new Vector4(); Vector4 curWeights = new Vector4(); ContentAssert.IsTrue((vcount[i] <= 4 && best.Length == vcount[i]) || best.Length == 4, "Invalid weight data", true); if (best.Length >= 1) { curIndices.X = best[0].JointIndex; curWeights.X = best[0].Weight; } if (best.Length >= 2) { curIndices.Y = best[1].JointIndex; curWeights.Y = best[1].Weight; } if (best.Length >= 3) { curIndices.Z = best[2].JointIndex; curWeights.Z = best[2].Weight; } if (best.Length == 4) { curIndices.W = best[3].JointIndex; curWeights.W = best[3].Weight; } // Normalize weights (sum must be 1) float sum = curWeights.X + curWeights.Y + curWeights.Z + curWeights.Z; if (sum > 0) { curWeights.X = 1.0f / sum * curWeights.X; curWeights.Y = 1.0f / sum * curWeights.Y; curWeights.Z = 1.0f / sum * curWeights.Z; curWeights.W = 1.0f / sum * curWeights.W; } jointIndices[i] = curIndices; jointWeights[i] = curWeights.XYZ(); if (k == data.Length) { reachedEnd = true; } } ContentAssert.IsTrue(reachedEnd, "Not all weights were read", true); // JointIndices are referring to indices in the joint source // so every index refers to a name in the source which refers to the // actual bone XmlNode xmlInput = xmlSkin.SelectSingleNode("vertex_weights/input[@semantic='JOINT']"); Debug.Assert(xmlInput != null, "No joint input in skin found"); Source jointSource = Source.FromInput(xmlInput, xmlSkin); var names = jointSource.GetData <string>(); // Create dictionary of model bones with source reference type as key // (source refers to joints either by name, idref or sidref) Dictionary <string, Joint> modelJoints = model.Joints.ToDictionary(j => j.GetAddressPart(jointSource.ColladaType)); // Check if the names actually refer to the joints in the dictionary if (modelJoints.ContainsKey(names[0]) == false) { Debug.Assert(model.Joints.All(j => j.Name != null || j.GlobalID != null || j.ScopedID != null), "Joints cannot be referenced"); // As of COLLADA 1.4 "name" can refer to name OR sid attribute! // If the former didn't work try the latter);) if (jointSource.ColladaType == "name") { // Only elements that actually have a sid can be part of the dictionary. // Elements which don't have a SID usually aren't referenced, so they are // not needed in the dictionary anyway modelJoints = model.Joints.Where(j => j.GetAddressPart("sid") != null). ToDictionary(j => j.GetAddressPart("sid")); } // If that still didn't help it's hopeless if (modelJoints.ContainsKey(names[0]) == false) { throw new ApplicationException("Invalid joint references in skin definition"); } } try { // replace index that points to source with index that points to model's joint for (int i = 0; i < jointIndices.Length; i++) { Vector4 indices = jointIndices[i]; // Find indices in model's joint collection indices.X = modelJoints[names[(int)indices.X]].Index; indices.Y = modelJoints[names[(int)indices.Y]].Index; indices.Z = modelJoints[names[(int)indices.Z]].Index; indices.W = modelJoints[names[(int)indices.W]].Index; jointIndices[i] = indices; } } catch (IndexOutOfRangeException e) { throw new ApplicationException("Invalid joint indices read"); } // Check data bool valid = jointIndices.All(v => Math.Abs((v.X + v.Y + v.Z + (1 - v.X - v.Y - v.Z)) - 1) < 0.001f); ContentAssert.IsTrue(valid, "All joint weights must sum up to 1f"); }
/// <summary> /// Returns the mesh referenced by given skin XML node if that mesh exists /// in the given model. Otherwise null is returned. /// </summary> /// <param name="xmlSkin">XML skin node</param> /// <param name="model">Model instance</param> /// <returns>instance of Mesh or null</returns> static Mesh GetMeshFromSkin(XmlNode xmlSkin, ColladaModel model) { string meshId = xmlSkin.Attributes["source"].Value.Substring(1); XmlNode xmlGeom = xmlSkin.OwnerDocument.DocumentElement. SelectSingleNode(".//geometry[@id='" + meshId + "']"); string name = xmlGeom.Attributes["name"] != null ? xmlGeom.Attributes["name"].Value : meshId; return (from mesh in model.Meshes where mesh.Name.Equals(name) select mesh).SingleOrDefault(); }
/// <summary> /// Retrieves all the joints referenced by the given skin. The bones are only /// references to the bones contained by the given model! While fetching /// the bones (by following references in XML) this method updates the /// inverse bind-pose matrix of each bone according to the XML INV_BIND_MATRIX /// input as found in the XML skin definition. /// </summary> /// <param name="xmlSkin">XML skin node</param> /// <param name="model">Model instance</param> /// <returns>Used bones with updated inverse bind-pose matrices</returns> static List<Joint> GetUpdatedJointsFromSkin(XmlNode xmlSkin, ColladaModel model) { if (model.Joints.Count <= 1) { throw new ArgumentException("Model does not contain any joints"); } // Inverse Bind-Pose Matrices XmlNode xmlMatInput = xmlSkin.SelectSingleNode("joints/input[@semantic='INV_BIND_MATRIX']"); Source matrixSource = Source.FromInput(xmlMatInput, xmlSkin); List<Matrix> invBindMatrices = matrixSource.GetData<Matrix>(); // Joints (as referenced by this skin) XmlNode xmlJointInput = xmlSkin.SelectSingleNode("joints/input[@semantic='JOINT']"); Source jointSource = Source.FromInput(xmlJointInput, xmlSkin); List<string> names = jointSource.GetData<string>(); List<Joint> joints = new List<Joint>(names.Count); int i = 0; // Dictionary of joints in model by their name Dictionary<string, Joint> modelJoints = model.Joints.ToDictionary(j => j.GetAddressPart(jointSource.ColladaType)); // Check if the names actually refer to the joints in the dictionary if (modelJoints.ContainsKey(names[0]) == false) { // As of COLLADA 1.4 "name" can refer to name OR sid attribute! // If the former didn't work try the latter if (jointSource.ColladaType == "name") { // Only elements that actually have a sid can be part of the dictionary. // Elements which don't have a SID usually aren't referenced, so they are // not needed in the dictionary anyway modelJoints = model.Joints.Where(j => j.GetAddressPart("sid") != null). ToDictionary(j => j.GetAddressPart("sid")); } // If that still didn't help it's hopeless if (modelJoints.ContainsKey(names[0]) == false) { throw new ApplicationException("Invalid joint references in skin definition"); } } // Look up bones within the model by name from JOINT input) foreach (Joint joint in names.Select(name => modelJoints[name])) { Debug.Assert(joint != null); // while we're at it update inverse bind-pose matrix joint.InvBindPose = invBindMatrices[i++]; joints.Add(joint); } return joints; }
static void ImportAnimations(XmlNodeList xmlAnimations, ColladaModel model) { // Joint dictionary Dictionary<string, Joint> joints = model.Joints.ToDictionary(joint => joint.GlobalID); // Import single animations List<JointAnimation> animations = (from XmlNode xmlAnim in xmlAnimations where DoesAnimationAffectJoints(joints, xmlAnim) select ImportAnimation(xmlAnim, joints)).ToList(); // Combine those affecting the same joint to one animation var combined = from c in (from anim in animations where anim.Channels.Length == 1 group anim by anim.Channels[0].Target) select CombineAnimations(c); // List of those who cannot be joined (because they have multiple channels) var rest = from anim in animations where anim.Channels.Length > 1 select anim; animations = combined.Union(rest).ToList(); // TODO: Implement Animation Clips (missing example so far) // Try to merge all animations (must be extended to consider animation clips) // NOTE: Didn't work for Maya animations, something's still of with them if (animations.Count > 1 && false) { try { var animation = MergeAnimations(animations); animations.Clear(); animations.Add(animation); } catch (Exception) { // didn't work, ignore } } model.JointAnimations = new JointAnimationList(animations); }
public static SkinnedModel LoadSkinnedModel(IXNAGame game, ColladaModel model) { SkinnedModelBuilder builder = new SkinnedModelBuilder(); return(builder.CreateSkinnedModel(game, model)); }
/// <summary> /// Imports a piece of geometry, but does only support meshes, no NURBS. /// </summary> /// <param name="xmlGeometryNode">XML node of the geometry</param> /// <exception cref="Exception">Only works with meshes</exception> Mesh ImportGeometry(XmlNode xmlGeometryNode, ColladaModel model) { // Find the mesh node XmlNode xmlMeshNode = xmlGeometryNode.SelectSingleNode(".//mesh"); if (xmlMeshNode == null) { throw new Exception("No supported geometry (Mesh) found"); } // Determine number of mesh parts XmlNodeList xmlTriangles = xmlMeshNode.SelectNodes("triangles|polygons|polylist"); if (xmlTriangles.Count == 0) { throw new Exception("No triangles found in mesh. Only triangles are supported"); } if (xmlTriangles[0].Name != "triangles") { // If there are polygons or a polylist, check that all of them are triangles if (xmlTriangles[0].Attributes["vcount"] != null) { var vcounts = XmlUtil.ParseInts(xmlTriangles[0].Attributes["vcount"].Value); var nonTriangles = vcounts.Where(count => count != 3); if (nonTriangles.Any()) { throw new Exception("Found polygon with " + nonTriangles.First() + " elements. Only triangles are supported"); } } } // Source data for this mesh used by all mesh parts. List <VertexSource> sources = ReadSources(xmlMeshNode); Vector4[] jointIndices; Vector3[] jointWeights; // Skinning Information, if available GetJointWeightsAndIndices(xmlMeshNode, model, out jointIndices, out jointWeights); if (sources.Count == 0) { throw new Exception("No data found"); } //------------------------------------------------------- // Create Mesh //------------------------------------------------------- Mesh mesh = new Mesh(); mesh.Name = XmlUtil.GetName(xmlGeometryNode); mesh.MeshParts = new MeshPart[xmlTriangles.Count]; // A mesh container for every mesh part, since every mesh part may use different // vertex types. This can be optimized in the content processor, if needed mesh.VertexContainers = new VertexContainer[xmlTriangles.Count]; string[] semantics = new string[] { "VERTEX", "COLOR", "NORMAL", "TEXCOORD", "TEXTANGENT", "TEXBINORMAL" }; //------------------------------------------------------- // Create Mesh Parts //------------------------------------------------------- for (int i = 0; i < xmlTriangles.Count; i++) { XmlNode xmlPart = xmlTriangles[i]; int numTriangles = int.Parse(xmlPart.Attributes["count"].Value); List <int> indexStream = new List <int>(numTriangles * 3); var pNodes = xmlPart.SelectNodes("p"); if (pNodes.Count > 1) { // Indices are scattered among numTriangles <p> tags foreach (XmlNode p in pNodes) { indexStream.AddRange(XmlUtil.ParseInts(p.InnerText)); } } else { // Indices are contained in one <p> tag indexStream.AddRange(XmlUtil.ParseInts(pNodes[0].InnerText)); } int[] indices = indexStream.ToArray(); MeshPart part = new MeshPart(); try { if (xmlPart.Attributes["material"] == null) { throw new Exception("no material attribute found"); } part.MaterialName = FindMaterial(xmlGeometryNode, xmlPart.Attributes["material"].Value); } catch (Exception) { // No Material found part.MaterialName = null; } // Read Vertex Channels List <VertexChannel> vertexChannels = new List <VertexChannel>(); foreach (String semantic in semantics) { XmlNode input = xmlPart.SelectSingleNode(".//input[@semantic='" + semantic + "']"); if (input == null) { continue; // no such vertex channel defined } int offset; String sourceId; if (!input.TryGetAttribute("source", out sourceId)) { throw new Exception("Referenced source of input with '" + semantic + "' semantic not found"); } if (!input.TryGetAttribute("offset", out offset)) { throw new Exception("No offset attribute of input with '" + semantic + "' semantic found"); } sourceId = sourceId.Replace("#", ""); VertexSource source = sources.Where(s => s.GlobalID.Equals(sourceId)).FirstOrDefault(); if (source == null) { if (semantic.Equals("VERTEX")) { sourceId = xmlGeometryNode.SelectSingleNode(".//input[@semantic='POSITION']/@source").InnerText.Substring(1); source = sources.Where(s => s.GlobalID.Equals(sourceId)).FirstOrDefault(); } if (source == null) { throw new Exception("Source '" + sourceId + "' not found"); } } VertexElement desc = new VertexElement(); desc.Offset = offset; desc.UsageIndex = 0; desc.VertexElementFormat = GetVertexElementFormat(source, semantic); desc.VertexElementUsage = GetVertexElementUsage(semantic); VertexChannel channel = new VertexChannel(source, desc); channel.CopyIndices(indices, offset, numTriangles * 3); vertexChannels.Add(channel); } var jointChannels = GenerateJointChannels(jointIndices, jointWeights, vertexChannels.Where(c => c.Description.VertexElementUsage == VertexElementUsage.Position).First().Indices); if (jointChannels != null) { vertexChannels.Add(jointChannels.Item1); vertexChannels.Add(jointChannels.Item2); } part.Vertices = new VertexContainer(vertexChannels); part.Indices = part.Vertices.Indices; mesh.VertexContainers[i] = part.Vertices; mesh.MeshParts[i] = part; } return(mesh); }
private void ImportAnimationClips(XmlNode xmlNode, ColladaModel model) { if (xmlNode == null || model.JointAnimations == null || !model.JointAnimations.Any()) return; XmlNodeList xmlClips = xmlNode.SelectNodes("animation_clip"); if (xmlClips == null) return; foreach (XmlNode xmlClip in xmlClips) { // Reference name of the animation clip: name > id > sid String name = xmlClip.GetAttributeString("name") ?? (xmlClip.GetAttributeString("id") ?? xmlClip.GetAttributeString("sid")); // Start and end times float start = float.Parse(xmlClip.Attributes["start"].Value, CultureInfo.InvariantCulture); float end = float.Parse(xmlClip.Attributes["end"].Value, CultureInfo.InvariantCulture); // Animations to be played XmlNodeList xmlInstances = xmlClip.SelectNodes("instance_animation"); if (xmlInstances == null) return; List<JointAnimation> animations = new List<JointAnimation>(); foreach (XmlNode xmlInstance in xmlInstances) { // Assume url="#id" String id = xmlInstance.GetAttributeString("url").Substring(1); // Find animation with given id var temp = from a in model.JointAnimations where a.GlobalID.Equals("id") select a; animations.AddRange(temp); } JointAnimationClip clip = new JointAnimationClip(animations.ToArray(), TimeSpan.FromSeconds(start), TimeSpan.FromSeconds(end)); model.JointAnimationClips.Add(clip); } }
/// <summary> /// Reads the given joints and all of their children recursively. Newly created joints /// are appended to the Children collection of the parent (if not null) and to the /// Joints collection of the given model. /// </summary> /// <param name="parent">Parent joint of joints to read</param> /// <param name="xmlNodes">XML joint nodes</param> /// <param name="model">Model instance with non-null joint collection</param> void ReadJoints(Joint parent, IEnumerable <XmlNode> xmlNodes, ColladaModel model) { bool jointWithoutId = false; foreach (XmlNode xmlNode in xmlNodes) { // Create joint from XML Joint joint = new Joint(GetNodeName(xmlNode)) { Parent = parent, Transform = CreateNodeTransform(xmlNode), GlobalID = xmlNode.GetAttributeString("id"), ScopedID = xmlNode.GetAttributeString("sid") }; // Check if this joint was already added, only possible if ID is set); if (!String.IsNullOrEmpty(joint.GlobalID)) { if (knownJoints.ContainsKey(joint.GlobalID)) { continue; } knownJoints.Add(joint.GlobalID, true); } else { // There was a joint without ID jointWithoutId = true; } // Append this joint to parents' children collection if (parent != null) { if (parent.Children == null) { parent.Children = new JointList { joint } } ; else { parent.Children.Add(joint); } } // Add joint to model's joint collection joint.Index = model.Joints.Count; model.Joints.Add(joint); // Read child nodes of this joint XmlNodeList xmlChildren = xmlNode.SelectNodes("node"); if (xmlChildren != null) { ReadJoints(joint, xmlChildren.OfType <XmlNode>(), model); } } if (jointWithoutId) { // There were joints without IDs, these bones could be contained // in model's joint collection multiple times // TODO: Remove double joints in case of Joint without Id } }
/// <summary> /// Reads the given joints and all of their children recursively. Newly created joints /// are appended to the Children collection of the parent (if not null) and to the /// Joints collection of the given model. /// </summary> /// <param name="parent">Parent joint of joints to read</param> /// <param name="xmlNodes">XML joint nodes</param> /// <param name="model">Model instance with non-null joint collection</param> void ReadJoints(Joint parent, IEnumerable<XmlNode> xmlNodes, ColladaModel model) { bool jointWithoutId = false; foreach (XmlNode xmlNode in xmlNodes) { // Create joint from XML Joint joint = new Joint(GetNodeName(xmlNode)) { Parent = parent, Transform = CreateNodeTransform(xmlNode), GlobalID = xmlNode.GetAttributeString("id"), ScopedID = xmlNode.GetAttributeString("sid") }; // Check if this joint was already added, only possible if ID is set); if (!String.IsNullOrEmpty(joint.GlobalID)) { if (knownJoints.ContainsKey(joint.GlobalID)) continue; knownJoints.Add(joint.GlobalID, true); } else { // There was a joint without ID jointWithoutId = true; } // Append this joint to parents' children collection if (parent != null) { if (parent.Children == null) parent.Children = new JointList { joint }; else parent.Children.Add(joint); } // Add joint to model's joint collection joint.Index = model.Joints.Count; model.Joints.Add(joint); // Read child nodes of this joint XmlNodeList xmlChildren = xmlNode.SelectNodes("node"); if (xmlChildren != null) { ReadJoints(joint, xmlChildren.OfType<XmlNode>(), model); } } if (jointWithoutId) { // There were joints without IDs, these bones could be contained // in model's joint collection multiple times // TODO: Remove double joints in case of Joint without Id } }