PointSelector.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. // Magica Cloth.
  2. // Copyright (c) MagicaSoft, 2020-2022.
  3. // https://magicasoft.jp
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. #pragma warning disable 0436
  8. namespace MagicaCloth
  9. {
  10. /// <summary>
  11. /// シーンビュー内での汎用ポイント選択ツール
  12. /// 使い方は PointSelectorTest / PointSelectorTestInspector を参照
  13. /// </summary>
  14. public class PointSelector
  15. {
  16. /// <summary>
  17. /// 編集状態
  18. /// </summary>
  19. public static bool EditEnable { get; private set; }
  20. private static int EditInstanceId = 0;
  21. private static UnityEngine.Object EditObject = null;
  22. //=========================================================================================
  23. /// <summary>
  24. /// Reload Domain 対応
  25. /// </summary>
  26. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
  27. static void Init()
  28. {
  29. EditEnable = false;
  30. EditInstanceId = 0;
  31. EditObject = null;
  32. }
  33. //=========================================================================================
  34. /// <summary>
  35. /// ポイントタイプ情報
  36. /// </summary>
  37. private class PointType
  38. {
  39. /// <summary>
  40. /// 表示名
  41. /// </summary>
  42. public string label;
  43. /// <summary>
  44. /// 表示カラー
  45. /// </summary>
  46. public Color col;
  47. /// <summary>
  48. /// 設定値
  49. /// </summary>
  50. public int value;
  51. }
  52. /// <summary>
  53. /// ポイントタイプリスト
  54. /// </summary>
  55. List<PointType> pointTypeList = new List<PointType>();
  56. /// <summary>
  57. /// ポイントタイプ辞書
  58. /// </summary>
  59. Dictionary<int, PointType> value2typeDict = new Dictionary<int, PointType>(); // 設定値からの逆引き辞書
  60. /// <summary>
  61. /// ポイントデータ
  62. /// </summary>
  63. public class PointData
  64. {
  65. /// <summary>
  66. /// ポイント座標(ワールド)
  67. /// </summary>
  68. public Vector3 pos;
  69. /// <summary>
  70. /// データインデックス
  71. /// </summary>
  72. public int index;
  73. /// <summary>
  74. /// データ値
  75. /// </summary>
  76. public int value;
  77. /// <summary>
  78. /// Z距離(ソート用)
  79. /// </summary>
  80. public float distance;
  81. }
  82. /// <summary>
  83. /// ポイントデータリスト
  84. /// </summary>
  85. List<PointData> pointList = new List<PointData>();
  86. /// <summary>
  87. /// ポイントサイズ
  88. /// </summary>
  89. float pointSize = 0.01f;
  90. /// <summary>
  91. /// 最近点のみ選択
  92. /// </summary>
  93. bool selectNearest = false;
  94. /// <summary>
  95. /// 現在の編集値
  96. /// </summary>
  97. int selectPointType = 0;
  98. //=========================================================================================
  99. /// <summary>
  100. /// ウインドウ有効設定
  101. /// </summary>
  102. public void EnableEdit()
  103. {
  104. // データは毎回クリアする
  105. //Clear();
  106. // SceneView.duringSceneGui += OnSceneView;
  107. // SceneView.RepaintAll();
  108. }
  109. /// <summary>
  110. /// ウインドウ無効設定
  111. /// </summary>
  112. public void DisableEdit(UnityEngine.Object obj)
  113. {
  114. EndEdit(obj);
  115. //Clear();
  116. // SceneView.duringSceneGui -= OnSceneView;
  117. // SceneView.RepaintAll();
  118. }
  119. /// <summary>
  120. /// 選択開始
  121. /// </summary>
  122. void StartEdit(UnityEngine.Object obj)
  123. {
  124. if (EditEnable)
  125. return;
  126. Clear();
  127. EditEnable = true;
  128. EditInstanceId = obj.GetInstanceID();
  129. EditObject = obj;
  130. pointSize = EditorPrefs.GetFloat("PointSelector_PointSize", 0.01f);
  131. selectNearest = EditorPrefs.GetBool("PointSelector_SelectNearest", false);
  132. SceneView.duringSceneGui += OnSceneView;
  133. SceneView.RepaintAll();
  134. }
  135. /// <summary>
  136. /// 選択終了
  137. /// </summary>
  138. void EndEdit(UnityEngine.Object obj)
  139. {
  140. if (EditEnable == false)
  141. return;
  142. if (IsEdit(obj) == false)
  143. return;
  144. EditEnable = false;
  145. EditInstanceId = 0;
  146. EditObject = null;
  147. Clear();
  148. EditorPrefs.SetFloat("PointSelector_PointSize", pointSize);
  149. EditorPrefs.SetBool("PointSelector_SelectNearest", selectNearest);
  150. SceneView.duringSceneGui -= OnSceneView;
  151. SceneView.RepaintAll();
  152. }
  153. public bool IsEdit(UnityEngine.Object obj)
  154. {
  155. return EditEnable && EditInstanceId == obj.GetInstanceID();
  156. }
  157. //=========================================================================================
  158. /// <summary>
  159. /// 情報クリア
  160. /// </summary>
  161. void Clear()
  162. {
  163. // ポイントタイプ情報
  164. pointTypeList.Clear();
  165. value2typeDict.Clear();
  166. selectPointType = 0;
  167. // ポイント情報
  168. pointList.Clear();
  169. }
  170. //=========================================================================================
  171. /// <summary>
  172. /// ポイントタイプ追加
  173. /// </summary>
  174. /// <param name="label"></param>
  175. /// <param name="col"></param>
  176. /// <param name="value"></param>
  177. public void AddPointType(string label, Color col, int value)
  178. {
  179. if (value2typeDict.ContainsKey(value))
  180. return;
  181. PointType pt = new PointType();
  182. pt.label = label;
  183. pt.col = col;
  184. pt.value = value;
  185. pointTypeList.Add(pt);
  186. // 逆引き辞書登録
  187. value2typeDict[value] = pt;
  188. }
  189. /// <summary>
  190. /// ポイント追加
  191. /// </summary>
  192. /// <param name="pos"></param>
  193. /// <param name="normal"></param>
  194. /// <param name="index"></param>
  195. /// <param name="value"></param>
  196. public void AddPoint(Vector3 pos, int index, int value)
  197. {
  198. PointData p = new PointData();
  199. p.pos = pos;
  200. p.index = index;
  201. p.value = value;
  202. pointList.Add(p);
  203. }
  204. /// <summary>
  205. /// 現在のポイントリストを返す
  206. /// 最後のデータ反映に利用する
  207. /// </summary>
  208. /// <returns></returns>
  209. public List<PointData> GetPointList()
  210. {
  211. return pointList;
  212. }
  213. /// <summary>
  214. /// ポイントカラー取得
  215. /// </summary>
  216. /// <param name="value"></param>
  217. /// <returns></returns>
  218. Color GetPointColor(int value)
  219. {
  220. PointType pt;
  221. if (value2typeDict.TryGetValue(value, out pt))
  222. {
  223. return pt.col;
  224. }
  225. return Color.black;
  226. }
  227. //=========================================================================================
  228. /// <summary>
  229. /// シーンビューハンドラ
  230. /// </summary>
  231. /// <param name="sceneView"></param>
  232. void OnSceneView(SceneView sceneView)
  233. {
  234. if (EditorApplication.isPlaying)
  235. return;
  236. if (EditEnable == false)
  237. return;
  238. if (EditObject == null)
  239. {
  240. return;
  241. }
  242. // シーンビュー・カメラ
  243. Camera cam = SceneView.currentDrawingSceneView.camera;
  244. Vector3 campos = cam.transform.position;
  245. Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
  246. Vector3 spos = ray.origin;
  247. Vector3 epos = spos + ray.direction * 1000.0f;
  248. int controlId = GUIUtility.GetControlID(FocusType.Passive);
  249. // マウス選択
  250. if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && !Event.current.alt)
  251. {
  252. hitTest(spos, epos, pointSize * 0.5f);
  253. // シーンビューのエリア選択を出さないために、とりあえずこうするらしい
  254. GUIUtility.hotControl = controlId;
  255. Event.current.Use(); // ?
  256. }
  257. if (Event.current.type == EventType.MouseDrag && Event.current.button == 0 && !Event.current.alt)
  258. {
  259. hitTest(spos, epos, pointSize * 0.5f);
  260. Event.current.Use();
  261. }
  262. if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && !Event.current.alt)
  263. {
  264. // マウスボタンUPでコントロールロックを解除するらしい
  265. GUIUtility.hotControl = 0;
  266. Event.current.Use(); // ?
  267. }
  268. if (Event.current.type == EventType.Repaint)
  269. {
  270. // Zソート
  271. ZSort(campos, cam.transform.forward);
  272. if (selectNearest == false)
  273. {
  274. // Z test off
  275. Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
  276. }
  277. else
  278. {
  279. // Z test on
  280. Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
  281. }
  282. // ポイント表示
  283. int pcnt = pointList.Count;
  284. for (int i = 0; i < pcnt; i++)
  285. {
  286. var p = pointList[i];
  287. Handles.color = GetPointColor(p.value);
  288. Handles.SphereHandleCap(0, p.pos, Quaternion.identity, pointSize, EventType.Repaint);
  289. }
  290. }
  291. // リペイントしないと即座に反映しない
  292. if (GUI.changed)
  293. {
  294. // シーンビューのリペイント
  295. HandleUtility.Repaint();
  296. }
  297. }
  298. /// <summary>
  299. /// ポイントを置くから表示するためZソートする
  300. /// </summary>
  301. void ZSort(Vector3 campos, Vector3 camdir)
  302. {
  303. // カメラ距離計測
  304. int pcnt = pointList.Count;
  305. for (int i = 0; i < pcnt; i++)
  306. {
  307. var p = pointList[i];
  308. p.distance = Vector3.Distance(p.pos, campos);
  309. }
  310. // ソート(距離の降順なので注意!)
  311. pointList.Sort((a, b) => a.distance > b.distance ? -1 : 1);
  312. }
  313. /// <summary>
  314. /// カメラレイヒットテスト
  315. /// </summary>
  316. /// <param name="spos"></param>
  317. /// <param name="epos"></param>
  318. /// <param name="hitRadius"></param>
  319. /// <returns></returns>
  320. bool hitTest(Vector3 spos, Vector3 epos, float hitRadius)
  321. {
  322. // 手前からチェック
  323. bool change = false;
  324. int pcnt = pointList.Count;
  325. for (int i = pcnt - 1; i >= 0; i--)
  326. {
  327. var p = pointList[i];
  328. // マウスレイからの距離を求める
  329. float sqlen = SqDistPointSegment(spos, epos, p.pos);
  330. if (sqlen <= hitRadius * hitRadius)
  331. {
  332. // ヒット!
  333. // 数値変更
  334. p.value = pointTypeList[selectPointType].value;
  335. change = true;
  336. // 最近点のみの場合はここで終了
  337. if (selectNearest)
  338. break;
  339. }
  340. }
  341. return change;
  342. }
  343. /// <summary>
  344. /// ■点Cと線分abの間の距離の平方を返す
  345. /// ゲームプログラミングのためのリアルタイム衝突判定 P.130
  346. /// </summary>
  347. /// <param name="a"></param>
  348. /// <param name="b"></param>
  349. /// <param name="c"></param>
  350. /// <returns></returns>
  351. float SqDistPointSegment(Vector3 a, Vector3 b, Vector3 c)
  352. {
  353. Vector3 ab = b - a;
  354. Vector3 ac = c - a;
  355. Vector3 bc = c - b;
  356. float e = Vector3.Dot(ac, ab);
  357. // Cがabの外側に射影される場合を扱う
  358. if (e <= 0)
  359. return Vector3.Dot(ac, ac);
  360. float f = Vector3.Dot(ab, ab);
  361. if (e >= f)
  362. return Vector3.Dot(bc, bc);
  363. // Cがab上に射影される場合を扱う
  364. return Vector3.Dot(ac, ac) - e * e / f;
  365. }
  366. //=========================================================================================
  367. /// <summary>
  368. /// カスタムGUI表示
  369. /// </summary>
  370. public void DrawInspectorGUI(
  371. UnityEngine.Object obj,
  372. System.Action<PointSelector> startAction,
  373. System.Action<PointSelector> endAction
  374. )
  375. {
  376. if (EditorApplication.isPlaying)
  377. return;
  378. EditorGUILayout.Space();
  379. if (EditEnable && IsEdit(obj) == false)
  380. return;
  381. bool change = false;
  382. using (var verticalScope = new GUILayout.VerticalScope(GUI.skin.box))
  383. {
  384. GUI.backgroundColor = Color.cyan;
  385. if (EditEnable == false && GUILayout.Button("Start Point Selection"))
  386. {
  387. GUI.backgroundColor = Color.white;
  388. StartEdit(obj);
  389. if (startAction != null)
  390. startAction(this);
  391. change = true;
  392. }
  393. else if (EditEnable && GUILayout.Button("End Point Selection"))
  394. {
  395. GUI.backgroundColor = Color.white;
  396. if (endAction != null)
  397. endAction(this);
  398. EndEdit(obj);
  399. change = true;
  400. }
  401. GUI.backgroundColor = Color.white;
  402. if (EditEnable && GUILayout.Button("Cancel Point Selection"))
  403. {
  404. // 内容は反映せずに終了
  405. EndEdit(obj);
  406. change = true;
  407. }
  408. if (EditEnable)
  409. {
  410. EditorGUILayout.Space();
  411. // ポイントサイズスライダー
  412. float psize = EditorGUILayout.Slider("Point Size", pointSize, 0.001f, 0.1f);
  413. if (psize != pointSize)
  414. {
  415. pointSize = psize;
  416. change = true;
  417. }
  418. EditorGUILayout.Space();
  419. // 最近点のみ選択
  420. EditorGUILayout.Space();
  421. var oldSelectNearest = selectNearest;
  422. selectNearest = EditorGUILayout.ToggleLeft("Z Test On & Select Near Point Only", selectNearest);
  423. if (oldSelectNearest != selectNearest)
  424. change = true;
  425. EditorGUILayout.Space();
  426. // ボタンカラーを変えたいのでGUILayout.Toolbar()は使わない
  427. EditorGUILayout.LabelField("Point Type");
  428. int tcnt = pointTypeList.Count;
  429. Color bcol = GUI.backgroundColor;
  430. using (new EditorGUILayout.HorizontalScope())
  431. {
  432. int nowtype = selectPointType;
  433. for (int i = 0; i < tcnt; i++)
  434. {
  435. // カラー
  436. GUI.backgroundColor = pointTypeList[i].col;
  437. bool ret = GUILayout.Toggle(i == nowtype, pointTypeList[i].label, EditorStyles.miniButtonLeft);
  438. if (ret)
  439. {
  440. nowtype = i;
  441. }
  442. }
  443. if (nowtype != selectPointType)
  444. {
  445. selectPointType = nowtype;
  446. }
  447. }
  448. GUI.backgroundColor = bcol;
  449. // 全塗りつぶし
  450. EditorGUILayout.Space();
  451. if (GUILayout.Button("Fill"))
  452. {
  453. foreach (var p in pointList)
  454. {
  455. p.value = pointTypeList[selectPointType].value;
  456. }
  457. change = true;
  458. }
  459. }
  460. }
  461. // リペイント
  462. if (change)
  463. {
  464. SceneView.RepaintAll();
  465. }
  466. }
  467. }
  468. }