public static IPXVertex LastVert(this IPXFace f, IPXVertex v, IPXVertex vv) { if (f.Vertex1 == v && f.Vertex2 == vv) { return(f.Vertex3); } if (f.Vertex2 == v && f.Vertex1 == vv) { return(f.Vertex3); } if (f.Vertex2 == v && f.Vertex3 == vv) { return(f.Vertex1); } if (f.Vertex3 == v && f.Vertex2 == vv) { return(f.Vertex1); } if (f.Vertex1 == v && f.Vertex3 == vv) { return(f.Vertex2); } if (f.Vertex3 == v && f.Vertex1 == vv) { return(f.Vertex2); } throw new Exception("頂点が"); }
// // ------- 判定 ------- // /// <summary> /// 指定座標が面のUV座標内であるかどうかを判定する /// </summary> public static bool UVIsInclude(this IPXFace face, V2 point) { // 外積の符号が全て等しいかの変数 bool allUpper0 = true; bool allLower0 = true; var uv = face.ExtructUV(); // 辺ベクトルを得る var V1V2 = uv[1] - uv[0]; var V2V3 = uv[2] - uv[1]; var V3V1 = uv[0] - uv[2]; // 外積の方向を調査 allUpper0 &= V1V2.Cross(point - uv[1]).Z >= 0.0f; allLower0 &= V1V2.Cross(point - uv[1]).Z <= 0.0f; allUpper0 &= V2V3.Cross(point - uv[2]).Z >= 0.0f; allLower0 &= V2V3.Cross(point - uv[2]).Z <= 0.0f; allUpper0 &= V3V1.Cross(point - uv[0]).Z >= 0.0f; allLower0 &= V3V1.Cross(point - uv[0]).Z <= 0.0f; // 外積のZ座標の符号が全て等しければ内側 return(allUpper0 | allLower0); }
public static V2[] ExtructUV(this IPXFace face) { return(new V2[] { face.Vertex1.UV, face.Vertex2.UV, face.Vertex3.UV }); }
public static List <PXSide> FromFace(IPXFace face) { var list = new List <PXSide>(); list.Add(new PXSide(face.Vertex1, face.Vertex2)); list.Add(new PXSide(face.Vertex2, face.Vertex3)); list.Add(new PXSide(face.Vertex3, face.Vertex1)); return(list); }
private int GetFaceIndex(IPXFace face, out int MaterialIndex) { int MaterialCount = 0; for (MaterialIndex = 0; MaterialIndex < pmx.Material.Count; MaterialCount += pmx.Material[MaterialIndex++].Faces.Count) { var id = pmx.Material[MaterialIndex].Faces.IndexOf(face); if (id != -1) { return(MaterialCount + id); } } return(-1); }
/// <summary> /// UV座標の境界領域を計算する /// </summary> /// <param name="face">対象面</param> /// <returns>UV座標の境界領域</returns> public static (V2 min, V2 max) ComputeUVBoundingBox(this IPXFace face) { (V2 min, V2 max)bb = (new V2(float.MaxValue, float.MaxValue), new V2(float.MinValue, float.MinValue)); foreach (var uv in face.ExtructUV()) { bb.min.X = bb.min.X < uv.X ? bb.min.X : uv.X; bb.min.Y = bb.min.Y < uv.Y ? bb.min.Y : uv.Y; bb.max.X = bb.max.X > uv.X ? bb.max.X : uv.X; bb.max.Y = bb.max.Y > uv.Y ? bb.max.Y : uv.Y; } return(bb); }
private int GetFaceIndex(IPXFace face, int MaterialIndex) { int materialCount = 0; for (int i = 0; i < MaterialIndex; i++) { materialCount += pmx.Material[i].Faces.Count; } int id = pmx.Material[MaterialIndex].Faces.IndexOf(face); if (id < 0) { return(-1); } else { return(materialCount + id); } }
public static void ReAssignFace(this IPXFace f, IPXVertex lastv, IPXVertex v1, IPXVertex v2) { if (f.Vertex1 == lastv) { f.Vertex2 = v1; f.Vertex3 = v2; return; } if (f.Vertex2 == lastv) { f.Vertex1 = v1; f.Vertex3 = v2; return; } if (f.Vertex3 == lastv) { f.Vertex1 = v1; f.Vertex2 = v2; return; } throw new Exception("最後の頂点が面にありませんでした"); }
/// <summary> /// <para>頂点インデックス配列に変換</para> /// <para>使用数が多いようなら材質のFaceToVertexIndices拡張メソッドの使用を検討すること</para> /// <para>頂点インデックス辞書の作成分のオーバーヘッドが発生するため</para> /// </summary> /// <param name="face">変換元面</param> /// <param name="pmx">頂点を内包したPMXデータ</param> /// <returns>面を構成する頂点のインデックス配列</returns> public static int[] ToVertexIndices(this IPXFace face, IPXPmx pmx) { var vtxIds = pmx.Vertex.Select((v, i) => (v, i)).ToDictionary(p => p.v, p => p.i); return(new int[] { vtxIds[face.Vertex1], vtxIds[face.Vertex2], vtxIds[face.Vertex3] }); }
/// <summary> /// 頂点配列に変換 /// </summary> /// <param name="face">変換元面</param> /// <returns>面を構成する頂点の配列</returns> public static IPXVertex[] ToVertices(this IPXFace face) => new IPXVertex[] { face.Vertex1, face.Vertex2, face.Vertex3 };
public static IEnumerable <UVEdge> FromFace(IPXFace face) => new[] { new UVEdge(face.Vertex1, face.Vertex2), new UVEdge(face.Vertex2, face.Vertex3), new UVEdge(face.Vertex3, face.Vertex1) };
/// <summary> /// 面を構成する頂点のUV座標を表示する文字列を返す /// </summary> /// <param name="f">表示する面</param> /// <param name="format">書式</param> /// <returns>"(U座標, V座標), (U座標, V座標), (U座標, V座標)"</returns> public static string PrintUV(this IPXFace f, string format = "") => $"{f.Vertex1.UV.Print(format)}, {f.Vertex2.UV.Print(format)}, {f.Vertex3.UV.Print(format)}";
public static Point[] ToPoint(this IPXFace face, int Width, int Height) { return(new Point[3] { face.Vertex1.UV.ToPoint(Width, Height), face.Vertex2.UV.ToPoint(Width, Height), face.Vertex3.UV.ToPoint(Width, Height) }); }
public static string PrintUV(this IPXFace f) => $"{f.Vertex1.UV.Print()}, {f.Vertex2.UV.Print()}, {f.Vertex3.UV.Print()}";
public static IPXVertex[] GetVertexArray(this IPXFace f) { return(new IPXVertex[] { f.Vertex1, f.Vertex2, f.Vertex3 }); }
/// <summary> /// Parses the Wavefront Object file at the provided path and returns the operation's result. /// </summary> public static ImportResult Import(string path, IPXPmxBuilder builder, ImportSettings settings, IOProgress progress) { // Cancel the process if needed if (progress.CancellationToken.IsCancellationRequested) { progress.CancellationToken.ThrowIfCancellationRequested(); } IPXPmx pmx = builder.Pmx(); pmx.Clear(); pmx.ModelInfo.ModelName = pmx.ModelInfo.ModelNameE = Path.GetFileNameWithoutExtension(path); pmx.ModelInfo.Comment = pmx.ModelInfo.CommentE = "(Imported from OBJ by WPlugins.ObjIO)"; StreamReader reader = null; System.Globalization.NumberFormatInfo fi = System.Globalization.NumberFormatInfo.InvariantInfo; System.Globalization.NumberStyles ns = System.Globalization.NumberStyles.Float; // Model elements List <V3> vList = new List <V3>(); List <V2> vtList = new List <V2>(); List <V3> vnList = new List <V3>(); Dictionary <Tuple <int, int, int>, int> vertexDictionary = new Dictionary <Tuple <int, int, int>, int>(); Dictionary <string, IPXMaterial> materials = new Dictionary <string, IPXMaterial>(); IPXMaterial currentMaterial = null; // Values derived from settings V3 positionScale = new V3(settings.ScaleX, settings.ScaleY, settings.ScaleZ) * (settings.UseMetricUnits ? 0.254f : 0.1f); // Statistics int lineNumber = 0; try { reader = new StreamReader(path); char[] separator = { ' ' }; while (!reader.EndOfStream) { System.Threading.Thread.Sleep(2); // Cancel the process if needed if (progress.CancellationToken.IsCancellationRequested) { progress.CancellationToken.ThrowIfCancellationRequested(); } string line = reader.ReadLine().Trim(); ++lineNumber; ++progress.LineNumber; progress.Report(IOProgress.Percent(reader.BaseStream.Position, reader.BaseStream.Length)); // Skip empty lines and comments if (string.IsNullOrWhiteSpace(line) || line[0] == '#') { continue; } string[] split = line.Split(separator, StringSplitOptions.RemoveEmptyEntries); switch (split[0]) { // Vertex position case "v": try { float x = float.Parse(split[1], ns, fi); float y = float.Parse(split[2], ns, fi); float z = float.Parse(split[3], ns, fi); vList.Add(new V3(x, y, -z)); } catch (FormatException ex) { if (progress.ReportError(string.Format("A format exception has occured: {0}", line))) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } vList.Add(new V3()); } break; // Vertex texture coordinates case "vt": try { // Technically this can be a V3 or any vector, but PMX only uses the first two elements for the main UV channel. float x = float.Parse(split[1], ns, fi); float y = float.Parse(split[2], ns, fi); vtList.Add(new V2(x, -y)); } catch (FormatException ex) { if (progress.ReportError(string.Format("A format exception has occured: {0}", line))) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } vtList.Add(new V2()); } break; // Vertex normal case "vn": try { float x = float.Parse(split[1], ns, fi); float y = float.Parse(split[2], ns, fi); float z = float.Parse(split[3], ns, fi); vnList.Add(new V3(x, y, -z)); } catch (FormatException ex) { if (progress.ReportError(string.Format("A format exception has occured: {0}", line))) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } vnList.Add(new V3()); } break; // Face definition case "f": if (currentMaterial == null) { progress.ReportWarning(string.Format("Encountered a face record when no active group was set.", lineNumber)); currentMaterial = builder.Material(); } // Triangle if (split.Length == 4) { int v = 0; int vt = 0; int vn = 0; bool newVertex; try { // Split each vertex assignment triple into its respective v/vt/vn indices. GetVertexElements(split[1], out v, out vt, out vn); // Based on the indices, determine if the vertex assignment is unique or already exists. A vertex is considered unique if one or more index is different, regardless of the vectors they represent. newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index1); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); // The new vertex is added to the end of the list, making its index equal to the list's count before the addition. if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex1 = pmx.Vertex[index1]; // Repeat the same process for the rest of the vertex triples. GetVertexElements(split[2], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index2); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex2 = pmx.Vertex[index2]; GetVertexElements(split[3], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index3); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex3 = pmx.Vertex[index3]; // Build the triangle and assign the vertices; use reverse order and negative normal vectors if the triangles are reversed. IPXFace face = builder.Face(); if (settings.FlipFaces) { vertex1.Normal *= -1; vertex2.Normal *= -1; vertex3.Normal *= -1; face.Vertex1 = vertex1; face.Vertex2 = vertex2; face.Vertex3 = vertex3; } else { face.Vertex1 = vertex3; face.Vertex2 = vertex2; face.Vertex3 = vertex1; } currentMaterial.Faces.Add(face); } catch (Exception ex) { if (progress.ReportError(ex.ToString())) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } } } // Quad else if (split.Length == 5) { int v = 0; int vt = 0; int vn = 0; bool newVertex; try { GetVertexElements(split[1], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index1); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex1 = pmx.Vertex[index1]; GetVertexElements(split[2], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index2); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex2 = pmx.Vertex[index2]; GetVertexElements(split[3], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index3); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex3 = pmx.Vertex[index3]; GetVertexElements(split[4], out v, out vt, out vn); newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index4); if (newVertex) { IPXVertex vert = builder.Vertex(); pmx.Vertex.Add(vert); if (v >= 0) { vert.Position = vList[v] * positionScale; } if (vt >= 0) { vert.UV = vtList[vt]; } if (vn >= 0) { vert.Normal = vnList[vn]; } } IPXVertex vertex4 = pmx.Vertex[index4]; int faceIndex1 = 0, faceIndex2 = 0; IPXFace face = builder.Face(); if (settings.FlipFaces) { face.Vertex3 = settings.TurnQuads ? vertex3 : vertex4; face.Vertex2 = vertex2; face.Vertex1 = vertex1; currentMaterial.Faces.Add(face); faceIndex1 = currentMaterial.Faces.Count - 1; face = builder.Face(); face.Vertex1 = settings.TurnQuads ? vertex1 : vertex2; face.Vertex2 = vertex3; face.Vertex3 = vertex4; currentMaterial.Faces.Add(face); faceIndex2 = currentMaterial.Faces.Count - 1; } else { face.Vertex1 = settings.TurnQuads ? vertex3 : vertex4; face.Vertex2 = vertex2; face.Vertex3 = vertex1; currentMaterial.Faces.Add(face); faceIndex1 = currentMaterial.Faces.Count - 1; face = builder.Face(); face.Vertex3 = settings.TurnQuads ? vertex1 : vertex2; face.Vertex2 = vertex3; face.Vertex1 = vertex4; currentMaterial.Faces.Add(face); faceIndex2 = currentMaterial.Faces.Count - 1; } if (settings.SaveTrianglePairs) { currentMaterial.Memo += string.Format("({0},{1})", faceIndex1, faceIndex2); } } catch (Exception ex) { if (progress.ReportError(ex.ToString())) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } } } else { if (progress.ReportError(string.Format("The OBJ file contains a polygon with an invalid number of vertices. Currently only triangles and quads are supported. Line content: {0}", line))) { return(ImportResult.Fail(new InvalidOperationException("Invalid polygon"), progress.WarningCount, progress.ErrorCount)); } } break; // Group assignment defines which PMX object (IPXMaterial instance) the subsequent faces belong to. case "g": currentMaterial = builder.Material(); currentMaterial.Name = currentMaterial.NameE = line.Trim().Substring(2); progress.Report("New object: " + currentMaterial.Name); pmx.Material.Add(currentMaterial); // Set default properties currentMaterial.Diffuse = new V4(1, 1, 1, 1); currentMaterial.Ambient = new V3(0.5f, 0.5f, 0.5f); break; // Material assignment defines which material template should be applied to the currently active PMX object. Any number of PMX objects can refer to a single material template. case "usemtl": if (currentMaterial == null) { progress.ReportWarning(string.Format("Encountered a material template reference when no active group was set.", lineNumber)); currentMaterial = builder.Material(); } { string name = line.Trim().Substring(7); IPXMaterial m = currentMaterial; // Active material IPXMaterial t = materials[name]; // Template material m.Diffuse = t.Diffuse; m.Specular = t.Specular; m.Power = t.Power; m.Ambient = t.Ambient; m.Diffuse = t.Diffuse; m.SelfShadow = t.SelfShadow; m.SelfShadowMap = t.SelfShadowMap; m.Shadow = t.Shadow; m.Tex = t.Tex; m.EdgeSize = t.EdgeSize; m.EdgeColor = t.EdgeColor; m.Edge = t.Edge; } break; // Material library, may occur multiple times in a model. case "mtllib": string materialLibraryName = line.Substring(7); progress.Report("Importing materials from " + materialLibraryName); // Try relative path string materialLibraryPath = Path.Combine(Path.GetDirectoryName(path), materialLibraryName); if (!File.Exists(materialLibraryPath)) { // Try absolute path materialLibraryPath = materialLibraryName; if (!File.Exists(materialLibraryPath)) { progress.ReportError(string.Format("Material library not found ({0}).", materialLibraryName)); break; } } Dictionary <string, IPXMaterial> tempDict = ImportMaterials(materialLibraryPath, builder, settings, progress); foreach (KeyValuePair <string, IPXMaterial> kvp in tempDict) { if (materials.ContainsKey(kvp.Key)) { progress.ReportWarning(string.Format("Duplicate material {0} imported from {1} has been discarded.", kvp.Key, materialLibraryName)); } else { materials.Add(kvp.Key, kvp.Value); } } progress.Report(string.Format("Imported {0} materials from {1}.", tempDict.Count, materialLibraryName)); break; // Smoothing group assignment (unused) case "s": break; default: break; } } // Second pass for bone weights and transformations because I'm lazy IPXBone bone = null; if (settings.CreateBone != ImportSettings.CreateBoneMode.None) { bone = builder.Bone(); bone.Name = bone.NameE = pmx.ModelInfo.ModelName.Replace(' ', '_'); } foreach (IPXVertex vertex in pmx.Vertex) { // Bone if (settings.CreateBone == ImportSettings.CreateBoneMode.Average) { bone.Position += vertex.Position; } vertex.Bone1 = settings.CreateBone != ImportSettings.CreateBoneMode.None ? bone : null; vertex.Weight1 = 1.0f; vertex.Bone2 = vertex.Bone3 = vertex.Bone4 = null; vertex.Weight2 = vertex.Weight3 = vertex.Weight4 = 0; // Axis swap if (settings.SwapYZ) { float temp = vertex.Position.Y; vertex.Position.Y = vertex.Position.Z; vertex.Position.Z = temp; temp = vertex.Normal.Y; vertex.Normal.Y = vertex.Normal.Z; vertex.Normal.Z = temp; } } if (settings.CreateBone == ImportSettings.CreateBoneMode.Average) { bone.Position /= pmx.Vertex.Count; } } catch (OperationCanceledException) { throw; } catch (Exception ex) { if (progress.ReportError(ex.ToString())) { return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount)); } } finally { if (reader != null) { reader.Close(); reader = null; } } return(ImportResult.Success(pmx, progress.WarningCount, progress.ErrorCount)); }
private int GetFaceIndex(IPXFace face) { int _; return(GetFaceIndex(face, out _)); }
public static Point[] ToPoint(this IPXFace face) { return(new Point[3] { face.Vertex1.UV.ToPoint(), face.Vertex2.UV.ToPoint(), face.Vertex3.UV.ToPoint() }); }
public static string FaceToString(this IPXFace face) { return(" { " + face.Vertex1.VertToString() + face.Vertex2.VertToString() + face.Vertex3.VertToString() + " } "); }
public static bool IsContainVert(this IPXFace f, IPXVertex v) { return(f.Vertex1 == v || f.Vertex2 == v || f.Vertex3 == v); }
/// <summary> /// 面を構成する頂点のUV座標の配列に変換する /// </summary> /// <param name="face">変換対象面</param> /// <returns>点の配列</returns> public static PointF[] UVToPointF(this IPXFace face) { return(new PointF[3] { face.Vertex1.UV.ToPointF(), face.Vertex2.UV.ToPointF(), face.Vertex3.UV.ToPointF() }); }