質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

5回答

7826閲覧

Unity2D:オブジェクトを二つに自由切断する方法

hobby-polite

総合スコア22

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/08/04 02:07

こんにちは。

Unity2dにおいて、一つのオブジェクトAを任意の方向に切断(例:指orカーソルでなぞる等)し、オブジェクトBとオブジェクトCに分割するやり方が見つかりません。
イメージ説明
具体的に説明します。上の図のように、始点から徐々に指やカーソルやプレイヤーを任意の方向へ移動させ始めます。この時点ではオブジェクトAは切断されていません。やがてカーソルが終点にたどり着いたとき、上の図では結果的に点線のような軌跡を描きます。終点にたどり着いた瞬間にオブジェクトAは破壊され、オブジェクトBとオブジェクトCが生成されます。(注:BとCの間の隙間は実際には存在しません。)

ここまでの手順をどのように実行するのか?というのが具体的な質問内容です。
スプライトを直線的に切断する方法はいくつか見つけることができたのですが、それらをこのように曲線的に切断する応用の仕方もうまく思いつきません。

付け加えさせていただくと、切断後にオブジェクトBとオブジェクトCにそれぞれ異なった操作を加えたいため、これら二つのオブジェクトをfindする大まかな手順も教えていただければとても助かります。

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

私も先のお二人の方針が妥当かと思います。izmktrさんの案はドローソフトのような、Ram.Type-0さんの案はペイントソフトのようなイメージが連想されますね。

興味深いご質問でしたので、試しにizmktrさん方式で実現の見込みがあるか検討してみました。ご参考になれば幸いです。
下記のようなプランはいかがでしょうか?

  • まず、スプライトのメッシュを構成する三角形を有向グラフのようなものと見なす。普通のメッシュなら内部の辺は行きと帰りがペアになっているはずなので、それらを削除するとメッシュの外周を時計回りに一周する経路が得られる(ちなみに穴の開いたメッシュの場合は穴の縁が反時計回りの経路として得られるはずですが、さしあたりそれは考えないことにしました)。

図1

  • 切断パスとメッシュ外周の交点を調べ、頂点を割り込ませてメッシュ外周をちょん切る。

図2

  • 切断パスに沿って、メッシュ内部に辺を張る。辺は行きと帰りをペアにした二重線にしておく。頂点を時計回りにたどると切断後のメッシュ外周が得られる。それぞれの多角形を三角形の集まりに分割すれば新しいメッシュができあがる。

図3

コードは少々長くなってしまったので省略しますが、下図のような感じになりました。

図4

ご意見のあった通り、切断パスに自己交差があるとややこしくなりそうですね。自己交差部分があるパスは一旦交差点で切って繋ぎ直し、自己交差のないパスに分割して複数回に分けて切断した方が楽かもしれません。

切断後の各パーツをどうやって取得するかについては、まずは切断処理を作ってから検討してもいいんじゃないでしょうか。おそらくさほど難しいことはないだろうと思います。切断時にパーツをリストかなにかに放り込んで管理してもいいでしょうし、規則的な名前を付けておいてFindで探すという手もあるでしょう(たとえば今回の実験では、切断後のパーツには元のオブジェクトの名前の後ろにハイフンと連番を入れた名前を付けてみました)。

投稿2019/08/07 16:06

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hobby-polite

2019/08/09 08:34

ご回答ありがとうございます! 実はUnityの公式コミュニティでも同様の質問をしたのですが、ありがたいことに一名の方からアドバイスをいただきましたのでご興味がございましたらどうぞ。https://answers.unity.com/questions/1654857/how-to-cut-an-object-in-any-direction.html?childToView=1655248#answer-1655248 Unityでの完全オリジナルのゲーム製作は初めての経験ですので、かなり手探りの状態ではありますが、皆さんのアドバイスからぼんやりと筋書きが固まってきました。なんとか頑張ってみます。 ありがとうございました!
hobby-polite

2019/08/13 16:15

こんにちは。 先日は詳細なご回答ありがとうございました。 日をまたいでの質問で申し訳ないのですが、切断パスとメッシュ外周の交点座標はどのように取得すれば良いのでしょうか。また、どのようにしてその取得した交点座標を適切に頂点リストに割り込ませるのでしょうか。 初歩的な質問になってしまい申し訳ございません。
Bongo

