public static PatchSkeletalMesh Copy(PatchSkeletalMesh org) { var m = PatchMesh.Copy(org.mesh); var s = PatchSkeleton.Copy(org.skl); PatchSkeletalMesh copy = new PatchSkeletalMesh(m, s, org.sections, false); return(copy); }
public static void Fitting(PatchSkeletalMesh smesh, PatchSkeleton skl) { FTimer.Resume("Fitting:SetCtrl"); // スケルトンに合わせて制御点を移動 foreach (var kv in smesh.skeletalControlPointDict) { PatchSkeletonBone b = kv.Key; List <PointF> orgPts = kv.Value; PatchSkeletonBone br = CorrespondingBone(skl, b); if (br == null) { continue; } if (orgPts.Count <= 1) { continue; } for (int i = 0; i < orgPts.Count; i++) { float t = (float)i / (orgPts.Count - 1); // smesh.skeletalControlPointExpandToXXXの値にしたがって、src, dst方向にそれぞれ伸長させる float exSrc = smesh.endJoints.Contains(b.src.name) ? smesh.srcStretchRatio : 0; float exDst = smesh.endJoints.Contains(b.dst.name) ? smesh.dstStretchRatio : 0; float dx = br.dst.position.X - br.src.position.X; float dy = br.dst.position.Y - br.src.position.Y; t *= 1 + exSrc + exDst; float lx = dx * t; float ly = dy * t; float ox = -dx * exSrc; float oy = -dy * exSrc; float x = br.src.position.X + ox + lx; float y = br.src.position.Y + oy + ly; // float x = br.src.position.X * (1 - t) + br.dst.position.X * t; // float y = br.src.position.Y * (1 - t) + br.dst.position.Y * t; PatchControlPoint c = smesh.mesh.FindControlPoint(orgPts[i]); if (c == null) { continue; } smesh.mesh.TranslateControlPoint(c.position, new PointF(x, y), false); } // スケルトン(mesh.skl)も動かす b.src.position = br.src.position; b.dst.position = br.dst.position; } FTimer.Pause("Fitting:SetCtrl"); // 制御点の移動を反映してメッシュ変形 FTimer.Resume("Fitting:FlushDefomation"); smesh.mesh.FlushDefomation(); FTimer.Pause("Fitting:FlushDefomation"); }
//----------------------------------------------------------------------------------- // FindConnectingSections() //----------------------------------------------------------------------------------- /// <summary> /// skl内の同一のボーンと交差していて、なおかつ向きが逆の切り口を探す /// </summary> private static bool FindConnectingSections( PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSkeleton skl, out PatchSection section1, out PatchSection section2, out PatchSkeletonBone crossingBone) { section1 = new PatchSection(0, -1); section2 = new PatchSection(0, -1); crossingBone = null; if (skl == null) { return(false); } if (smesh1 == null || smesh2 == null) { return(false); } foreach (var bone in skl.bones) { List <PatchSection> sections1, sections2; List <float> dir1, dir2; // 各切り口がboneと交差しているか if (!FindCrossingSection(smesh1, bone, out sections1, out dir1)) { continue; } if (!FindCrossingSection(smesh2, bone, out sections2, out dir2)) { continue; } for (int i = 0; i < sections1.Count; i++) { for (int j = 0; j < sections2.Count; j++) { // 2つの切り口が逆向きか if (dir1[i] * dir2[j] >= 0) { continue; } section1 = sections1[i]; section2 = sections2[j]; crossingBone = bone; return(true); } } } return(false); }
/// <summary> /// /// - ざっくり位置合わせ(オーバーラップさせる) /// - 接続後の境界を計算 /// - それにあせてボーンと垂直方向に画像を変形 /// - リサンプリング /// /// ・対応する境界線を探す(2D) /// ・ざっくり重なるように移動・拡大操作(2D) /// ・seam carving method(3D?) /// ・graphcut textures(2D) /// ・poisson blending(2D) (opencvを使う) /// ・resampleしてpatchmeshに戻す(3D) /// </summary> /// <param name="patches"></param> /// <param name="refSkeleton"></param> /// <param name="resources"></param> /// <returns></returns> public static PatchSkeletalMesh Connect(List <PatchSkeletalMesh> patches, PatchSkeleton refSkeleton, PatchMeshRenderResources resources) { var overlapPairs = OverlapPatchPairs(patches, refSkeleton); if (overlapPairs.Count <= 0) { return(null); } var newPatch = overlapPairs[0].Item1; List <PatchSkeletalMesh> aggregated = new List <PatchSkeletalMesh>(); aggregated.Add(overlapPairs[0].Item1); for (int j = 0; j < overlapPairs.Count; j++) { // newPatchと接続可能なpatchをひとつ探して繋げる // O(N^2)だけど、patchesの総数はせいぜい十数個なので問題ない for (int i = 0; i < overlapPairs.Count; i++) { var pair = overlapPairs[i]; if (aggregated.Contains(pair.Item1) && !aggregated.Contains(pair.Item2)) { var connected = Connect(newPatch, pair.Item2, refSkeleton, resources); if (connected != null) { newPatch = connected; aggregated.Add(pair.Item2); overlapPairs.RemoveAt(i); break; } } else if (!aggregated.Contains(pair.Item1) && aggregated.Contains(pair.Item2)) { if (newPatch != null) { var connected = Connect(newPatch, pair.Item1, refSkeleton, resources); if (connected != null) { newPatch = connected; aggregated.Add(pair.Item1); overlapPairs.RemoveAt(i); break; } } } } } return(newPatch); }
static PatchSkeletonBone RefBone(PatchSkeleton refSkeleton, PatchSkeletonBone bone) { PatchSkeletonBone refBone = null; foreach (var b in refSkeleton.bones) { if (bone == b) { refBone = b; break; } } return(refBone); }
static void MoveJoint(PatchSkeleton skl, List <PatchSkeletonJoint> joints, float dx, float dy, HashSet <PatchSkeletonJoint> haschecked) { if (joints.Count <= 0) { return; } foreach (var j in joints) { j.position = new PointF(j.position.X + dx, j.position.Y + dy); haschecked.Add(j); MoveJoint(skl, skl.bones.Where(b => b.src == j).Select(b => b.dst).ToList(), dx, dy, haschecked); } }
// スケルトン内のbと同じ(jointの名前が同じ)ボーンを探す static PatchSkeletonBone CorrespondingBone(PatchSkeleton skl, PatchSkeletonBone b) { if (skl == null || b == null || skl.bones == null) { return(null); } foreach (var bb in skl.bones) { // オーバーライドしてるjointの名前が同じならtrue if (b == bb) { return(bb); } } return(null); }
public void Stretch(float srcRatio, float dstRatio, PatchSkeleton refSkeleton) { var ends = EndJointNames(skl); var ends_ref = EndJointNames(refSkeleton); endJoints.Clear(); foreach (var j in ends) { if (!ends_ref.Contains(j)) { endJoints.Add(j); } } srcStretchRatio = srcRatio; dstStretchRatio = dstRatio; }
// // AdjustPosition() // static void AdjustPosition( PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSkeleton skl, PatchSection section1, PatchSection section2, PatchSkeletonBone bone) { PatchSkeletonBone refBone = null; foreach (var b in skl.bones) { if (bone == b) { refBone = b; break; } } if (refBone == null) { return; } // 切り口の中心とボーンの軸とのずれ(ボーンと垂直な方向について) float height1 = SectionHeight(smesh1, section1, refBone); float height2 = SectionHeight(smesh2, section2, refBone); // ボーンと水平なベクトルと垂直なベクトルをそれぞれx, yとして取得 PointF x, y; CalcBoneCoordinate(refBone, out x, out y); // smesh2の切り口の中心がsmesh1の切り口の中心に重なるようにsmesh2をずらす if (Math.Abs(height1 - height2) > 1e-4) { float dx = y.X * (height1 - height2); float dy = y.Y * (height1 - height2); // mesh2の頂点・制御点を平行移動する foreach (var v in smesh2.mesh.vertices) { v.position = new PointF(v.position.X + dx, v.position.Y + dy); } foreach (var c in smesh2.mesh.CopyControlPoints()) { smesh2.mesh.TranslateControlPoint(c.position, new PointF(c.position.X + dx, c.position.Y + dy), false); } } }
private static PatchSkeleton CombineSkeleton(PatchSkeleton skl1, PatchSkeleton skl2) { PatchSkeleton skl = new PatchSkeleton(); // 関節を追加 foreach (var j in skl1.joints) { skl.joints.Add(new PatchSkeletonJoint(j.name, j.position)); } foreach (var j in skl2.joints) { if (!skl.joints.Any(_j => _j.name == j.name)) { skl.joints.Add(new PatchSkeletonJoint(j.name, j.position)); } } // ボーンを追加 foreach (var b in skl1.bones) { if (skl.joints.Any(j => j.name == b.src.name) && skl.joints.Any(j => j.name == b.dst.name)) { var newBone = new PatchSkeletonBone( skl.joints.First(j => j.name == b.src.name), skl.joints.First(j => j.name == b.dst.name)); skl.bones.Add(newBone); } } foreach (var b in skl2.bones) { if (skl.joints.Any(j => j.name == b.src.name) && skl.joints.Any(j => j.name == b.dst.name)) { var newBone = new PatchSkeletonBone( skl.joints.First(j => j.name == b.src.name), skl.joints.First(j => j.name == b.dst.name)); if (!skl.bones.Contains(newBone)) { skl.bones.Add(newBone); } } } return(skl); }
public static PatchSkeletalMesh Connect(List <PatchSkeletalMesh> smeshes, PatchSkeleton refSkeleton, PatchMeshRenderResources resources) { var overlapPairs = GetOverlappedPairs(smeshes, refSkeleton); if (overlapPairs.Count <= 0) { return(null); } var newMesh = overlapPairs[0].Item1; List <PatchSkeletalMesh> aggregated = new List <PatchSkeletalMesh>(); aggregated.Add(overlapPairs[0].Item1); int _orgCnt = overlapPairs.Count; for (int j = 0; j < overlapPairs.Count; j++) { for (int i = 0; i < overlapPairs.Count; i++) { var pair = overlapPairs[i]; if (aggregated.Contains(pair.Item1) && !aggregated.Contains(pair.Item2)) { newMesh = Connect(newMesh, pair.Item2, refSkeleton, resources); aggregated.Add(pair.Item2); overlapPairs.RemoveAt(i); break; } else if (!aggregated.Contains(pair.Item1) && aggregated.Contains(pair.Item2)) { newMesh = Connect(newMesh, pair.Item1, refSkeleton, resources); aggregated.Add(pair.Item1); overlapPairs.RemoveAt(i); break; } } System.Diagnostics.Debug.Assert(aggregated.Count == j + 2); System.Diagnostics.Debug.Assert(overlapPairs.Count == _orgCnt - j - 1); } return(newMesh); }
public static bool CanConnect(List <PatchSkeletalMesh> smeshes, PatchSkeleton refSkeleton) { if (smeshes == null || refSkeleton == null || smeshes.Count <= 1) { return(false); } // 1) ボーンの重なりが最大2 // 2) 重なっているボーンで全てのメッシュが連結される // -FindConnectingSections() == true var overlapPairs = GetOverlappedPairs(smeshes, refSkeleton); HashSet <PatchSkeletalMesh> overlapSMeshes = new HashSet <PatchSkeletalMesh>(); foreach (var pair in overlapPairs) { overlapSMeshes.Add(pair.Item1); overlapSMeshes.Add(pair.Item2); } if (overlapSMeshes.Count != smeshes.Count) { return(false); } foreach (var pair in overlapPairs) { PatchSection section1; PatchSection section2; PatchSkeletonBone crossingBone; bool found = FindConnectingSections(pair.Item1, pair.Item2, refSkeleton, out section1, out section2, out crossingBone); if (!found) { return(false); } } return(true); }
PatchSkeleton(PatchSkeleton skl) { if (skl == null) { return; } this.joints = new List <PatchSkeletonJoint>(); foreach (var j in skl.joints) { this.joints.Add(new PatchSkeletonJoint(j.name, j.position)); } this.bones = new List <PatchSkeletonBone>(); foreach (var b in skl.bones) { this.bones.Add(new PatchSkeletonBone( this.joints.First(j => j.name == b.src.name), this.joints.First(j => j.name == b.dst.name))); } }
// 超簡易的なFK // 子ノードを親ノードと同じだけ平行移動させる public static void MoveJoint(PatchSkeleton skl, PatchSkeletonJoint joint, PointF to) { if (skl == null) { return; } if (joint == null) { return; } float dx = to.X - joint.position.X; float dy = to.Y - joint.position.Y; joint.position = to; HashSet <PatchSkeletonJoint> haschecked = new HashSet <PatchSkeletonJoint>(); haschecked.Add(joint); MoveJoint(skl, skl.bones.Where(b => b.src == joint).Select(b => b.dst).ToList(), dx, dy, haschecked); }
HashSet <string> EndJointNames(PatchSkeleton skl) { var ends = new HashSet <string>(); var cntDict = new Dictionary <string, int>(); foreach (var j in skl.joints) { cntDict[j.name] = 0; } foreach (var b in skl.bones) { cntDict[b.src.name]++; cntDict[b.dst.name]++; } foreach (var kv in cntDict) { if (kv.Value == 1) { ends.Add(kv.Key); } } return(ends); }
// コピーせずに参照をそのまま格納する public PatchSkeletalMesh(PatchMesh mesh, PatchSkeleton skl, List <PatchSection> sections, bool setSkeletalControlPoints = true) { this.mesh = mesh; this.skl = skl; this.sections = new List <PatchSection>(sections); this.skeletalControlPointDict = CreateSkeletalControlPoints(skl, 20, 3); // 既存の制御点を消して、スケルトン周りの制御点を追加 if (setSkeletalControlPoints) { this.mesh.ClearControlPoints(); foreach (var ls in this.skeletalControlPointDict.Values) { foreach (var pt in ls) { this.mesh.AddControlPoint(pt, pt); } } } srcStretchRatio = 0; endJoints = new HashSet <string>(); }
//-------------------------------------------------------------------------- // Combine() //-------------------------------------------------------------------------- /// <summary> /// 2つのメッシュを統合して1つのARAP可能なメッシュを作成する /// TODO: さすがに関数にわけるべき /// </summary> static PatchSkeletalMesh Combine(PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSection section1, PatchSection section2) { // // Meshを作成 // List <PatchVertex> vertices = CombineVertices(smesh1.mesh.vertices, smesh2.mesh.vertices); // 頂点から各種インデックスへの辞書を作っておく Dictionary <PointF, int> p2i = new Dictionary <PointF, int>(); for (int i = 0; i < vertices.Count; i++) { p2i[vertices[i].position] = i; } Dictionary <PointF, int> pt2part = vertices.ToDictionary(v => v.position, v => v.part); Dictionary <int, int> part2part_1 = new Dictionary <int, int>(); // smesh1の各パートが新しいメッシュのどのパートになるか foreach (var v in smesh1.mesh.vertices) { part2part_1[v.part] = pt2part[v.position]; } Dictionary <int, int> part2part_2 = new Dictionary <int, int>(); // smesh2の各パートが新しいメッシュのどのパートになるか foreach (var v in smesh2.mesh.vertices) { part2part_2[v.part] = pt2part[v.position]; } List <PatchControlPoint> controlPoints = CombineControlPoints(smesh1.mesh.CopyControlPoints(), smesh2.mesh.CopyControlPoints(), part2part_1, part2part_2); List <PatchTriangle> triangles = CombineTriangles(smesh1.mesh.triangles, smesh2.mesh.triangles, smesh1.mesh.vertices, smesh2.mesh.vertices, p2i); List <int> path = CombinePath(smesh1.mesh.pathIndices, smesh2.mesh.pathIndices, smesh1.mesh.vertices, smesh2.mesh.vertices, section1, section2, p2i); PatchMesh rawMesh = new PatchMesh(vertices, controlPoints, triangles, path); // // 骨格を統合 // PatchSkeleton skl = CombineSkeleton(smesh1.skl, smesh2.skl); // // 切り口を統合. // List <PatchSection> sections = CombineSections( smesh1.sections, smesh2.sections, section1, section2, smesh1.mesh.vertices, smesh2.mesh.vertices, smesh1.Mesh.pathIndices, smesh2.Mesh.pathIndices, path, p2i); // // SkeletalMeshを作成. // PatchSkeletalMesh newMesh = new PatchSkeletalMesh(rawMesh, skl, sections); return(newMesh); }
/// <summary> /// 2つの骨格つきメッシュをスケルトン情報を元に結合する /// 1. スケルトンに合わせて各メッシュをざっくり移動・ボーン方向にARAP /// 2. メッシュ同士が自然に繋がるように位置・角度・スケールを調整(fitting(), adjustposition()) /// 3. 繋ぎ目が重なるようにARAP(expand()) /// 4. 新しいARAP可能なひとつのメッシュを生成(combine) /// </summary> /// (5. リソースの更新。これはここでやるべきなのだろうか?) /// </summary> public static PatchSkeletalMesh Connect(PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSkeleton refSkeleton, PatchMeshRenderResources resources) { if (refSkeleton == null) { return(null); } // メッシュ・骨格データはConnect()内で変更されうるのでコピーしたものを使う PatchSkeletalMesh smesh1_t = PatchSkeletalMesh.Copy(smesh1); PatchSkeletalMesh smesh2_t = PatchSkeletalMesh.Copy(smesh2); #if DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1_t, smesh1_t.sections, alignment: true).Save("smesh1_t.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2_t, smesh2_t.sections, alignment: true).Save("smesh2_t.png"); #endif var refSkeleton_t = PatchSkeleton.Copy(refSkeleton); // smesh1, smesh2で同じボーンを共有している(結合すべき)切り口を探す // これらの切り口の付近を変形することでメッシュを繋げる PatchSection section1; PatchSection section2; PatchSkeletonBone crossingBone; bool canConnect = CanConnect(new List <PatchSkeletalMesh>() { smesh1, smesh2 }, refSkeleton); bool canConnect_t = CanConnect(new List <PatchSkeletalMesh>() { smesh1_t, smesh2_t }, refSkeleton_t); bool found = FindConnectingSections(smesh1_t, smesh2_t, refSkeleton_t, out section1, out section2, out crossingBone); if (!found) { return(null); } #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1_t, new List <CharacterRange>() { section1 }).Save("output/3_mesh1_t_seciton1.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2_t, new List <CharacterRange>() { section2 }).Save("output/4_mesh2_t_section2.png"); #endif // 2つのsmeshが重なるように位置調整およびARAP変形をする smesh1_t.mesh.BeginDeformation(); smesh2_t.mesh.BeginDeformation(); Deform(smesh1_t, smesh2_t, refSkeleton_t, section1, section2, crossingBone); smesh2_t.mesh.EndDeformation(); smesh1_t.mesh.EndDeformation(); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1_t).Save("output/6_mesh1_t_deformed.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2_t).Save("output/7_mesh2_t_deformed.png"); #endif // 2つの変形済みのsmeshを1つのsmeshに結合して、ARAPできるようにする var combinedSMesh = Combine(smesh1_t, smesh2_t, section1, section2); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(combinedSMesh, combinedSMesh.sections).Save("output/8_conbinedMesh.png"); #endif if (resources != null) { List <string> textureKeys = resources.GetResourceKeyByPatchMesh(smesh1.mesh); textureKeys.AddRange(resources.GetResourceKeyByPatchMesh(smesh2.mesh)); foreach (var key in textureKeys) { string patchKey = key.Split(':').Last(); string newKey = PatchMeshRenderResources.GenerateResourceKey(combinedSMesh.mesh, patchKey); // TODO: テクスチャはコピーしたほうが良い? resources.Add(newKey, resources.GetTexture(key)); } } return(combinedSMesh); }
// // Expand() // /// <summary> /// smesh1, smesh2の輪郭をずらして重ねる。輪郭に制御点をおいてARAPする /// </summary> static void Expand(PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSkeleton skl, PatchSection section1, PatchSection section2, PatchSkeletonBone bone) { List <PatchVertex> rawPath1 = GetPath(smesh1); List <PatchVertex> rawPath2 = GetPath(smesh2); List <PointF> path1 = rawPath1.Select(v => v.position).ToList(); List <PointF> path2 = rawPath2.Select(v => v.position).ToList(); // // 輪郭を変形できるように制御点を作り直す // // smesh1 smesh1.mesh.ClearControlPoints(); foreach (var v in rawPath1) { smesh1.mesh.AddControlPoint(v.position, v.orgPosition); } smesh1.mesh.BeginDeformation(); // smesh2 smesh2.mesh.ClearControlPoints(); foreach (var v in rawPath2) { smesh2.mesh.AddControlPoint(v.position, v.orgPosition); } smesh2.mesh.BeginDeformation(); // // 切り口に隣接する2曲線を各切り口について取得し、これらが重なるように輪郭をずらす // // 切り口に隣接する2曲線をそれぞれ取得 var rawCurves1 = SectionToAdjuscentCurves(path1, section1, 5, 30); var rawCurves2 = SectionToAdjuscentCurves(path2, section2, 5, 30); if (rawCurves1 == null || rawCurves2 == null) { return; } PatchSkeletonBone refBone = null; foreach (var b in skl.bones) { if (bone == b) { refBone = b; break; } } // curves1, curves2の第一要素、第二要素がそれぞれ向かい合う(ボーンにとって同じ側の)切り口となるように並び替える var curves1 = GetSortedCurves(path1, rawCurves1, refBone); var curves2 = GetSortedCurves(path2, rawCurves2, refBone); if (curves1.Count != 2 || curves2.Count != 2) { return; } // curves1, curves2の移動履歴を記録 List <Tuple <PointF, PointF> > move1 = new List <Tuple <PointF, PointF> >(); List <Tuple <PointF, PointF> > move2 = new List <Tuple <PointF, PointF> >(); // 対応する曲線間で2点がかぶる(同じ座標になる)ように変形。 for (int i = 0; i < 2; i++) { var p1 = curves1[i].First(); var v1 = new PointF(p1.X - curves1[i].Last().X, p1.Y - curves1[i].Last().Y); var p2 = curves2[i].First(); var v2 = new PointF(curves2[i].Last().X - p2.X, curves2[i].Last().Y - p2.Y); // 2点かぶらせる int cnt = curves1[i].Count + curves2[i].Count - 2; if (cnt <= 1) { continue; } for (int j = 0; j < curves1[i].Count; j++) { PointF to = FMath.HelmitteInterporate(p1, v1, p2, v2, (float)j / (cnt - 1)); if (j == curves1[i].Count - 1) { move1.Add(new Tuple <PointF, PointF>(curves1[i][j], to)); } smesh1.mesh.TranslateControlPoint(curves1[i][j], to, false); } for (int j = 0; j < curves2[i].Count; j++) { PointF to = FMath.HelmitteInterporate(p1, v1, p2, v2, (float)(-j + cnt - 1) / (cnt - 1)); if (j == curves2[i].Count - 1) { move2.Add(new Tuple <PointF, PointF>(curves2[i][j], to)); } smesh2.mesh.TranslateControlPoint(curves2[i][j], to, false); } } // // 各曲線の動きに合わせて切り口を動かす // List <PointF> sections1 = new List <PointF>(); for (int i = section1.First + 1; i < section1.First + section1.Length - 1; i++) { sections1.Add(path1[FMath.Rem(i, path1.Count)]); } List <PointF> newSection1 = ARAPDeformation.ARAPDeformation.Deform(sections1, move1); if (newSection1.Count == sections1.Count) { for (int i = 0; i < newSection1.Count; i++) { smesh1.mesh.TranslateControlPoint(sections1[i], newSection1[i], false); } } List <PointF> sections2 = new List <PointF>(); for (int i = section2.First + 1; i < section2.First + section2.Length - 1; i++) { sections2.Add(path2[FMath.Rem(i, path2.Count)]); } List <PointF> newSection2 = ARAPDeformation.ARAPDeformation.Deform(sections2, move2); if (newSection2.Count == sections2.Count) { for (int i = 0; i < newSection2.Count; i++) { smesh2.mesh.TranslateControlPoint(sections2[i], newSection2[i], false); } } // // 変形 // smesh1.mesh.FlushDefomation(); smesh2.mesh.FlushDefomation(); // // 変形終了 // smesh1.mesh.EndDeformation(); smesh2.mesh.EndDeformation(); }
public static PatchSkeleton Load(string filepath, Bitmap refSkeletonBmp) { if (!File.Exists(filepath)) { return(null); } filepath = Path.GetFullPath(filepath); var an = new PatchSkeleton(); an.joints = File.ReadAllLines(filepath) .Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(':')) .Select(line => { var tokens = line.Split(':'); if (tokens.Length != 2) { return(null); } var xys = tokens[1].Split(','); if (xys.Length != 2) { return(null); } float x, y; if (!float.TryParse(xys[0], out x) || !float.TryParse(xys[1], out y)) { return(null); } return(new PatchSkeletonJoint(tokens[0], new PointF(x, y))); }) .Where(j => j != null) .ToList(); an.bones = File.ReadAllLines(filepath) .Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains('>')) .Select(line => { var tokens = line.Split('>'); if (tokens.Length != 2) { return(null); } PatchSkeletonJoint src = null, dst = null; foreach (var j in an.joints) { if (j.name == tokens[0]) { src = j; } if (j.name == tokens[1]) { dst = j; } } if (src == null || dst == null) { return(null); } return(new PatchSkeletonBone(src, dst)); }) .Where(b => b != null) .ToList(); return(an); }
//----------------------------------------------------------------------------------- // Deform() //----------------------------------------------------------------------------------- /// <summary> /// 切り口付近で2つのsmeshが重なるように変形する /// </summary> private static void Deform(PatchSkeletalMesh smesh1, PatchSkeletalMesh smesh2, PatchSkeleton skl, PatchSection section1, PatchSection section2, PatchSkeletonBone crossingBone) { if (smesh1 == null || smesh2 == null) { return; } // 各メッシュを大雑把にスケルトンに合わせる PatchSkeletonFitting.Fitting(smesh1, skl); PatchSkeletonFitting.Fitting(smesh2, skl); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1, new List <PatchSection>() { section1 }).Save("output/5_1_mesh1_fitting.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2, new List <PatchSection>() { section2 }).Save("output/5_2_mesh2_fitting.png"); #endif // サイズの修正は手動でやる // 回転はFitting()でやってるから必要ない // 位置の調整 AdjustPosition(smesh1, smesh2, skl, section1, section2, crossingBone); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1, new List <PatchSection>() { section1 }).Save("output/5_3_mesh1_AdjustPosition.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2, new List <PatchSection>() { section2 }).Save("output/5_4_mesh2_AdjustPosition.png"); #endif // メッシュを伸ばして繋げる Expand(smesh1, smesh2, skl, section1, section2, crossingBone); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(smesh1, new List <PatchSection>() { section1 }).Save("output/5_5_mesh1_ExpandPatches.png"); PatchSkeletalMeshRenderer.ToBitmap(smesh2, new List <PatchSection>() { section2 }).Save("output/5_6_mesh2_ExpandPatches.png"); #endif }
static Dictionary <PatchSkeletonBone, List <PointF> > CreateSkeletalControlPoints(PatchSkeleton skl, int linearSpan, int minPtNum) { Dictionary <PatchSkeletonBone, List <PointF> > dict = new Dictionary <PatchSkeletonBone, List <PointF> >(); // ボーン沿いに制御点を追加 if (skl != null && linearSpan >= 1) { foreach (var b in skl.bones) { dict[b] = new List <PointF>(); float dist = FLib.FMath.Distance(b.src.position, b.dst.position); int ptNum = Math.Max(2, (int)(dist / linearSpan) + 1); if (ptNum < minPtNum) { ptNum = minPtNum; } for (int i = 0; i < ptNum; i++) { float t = (float)i / (ptNum - 1); // 誤差の問題があるので i == 0, ptNum - 1のときは明示的に端点を代入する PointF p; if (i == 0) { p = b.src.position; } else if (i == ptNum - 1) { p = b.dst.position; } else { p = FLib.FMath.Interpolate(b.src.position, b.dst.position, t); } dict[b].Add(p); } } } return(dict); }
public PatchSkeleton CopySkeleton() { return(PatchSkeleton.Copy(skl)); }
static List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> > GetOverlappedPairs(List <PatchSkeletalMesh> smeshes, PatchSkeleton refSkeleton) { Dictionary <PatchSkeletonBone, List <PatchSkeletalMesh> > overlaps = new Dictionary <PatchSkeletonBone, List <PatchSkeletalMesh> >(); foreach (var b in refSkeleton.bones) { overlaps[b] = new List <PatchSkeletalMesh>(); } foreach (var sm in smeshes) { foreach (var b in sm.skl.bones) { if (overlaps.ContainsKey(b)) { overlaps[b].Add(sm); if (overlaps[b].Count >= 3) { // System.Diagnostics.Debug.Assert(false); return(new List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> >()); } } } } var pairs = new List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> >(); foreach (var kv in overlaps) { if (kv.Value.Count != 2) { continue; } pairs.Add(new Tuple <PatchSkeletalMesh, PatchSkeletalMesh>(kv.Value[0], kv.Value[1])); } return(pairs); }
public static PatchSkeletalMesh Connect(PatchSkeletalMesh patch1, PatchSkeletalMesh patch2, PatchSkeleton refSkeleton, PatchMeshRenderResources resources) { if (refSkeleton == null) { return(null); } if (!System.IO.Directory.Exists("output_Connector2")) { System.IO.Directory.CreateDirectory("output_Connector2"); } FLib.FileManager.OpenExplorer("output_Connector2"); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(patch1).Save("output_Connector2/1_patch1.png"); PatchSkeletalMeshRenderer.ToBitmap(patch2).Save("output_Connector2/1_patch2.png"); #endif // 1. パッチをコピー PatchSkeletalMesh patch1_t = PatchSkeletalMesh.Copy(patch1); PatchSkeletalMesh patch2_t = PatchSkeletalMesh.Copy(patch2); var refSkeleton_t = PatchSkeleton.Copy(refSkeleton); // 2. patch1, patch2の接続面および対応するボーンを探す PatchSection section1; PatchSection section2; PatchSkeletonBone crossingBone; bool swap; bool found = ConnectableSections(patch1_t, patch2_t, refSkeleton_t, out section1, out section2, out crossingBone, out swap); if (!found) { return(null); } if (swap) { FMath.Swap(ref patch1_t, ref patch2_t); FMath.Swap(ref section1, ref section2); } #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(patch1_t, new List <CharacterRange>() { section1 }).Save("output_Connector2/2_patch1.png"); PatchSkeletalMeshRenderer.ToBitmap(patch2_t, new List <CharacterRange>() { section2 }).Save("output_Connector2/2_patch2.png"); #endif // 3. 2つのパッチが重なるように位置調整およびARAP変形 patch1_t.mesh.BeginDeformation(); patch2_t.mesh.BeginDeformation(); Deform(patch1_t, patch2_t, refSkeleton_t, section1, section2, crossingBone); patch2_t.mesh.EndDeformation(); patch1_t.mesh.EndDeformation(); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(patch1_t, new List <CharacterRange>() { section1 }).Save("output_Connector2/3_patch1.png"); PatchSkeletalMeshRenderer.ToBitmap(patch2_t, new List <CharacterRange>() { section2 }).Save("output_Connector2/3_patch2.png"); #endif // 4. 2つのパッチのテクスチャを合成して、新しいパッチを生成 // TODO:メッシュをビットマップ画像として書き出してリサンプリング // todo var combinedSMesh = Combine(patch1_t, patch2_t, section1, section2); // 5. 新しいパッチに使うテクスチャをリソースに登録 // todo if (resources != null) { List <string> textureKeys = resources.GetResourceKeyByPatchMesh(patch1.mesh); textureKeys.AddRange(resources.GetResourceKeyByPatchMesh(patch2.mesh)); foreach (var key in textureKeys) { string patchKey = key.Split(':').Last(); string newKey = PatchMeshRenderResources.GenerateResourceKey(combinedSMesh.mesh, patchKey); resources.Add(newKey, resources.GetTexture(key)); } } return(combinedSMesh); }
// // Expand() // /// <summary> /// smesh1, smesh2の輪郭をずらして重ねる。輪郭に制御点をおいてARAPする /// curveOffset, curveLengthは切り口付近の補間に使う部分曲線 /// </summary> static void Expand( PatchSkeletalMesh patch1, PatchSkeletalMesh patch2, PatchSkeleton refSkeleton, PatchSection section1, PatchSection section2, PatchSkeletonBone bone, float curveLength, float curveBuffer) { // メッシュを固定 patch1.mesh.FreezeMesh(true); patch2.mesh.FreezeMesh(true); List <PatchVertex> rawPath1 = GetPath(patch1); List <PatchVertex> rawPath2 = GetPath(patch2); List <PointF> path1 = rawPath1.Select(v => v.position).ToList(); List <PointF> path2 = rawPath2.Select(v => v.position).ToList(); // 切り口に隣接する2曲線をそれぞれ取得 var rawCurves1 = section2adjCurves(rawPath1, section1); var rawCurves2 = section2adjCurves(rawPath2, section2); if (rawCurves1 == null || rawCurves2 == null) { return; } var trimCurves1 = TrimCurves(rawPath1, rawCurves1, 0, curveLength); var trimCurves2 = TrimCurves(rawPath2, rawCurves2, 0, curveLength); PatchSkeletonBone refBone = RefBone(refSkeleton, bone); var sorted1 = GetSortedCurves(path1, trimCurves1, refBone); var sorted2 = GetSortedCurves(path2, trimCurves2, refBone); if (sorted1.Count != 2 || sorted2.Count != 2) { return; } // patch1の制御点 patch1.mesh.ClearControlPoints(); for (int i = 0; i < 2; i++) { foreach (var p in sorted1[i]) { patch1.mesh.AddControlPoint(p, p); } } patch1.mesh.BeginDeformation(); // patch2の制御点 patch2.mesh.ClearControlPoints(); for (int i = 0; i < 2; i++) { foreach (var p in sorted2[i]) { patch2.mesh.AddControlPoint(p, p); } } patch2.mesh.BeginDeformation(); // 第一要素、第二要素がそれぞれボーンにとって同じ側の切り口となるように並び替える // 切り口に近づく向きに点が並んでいる float h10 = CurveHeight(sorted1[0], refBone); float h11 = CurveHeight(sorted1[1], refBone); float h20 = CurveHeight(sorted2[0], refBone); float h21 = CurveHeight(sorted2[1], refBone); var curve10 = sorted1[0].ToList(); var curve11 = sorted1[1].ToList(); var curve20 = sorted2[0].ToList(); var curve21 = sorted2[1].ToList(); // curveの重心を揃える PointF x, y; BoneCoordinate(refBone, out x, out y); float c0 = (h10 + h20) * 0.5f; float dy10 = c0 - h10; for (int i = 0; i < curve10.Count; i++) { curve10[i] = new PointF(curve10[i].X + dy10 * y.X, curve10[i].Y + dy10 * y.Y); } float dy11 = c0 - h20; for (int i = 0; i < curve20.Count; i++) { curve20[i] = new PointF(curve20[i].X + dy11 * y.X, curve20[i].Y + dy11 * y.Y); } float c1 = (h11 + h21) * 0.5f; float dy20 = c1 - h11; for (int i = 0; i < curve11.Count; i++) { curve11[i] = new PointF(curve11[i].X + dy20 * y.X, curve11[i].Y + dy20 * y.Y); } float dy21 = c1 - h21; for (int i = 0; i < curve21.Count; i++) { curve21[i] = new PointF(curve21[i].X + dy21 * y.X, curve21[i].Y + dy21 * y.Y); } // curveをエルミート保管して繋げる var ends0 = new[] { curve10[0], curve10.Last(), curve20[0], curve20.Last() }; int min0, max0; FMath.GetMinElement(ends0, p => FMath.ParameterOnLine(p, refBone.src.position, refBone.dst.position), out min0); FMath.GetMinElement(ends0, p => - FMath.ParameterOnLine(p, refBone.src.position, refBone.dst.position), out max0); var ends1 = new[] { curve11[0], curve11.Last(), curve21[0], curve21.Last() }; int min1, max1; FMath.GetMinElement(ends1, p => FMath.ParameterOnLine(p, refBone.src.position, refBone.dst.position), out min1); FMath.GetMinElement(ends1, p => - FMath.ParameterOnLine(p, refBone.src.position, refBone.dst.position), out max1); PointF[] es = new[] { ends0[min0], ends1[min1], ends0[max0], ends1[max1], }; PointF[] vs = new[] { new PointF(curve10[0].X - curve10[curve10.Count - 1].X, curve10[0].Y - curve10[curve10.Count - 1].Y), new PointF(curve11[0].X - curve11[curve11.Count - 1].X, curve11[0].Y - curve11[curve11.Count - 1].Y), new PointF(-(curve20[0].X - curve20[curve20.Count - 1].X), -(curve20[0].Y - curve20[curve20.Count - 1].Y)), new PointF(-(curve21[0].X - curve21[curve21.Count - 1].X), -(curve21[0].Y - curve21[curve21.Count - 1].Y)), }; float[] ps = new[] { FMath.ParameterOnLine(es[0], refBone.src.position, refBone.dst.position), FMath.ParameterOnLine(es[1], refBone.src.position, refBone.dst.position), FMath.ParameterOnLine(es[2], refBone.src.position, refBone.dst.position), FMath.ParameterOnLine(es[3], refBone.src.position, refBone.dst.position), }; for (int i = 0; i < 2; i++) { var e1 = es[i]; var v1 = vs[i]; float param1 = ps[i]; var e2 = es[i + 2]; var v2 = vs[i + 2]; float param2 = ps[i + 2]; if (Math.Abs(param2 - param1) <= 1e-4) { continue; } float paramRatio = 1 / (param2 - param1); for (int j = 0; j < sorted1[i].Count; j++) { float param = FMath.ParameterOnLine(sorted1[i][j], refBone.src.position, refBone.dst.position); float t = (param - param1) * paramRatio; PointF to = FMath.HelmitteInterporate(e1, v1, e2, v2, t); patch1.mesh.TranslateControlPoint(sorted1[i][j], to, false); } for (int j = 0; j < sorted2[i].Count; j++) { float param = FMath.ParameterOnLine(sorted2[i][j], refBone.src.position, refBone.dst.position); float t = (param - param1) * paramRatio; PointF to = FMath.HelmitteInterporate(e1, v1, e2, v2, t); patch2.mesh.TranslateControlPoint(sorted2[i][j], to, false); } } // 変形 patch1.mesh.FlushDefomation(); patch2.mesh.FlushDefomation(); // 変形終了 patch1.mesh.EndDeformation(); patch2.mesh.EndDeformation(); }
// ボーンのdst方向にあるのがpatch2 static void OverlayPatches(PatchSkeletalMesh patch1, PatchSkeletalMesh patch2, PatchSkeleton refSkeleton, PatchSection section1, PatchSection section2, PatchSkeletonBone bone, float border) { PatchSkeletonBone refBone = RefBone(refSkeleton, bone); float min1 = float.MaxValue; float max2 = float.MinValue; List <PatchVertex> path1 = GetPath(patch1); List <PatchVertex> path2 = GetPath(patch2); for (int i = section1.First; i < section1.First + section1.Length; i++) { var pt = path1[FMath.Rem(i, path1.Count)].position; float param = FMath.ParameterOnLine(pt, refBone.src.position, refBone.dst.position); if (float.IsNaN(param)) { continue; } min1 = Math.Min(min1, param); } for (int i = section2.First; i < section2.First + section2.Length; i++) { var pt = path2[FMath.Rem(i, path2.Count)].position; float param = FMath.ParameterOnLine(pt, refBone.src.position, refBone.dst.position); if (float.IsNaN(param)) { continue; } max2 = Math.Max(max2, param); } PointF x, y; BoneCoordinate(refBone, out x, out y); float boneLength = FMath.Distance(refBone.src.position, refBone.dst.position); float dparam = border / boneLength; float delta = (min1 - max2 - dparam) * boneLength; float dx = x.X * delta; float dy = x.Y * delta; // mesh2の頂点・制御点を平行移動する foreach (var v in patch2.mesh.vertices) { v.position = new PointF(v.position.X + dx, v.position.Y + dy); } foreach (var c in patch2.mesh.CopyControlPoints()) { patch2.mesh.TranslateControlPoint(c.position, new PointF(c.position.X + dx, c.position.Y + dy), false); } }
private static float OverlapWidth(PatchSkeletalMesh patch1, PatchSkeletalMesh patch2, PatchSkeleton refSkeleton, PatchSection section1, PatchSection section2, PatchSkeletonBone bone, int maxLength, int maxAngle) { var refBone = RefBone(refSkeleton, bone); var path1 = GetPath(patch1); var curves1 = section2adjCurves(path1, section1); var sorted1 = GetSortedCurves(path1.Select(v => v.position).ToList(), curves1, refBone); var path2 = GetPath(patch2); var curves2 = section2adjCurves(path2, section2); var sorted2 = GetSortedCurves(path2.Select(v => v.position).ToList(), curves2, refBone); float[] param = new float[] { 0, 0, 0, 0 }; var sortedCurves = new[] { sorted1[0], sorted1[1], sorted2[0], sorted2[1], }; float minCos = (float)Math.Cos(Math.PI / 180 * maxAngle); for (int i = 0; i < sortedCurves.Length; i++) { var c = sortedCurves[i]; var path = i < 2 ? path1 : path2; List <PointF> pts = new List <PointF>(); float total = 0; for (int j = c.Count - 1; j >= 0; j--) { var p = c[j]; pts.Add(p); if (pts.Count >= 3 && FMath.GetAngleCos(pts) < minCos) { pts.RemoveAt(pts.Count - 1); break; } if (pts.Count >= 2) { total += FMath.Distance(pts[pts.Count - 2], pts[pts.Count - 1]); } if (total >= maxLength) { pts.RemoveAt(pts.Count - 1); break; } } param[i] = FMath.ParameterOnLine(pts.Last(), refBone.src.position, refBone.dst.position); } float min1 = Enumerable.Range(section1.First, section1.Length).Min(idx => FMath.ParameterOnLine(path1[FMath.Rem(idx, path1.Count)].position, refBone.src.position, refBone.dst.position)); float max1 = Enumerable.Range(section1.First, section1.Length).Max(idx => FMath.ParameterOnLine(path1[FMath.Rem(idx, path1.Count)].position, refBone.src.position, refBone.dst.position)); int idx1 = Enumerable.Range(section1.First, section1.Length).First(idx => max1 == FMath.ParameterOnLine(path1[FMath.Rem(idx, path1.Count)].position, refBone.src.position, refBone.dst.position)); float min2 = Enumerable.Range(section2.First, section2.Length).Min(idx => FMath.ParameterOnLine(path2[FMath.Rem(idx, path2.Count)].position, refBone.src.position, refBone.dst.position)); float max2 = Enumerable.Range(section2.First, section2.Length).Max(idx => FMath.ParameterOnLine(path2[FMath.Rem(idx, path2.Count)].position, refBone.src.position, refBone.dst.position)); float idx2 = Enumerable.Range(section2.First, section2.Length).First(idx => max2 == FMath.ParameterOnLine(path2[FMath.Rem(idx, path2.Count)].position, refBone.src.position, refBone.dst.position)); System.Diagnostics.Debug.Assert(param[0] <= max1); System.Diagnostics.Debug.Assert(param[1] <= max1); System.Diagnostics.Debug.Assert(param[2] >= min2); System.Diagnostics.Debug.Assert(param[3] >= min2); float[] dParams = new[] { Math.Max(0, min1 - param[0]), Math.Max(0, min1 - param[1]), Math.Max(0, param[2] - max2), Math.Max(0, param[3] - max2), }; float minDParam = dParams.Min(); float boarderWidth = minDParam * FMath.Distance(refBone.src.position, refBone.dst.position); return(boarderWidth); }
//---------------------------------------------------------------- #region Connectability static List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> > OverlapPatchPairs(List <PatchSkeletalMesh> smeshes, PatchSkeleton refSkeleton) { var overlaps = new Dictionary <PatchSkeletonBone, List <PatchSkeletalMesh> >(); foreach (var b in refSkeleton.bones) { overlaps[b] = new List <PatchSkeletalMesh>(); } foreach (var sm in smeshes) { foreach (var b in sm.skl.bones) { if (overlaps.ContainsKey(b)) { overlaps[b].Add(sm); if (overlaps[b].Count >= 3) { return(new List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> >()); } } } } var pairs = new List <Tuple <PatchSkeletalMesh, PatchSkeletalMesh> >(); foreach (var kv in overlaps) { if (kv.Value.Count != 2) { continue; } pairs.Add(new Tuple <PatchSkeletalMesh, PatchSkeletalMesh>(kv.Value[0], kv.Value[1])); } return(pairs); }
//----------------------------------------------------------------------------------- // Deform() //----------------------------------------------------------------------------------- /// <summary> /// 切り口付近で2つのsmeshが重なるように変形する /// patch2がdst方向 /// </summary> private static void Deform(PatchSkeletalMesh patch1, PatchSkeletalMesh patch2, PatchSkeleton refSkeleton, PatchSection section1, PatchSection section2, PatchSkeletonBone crossingBone) { if (patch1 == null || patch2 == null) { return; } // 各メッシュを大雑把にスケルトンに合わせる PatchSkeletonFitting.Fitting(patch1, refSkeleton); PatchSkeletonFitting.Fitting(patch2, refSkeleton); // TODO: サイズの修正 // 回転はFitting()でやってるから必要ない // 位置の調整 float overlap = OverlapWidth(patch1, patch2, refSkeleton, section1, section2, crossingBone, 100, 45); AdjustHeight(patch1, patch2, refSkeleton, section1, section2, crossingBone); OverlayPatches(patch1, patch2, refSkeleton, section1, section2, crossingBone, overlap); #if _DEBUG PatchSkeletalMeshRenderer.ToBitmap(patch1).Save("output_Connector2/4_patch1.png"); PatchSkeletalMeshRenderer.ToBitmap(patch2).Save("output_Connector2/4_patch2.png"); #endif // メッシュを伸ばして繋げる Expand(patch1, patch2, refSkeleton, section1, section2, crossingBone, overlap, overlap); }