public void Serialize(Stream dst, Entities data) { if (null == dst || null == data.meshes || null == data.rootNode) { throw new ArgumentException("CDaeSerializer::Serialize: Passed 'null'-Argument"); } XmlWriter writer = XmlWriter.Create(dst, new XmlWriterSettings() { Indent = true, IndentChars = "\t", // defaults NewLineChars = "\r\n", NewLineHandling = NewLineHandling.Replace, NewLineOnAttributes = false, Encoding = Encoding.UTF8 }); var materialNames = data.meshes .SelectMany(msh => msh.MaterialSlots) .Select(mat => mat.name) .Distinct(); dst.Position = 0; string srcId, baseName; List <Mdl.CFrame> nodesList = data.rootNode.Childs; if (!String.IsNullOrWhiteSpace(RootName)) { baseName = RootName; } else if (!String.IsNullOrWhiteSpace(data.rootNode.Name)) { baseName = data.rootNode.Name; } else { baseName = "RootNode"; } // XML Header writer.WriteStartDocument(); writer.WriteStartElement("COLLADA", "http://www.collada.org/2005/11/COLLADASchema"); // root with default namespace writer.WriteAttributeString("version", "1.4.1"); writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); // declares a namespace prefixed with xsi #region File meta writer.WriteStartElement("asset"); { writer.WriteStartElement("contributor"); { writer.WriteElementString("author", "Nitro+"); writer.WriteElementString("authoring_tool", "Nitro Mdl-Converter"); writer.WriteEndElement(); } writer.WriteElementString("created", DateTime.Now.ToString("s")); // Unit writer.WriteStartElement("unit"); writer.WriteAttributeString(NAME, "millimeter"); // Millimeters? writer.WriteAttributeString("meter", "0.001"); writer.WriteEndElement(); writer.WriteElementString("up_axis", "Z_UP"); // Geometries seem to be at least on Zup writer.WriteEndElement(); // asset } #endregion // Texture paths (not supported, requires parsing .mtl files) writer.WriteStartElement("library_images"); writer.WriteEndElement(); #region Material shading writer.WriteStartElement("library_effects"); { writer.WriteStartElement("effect"); { writer.WriteAttributeString(ID, "shadeless-effect"); writer.WriteStartElement("profile_COMMON"); { writer.WriteStartElement("technique"); { writer.WriteAttributeString(SID, "common"); writer.WriteStartElement("lambert"); { addEffectItem("emission", COLOR, V4_ZERO); addEffectItem("ambient", COLOR, V4_ZERO); addEffectItem("diffuse", COLOR, "1 1 1 1"); //addEffectItem("specular", COLOR, V4_HALF); //addEffectItem("shininess", FLOAT, "50"); addEffectItem("index_of_refraction", FLOAT, "1"); writer.WriteEndElement(); // phong } writer.WriteEndElement(); // technique } writer.WriteEndElement(); // profile } writer.WriteEndElement(); // effect } writer.WriteEndElement(); // effects void addEffectItem(string item, string type, string value) { writer.WriteStartElement(item); writer.WriteStartElement(type); writer.WriteAttributeString(SID, item); writer.WriteString(value); writer.WriteEndElement(); // type writer.WriteEndElement(); // item } } #endregion #region Materials writer.WriteStartElement("library_materials"); foreach (string name in materialNames) { writer.WriteStartElement("material"); writer.WriteAttributeString(ID, $"{name}-material"); writer.WriteAttributeString(NAME, name); writer.WriteStartElement("instance_effect"); writer.WriteAttributeString("url", "#shadeless-effect"); writer.WriteEndElement(); // instance_effect writer.WriteEndElement(); // material } writer.WriteEndElement(); // materials #endregion #region Meshes writer.WriteStartElement("library_geometries"); { string meshId; bool hasUV; Mdl.CMesh mesh; var meshGrps = data.meshes.GroupBy(msh => msh.Name); foreach (var meshSet in meshGrps) { mesh = meshSet.First(msh => msh.IsValid() && !String.IsNullOrWhiteSpace(msh.Name)); if ((null == mesh) || mesh.HasInvalidFaces()) { Console.WriteLine("CDaeSerializer: Skipped a mesh geometry!"); continue; } hasUV = mesh.HasUV(); meshId = mesh.IsMorphed ? $"{mesh.OriginName}_{mesh.Name}{_MESH}" : mesh.Name + _MESH; writer.WriteStartElement("geometry"); { writer.WriteAttributeString(ID, meshId); writer.WriteAttributeString(NAME, mesh.Name); writer.WriteStartElement("mesh"); { // 3D Coordinates (mesh) addSource3D(mesh.Vertices, id: $"{meshId}-positions"); addSource3D(mesh.Normals, id: $"{meshId}-normals"); // UV mapping if (hasUV) { writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId = $"{meshId}-map-0"); var pts2D = mesh.TexCoords; int cnt = pts2D.Count; writer.WriteStartElement("float_array"); { writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, (2 * cnt).ToString()); writer.WriteString(pts2D[0].ToString()); for (int vn = 1; vn < cnt; ++vn) { writer.WriteWhitespace(SEPARATOR); writer.WriteString(pts2D[vn].ToString()); } writer.WriteEndElement(); // float array } addTechCommon(srcId, cnt, 2, FLOAT, "S", "T"); writer.WriteEndElement(); // map-0 } } // has uv // Vertices writer.WriteStartElement("vertices"); writer.WriteAttributeString("id", $"{meshId}-vertices"); writer.WriteAttElement(INPUT, ASTR_SEM_SRC, "POSITION", $"#{meshId}-positions"); writer.WriteEndElement(); // vertices // Faces var meshFaces = mesh.Faces; // creates readOnly on call var matRefs = mesh.MaterialSlots; for (int mn = 0; mn < matRefs.Count; ++mn) { addTrianglesByMaterial(meshFaces, matRefs[mn]); } writer.WriteEndElement(); // mesh } writer.WriteEndElement(); // geometry } }// foreach mesh writer.WriteEndElement(); // geometries void addSource3D(IReadOnlyList <Vector3DF> pts3d, string id) { writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, id); string mpa = id + _ARRAY; writer.WriteStartElement("float_array"); { writer.WriteAttributeString(ID, mpa); writer.WriteAttributeString(COUNT, (pts3d.Count * 3).ToString()); writer.WriteString(pts3d[0].ToString()); // not in specific xml format for (int vn = 1; vn < pts3d.Count; ++vn) { writer.WriteWhitespace(SEPARATOR); writer.WriteString(pts3d[vn].ToString()); } writer.WriteEndElement(); // float array } addTechCommon(mpa, pts3d.Count, 3, FLOAT, "X", "Y", "Z"); writer.WriteEndElement(); // source } } void addTrianglesByMaterial(IReadOnlyList <Triangle> triSrc, Reference matRef) { if (matRef.IndexCount() < 1) {// no faces assigned return; } writer.WriteStartElement("triangles"); writer.WriteAttributeString("material", $"{matRef.name}-material"); writer.WriteAttributeString(COUNT, matRef.IndexCount().ToString()); // List mapped components string[] inputAtrb = new string[] { SEMANTIC, SOURCE, "offset", "set" }; writer.WriteAttElement(INPUT, inputAtrb, "VERTEX", $"#{meshId}-vertices", "0"); writer.WriteAttElement(INPUT, inputAtrb, "NORMAL", $"#{meshId}-normals", "1"); if (hasUV) { writer.WriteAttElement(INPUT, inputAtrb, "TEXCOORD", $"#{meshId}-map-0", "2", "0"); } // Write indices of each component that is mapped to the triangle edges (interlaced) // Currently handles only rectangular mapping (each mapped component has equal element count) writer.WriteStartElement("p"); long upperLim = Math.Min(matRef.iLast, triSrc.Count - 1); for (int faceN = (int)Math.Min(matRef.iFirst, triSrc.Count); faceN <= upperLim; ++faceN) { ushort ia = triSrc[faceN].iVert1; ushort ib = triSrc[faceN].iVert2; ushort ic = triSrc[faceN].iVert3; if (hasUV) {// vertice, normal and texture coordinate indices writer.WriteSpacedValues(ia, ia, ia, ib, ib, ib, ic, ic, ic); } else { writer.WriteSpacedValues(ia, ia, ib, ib, ic, ic); } if (faceN < upperLim) { writer.WriteWhitespace(SEPARATOR); } } writer.WriteEndElement(); // p writer.WriteEndElement(); // triangles } } #endregion var baseMeshes = data.meshes.Where(msh => !msh.IsMorphed); int meshNodesCount = baseMeshes.Count(); #region Skinning writer.WriteStartElement("library_controllers"); if (nodesList.Count > 1) { IReadOnlyList <VertexGrp> meshWeights; int weightsCnt; bool hasBones, hasWeights; string frameName; var frameGrps = nodesList.GetRange(1, nodesList.Count - 1) // leave out first, which is armature .Where(frm => !String.IsNullOrWhiteSpace(frm.Name)) .GroupBy(frm => frm.Name); foreach (var frmSet in frameGrps) { Mdl.CMesh mesh = null; frameName = frmSet.First().Name; // index 0 MUST be armature and the order of meshes and related nodes in the list must be same int meshIdx = nodesList.FindIndex(frm => frm.Name == frameName) - 1; if (meshIdx >= 0 && (meshIdx < meshNodesCount)) { mesh = baseMeshes.ElementAt(meshIdx); } if (null == mesh) { continue; } srcId = $"{baseName}_{frameName}{_SKIN}"; writer.WriteStartElement("controller"); { writer.WriteAttributeString(ID, srcId); writer.WriteAttributeString(NAME, baseName); writer.WriteStartElement("skin"); { writer.WriteAttributeString(SOURCE, $"#{mesh.Name}{_MESH}"); // Transforms object to world space, // also visible as mesh Transform // Effectively moves origin of mesh, but must fit to bone transform writer.WriteStartElement("bind_shape_matrix"); writer.WriteSpacedValues( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); // identity writer.WriteEndElement(); // bind shape meshWeights = mesh.Weights; weightsCnt = meshWeights.Count; if (weightsCnt < 1) { writer.WriteEndElement(); // skin writer.WriteEndElement(); // controller continue; // next mesh } // Bone src var distinctBones = meshWeights .Select(w => w.groupName) .Where(gn => !String.IsNullOrWhiteSpace(gn)) .Distinct(); // TODO: Merge identically named groups? if (hasBones = distinctBones.Any()) { writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId += "-joints"); writer.WriteStartElement("Name_array"); writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, weightsCnt.ToString()); writer.WriteValue(distinctBones); // IEnumerable writer.WriteEndElement(); // name array addTechCommon(srcId, weightsCnt, stride: 1, type: NAME, values: "JOINT"); writer.WriteEndElement(); // source joints } } // Bind-pose src // transforms bones from world space to space of the root bone srcId = $"{baseName}_{frameName}{_SKIN}"; writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId += "-bind_poses"); writer.WriteStartElement("float_array"); writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, (weightsCnt * 16).ToString()); // TODO: matrices could be undefined writer.WriteString(meshWeights[0].bindMatrix.ToString()); // returns 16 floats as string for (int wn = 1; wn < weightsCnt; ++wn) { writer.WriteWhitespace(SEPARATOR); writer.WriteString(meshWeights[wn].bindMatrix.ToString()); } writer.WriteEndElement(); // array addTechCommon(srcId, weightsCnt, stride: 16, type: "float4x4", values: "TRANSFORM"); writer.WriteEndElement(); // source binds } // Weight src int weightsLen = mesh.WeightsLenght(); if (hasWeights = (weightsLen > 0)) { srcId = $"{baseName}_{frameName}{_SKIN}"; writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId += "-weights"); writer.WriteStartElement("float_array"); writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, weightsLen.ToString()); if (!meshWeights[0].IsNullOrEmpty()) // Has assigned bone, but no weight paint { writer.WriteValue(meshWeights[0].mapping.Values); // ValueCollection } for (int wn = 1; wn < weightsCnt; ++wn) { if (!meshWeights[wn].IsNullOrEmpty()) { writer.WriteWhitespace(SEPARATOR); writer.WriteValue(meshWeights[wn].mapping.Values); } } writer.WriteEndElement(); // array addTechCommon(srcId, weightsLen, stride: 1, type: FLOAT, values: "WEIGHT"); writer.WriteEndElement(); // source weights } } // if weights assigned // Bone-binding if (hasBones) { srcId = $"#{baseName}_{frameName}{_SKIN}"; writer.WriteStartElement("joints"); writer.WriteAttElement(INPUT, ASTR_SEM_SRC, "JOINT", srcId + "-joints"); writer.WriteAttElement(INPUT, ASTR_SEM_SRC, "INV_BIND_MATRIX", srcId + "-bind_poses"); writer.WriteEndElement(); // joints } // Vertex-Weight map if (hasBones && hasWeights) { int vertCnt = mesh.VerticesCount(); writer.WriteStartElement("vertex_weights"); { writer.WriteAttributeString(COUNT, vertCnt.ToString()); string[] inputAtrb = new string[] { SEMANTIC, SOURCE, "offset" }; writer.WriteAttElement(INPUT, inputAtrb, "JOINT", srcId + "-joints", "0"); writer.WriteAttElement(INPUT, inputAtrb, "WEIGHT", srcId + "-weights", "1"); // Collect bindings List <int> vMap = new List <int>(); var boneWeightSeq = new List <int>(); int idxOffs; writer.WriteStartElement("vcount"); // number of assigned bones per vertex for (int vn = 0; vn < vertCnt; ++vn) { // iterate mesh vertices vMap.Clear(); idxOffs = 0; Dictionary <int, float> wMap; for (int wn = 0; wn < weightsCnt; wn++) { // iterate assigned bone influences wMap = meshWeights[wn].mapping; if ((wMap?.ContainsKey(vn) ?? false) && !String.IsNullOrWhiteSpace(meshWeights[wn].groupName)) { vMap.Add(wn); // bone index int mN = 0; foreach (var kvp in wMap) { // iterate map to find index of weight // keys are not in order of addition, but sorted by weight-index // the dictionary MUST NOT have changed since writing out the values if (kvp.Key == vn) { vMap.Add(idxOffs + mN); // map element index break; } ++mN; } } idxOffs += wMap.Count; } writer.WriteValue(vMap.Count >> 1); writer.WriteWhitespace(SEPARATOR); boneWeightSeq.AddRange(vMap); } writer.WriteEndElement(); // vcount writer.WriteStartElement("v"); writer.WriteValue(boneWeightSeq); // List<T> boneWeightSeq.Clear(); writer.WriteEndElement(); // v writer.WriteEndElement(); // vertex weights } } // has bones or weight assigned writer.WriteEndElement(); // skin } writer.WriteEndElement(); // controller } }// each mesh }// if has nodes #endregion #region Morphes foreach (var mesh in baseMeshes) { var shapes = mesh.Shapes.Keys.ToArray(); if (null == mesh || shapes.Length < 1) { continue; } // Link shape keys to target mesh srcId = mesh.OriginName + "_" + mesh.Name; // origin or base name? writer.WriteStartElement("controller"); { writer.WriteAttributeString(ID, srcId + "-morph"); writer.WriteAttributeString(NAME, mesh.Name); writer.WriteStartElement("morph"); { writer.WriteAttributeString(SOURCE, "#" + mesh.Name + _MESH); //references the geometry writer.WriteAttributeString("method", "NORMALIZED"); writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId += "-targets"); writer.WriteStartElement("IDREF_array"); writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, shapes.Length.ToString()); writer.WriteString($"{mesh.Name}_{shapes[0]}{_MESH}"); for (int i = 1; i < shapes.Length; ++i) { writer.WriteWhitespace(SEPARATOR); writer.WriteString($"{mesh.Name}_{shapes[i]}{_MESH}"); } writer.WriteEndElement(); // array addTechCommon(srcId, shapes.Length, stride: 1, type: "IDREF", values: "IDREF"); writer.WriteEndElement(); // target source } srcId = mesh.OriginName + "_" + mesh.Name; writer.WriteStartElement(SOURCE); { writer.WriteAttributeString(ID, srcId += "-weights"); writer.WriteStartElement("float_array"); writer.WriteAttributeString(ID, srcId += _ARRAY); writer.WriteAttributeString(COUNT, shapes.Length.ToString()); writer.WriteValue(mesh.Shapes.Values); writer.WriteEndElement(); // float array addTechCommon(srcId, shapes.Length, stride: 1, type: FLOAT, values: "MORPH_WEIGHT"); writer.WriteEndElement(); // weight source } srcId = "#" + mesh.OriginName + "_" + mesh.Name; writer.WriteStartElement("targets"); writer.WriteAttElement(INPUT, ASTR_SEM_SRC, "MORPH_TARGET", srcId + "-targets"); writer.WriteAttElement(INPUT, ASTR_SEM_SRC, "MORPH_WEIGHT", srcId + "-weights"); writer.WriteEndElement(); //targets writer.WriteEndElement(); //morph } writer.WriteEndElement(); // morph controller } }// each non-morph writer.WriteEndElement(); // controller lib #endregion #region Object lib writer.WriteStartElement("library_visual_scenes"); { writer.WriteStartElement("visual_scene"); { writer.WriteAttributeString(ID, "Scene"); writer.WriteAttributeString(NAME, "Scene"); // Object nodes // Origin of the object instances if (data.rootNode.HasChilds()) { string node0_Id; // TODO: find root of multiple armatures // Armature writer.WriteStartElement("node"); { writer.WriteAttributeString(ID, baseName); writer.WriteAttributeString(NAME, baseName); writer.WriteAttributeString(TYPE, "NODE"); writer.WriteStartElement("matrix"); writer.WriteAttributeString(SID, "transform"); writer.WriteString(Transform3DF.Identity().ToString()); //data.rootNode.Transform.ToString() writer.WriteEndElement(); // matrix node0_Id = addJoint(nodesList[0]); // first must be skeleton writer.WriteEndElement(); // node } // Mesh nodes string nodeName; for (int fn = 1; fn < nodesList.Count; ++fn) { if (!String.IsNullOrWhiteSpace(nodeName = nodesList[fn].Name) && (baseMeshes.Count() > (fn - 1))) { // This fails when multiple meshes contain same name fragment: //boundMaterialNames: data.meshes.Find(msh => nodeName.Contains(msh.Name))?.MaterialSlots.Select(slot => slot.name), // This works only when nodes and related meshes in the list appear in same order! addMeshNode( nodesList[fn], boundMaterialNames: baseMeshes.ElementAt(fn - 1).MaterialSlots.Select(slot => slot.name), controllerId: $"{baseName}_{nodeName}{_SKIN}", skeletionId: node0_Id ); } } } writer.WriteEndElement(); // vis scene } writer.WriteEndElement(); // vis scene lib string addJoint(Mdl.CFrame node) { if (null == node) { throw new ArgumentException("CDaeSerializer::Serialize::addJoint: Passed 'null'-argument", "node"); } string skeletonId = String.IsNullOrWhiteSpace(baseName) ? node.Name : $"{baseName}_{node.Name}"; writer.WriteStartElement("node"); writer.WriteAttributeString(ID, skeletonId); writer.WriteAttributeString(NAME, node.Name); writer.WriteAttributeString(SID, node.Name); writer.WriteAttributeString(TYPE, "JOINT"); writer.WriteStartElement("matrix"); writer.WriteAttributeString(SID, "transform"); writer.WriteString(node.Transform.ToString()); writer.WriteEndElement(); // matrix if (node.HasChilds()) { foreach (var child in node.Childs) { addJoint(child); } } writer.WriteEndElement(); // node return(skeletonId); } void addMeshNode(Mdl.CFrame node, IEnumerable <string> boundMaterialNames, string controllerId, string skeletionId) { if ((null == node) || (null == boundMaterialNames) || (null == controllerId) || (null == skeletionId)) { throw new ArgumentException("CDaeSerializer::Serialize::addMeshNode: Passed 'null'-argument"); } writer.WriteStartElement("node"); writer.WriteAttributeString(ID, node.Name); // does not support nested meshes writer.WriteAttributeString(NAME, node.Name); writer.WriteAttributeString(TYPE, "NODE"); // This seem to have no effect at Blender import // //writer.WriteStartElement("matrix"); //{ writer.WriteAttributeString(SID, "transform"); // writer.WriteString(node.Transform.ToString()); // writer.WriteEndElement(); // matrix //} writer.WriteStartElement("instance_controller"); { writer.WriteAttributeString("url", "#" + controllerId); writer.WriteElementString("skeleton", "#" + skeletionId); writer.WriteStartElement("bind_material"); { writer.WriteStartElement("technique_common"); string[] atrb = new string[] { "symbol", "target" }; foreach (string mat in boundMaterialNames) { writer.WriteAttElement("instance_material", atrb, mat + "-material", $"#{mat}-material"); } writer.WriteEndElement(); // tech writer.WriteEndElement(); // bind mat } writer.WriteEndElement(); // instance controller } writer.WriteEndElement(); // node } } #endregion // Instances writer.WriteStartElement("scene"); writer.WriteAttElement("instance_visual_scene", new string[] { "url" }, "#Scene"); writer.WriteEndElement(); //scene writer.Close(); writer.Dispose(); return; void addTechCommon(string id, int count, int stride, string type, params string[] values) { writer.WriteStartElement("technique_common"); writer.WriteStartElement("accessor"); writer.WriteAttributeString(SOURCE, "#" + id); writer.WriteAttributeString(COUNT, count.ToString()); writer.WriteAttributeString("stride", stride.ToString()); string[] paramAtrb = new string[] { NAME, TYPE }; foreach (var val in values) { writer.WriteAttElement(PARAM, paramAtrb, val, type); } writer.WriteEndElement(); // accessor writer.WriteEndElement(); // technique } }// Serialize
public CFrame(string name) { Name = name ?? String.Empty; Transform = Transform3DF.Identity(); }
public static bool TryParse(CMdlFileNavigator reader, out CMesh parsedMesh) { parsedMesh = null; if (null == reader) { return(false); } CMesh mesh = new CMesh { Name = reader.ReadText() }; string meshTag; // Data could contain end block symbol. // If errors occur, the whole process must be canceled, // because reader can be at unexpected position (segmentation fault). bool passed = true; reader.SeekBlockStart(); // begin mesh while (passed && !String.IsNullOrEmpty(meshTag = reader.ReadTag(mTags))) { reader.SeekBlockStart(); // begin data switch (meshTag) { case TagInfo.vertex: // repeatedly entering here appends more vertice points to same mesh { float[] pt3d = new float[3]; passed &= reader.TryOnDataSequence(onElement : delegate() { // basically a foreach vec3 for (int i = 0; i < 3; ++i) { pt3d[i] = reader.ReadSingle(); } reader.MoveCursor(4); // we discard the 4th dimension (4B) mesh.AddVertex(pt3d); }); break; }//VRTX case TagInfo.normal: // repeatedly entering here appends more normals to same mesh { float[] pt3d = new float[3]; passed &= reader.TryOnDataSequence(onElement : delegate() { for (int i = 0; i < 3; ++i) { pt3d[i] = reader.ReadSingle(); } mesh.AddNormal(pt3d); // stores as flipped }); break; }//NRML case TagInfo.textureUV: // repeatedly entering here appends more coordinates to same mesh { float u, v; passed &= reader.TryOnDataSequence(onElement : delegate() { u = reader.ReadSingle(); v = reader.ReadSingle(); mesh.AddTexCoord(u, v); // expect normalized values and mirrors them }); break; }//TXUV case TagInfo.face: { ushort[] idx = new ushort[3]; passed &= reader.TryOnDataSequence(onElement : delegate() { for (int i = 0; i < 3; ++i) { idx[i] = reader.ReadUInt16(); } mesh.AssignFace(idx); }); break; }//FACE case TagInfo.materials: passed &= ReadInMaterialList(); // MTRL reader.SkipWhitespace(); // \r\n while ('}' != reader.PeekChar()) { // improve parsing stability on garbage text passages reader.SeekBlockEnd(); reader.SkipWhitespace(); } break; case TagInfo.weight: while (reader.IsAtTag(mBONE_ANSI)) { reader.MoveCursor(4); // skip 'BONE' passed &= ReadInWeights(out VertexGrp influence); if (!influence.IsNullOrEmpty()) { mesh.AssignWeight(influence); } } break; case TagInfo.unsupported1: try { int valCnt = reader.ReadInt32(); reader.MoveCursor(valCnt * 24); } catch { passed = false; } break; default: // try to skip with block end (but no error message) //passed = false; break; }//switch reader.SeekBlockEnd(); // end data } reader.SeekBlockEnd(); // end mesh //if (!mesh.IsValid()) //{ // return false; //} parsedMesh = mesh; return(passed); bool ReadInMaterialList() { // Read material count and index ranges uint size = 0; uint[] iStart; uint[] iStop; try{ size = reader.ReadUInt32(); // Empty MTRL bodys might follow that are not counted! iStart = new uint[size]; iStop = new uint[size]; for (int i = 0; i < size; i++) { iStart[i] = reader.ReadUInt32(); iStop[i] = reader.ReadUInt32(); } } catch { return(false); } // Begin of material description string matNam; long currPos; int bindsCnt; for (int matN = 0; matN < size; ++matN) { reader.SkipWhitespace(); if (!reader.IsAtTag(mMTRL_ANSI)) { return(false); } reader.MoveCursor(4); // Fetch material name matNam = null; currPos = reader.ReaderPos; // right after tag CTextUtil.DoOnNewLine(reader.Data, currPos, onNewLine : delegate(long nLPos) { if (nLPos > currPos) { byte[] lineSeq = new byte[nLPos - currPos - 1]; // exclude '{' Array.Copy(reader.Data, currPos, lineSeq, 0L, lineSeq.Length); matNam = CTextUtil.AnsiToStr(lineSeq).Trim(); // remove preceeding whitespace currPos = reader.ReaderPos = nLPos + 2; // after \r\n } }); if (String.IsNullOrEmpty(matNam)) { return(false); } mesh?.AssignMaterial(matNam, iStart[matN], iStop[matN]); // Determine lenght of description part bindsCnt = 0; reader.SeekBlockStart(); // after SBST{ CTextUtil.DoOnNewLine(reader.Data, reader.ReaderPos, delegate(long nLPos) { reader.ReaderPos = nLPos + 2; // in newline if (!Int32.TryParse(CTextUtil.AnsiToStr(reader.PeekLine()).Trim(), out bindsCnt)) { bindsCnt = -1; } }); if (bindsCnt < 1) { return(false); } // Skip material description do { reader.SeekBlockEnd(); // end of binding block bindsCnt--; } while (bindsCnt > 0); reader.SeekBlockEnd(); // end of material description reader.SeekBlockEnd(); // end of material block }//for matN return(true); // may contain no materials } bool ReadInWeights(out VertexGrp boneInfluence) { boneInfluence = new VertexGrp(); // Read bone influence reader.SeekBlockStart(); string boneNam = reader.ReadText(); uint size = 0; int[] ids; float[] weights; try{ size = reader.ReadUInt32(); ids = new int[size]; weights = new float[size]; for (uint i = 0; i < size; ++i) { ids[i] = reader.ReadInt32(); } for (uint i = 0; i < size; ++i) { weights[i] = reader.ReadSingle(); } } catch { return(false); } // Read bind matrix float[] buff = new float[16]; int numVal = 0; Transform3DF mat; try { for (; (numVal < buff.Length) && reader.HasMore(4); ++numVal) // 4B float { buff[numVal] = reader.ReadSingle(); } mat = new Transform3DF { m00 = buff[0], m01 = buff[4], m02 = buff[8], m03 = buff[12], m10 = buff[1], m11 = buff[5], m12 = buff[9], m13 = buff[13], m20 = buff[2], m21 = buff[6], m22 = buff[10], m23 = buff[14] }; } catch { if (numVal < 64) { reader.MoveCursor(64 - numVal * 4); // sizeOf(mat) == 64 } mat = Transform3DF.Identity(); } reader.SeekBlockEnd(); // Assign bone weight map if (size > 0) { //var weightMap = new System.Collections.Specialized.OrderedDictionary(); var weightMap = new Dictionary <int, float>(); for (int i = 0; i < size; ++i) { weightMap[ids[i]] = weights[i]; // overwrite if exist, add if not } boneInfluence.groupName = boneNam; boneInfluence.mapping = weightMap; boneInfluence.bindMatrix = mat; } return(true); } }//tryparse