2019/08/13 21:10

図中のPictureオブジェクトにアタッチしたスクリプトを別回答に追記しました(なんとか2回答分におさまりました...)。実験目的で思いつくままに書いた状態ですので、十分に整理されていなかったり非効率的な面もあるかと思います。動作テストも十分に行ったわけではありませんので、あくまでもご参考として...ということでお願いします。 切断パスと外周パスの交点は、パスを構成する各線分について直線同士の交差をとることによって求めました。http://marupeke296.com/COL_2D_No10_SegmentAndSegment.html に図入りで理屈が紹介されており、ご参考になりそうです。 交点を割り込ませる段階(CreateCutOutlinesメソッド)では、まず切断パス・外周パスはVertexオブジェクトを双方向連結リスト状に繋いだ形に直し、追加した新しいVertexオブジェクトに対して前後の頂点を接続し直す方式でやってみました。ご覧の通りごちゃごちゃしたコードですみません...
hobby-polite

2019/08/14 06:00

あああありがとうございます!!! 参考になるサイトまで何から何まで本当にありがとうございます! プログラミング初心者なのでこのスクリプトはかなりチャレンジングに見えますが、自分なりに理解できるよう頑張ります!
guest

0

1.線を書き始めた時点で線が書かれたSpriteと同じ大きさのテクスチャを生成します。
2.線が引き終わった時点で片側のみを塗りつぶします。
3.この塗りつぶされたテクスチャをマスクテクスチャとして利用し、塗りつぶされた部分だけを描写するシェーダー、塗りつぶされていない部分だけを描写するシェーダーのマテリアルを貼り付けたGameObjectをそれぞれ作成する。

この手順で2つ生成されたGameObjectがそれぞれ切断されたオブジェクトを表現することができ、この方法であれば自己交差等にも対応することができます。

テクスチャに線を書く方法についてはこちらのサイトが参考になると思います。

また、引き終わった時点の判定、塗りつぶし処理などに関しては「塗りつぶし アルゴリズム」などで検索すれば割と一般的な処理であることもありたくさん資料が出てきます。

投稿2019/08/06 12:02

Ram.Type-0

総合スコア424

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hobby-polite

2019/08/09 08:35

ご回答ありがとうございます! 実はUnityの公式コミュニティでも同様の質問をしたのですが、ありがたいことに一名の方からアドバイスをいただきましたのでご興味がございましたらどうぞ。https://answers.unity.com/questions/1654857/how-to-cut-an-object-in-any-direction.html?childToView=1655248#answer-1655248 Unityでの完全オリジナルのゲーム製作は初めての経験ですので、かなり手探りの状態ではありますが、皆さんのアドバイスからぼんやりと筋書きが固まってきました。なんとか頑張ってみます。 ありがとうございました!
guest

0

ベストアンサー

結論から言うと曲線できることは出来ません
曲線を直線の集合体として切ることになると思います

ABCDという矩形をEFを通る直線できったとき、
一例として、ABEFとEFCDという図形に区切られる場合、
曲線っぽい直線の集合体として、ABExxxxFとExxxxFCDという図形にする感じだと思います

あとは、自己交差をした場合(曲線が輪を持っている場合)の挙動が面倒ですね
自己交差は禁止したほうが楽だと思います

投稿2019/08/05 01:51

izmktr

総合スコア2856

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hobby-polite

2019/08/09 08:35

ご回答ありがとうございます! 実はUnityの公式コミュニティでも同様の質問をしたのですが、ありがたいことに一名の方からアドバイスをいただきましたのでご興味がございましたらどうぞ。https://answers.unity.com/questions/1654857/how-to-cut-an-object-in-any-direction.html?childToView=1655248#answer-1655248 Unityでの完全オリジナルのゲーム製作は初めての経験ですので、かなり手探りの状態ではありますが、皆さんのアドバイスからぼんやりと筋書きが固まってきました。なんとか頑張ってみます。 ありがとうございました!
guest

0

後半

C#

