public IPXPmx Import(string path, IPERunArgs args) { try { if (File.Exists(path + ".log")) { File.Delete(path + ".log"); } } catch { } IPXPmxBuilder builder = PEStaticBuilder.Pmx; ImportForm form = new ImportForm(path, args); // This call blocks the thread until the dialog is closed. DialogResult dialogResult = form.ShowDialog(); if (dialogResult == DialogResult.OK) { // DialogResult.OK does not mean a successful outcome, only that the user has initiated the process and then the form was closed for whatever reason. // The outcome of the actual import operation is indicated by ImportForm.ImportResult. switch (form.ImportResult.Result) { case ImportResult.ResultType.Success: WriteLogFile(path + ".log", string.Format("Import operation was completed successfully with {0} errors and {1} warnings.", form.ImportResult.ErrorCount, form.ImportResult.WarningCount)); return((IPXPmx)form.ImportResult.Pmx); case ImportResult.ResultType.Fail: MessageBox.Show(string.Format("The import operation has failed.\n\n{0}", form.ImportResult.Pmx as Exception)); WriteLogFile(path + ".log", string.Format("Import operation has failed with {0} errors and {1} warnings.", form.ImportResult.ErrorCount, form.ImportResult.WarningCount)); break; case ImportResult.ResultType.Cancel: MessageBox.Show("The operation was cancelled by the user."); WriteLogFile(path + ".log", "The operation was cancelled by the user."); break; default: MessageBox.Show("The import operation was halted before a valid result was obtained."); WriteLogFile(path + ".log", "The import operation was halted for an unknown reason before a valid result was obtained."); break; } } else { MessageBox.Show("Test"); } // If control reaches this point, then the import operation was not completed successfully and there is no valid data to return to the application. return(null); }
public void RegistToPmx(IPXPmx pmx, IPXPmxBuilder bld, FileFormat.MQOFile mqo, WorkVertexDict dict, BackgroundWorker bw) { int N = list.Length; for (int i = 0; i < N; i++) { bw.ReportProgress(100 * i / N, "面の登録中"); list[i].ForEach(f => { var xf = bld.Face(); xf.Vertex1 = dict.GetVertex(f.V0); xf.Vertex2 = dict.GetVertex(f.V1); xf.Vertex3 = dict.GetVertex(f.V2); pmx.Material[i].Faces.Add(xf); }); } }
private async void executeButton_Click(object sender, EventArgs e) { cancelButton.Enabled = true; IPXPmx pmx = _args.Host.Connector.Pmx.GetCurrentState(); IPXPmxBuilder builder = _args.Host.Builder.Pmx; XmlDocument doc = new XmlDocument(); doc.Load(pathText.Text); RunnerResult result = await Task.Run(() => { return(Runner.Execute(doc, pmx, builder, _progress)); }); if (result == RunnerResult.Success) { UpdatePmx(pmx); } cancelButton.Enabled = false; }
// Generate straight bone chain public static IPXBone[] GenerateChain(IPXPmxBuilder builder, int count, Vector3 distance, Vector3 center, string name, string nameE) { IPXBone[] bones = new IPXBone[count]; for (int i = 0; i < count; ++i) { IPXBone bone = builder.Bone(); bone.Name = name.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.NameE = nameE.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.Position = center + (distance * i); if (i > 0) { bone.Parent = bones[i - 1]; bone.Parent.ToBone = bone; } bones[i] = bone; } return(bones); }
// Create segmented bone chain public static IPXBone[] GenerateSegmentedChain(IPXPmxBuilder builder, int count, Vector3[] points, string name, string nameE) { IPXBone[] bones = new IPXBone[count]; int segmentCount = points.Length - 1; float totalLength = 0; float[] segmentLengths = new float[segmentCount]; // Determine the lengths of segments for (int i = 0; i < segmentCount; ++i) { float length = points[i].Distance(points[i + 1]); segmentLengths[i] = length; totalLength += length; } // Determine the number of links that can fit into each segment float avgDist = totalLength / count; int[] bonesPerSegment = new int[segmentCount]; int remaining = count; for (int i = 0; i < segmentCount - 1; ++i) { bonesPerSegment[i] = Mathf.RoundToInt(segmentLengths[i] / avgDist); remaining -= bonesPerSegment[i]; } // Put any remaining bones into the last segment bonesPerSegment[segmentCount - 1] = remaining; // Create the bones for (int i = 0; i < segmentCount; ++i) { System.Windows.Forms.MessageBox.Show($"{i}\t{segmentLengths[i]}"); } return(bones); }
public void RegistToPmx(IPXPmx pmx, IPXPmxBuilder bld, FileFormat.MQOFile mqo, BackgroundWorker bw) { int N = dict.Count; for (int i = 0; i < N; i++) { bw.ReportProgress(100 * i / N, "頂点の登録中"); var wv = dict[i]; IPXVertex v = bld.Vertex(); var mObj = mqo.Object[wv.ObjID]; var mv = mObj.Vertex[wv.VertID]; v.Position.X = (float)(mv.X); v.Position.Y = (float)(mv.Y); v.Position.Z = -(float)(mv.Z); FileFormat.MQOUV muv = mObj.UV[wv.UvID]; v.UV.U = (float)muv.U; v.UV.V = (float)muv.V; v.Normal.X = (float)mObj.Normal[wv.NormID].X; v.Normal.Y = (float)mObj.Normal[wv.NormID].Y; v.Normal.Z = -(float)mObj.Normal[wv.NormID].Z; wv.vertex = v; pmx.Vertex.Add(v); } }
// Create uneven bone chain along spline public static IPXBone[] GenerateSplineChain(IPXPmxBuilder builder, int count, Vector3[] points, string name, string nameE) { IPXBone[] bones = new IPXBone[count]; // Construct a cubic Bézier curve from the points // Place a link at every t = (1 / count) point float dt = 1.0f / count; for (int i = 0; i < count; ++i) { IPXBone bone = builder.Bone(); bone.Name = name.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.NameE = nameE.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.Position = Mathf.Curve.BezierPoint(dt * i, points); if (i > 0) { bone.Parent = bones[i - 1]; bone.Parent.ToBone = bone; } bones[i] = bone; } return(bones); }
//DoWorkイベントハンドラ private void ProgressDialog_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (BackgroundWorker)sender; bw.ReportProgress(0, "ファイル読み込み中(バーは動きません (^-^;)"); using (FileFormat.MQOFile mqo = FileFormat.MQOFile.load(mqopath, true)) // 三角面化して読み込む { if (mqo == null) throw new Exception("読み込み失敗。おそらくmqoファイルの構文エラー。"); if (mqo.Object.Count == 0) throw new Exception("オブジェクトが空です。"); // pmx作成 bld = PEStaticBuilder.Pmx; pmx = bld.Pmx(); pmx.Clear(); // モデル名は最初のオブジェクト名を利用する pmx.ModelInfo.ModelName = mqo.Object[0].Name; // 材質 int mc = mqo.Material.Count; if (mc == 0) throw new Exception("材質がありません。少なくとも1つ材質が必要です。"); int cw = 100 / mc; int pc = 0; mqo.Material.ForEach(m => { bw.ReportProgress(cw * pc++, "材質の変換中"); IPXMaterial pm = bld.Material(); pm.Name = m.Name; pm.Diffuse.R = (float)(m.Color.R * m.Diffuse); pm.Diffuse.G = (float)(m.Color.G * m.Diffuse); pm.Diffuse.B = (float)(m.Color.B * m.Diffuse); pm.Diffuse.A = (float)m.Color.A; pm.Ambient.R = (float)(m.Color.R * m.Ambient); pm.Ambient.G = (float)(m.Color.G * m.Ambient); pm.Ambient.B = (float)(m.Color.B * m.Ambient); pm.Specular.R = (float)(m.Color.R * m.Specular); pm.Specular.G = (float)(m.Color.G * m.Specular); pm.Specular.B = (float)(m.Color.B * m.Specular); pm.Power = (float)m.Power; pm.Tex = m.Tex; pmx.Material.Add(pm); }); // 各オブジェクトを処理 // ただし、非表示オブジェクトはスキップ mc = mqo.Object.Count; cw = 100 / mc; bw.ReportProgress(0, "法線を計算中"); Parallel.ForEach(mqo.Object, mObj => { if (mObj.Visible) mObj.CalcNormals(); bw.ReportProgress(cw, 1); }); // 先に頂点をすべて登録してから面を登録する // 頂点登録と面登録を交互に行うととんでもなく遅くなる mc = mqo.Material.Count; WorkFaceList workfacelist = new WorkFaceList(mc); WorkVertexDict workvertexdict = new WorkVertexDict(); mc = mqo.Object.Count; cw = 100 / mc; pc = 0; for (int objID=0; objID<mc; objID++) { var mObj = mqo.Object[objID]; bw.ReportProgress(cw * pc++, String.Format("'{0}'の変換中", mObj.Name)); mObj.Face.ForEach(fc => { if (!mObj.Visible) return; // 非表示オブジェクトは無視 // 材質割り当てのない面は材質0として処理 int matID = fc.MatID < 0 ? 0 : fc.MatID; Func<int, int> get_vertex = i => workvertexdict.RegistVertex(objID, fc.VertexID[i], fc.UVID[i], fc.NormalID[i]); workfacelist.AddFace(matID, get_vertex(0), get_vertex(1), get_vertex(2)); }); } workvertexdict.RegistToPmx(pmx, bld, mqo, bw); workfacelist.RegistToPmx(pmx, bld, mqo, workvertexdict, bw); } }
public void RegistToPmx(IPXPmx pmx, IPXPmxBuilder bld, FileFormat.MQOFile mqo, BackgroundWorker bw) { int N = dict.Count; for(int i=0; i<N; i++) { bw.ReportProgress(100 * i / N, "頂点の登録中"); var wv = dict[i]; IPXVertex v = bld.Vertex(); var mObj = mqo.Object[wv.ObjID]; var mv = mObj.Vertex[wv.VertID]; v.Position.X = (float)(mv.X); v.Position.Y = (float)(mv.Y); v.Position.Z = -(float)(mv.Z); FileFormat.MQOUV muv = mObj.UV[wv.UvID]; v.UV.U = (float)muv.U; v.UV.V = (float)muv.V; v.Normal.X = (float)mObj.Normal[wv.NormID].X; v.Normal.Y = (float)mObj.Normal[wv.NormID].Y; v.Normal.Z = -(float)mObj.Normal[wv.NormID].Z; wv.vertex = v; pmx.Vertex.Add(v); } }
public RunnerArgs(XmlDocument document, IPXPmx pmx, IPXPmxBuilder builder) { Document = document; Pmx = pmx; Builder = builder; }
/// <summary> /// Parses the Material Template Library at the provided path and returns a list of materials. /// </summary> private static Dictionary <string, IPXMaterial> ImportMaterials(string path, IPXPmxBuilder builder, ImportSettings settings, IOProgress progress) { // Cancel the process if needed if (progress.CancellationToken.IsCancellationRequested) { progress.CancellationToken.ThrowIfCancellationRequested(); } StreamReader reader = null; System.Globalization.NumberFormatInfo fi = System.Globalization.NumberFormatInfo.InvariantInfo; System.Globalization.NumberStyles ns = System.Globalization.NumberStyles.Float; Dictionary <string, IPXMaterial> materials = new Dictionary <string, IPXMaterial>(); IPXMaterial current = null; int lineNumber = 0; char[] separator = { ' ' }; try { reader = new StreamReader(path); while (!reader.EndOfStream) { // Cancel the process if needed if (progress.CancellationToken.IsCancellationRequested) { progress.CancellationToken.ThrowIfCancellationRequested(); } string line = reader.ReadLine().Trim(); ++lineNumber; if (string.IsNullOrWhiteSpace(line) || line[0] == '#') { continue; } string[] split = line.Split(separator, StringSplitOptions.RemoveEmptyEntries); switch (split[0]) { // Diffuse color and opacity case "Kd": if (current != null) { float r = 0, g = 0, b = 0, a = 1; float.TryParse(split[1], ns, fi, out r); float.TryParse(split[2], ns, fi, out g); float.TryParse(split[3], ns, fi, out b); if (split.Length > 4) { float.TryParse(split[4], ns, fi, out a); } current.Diffuse = new V4(r, g, b, a); } break; // Ambient - used as emissive case "Ka": if (current != null) { float r = 0, g = 0, b = 0; float.TryParse(split[1], ns, fi, out r); float.TryParse(split[2], ns, fi, out g); float.TryParse(split[3], ns, fi, out b); current.Ambient = new V3(r, g, b); } break; // Specular color case "Ks": if (current != null) { float r = 0, g = 0, b = 0; float.TryParse(split[1], ns, fi, out r); float.TryParse(split[2], ns, fi, out g); float.TryParse(split[3], ns, fi, out b); current.Specular = new V3(r, g, b); } break; // Emissive case "Ke": if (current != null) { float r = 0, g = 0, b = 0; float.TryParse(split[1], ns, fi, out r); float.TryParse(split[2], ns, fi, out g); float.TryParse(split[3], ns, fi, out b); current.Ambient = new V3(r, g, b); } break; // Opacity (1 is fully opaque) case "d": if (current != null) { if (float.TryParse(split[1], ns, fi, out float a)) { current.Diffuse.A = a; } } break; // Transparency (1 is fully transparent) case "Tr": if (current != null) { if (float.TryParse(split[1], ns, fi, out float a)) { current.Diffuse.A = 1.0f - a; } } break; // Specular exponent case "Ns": if (current != null) { if (float.TryParse(split[1], ns, fi, out float a)) { current.Power = a; } } break; // Illumination mode (unused) case "illum": break; // Diffuse map case "map_Kd": if (current != null) { current.Tex = line.Substring(7); if (settings.WhiteMaterialIfTextured) { current.Diffuse = new V4(1, 1, 1, current.Diffuse.A); current.Ambient = new V3(0.5f, 0.5f, 0.5f); } } break; // Specular map case "map_Ks": break; // Ambient map case "map_Ka": break; // Begin a new material case "newmtl": current = builder.Material(); current.Name = current.NameE = line.Substring(7); current.Diffuse = new V4(1, 1, 1, 1); current.Ambient = new V3(0.5f, 0.5f, 0.5f); current.Specular = new V3(0, 0, 0); current.Power = 20; current.SelfShadow = true; current.SelfShadowMap = true; current.Shadow = true; current.BothDraw = false; if (materials.ContainsKey(current.Name)) { progress.ReportWarning(string.Format("[MTL {1}] Duplicate material found: \"{0}\".", current.Name, lineNumber)); } else { materials.Add(current.Name, current); } break; default: break; } } } catch (OperationCanceledException) { throw; } finally { if (reader != null) { reader.Close(); reader = null; } } return(materials); }
// Create a physics chain that fits onto the bone chain public static void GeneratePhysics(IPXPmxBuilder builder, IPXBone[] bones, PhysicsSettings settings, out IPXBody[] rigidbodies, out IPXJoint[] joints) { rigidbodies = new IPXBody[bones.Length - 1]; joints = new IPXJoint[bones.Length - 2]; for (int i = 0; i < rigidbodies.Length; ++i) { // Basic setup IPXBody b = rigidbodies[i] = builder.Body(); b.Position = Vector3.Average(bones[i].Position, bones[i + 1].Position); b.Bone = bones[i]; b.Name = b.Bone.Name; b.NameE = b.Bone.NameE; b.Mode = settings.BodyMode; // Size b.BoxKind = settings.Shape; float w = settings.Width; float h = settings.Height; float length; switch (settings.LengthCalculation) { case PhysicsSettings.LengthCalculationMode.Relative: length = Vector3.Distance(bones[i].Position, bones[i + 1].Position) * settings.Length; break; case PhysicsSettings.LengthCalculationMode.DistanceFromEnds: length = Math.Max(Vector3.Distance(bones[i].Position, bones[i + 1].Position) - settings.Length * 2, 0); break; default: length = settings.Length; break; } if (settings.Shape == PEPlugin.Pmd.BodyBoxKind.Sphere) { b.BoxSize = new V3(length / 2.0f, 1, 1); } else { b.BoxSize = new Vector3(w, length / 2.0f, h); } // Angle V3 dir = bones[i + 1].Position - bones[i].Position; dir.Normalize(); float heading = Mathf.Atan2(dir.X, dir.Z); float theta = -heading; V3 elev = new V3(Mathf.Cos(theta) * dir.X + Mathf.Sin(theta) * dir.Z, dir.Y, -Mathf.Sin(theta) * dir.X + Mathf.Cos(theta) * dir.Z); b.Rotation = new V3(Mathf.Atan2(elev.Z, elev.Y), heading, 0); } for (int i = 0; i < joints.Length; ++i) { IPXJoint j = joints[i] = builder.Joint(); j.Position = bones[i + 1].Position; j.BodyA = rigidbodies[i]; j.BodyB = rigidbodies[i + 1]; j.Name = j.BodyA.Name; j.NameE = j.BodyA.NameE; } }
// Create evenly spaced bone chain along spline public static IPXBone[] GenerateSplineChainEvenSpaced(IPXPmxBuilder builder, int count, Vector3[] points, string name, string nameE) { List <IPXBone> bones = new List <IPXBone>(); // Identify the t values that divide the spline into a count number of equal pieces float deltaT = 1.0f / CURVE_POINT_COUNT; // The density of t parameters Vector3[] curve = new Vector3[CURVE_POINT_COUNT]; // Calculated curve points float curveLength = 0; // The total arc length of the curve Vector3 previousPoint = points[0]; for (int i = 0; i < CURVE_POINT_COUNT; ++i) { Vector3 pt = Mathf.Curve.BezierPoint(deltaT * i, points); curveLength += pt.Distance(previousPoint); previousPoint = curve[i] = pt; } float distance = curveLength / count; // The desired arc length between links float[] linkT = new float[count]; // The t parameters that divide the spline into roughly equal parts; the first and last are always 0 and 1. linkT[0] = 0; linkT[count - 1] = 1; int linkNumber = 1; float distSoFar = 0; for (int i = 1; i < CURVE_POINT_COUNT; ++i) { distSoFar += curve[i].Distance(curve[i - 1]); if (distSoFar >= distance) { linkT[linkNumber] = deltaT * i; distSoFar = 0; ++linkNumber; } if (linkNumber >= count) { break; } } if (DEBUG) { System.Text.StringBuilder sb = new System.Text.StringBuilder("t\tpoint\n\n"); foreach (float t in linkT) { sb.AppendFormat("{0:f4}\t{1}\n", t, Mathf.Curve.BezierPoint(t, points)); } System.Windows.Forms.MessageBox.Show(sb.Append("All t values should be between 0 and 1, in incremental order.").ToString()); } // Place the links at the desired t parameters for (int i = 0; i < count; ++i) { IPXBone bone = builder.Bone(); bone.Name = name.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.NameE = nameE.Replace("#", i.ToString()).Replace("&", (i + 1).ToString()); bone.Position = Mathf.Curve.BezierPoint(linkT[i], points); if (i > 0) { bone.Parent = bones[i - 1]; bone.Parent.ToBone = bone; } bones.Add(bone); } return(bones.ToArray()); }
//DoWorkイベントハンドラ private void ProgressDialog_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (BackgroundWorker)sender; bw.ReportProgress(0, "ファイル読み込み中(バーは動きません (^-^;)"); using (FileFormat.MQOFile mqo = FileFormat.MQOFile.load(mqopath, true)) // 三角面化して読み込む { if (mqo == null) { throw new Exception("読み込み失敗。おそらくmqoファイルの構文エラー。"); } if (mqo.Object.Count == 0) { throw new Exception("オブジェクトが空です。"); } // pmx作成 bld = PEStaticBuilder.Pmx; pmx = bld.Pmx(); pmx.Clear(); // モデル名は最初のオブジェクト名を利用する pmx.ModelInfo.ModelName = mqo.Object[0].Name; // 材質 int mc = mqo.Material.Count; if (mc == 0) { throw new Exception("材質がありません。少なくとも1つ材質が必要です。"); } int cw = 100 / mc; int pc = 0; mqo.Material.ForEach(m => { bw.ReportProgress(cw * pc++, "材質の変換中"); IPXMaterial pm = bld.Material(); pm.Name = m.Name; pm.Diffuse.R = (float)(m.Color.R * m.Diffuse); pm.Diffuse.G = (float)(m.Color.G * m.Diffuse); pm.Diffuse.B = (float)(m.Color.B * m.Diffuse); pm.Diffuse.A = (float)m.Color.A; pm.Ambient.R = (float)(m.Color.R * m.Ambient); pm.Ambient.G = (float)(m.Color.G * m.Ambient); pm.Ambient.B = (float)(m.Color.B * m.Ambient); pm.Specular.R = (float)(m.Color.R * m.Specular); pm.Specular.G = (float)(m.Color.G * m.Specular); pm.Specular.B = (float)(m.Color.B * m.Specular); pm.Power = (float)m.Power; pm.Tex = m.Tex; pmx.Material.Add(pm); }); // 各オブジェクトを処理 // ただし、非表示オブジェクトはスキップ mc = mqo.Object.Count; cw = 100 / mc; bw.ReportProgress(0, "法線を計算中"); Parallel.ForEach(mqo.Object, mObj => { if (mObj.Visible) { mObj.CalcNormals(); } bw.ReportProgress(cw, 1); }); // 先に頂点をすべて登録してから面を登録する // 頂点登録と面登録を交互に行うととんでもなく遅くなる mc = mqo.Material.Count; WorkFaceList workfacelist = new WorkFaceList(mc); WorkVertexDict workvertexdict = new WorkVertexDict(); mc = mqo.Object.Count; cw = 100 / mc; pc = 0; for (int objID = 0; objID < mc; objID++) { var mObj = mqo.Object[objID]; bw.ReportProgress(cw * pc++, String.Format("'{0}'の変換中", mObj.Name)); mObj.Face.ForEach(fc => { if (!mObj.Visible) { return; // 非表示オブジェクトは無視 } // 材質割り当てのない面は材質0として処理 int matID = fc.MatID < 0 ? 0 : fc.MatID; Func <int, int> get_vertex = i => workvertexdict.RegistVertex(objID, fc.VertexID[i], fc.UVID[i], fc.NormalID[i]); workfacelist.AddFace(matID, get_vertex(0), get_vertex(1), get_vertex(2)); }); } workvertexdict.RegistToPmx(pmx, bld, mqo, bw); workfacelist.RegistToPmx(pmx, bld, mqo, workvertexdict, bw); } }
/// <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)); }
/// <summary> /// Executes the provided XmlDocument on the PMX object. /// </summary> public static RunnerResult Execute(XmlDocument doc, IPXPmx pmx, IPXPmxBuilder builder, RunnerProgress progress) { XmlElement root = doc.DocumentElement; progress.Report(0, "> Execution started."); // Begin processing // Only the first level of elements are interpreted as commands. XmlElement[] commands = root.ChildNodes.OfType <XmlElement>().ToArray(); for (int i = 0; i < commands.Length; ++i) { try { XmlElement node = commands[i]; // The meaningful content of each case constitutes a separate block to avoid mixing variables. Might be unnecessary, but right now I'm not feeling too hot and my output WILL be sloppy. // Should review this code later. switch (node.Name.ToLowerInvariant()) { // Creates a new bone with the provided properties. case "bone": { string name = Math.Abs(Guid.NewGuid().GetHashCode()).ToString(); IPXBone bone = null; // Get name and check for collision if (node["name"] != null) { name = node["name"].InnerText; IPXBone existing = pmx.Bone.Where(b => b.Name == name).FirstOrDefault(); if (existing != null) { // If there is a collision, refer to the collision attribute switch (node["name"].GetAttribute("collision")) { // If the attribute is "update" or "replace", refer to the existing bone for the rest of the command. case "replace": case "update": bone = existing; break; // If the attribute is "skip", don't do anything with the bone and skip the command. case "skip": continue; // If the attribute is not valid, create a new bone and ignore the old one. default: bone = builder.Bone(); bone.Name = bone.NameE = name; break; } } else { bone = builder.Bone(); bone.Name = bone.NameE = name; } } else { bone = builder.Bone(); bone.Name = bone.NameE = name; } if (node["position"] != null) { bone.Position = node["position"].GetV3(); } if (node["translation"] != null) { bone.IsTranslation = bool.TryParse(node["translation"].InnerText, out bool translation) ? translation : false; } if (node["parent"] != null) { bone.Parent = Selector.Bone.Selector(node["parent"], pmx).FirstOrDefault(); } if (node["axis"] != null) { V3 v = node["axis"].GetV3(); v.Normalize(); bone.IsFixAxis = true; bone.FixAxis = v; } if (node["weight"] != null) { //IEnumerable<IPXVertex> vertices = Selector.Vertex.SelectorList(node["weight"].ChildNodes, pmx); IEnumerable <IPXVertex> vertices = Selector.Vertex.SelectorNode(node["weight"], pmx); foreach (IPXVertex v in vertices) { v.Bone1 = bone; v.Weight1 = 1; v.Bone2 = v.Bone3 = v.Bone4 = null; v.Weight2 = v.Weight3 = v.Weight4 = 0; } } pmx.Bone.Add(bone); progress.Report(Percent(i + 1, commands.Length), string.Format("[bone] Added new bone {0}.", bone.Name)); } break; // Assigns the selected vertices to the selected bone using BDEF1. case "weight": { IPXBone bone = null; if (node["target"] != null) { bone = Selector.Bone.Selector(node["target"], pmx).FirstOrDefault(); if (bone == null) { progress.Report(Percent(i + 1, commands.Length), string.Format("[weight] Bone was not found.", node["target"].InnerText)); break; } } else { progress.Report(Percent(i + 1, commands.Length), "[weight] Bone selector was not specified."); break; } // Set vertex weights if (node["select"] == null) { progress.Report(Percent(i + 1, commands.Length), "[weight] No vertices were selected."); break; } IEnumerable <IPXVertex> vertices = Selector.Vertex.SelectorNode(node["select"], pmx); foreach (IPXVertex v in vertices) { v.Bone1 = bone; v.Weight1 = 1; v.Bone2 = v.Bone3 = v.Bone4 = null; v.Weight2 = v.Weight3 = v.Weight4 = 0; } progress.Report(Percent(i + 1, commands.Length), string.Format("[weight] Weighted {0} vertices to {1} ({2}).", vertices.Count(), bone.Name, bone.NameE)); } break; // Creates a new UV morph. case "uvmorph": { // The morph's properties are defined in attributes. IPXMorph morph = builder.Morph(); morph.Kind = MorphKind.UV; // Set the UV channel if (int.TryParse(node.GetAttribute("channel"), out int ch)) { switch (ch) { case 1: morph.Kind = MorphKind.UVA1; break; case 2: morph.Kind = MorphKind.UVA2; break; case 3: morph.Kind = MorphKind.UVA3; break; case 4: morph.Kind = MorphKind.UVA4; break; default: morph.Kind = MorphKind.UV; break; } } // Set the group panel if (int.TryParse(node.GetAttribute("group"), out int group) && group <= 4 && group >= 0) { morph.Panel = group; } else { switch (node.GetAttribute("group").ToLowerInvariant()) { case "hidden": case "hide": case "none": case "0": morph.Panel = 0; break; case "eyebrow": case "eyebrows": case "1": morph.Panel = 1; break; case "eye": case "eyes": case "2": morph.Panel = 2; break; case "mouth": case "3": morph.Panel = 3; break; default: morph.Panel = 4; break; } } // Set the name, ignore collisions if (node.HasAttribute("name")) { morph.Name = morph.NameE = node.GetAttribute("name"); } else { morph.Name = morph.NameE = morph.GetHashCode().ToString(); } // Each morph contains one or more steps. Each step has its own selectors and transforms, which are applied independently. foreach (XmlElement step in node.ChildNodes.OfType <XmlElement>().Where(el => el.Name.ToLowerInvariant() == "step")) { // Skip steps that have no selectors if (step["select"] == null) { continue; } // Select vertices IEnumerable <IPXVertex> vertices = Selector.Vertex.SelectorNode(step["select"], pmx); foreach (XmlElement e in step) { switch (e.Name.ToLowerInvariant()) { case "pan": case "translate": { V2 v = e.GetV2(); foreach (IPXVertex vertex in vertices) { IPXUVMorphOffset offset = builder.UVMorphOffset(); offset.Vertex = vertex; offset.Offset = new V4(v.X, v.Y, 0, 0); morph.Offsets.Add(offset); } } break; case "rotate": // TODO: UV morph rotation case "scale": // TODO: UV morph scaling default: break; } } } pmx.Morph.Add(morph); progress.Report(Percent(i + 1, commands.Length), string.Format("[uvmorph] Created UV morph {0} with {1} offsets.", morph.Name, morph.Offsets.Count)); } break; // No particular functionality, testing only. case "test": { //foreach (IPXMorph morph in pmx.Morph.Where(m => m.Kind == MorphKind.UV)) //{ // foreach (IPXUVMorphOffset o in morph.Offsets) // { // _report(string.Format("{0:f2}, {1:f2}, {2:f2}, {3:f2}", o.Offset.X, o.Offset.Y, o.Offset.Z, o.Offset.W), -1); // } //} } break; // Sets up the selected vertices for use with AutoLuminous. case "autoluminous": { if (node["select"] == null) { progress.Report(Percent(i + 1, commands.Length), "[weight] No vertices were selected."); break; } IEnumerable <IPXVertex> vertices = Selector.Vertex.SelectorNode(node["select"], pmx); // Set required values. pmx.Header.UVACount = Math.Max(pmx.Header.UVACount, 3); // UV1 int texture = 0; int hsv = 0; float flashFrequency = 0; // UV2 V4 baseColor = new V4(0, 0, 0, 0); // UV3 float texSubtract = 0; float flashBias = 0; float push = 0; // Base emissive color if (node["color"] != null) { baseColor = node["color"].GetV4(); } // Base emissive power if (node["power"] != null) { baseColor.W = node["power"].GetSingle(); } if (node["texture"] != null) { int.TryParse(node["texture"].InnerText, System.Globalization.NumberStyles.Integer, System.Globalization.NumberFormatInfo.InvariantInfo, out texture); } if (node["colormode"] != null) { switch (node["colormode"].InnerText.ToLowerInvariant()) { case "hsv": hsv = 10; break; default: hsv = 0; break; } } if (node["flash"] != null) { float.TryParse(node["flash"].GetAttribute("bias"), System.Globalization.NumberStyles.Float, System.Globalization.NumberFormatInfo.InvariantInfo, out flashBias); flashFrequency = node["flash"].GetSingle(); } if (node["subtract"] != null) { texSubtract = node["subtract"].GetSingle(); } if (node["push"] != null) { push = node["push"].GetSingle(); } foreach (IPXVertex v in vertices) { v.UVA1.X = 0.2f; // Required value v.UVA1.Y = 0.7f; // Required value v.UVA1.Z = flashFrequency; v.UVA1.W = texture + hsv; v.UVA2 = baseColor; v.UVA3.X = texSubtract; v.UVA3.Y = flashBias; v.UVA3.Z = push; } } break; // Creates a vertex morph for AutoLuminous properties. case "almorph": { if (node["select"] == null) { progress.Report(Percent(i + 1, commands.Length), "[weight] No vertices were selected."); break; } HashSet <IPXVertex> vertices = new HashSet <IPXVertex>(Selector.Vertex.SelectorNode(node["select"], pmx)); V4 color = new V4(); if (node["color"] != null) { color = node["color"].GetV4(); } // TODO: Implement the rest of the AL properties in ALMorph IPXMorph morph = builder.Morph(); morph.Kind = MorphKind.UVA2; if (node.HasAttribute("name")) { morph.Name = morph.NameE = node.GetAttribute("name"); } else { morph.Name = morph.NameE = morph.GetHashCode().ToString(); } foreach (IPXVertex v in vertices) { // Set required values in case the vertex isn't already AL-enabled v.UVA1.X = 0.2f; v.UVA1.Y = 0.7f; // Create the offset morph.Offsets.Add(builder.UVMorphOffset(v, color)); } } break; // Sets environment variables. case "settings": { foreach (XmlElement child in node.ChildNodes.OfType <XmlElement>()) { switch (child.Name.ToLowerInvariant()) { case "exception": break; case "regex": bool regexCI = GlobalSettings.RegexCaseInsensitive; bool.TryParse(child.GetAttributeCI("caseinsensitive"), out regexCI); break; case "colortype": GlobalSettings.ColorIsFloat = child.InnerText.ToLowerInvariant() == "float"; break; default: break; } } } break; // Displays a MessageBox with the provided content. Optionally perform certain actions based on user input. case "mbox": case "messagebox": case "prompt": { if (int.TryParse(node.GetAttributeCI("skip"), out int skipNumber) && skipNumber > 0) { StringBuilder sb = new StringBuilder(string.Format("Would you like to skip the following {0} command(s)?\n", skipNumber)); for (int j = 1; j <= skipNumber; ++j) { sb.Append(commands[i + j].Name); } MessageBox.Show(sb.ToString(), "Prompt: skip", MessageBoxButtons.YesNo, MessageBoxIcon.Question); } else { MessageBox.Show(node.InnerText, "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); } } break; // Prints a line in the console. case "echo": case "print": progress.Report(Percent(i + 1, commands.Length), node.InnerText); break; default: progress.Report(Percent(i + 1, commands.Length), "Skipping unknown command " + node.Name); break; } } catch (Exception ex) { if (GlobalSettings.Exception == GlobalSettings.ErrorHandling.Ask) { if (MessageBox.Show(string.Format("The following exception has occured:\n\n{0}\n\nWould you like to continue execution?", ex.ToString()), "Exception", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Error) == System.Windows.Forms.DialogResult.No) { return(RunnerResult.Fail); } } else if (GlobalSettings.Exception == GlobalSettings.ErrorHandling.Abort) { return(RunnerResult.Fail); } } } // If control has reached this point, the execution is considered successful. return(RunnerResult.Success); }
public void InitVariables(IPERunArgs args) { // 常用接続変数一括登録 // ホスト配下 host = args.Host; builder = host.Builder; bd = builder.SC; // 短絡系ビルダ bdx = builder.Pmx; // PMXビルダ connect = host.Connector; view = connect.View.PMDView; // PMX関連 pmx = connect.Pmx.GetCurrentState(); // PMX取得 header = pmx.Header; // header :ヘッダ info = pmx.ModelInfo; // info :モデル情報 vertex = pmx.Vertex; // vertex :頂点 | リスト material = pmx.Material; // material :材質 | リスト bone = pmx.Bone; // bone :ボーン | リスト morph = pmx.Morph; // morph :モーフ | リスト node = pmx.Node; // node :表示枠 | リスト body = pmx.Body; // body :剛体 | リスト joint = pmx.Joint; // joint :Joint | リスト }