1 var materialProperties = new MaterialPropertyBlock(); 2 meshRenderer.GetPropertyBlock(materialProperties); 3 materialProperties.SetTexture(MainTexProperty, this.sourceSprite.texture); 4 meshRenderer.SetPropertyBlock(materialProperties); 5 6 // メッシュの外周を求め、その形のPolygonCollider2Dを付ける 7 this.shapeCollider = this.gameObject.AddComponent<PolygonCollider2D>(); 8 var meshOutlines = GetOutlines(this.shapeMesh); 9 Debug.Assert(meshOutlines.Length == 1); 10 var meshOutline = meshOutlines[0]; 11 var meshVertices = this.shapeMesh.vertices; 12 this.shapeOutlinePathPositions = meshOutline.Select(i => (Vector2)meshVertices[i]).ToArray(); 13 this.shapeCollider.points = this.shapeOutlinePathPositions; 14 15 // 必要に応じてRigidbody2Dも付ける 16 if (this.attachRigidbody2D) 17 { 18 this.gameObject.AddComponent<Rigidbody2D>(); 19 } 20 } 21 22 // パスに自己交差部分があるかを検査し、交差が見つかればtrueを返す 23 private static bool CheckWhetherPathHasSelfIntersection(IList<Edge> pathEdges, IList<Vector2> pathPositions) 24 { 25 Debug.Assert(pathEdges != null); 26 return pathEdges.Where((e0, i) => pathEdges.Skip(i + 2).Any(e1 => GetIntersection(e0, e1, pathPositions, out _, out _))).Any(); 27 } 28 29 // パス座標を基に、連結リスト状につながったVertex群を作る 30 // 入力される座標リストは連結順に並んでいることが前提 31 private static Vertex CreateVerticesFromPath( 32 IList<Vector2> sortedPathPositions, 33 bool closed, 34 IList<bool> signs = null) 35 { 36 Debug.Assert(sortedPathPositions != null); 37 var firstV = new Vertex(sortedPathPositions.First()); 38 var vertexList = new List<Vertex>(); 39 var v = firstV; 40 vertexList.Add(v); 41 foreach (var p in sortedPathPositions.Skip(1)) 42 { 43 var nextV = new Vertex(p); 44 v.To = nextV; 45 nextV.From = v; 46 v = nextV; 47 vertexList.Add(v); 48 } 49 50 if (closed) 51 { 52 firstV.From = v; 53 v.To = firstV; 54 } 55 56 if (signs != null) 57 { 58 Debug.Assert(signs.Count == vertexList.Count); 59 var count = signs.Count; 60 for (var i = 0; i < count; i++) 61 { 62 vertexList[i].Sign = signs[i] ? Vertex.VertexSign.Inside : Vertex.VertexSign.Outside; 63 } 64 } 65 66 return firstV; 67 } 68 69 // Edgeを引数とするGetIntersection 70 private static bool GetIntersection(Edge e0, Edge e1, IList<Vector2> pathPositions, out float t0, out float t1) 71 { 72 Debug.Assert(pathPositions != null); 73 return GetIntersection(pathPositions[e0.From], pathPositions[e0.To], pathPositions[e1.From], pathPositions[e1.To], out t0, out t1); 74 } 75 76 // 二つの線分の交点の、線分上の内分点を得る 77 // t0、t1がいずれも0以上1以下ならtrueを返す 78 // 線分が平行な場合は交差なしと見なしfalseを返す 79 private static bool GetIntersection( 80 Vector2 p00, 81 Vector2 p01, 82 Vector2 p10, 83 Vector2 p11, 84 out float t0, 85 out float t1) 86 { 87 var vc = p10 - p00; 88 var v0 = p01 - p00; 89 var v1 = p11 - p10; 90 var cc = CrossZ(v0, v1); 91 if (Mathf.Approximately(cc, 0.0f)) 92 { 93 t0 = 0.0f; 94 t1 = 0.0f; 95 return false; 96 } 97 98 var c0 = CrossZ(vc, v0); 99 var c1 = CrossZ(vc, v1); 100 t0 = c1 / cc; 101 t1 = c0 / cc; 102 return (t0 >= 0.0f) && (t0 <= 1.0f) && (t1 >= 0.0f) && (t1 <= 1.0f); 103 } 104 105 // a、bを3次元ベクトルと見なし、外積のZ成分を返す 106 private static float CrossZ(Vector2 a, Vector2 b) 107 { 108 return (a.x * b.y) - (a.y * b.x); 109 } 110 111 // パス上の各点が形状の内側にあるかを調べて返す 112 private static List<bool> GetPathCornerSigns(IList<Vector2> pathPositions, Collider2D collider) 113 { 114 Debug.Assert(pathPositions != null); 115 Debug.Assert(collider != null); 116 return pathPositions.Select(collider.OverlapPoint).ToList(); 117 } 118 119 // パスのインデックスリストから辺のリストを得る 120 private static List<Edge> CreateEdgeListFromPath(IList<int> path, bool closed) 121 { 122 Debug.Assert(path != null); 123 var pointCount = path.Count; 124 var points = closed ? path : path.Take(pointCount - 1); 125 return points.Select((index, i) => new Edge(index, path[(i + 1) % pointCount])).ToList(); 126 } 127 128 // メッシュ境界をたどる頂点のインデックスを得る経路の配列を得る 129 // 外周は時計回り、内周は反時計回り 130 private static int[][] GetOutlines(Mesh mesh) 131 { 132 Debug.Assert(mesh != null); 133 var indices = mesh.triangles; 134 var triangleCount = indices.Length / 3; 135 var edges = Enumerable.Range(0, triangleCount).SelectMany( 136 i => 137 { 138 var o = i * 3; 139 return Enumerable.Range(0, 3).Select(j => new Edge(indices[o + j], indices[o + ((j + 1) % 3)])); 140 }).ToList(); 141 var outlineEdges = edges.Where(e => !edges.Contains(e.Inverse())).ToList(); 142 var result = new List<int[]>(); 143 while (outlineEdges.Any()) 144 { 145 var outlineIndices = new List<int>(); 146 var e0 = outlineEdges.First(); 147 var firstIndex = e0.From; 148 var e1 = outlineEdges.First(e => e.From == e0.To); 149 while (true) 150 { 151 outlineIndices.Add(e0.From); 152 outlineEdges.Remove(e0); 153 e0 = e1; 154 if (e0.To == firstIndex) 155 { 156 outlineIndices.Add(e0.From); 157 outlineEdges.Remove(e0); 158 break; 159 } 160 161 e1 = outlineEdges.First(e => e.From == e0.To); 162 } 163 164 result.Add(outlineIndices.ToArray()); 165 } 166 167 return result.ToArray(); 168 } 169 170 private struct Edge 171 { 172 public readonly int From; 173 public readonly int To; 174 175 public Edge(int from, int to) 176 { 177 this.From = from; 178 this.To = to; 179 } 180 181 public Edge Inverse() 182 { 183 return new Edge(this.To, this.From); 184 } 185 } 186 187 private class Vertex 188 { 189 public enum VertexSign 190 { 191 None, 192 Inside, 193 Outside 194 } 195 196 public readonly Vector2 Position; 197 public VertexSign Sign; 198 public Vertex From; 199 public Vertex To; 200 public Vertex Other; 201 202 public Vertex(Vector2 position) 203 { 204 this.Position = position; 205 this.Sign = VertexSign.None; 206 } 207 208 /// <inheritdoc /> 209 public override string ToString() 210 { 211 return $"{(this.From == null ? "null" : this.From.Position.ToString())}->[{(this.Sign == VertexSign.Inside ? "+" : this.Sign == VertexSign.Outside ? "-" : "0")}]{this.Position.ToString()}->{(this.To == null ? "null" : this.To.Position.ToString())}"; 212 } 213 } 214 215 private struct Intersection 216 { 217 public readonly Vertex OutlineIn; 218 public readonly Vertex OutlineOut; 219 public readonly float T; 220 221 private Intersection(Vertex outlineIn, Vertex outlineOut, float t) 222 { 223 this.OutlineIn = outlineIn; 224 this.OutlineOut = outlineOut; 225 this.T = t; 226 } 227 228 public static bool CreateIntersection(Vertex pathFrom, Vertex outlineFrom, out Intersection intersection) 229 { 230 var p00 = pathFrom.Position; 231 var p01 = pathFrom.To.Position; 232 var p10 = outlineFrom.Position; 233 var p11 = outlineFrom.To.Position; 234 if (GetIntersection(p00, p01, p10, p11, out var t0, out _)) 235 { 236 var position = Vector2.Lerp(p00, p01, t0); 237 var outlineIn = new Vertex(position); 238 var outlineOut = new Vertex(position); 239 outlineIn.From = outlineFrom; 240 outlineIn.To = outlineOut; 241 outlineOut.From = outlineIn; 242 outlineOut.To = outlineFrom.To; 243 outlineFrom.To.From = outlineOut; 244 outlineFrom.To = outlineIn; 245 intersection = new Intersection(outlineIn, outlineOut, t0); 246 return true; 247 } 248 intersection = new Intersection(); 249 return false; 250 } 251 } 252 } 253 254 // runevisionさんによるTriangulator(http://wiki.unity3d.com/index.php/Triangulator) 255 public class Triangulator 256 { 257 private readonly List<Vector2> m_points = new List<Vector2>(); 258 259 public Triangulator(Vector2[] points) 260 { 261 this.m_points = new List<Vector2>(points); 262 } 263 264 public int[] Triangulate() 265 { 266 var indices = new List<int>(); 267 var n = this.m_points.Count; 268 if (n < 3) 269 { 270 return indices.ToArray(); 271 } 272 var V = new int[n]; 273 if (this.Area() > 0) 274 { 275 for (var v = 0; v < n; v++) 276 { 277 V[v] = v; 278 } 279 } 280 else 281 { 282 for (var v = 0; v < n; v++) 283 { 284 V[v] = n - 1 - v; 285 } 286 } 287 var nv = n; 288 var count = 2 * nv; 289 for (var v = nv - 1; nv > 2;) 290 { 291 if (count-- <= 0) 292 { 293 return indices.ToArray(); 294 } 295 var u = v; 296 if (nv <= u) 297 { 298 u = 0; 299 } 300 v = u + 1; 301 if (nv <= v) 302 { 303 v = 0; 304 } 305 var w = v + 1; 306 if (nv <= w) 307 { 308 w = 0; 309 } 310 if (this.Snip(u, v, w, nv, V)) 311 { 312 int a, b, c, s, t; 313 a = V[u]; 314 b = V[v]; 315 c = V[w]; 316 indices.Add(a); 317 indices.Add(b); 318 indices.Add(c); 319 for (s = v, t = v + 1; t < nv; s++, t++) 320 { 321 V[s] = V[t]; 322 } 323 nv--; 324 count = 2 * nv; 325 } 326 } 327 indices.Reverse(); 328 return indices.ToArray(); 329 } 330 331 private float Area() 332 { 333 var n = this.m_points.Count; 334 var A = 0.0f; 335 for (int p = n - 1, q = 0; q < n; p = q++) 336 { 337 var pval = this.m_points[p]; 338 var qval = this.m_points[q]; 339 A += (pval.x * qval.y) - (qval.x * pval.y); 340 } 341 return A * 0.5f; 342 } 343 344 private bool Snip(int u, int v, int w, int n, int[] V) 345 { 346 int p; 347 var A = this.m_points[V[u]]; 348 var B = this.m_points[V[v]]; 349 var C = this.m_points[V[w]]; 350 if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))) 351 { 352 return false; 353 } 354 for (p = 0; p < n; p++) 355 { 356 if ((p == u) || (p == v) || (p == w)) 357 { 358 continue; 359 } 360 var P = this.m_points[V[p]]; 361 if (this.InsideTriangle(A, B, C, P)) 362 { 363 return false; 364 } 365 } 366 return true; 367 } 368 369 private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) 370 { 371 float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; 372 float cCROSSap, bCROSScp, aCROSSbp; 373 ax = C.x - B.x; 374 ay = C.y - B.y; 375 bx = A.x - C.x; 376 by = A.y - C.y; 377 cx = B.x - A.x; 378 cy = B.y - A.y; 379 apx = P.x - A.x; 380 apy = P.y - A.y; 381 bpx = P.x - B.x; 382 bpy = P.y - B.y; 383 cpx = P.x - C.x; 384 cpy = P.y - C.y; 385 aCROSSbp = (ax * bpy) - (ay * bpx); 386 cCROSSap = (cx * apy) - (cy * apx); 387 bCROSScp = (bx * cpy) - (by * cpx); 388 return (aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f); 389 } 390 } 391}

投稿2019/08/13 21:09

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

前半

C#

1using System.Collections.Generic; 2using System.Linq; 3using UnityEngine; 4 5namespace SpriteJigsaw 6{ 7 public class SpriteJigsaw : MonoBehaviour 8 { 9 private static readonly int MainTexProperty = Shader.PropertyToID("_MainTex"); 10 11 [SerializeField] private bool attachRigidbody2D = true; 12 13 private Sprite sourceSprite; 14 private Material spriteMaterial; 15 private Mesh shapeMesh; 16 private Vector2[] shapeOutlinePathPositions; 17 private PolygonCollider2D shapeCollider; 18 19 // このオブジェクトをワールド座標系のパスで切断し、新しいオブジェクトを返す 20 // 切断に成功した場合、このオブジェクトは破壊される 21 public List<SpriteJigsaw> Cut(IList<Vector2> cuttingPathPositions) 22 { 23 Debug.Log($"Cut {this.name}..."); 24 Debug.Assert(cuttingPathPositions != null); 25 Debug.Assert(this.shapeCollider != null); 26 27 // 始点・終点の点検 28 var cuttingPathSigns = GetPathCornerSigns(cuttingPathPositions, this.shapeCollider); 29 if (cuttingPathSigns.First()) 30 { 31 Debug.LogError("First point is inside collider!"); 32 return null; 33 } 34 if (cuttingPathSigns.Last()) 35 { 36 Debug.LogError("Last point is inside collider!"); 37 return null; 38 } 39 40 // 自己交差の点検 41 var cuttingPathCornerCount = cuttingPathPositions.Count; 42 var cuttingPath = Enumerable.Range(0, cuttingPathCornerCount).ToList(); 43 var cuttingPathEdges = CreateEdgeListFromPath(cuttingPath, false); 44 if (CheckWhetherPathHasSelfIntersection(cuttingPathEdges, cuttingPathPositions)) 45 { 46 Debug.LogError("Path has self intersections!"); 47 return null; 48 } 49 50 // 切断パスをローカル空間に移す 51 var worldToLocal = this.transform.worldToLocalMatrix; 52 var cuttingPathLocalPositions = cuttingPathPositions.Select(p => (Vector2)worldToLocal.MultiplyPoint3x4(p)).ToList(); 53 54 // 切断後のアウトラインを求める 55 var cutOutlines = CreateCutOutlines( 56 this.shapeOutlinePathPositions, 57 cuttingPathLocalPositions, 58 cuttingPathSigns); 59 60 // 新しいオブジェクトを作り... 61 var result = cutOutlines.Select( 62 (loop, i) => 63 { 64 var newName = $"{this.name}-{i}"; 65 var loopArray = loop.ToArray(); 66 var indices = new Triangulator(loopArray).Triangulate().Select(j => (ushort)j).ToArray(); 67 var newSprite = Instantiate(this.sourceSprite); 68 var spriteSize = newSprite.rect.size; 69 var spritePivot = newSprite.pivot; 70 var spritePixelsPerUnit = new Vector2(newSprite.pixelsPerUnit, newSprite.pixelsPerUnit); 71 var spriteSpaceLoopArray = loopArray.Select( 72 p => Vector2.Min( 73 Vector2.Max((p * spritePixelsPerUnit) + spritePivot, Vector2.zero), 74 spriteSize)).ToArray(); 75 newSprite.OverrideGeometry(spriteSpaceLoopArray, indices); 76 var newMesh = new Mesh 77 { 78 name = newName, 79 vertices = newSprite.vertices.Select(v => (Vector3)v).ToArray(), 80 uv = newSprite.uv, 81 triangles = newSprite.triangles.Select(j => (int)j).ToArray() 82 }; 83 var newObject = Instantiate(this); 84 newObject.name = newName; 85 newObject.sourceSprite = newSprite; 86 newObject.shapeMesh = newMesh; 87 var newMeshFilter = newObject.GetComponent<MeshFilter>(); 88 newMeshFilter.sharedMesh = newMesh; 89 var newRenderer = newObject.GetComponent<MeshRenderer>(); 90 var materialProperties = new MaterialPropertyBlock(); 91 newRenderer.GetPropertyBlock(materialProperties); 92 materialProperties.SetTexture(MainTexProperty, newSprite.texture); 93 newRenderer.SetPropertyBlock(materialProperties); 94 var newCollider = newObject.GetComponent<PolygonCollider2D>(); 95 newCollider.points = loopArray; 96 newObject.shapeCollider = newCollider; 97 newObject.shapeOutlinePathPositions = loopArray; 98 return newObject; 99 }).ToList(); 100 101 // 自分自身は破壊する 102 Destroy(this.gameObject); 103 return result; 104 } 105 106 // 切断後のアウトラインを表す頂点座標ループ群を作る 107 private static List<List<Vector2>> CreateCutOutlines( 108 IList<Vector2> shapeOutlinePathPositions, 109 IList<Vector2> cuttingPathLocalPositions, 110 IList<bool> cuttingPathSigns) 111 { 112 // アウトラインパス、切断パスをともにVertexチェーンに変換する 113 var shapeOutlineFirstVertex = CreateVerticesFromPath(shapeOutlinePathPositions, true); 114 var cuttingPathFirstVertex = CreateVerticesFromPath(cuttingPathLocalPositions, false, cuttingPathSigns); 115 116 // 後で頂点を総ざらいするときのため、パス作成に関与している頂点をこれに覚えておく 117 var vertexBag = new HashSet<Vertex>(); 118 { 119 var intersectionsForVertex = new Dictionary<Vertex, List<Intersection>>(); 120 121 // 切断パスをたどっていき... 122 var cuttingVertex = cuttingPathFirstVertex; 123 while (cuttingVertex.To != null) 124 { 125 vertexBag.Add(cuttingVertex); 126 var intersections = new List<Intersection>(); 127 intersectionsForVertex.Add(cuttingVertex, intersections); 128 129 // アウトラインパスをたどっていき... 130 var outlineVertex = shapeOutlineFirstVertex; 131 do 132 { 133 vertexBag.Add(outlineVertex); 134 if (Intersection.CreateIntersection(cuttingVertex, outlineVertex, out var intersection)) 135 { 136 // 交点が見つかれば、生成された交点情報を覚えておく 137 // このとき同時にアウトラインパスに切れ目が挿入されている 138 intersections.Add(intersection); 139 vertexBag.Add(intersection.OutlineIn); 140 vertexBag.Add(intersection.OutlineOut); 141 outlineVertex = intersection.OutlineOut; 142 } 143 144 outlineVertex = outlineVertex.To; 145 } while (outlineVertex != shapeOutlineFirstVertex); 146 147 cuttingVertex = cuttingVertex.To; 148 } 149 150 // アウトラインパスに一通り切れ目を入れた後で、もう一度切断パスをたどっていき... 151 cuttingVertex = cuttingPathFirstVertex; 152 while (cuttingVertex.To != null) 153 { 154 var segmentTerminalVertex = cuttingVertex.To; 155 var intersections = intersectionsForVertex[cuttingVertex]; 156 var intersectionCount = intersections.Count; 157 var currentVertexSign = cuttingVertex.Sign == Vertex.VertexSign.Inside; 158 var nextVertexSign = cuttingVertex.To.Sign == Vertex.VertexSign.Inside; 159 160 // この頂点と同じ位置に頂点を追加してペアを作り... 161 var otherCuttingVertex = new Vertex(cuttingVertex.Position); 162 vertexBag.Add(otherCuttingVertex); 163 cuttingVertex.Other = otherCuttingVertex; 164 otherCuttingVertex.Other = cuttingVertex; 165 166 // 頂点が形状の内側かを調べ... 167 if (currentVertexSign) 168 { 169 // 内側なら、起点が形状内であることを許さないルールにより 170 // 必ず一つ前の頂点が存在するはず 171 Debug.Assert(cuttingVertex.From != null); 172 173 // 内側なら自身の双子頂点と一つ前の双子頂点の間に逆向きの接続を作る 174 cuttingVertex.From.Other.From = otherCuttingVertex; 175 otherCuttingVertex.To = cuttingVertex.From.Other; 176 } 177 else 178 { 179 // 外側なら、ここまでの処理の過程でこの頂点よりも前の頂点との接続は切ってあるはず 180 Debug.Assert(cuttingVertex.From == null); 181 182 // 次の頂点への接続は不要なので切断、この頂点ペアは廃棄する 183 cuttingVertex.To.From = null; 184 cuttingVertex.To = null; 185 vertexBag.Remove(cuttingVertex); 186 vertexBag.Remove(otherCuttingVertex); 187 } 188 189 if (intersectionCount <= 0) 190 { 191 // この頂点から次の頂点まで交点がないなら、両頂点の内外符号は同じはず 192 Debug.Assert(currentVertexSign == nextVertexSign); 193 } 194 else 195 { 196 // この頂点から次の頂点までの交点が偶数なら両頂点の内外符号は同じ、奇数なら異なるはず 197 Debug.Assert((intersectionCount % 2) == 0 == currentVertexSign == nextVertexSign); 198 199 // 起点に近い順に交点情報を並べて... 200 intersections = intersections.OrderBy(i => i.T).ToList(); 201 202 // 頂点をつなぎ変えていく 203 var intersectionPointerVertex = cuttingVertex; 204 var sign = currentVertexSign; 205 for (var i = 0; i < intersectionCount; i++) 206 { 207 var intersection = intersections[i]; 208 if (sign) 209 { 210 // 交差部分の双子頂点に対して、出る側と入る側を接続する 211 intersectionPointerVertex.To = intersection.OutlineOut; 212 intersection.OutlineOut.From = intersectionPointerVertex; 213 intersectionPointerVertex.Other.From = intersection.OutlineIn; 214 intersection.OutlineIn.To = intersectionPointerVertex.Other; 215 } 216 217 // 次の交差点へ 218 intersectionPointerVertex = intersection.OutlineIn; 219 intersectionPointerVertex.Other = intersection.OutlineOut; 220 sign = !sign; 221 } 222 223 // 最後の交差点とセグメント末端を接続する 224 Debug.Assert(sign == nextVertexSign); 225 if (sign) 226 { 227 intersectionPointerVertex.To = segmentTerminalVertex; 228 segmentTerminalVertex.From = intersectionPointerVertex; 229 } 230 else 231 { 232 segmentTerminalVertex.From = null; 233 } 234 } 235 236 cuttingVertex = segmentTerminalVertex; 237 } 238 } 239 240 // 出来上がった頂点群から新しいアウトラインを作る 241 var result = new List<List<Vector2>>(); 242 while (vertexBag.Count > 0) 243 { 244 // 頂点を一つ取り出し... 245 var firstVertex = vertexBag.First(); 246 vertexBag.Remove(firstVertex); 247 if (firstVertex.To == firstVertex) 248 { 249 continue; 250 } 251 252 var loop = new List<Vector2>(); 253 var vertex = firstVertex; 254 var nextVertex = vertex.To; 255 do 256 { 257 // 頂点をたどって輪を作る 258 Debug.Assert(vertex.From != null); 259 Debug.Assert(vertex.To != null); 260 loop.Add(vertex.Position); 261 Debug.Assert((vertex == firstVertex) || vertexBag.Contains(vertex)); 262 vertexBag.Remove(vertex); 263 vertex = nextVertex; 264 nextVertex = vertex.To; 265 } while (vertex != firstVertex); 266 267 result.Add(loop); 268 } 269 270 return result; 271 } 272 273 private void Start() 274 { 275 if (this.shapeMesh != null) 276 { 277 return; 278 } 279 280 var spriteRenderer = this.GetComponent<SpriteRenderer>(); 281 if (spriteRenderer == null) 282 { 283 return; 284 } 285 286 this.sourceSprite = spriteRenderer.sprite; 287 if (this.sourceSprite == null) 288 { 289 return; 290 } 291 292 // SpriteRendererをMeshRendererに差し替える 293 this.shapeMesh = new Mesh 294 { 295 name = this.name, 296 vertices = this.sourceSprite.vertices.Select(v => (Vector3)v).ToArray(), 297 uv = this.sourceSprite.uv, 298 triangles = this.sourceSprite.triangles.Select(i => (int)i).ToArray() 299 }; 300 this.spriteMaterial = spriteRenderer.sharedMaterial; 301 DestroyImmediate(spriteRenderer); 302 var meshFilter = this.gameObject.AddComponent<MeshFilter>(); 303 meshFilter.mesh = this.shapeMesh; 304 var meshRenderer = this.gameObject.AddComponent<MeshRenderer>(); 305 meshRenderer.sharedMaterial = this.spriteMaterial;

投稿2019/08/13 21:09

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問