// Magica Cloth. // Copyright (c) MagicaSoft, 2020-2022. // https://magicasoft.jp using System.Collections.Generic; using UnityEditor; using UnityEngine; #pragma warning disable 0436 namespace MagicaCloth { /// /// シーンビュー内での汎用ポイント選択ツール /// 使い方は PointSelectorTest / PointSelectorTestInspector を参照 /// public class PointSelector { /// /// 編集状態 /// public static bool EditEnable { get; private set; } private static int EditInstanceId = 0; private static UnityEngine.Object EditObject = null; //========================================================================================= /// /// Reload Domain 対応 /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Init() { EditEnable = false; EditInstanceId = 0; EditObject = null; } //========================================================================================= /// /// ポイントタイプ情報 /// private class PointType { /// /// 表示名 /// public string label; /// /// 表示カラー /// public Color col; /// /// 設定値 /// public int value; } /// /// ポイントタイプリスト /// List pointTypeList = new List(); /// /// ポイントタイプ辞書 /// Dictionary value2typeDict = new Dictionary(); // 設定値からの逆引き辞書 /// /// ポイントデータ /// public class PointData { /// /// ポイント座標(ワールド) /// public Vector3 pos; /// /// データインデックス /// public int index; /// /// データ値 /// public int value; /// /// Z距離(ソート用) /// public float distance; } /// /// ポイントデータリスト /// List pointList = new List(); /// /// ポイントサイズ /// float pointSize = 0.01f; /// /// 最近点のみ選択 /// bool selectNearest = false; /// /// 現在の編集値 /// int selectPointType = 0; //========================================================================================= /// /// ウインドウ有効設定 /// public void EnableEdit() { // データは毎回クリアする //Clear(); // SceneView.duringSceneGui += OnSceneView; // SceneView.RepaintAll(); } /// /// ウインドウ無効設定 /// public void DisableEdit(UnityEngine.Object obj) { EndEdit(obj); //Clear(); // SceneView.duringSceneGui -= OnSceneView; // SceneView.RepaintAll(); } /// /// 選択開始 /// void StartEdit(UnityEngine.Object obj) { if (EditEnable) return; Clear(); EditEnable = true; EditInstanceId = obj.GetInstanceID(); EditObject = obj; pointSize = EditorPrefs.GetFloat("PointSelector_PointSize", 0.01f); selectNearest = EditorPrefs.GetBool("PointSelector_SelectNearest", false); SceneView.duringSceneGui += OnSceneView; SceneView.RepaintAll(); } /// /// 選択終了 /// void EndEdit(UnityEngine.Object obj) { if (EditEnable == false) return; if (IsEdit(obj) == false) return; EditEnable = false; EditInstanceId = 0; EditObject = null; Clear(); EditorPrefs.SetFloat("PointSelector_PointSize", pointSize); EditorPrefs.SetBool("PointSelector_SelectNearest", selectNearest); SceneView.duringSceneGui -= OnSceneView; SceneView.RepaintAll(); } public bool IsEdit(UnityEngine.Object obj) { return EditEnable && EditInstanceId == obj.GetInstanceID(); } //========================================================================================= /// /// 情報クリア /// void Clear() { // ポイントタイプ情報 pointTypeList.Clear(); value2typeDict.Clear(); selectPointType = 0; // ポイント情報 pointList.Clear(); } //========================================================================================= /// /// ポイントタイプ追加 /// /// /// /// public void AddPointType(string label, Color col, int value) { if (value2typeDict.ContainsKey(value)) return; PointType pt = new PointType(); pt.label = label; pt.col = col; pt.value = value; pointTypeList.Add(pt); // 逆引き辞書登録 value2typeDict[value] = pt; } /// /// ポイント追加 /// /// /// /// /// public void AddPoint(Vector3 pos, int index, int value) { PointData p = new PointData(); p.pos = pos; p.index = index; p.value = value; pointList.Add(p); } /// /// 現在のポイントリストを返す /// 最後のデータ反映に利用する /// /// public List GetPointList() { return pointList; } /// /// ポイントカラー取得 /// /// /// Color GetPointColor(int value) { PointType pt; if (value2typeDict.TryGetValue(value, out pt)) { return pt.col; } return Color.black; } //========================================================================================= /// /// シーンビューハンドラ /// /// void OnSceneView(SceneView sceneView) { if (EditorApplication.isPlaying) return; if (EditEnable == false) return; if (EditObject == null) { return; } // シーンビュー・カメラ Camera cam = SceneView.currentDrawingSceneView.camera; Vector3 campos = cam.transform.position; Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); Vector3 spos = ray.origin; Vector3 epos = spos + ray.direction * 1000.0f; int controlId = GUIUtility.GetControlID(FocusType.Passive); // マウス選択 if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && !Event.current.alt) { hitTest(spos, epos, pointSize * 0.5f); // シーンビューのエリア選択を出さないために、とりあえずこうするらしい GUIUtility.hotControl = controlId; Event.current.Use(); // ? } if (Event.current.type == EventType.MouseDrag && Event.current.button == 0 && !Event.current.alt) { hitTest(spos, epos, pointSize * 0.5f); Event.current.Use(); } if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && !Event.current.alt) { // マウスボタンUPでコントロールロックを解除するらしい GUIUtility.hotControl = 0; Event.current.Use(); // ? } if (Event.current.type == EventType.Repaint) { // Zソート ZSort(campos, cam.transform.forward); if (selectNearest == false) { // Z test off Handles.zTest = UnityEngine.Rendering.CompareFunction.Always; } else { // Z test on Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; } // ポイント表示 int pcnt = pointList.Count; for (int i = 0; i < pcnt; i++) { var p = pointList[i]; Handles.color = GetPointColor(p.value); Handles.SphereHandleCap(0, p.pos, Quaternion.identity, pointSize, EventType.Repaint); } } // リペイントしないと即座に反映しない if (GUI.changed) { // シーンビューのリペイント HandleUtility.Repaint(); } } /// /// ポイントを置くから表示するためZソートする /// void ZSort(Vector3 campos, Vector3 camdir) { // カメラ距離計測 int pcnt = pointList.Count; for (int i = 0; i < pcnt; i++) { var p = pointList[i]; p.distance = Vector3.Distance(p.pos, campos); } // ソート(距離の降順なので注意!) pointList.Sort((a, b) => a.distance > b.distance ? -1 : 1); } /// /// カメラレイヒットテスト /// /// /// /// /// bool hitTest(Vector3 spos, Vector3 epos, float hitRadius) { // 手前からチェック bool change = false; int pcnt = pointList.Count; for (int i = pcnt - 1; i >= 0; i--) { var p = pointList[i]; // マウスレイからの距離を求める float sqlen = SqDistPointSegment(spos, epos, p.pos); if (sqlen <= hitRadius * hitRadius) { // ヒット! // 数値変更 p.value = pointTypeList[selectPointType].value; change = true; // 最近点のみの場合はここで終了 if (selectNearest) break; } } return change; } /// /// ■点Cと線分abの間の距離の平方を返す /// ゲームプログラミングのためのリアルタイム衝突判定 P.130 /// /// /// /// /// float SqDistPointSegment(Vector3 a, Vector3 b, Vector3 c) { Vector3 ab = b - a; Vector3 ac = c - a; Vector3 bc = c - b; float e = Vector3.Dot(ac, ab); // Cがabの外側に射影される場合を扱う if (e <= 0) return Vector3.Dot(ac, ac); float f = Vector3.Dot(ab, ab); if (e >= f) return Vector3.Dot(bc, bc); // Cがab上に射影される場合を扱う return Vector3.Dot(ac, ac) - e * e / f; } //========================================================================================= /// /// カスタムGUI表示 /// public void DrawInspectorGUI( UnityEngine.Object obj, System.Action startAction, System.Action endAction ) { if (EditorApplication.isPlaying) return; EditorGUILayout.Space(); if (EditEnable && IsEdit(obj) == false) return; bool change = false; using (var verticalScope = new GUILayout.VerticalScope(GUI.skin.box)) { GUI.backgroundColor = Color.cyan; if (EditEnable == false && GUILayout.Button("Start Point Selection")) { GUI.backgroundColor = Color.white; StartEdit(obj); if (startAction != null) startAction(this); change = true; } else if (EditEnable && GUILayout.Button("End Point Selection")) { GUI.backgroundColor = Color.white; if (endAction != null) endAction(this); EndEdit(obj); change = true; } GUI.backgroundColor = Color.white; if (EditEnable && GUILayout.Button("Cancel Point Selection")) { // 内容は反映せずに終了 EndEdit(obj); change = true; } if (EditEnable) { EditorGUILayout.Space(); // ポイントサイズスライダー float psize = EditorGUILayout.Slider("Point Size", pointSize, 0.001f, 0.1f); if (psize != pointSize) { pointSize = psize; change = true; } EditorGUILayout.Space(); // 最近点のみ選択 EditorGUILayout.Space(); var oldSelectNearest = selectNearest; selectNearest = EditorGUILayout.ToggleLeft("Z Test On & Select Near Point Only", selectNearest); if (oldSelectNearest != selectNearest) change = true; EditorGUILayout.Space(); // ボタンカラーを変えたいのでGUILayout.Toolbar()は使わない EditorGUILayout.LabelField("Point Type"); int tcnt = pointTypeList.Count; Color bcol = GUI.backgroundColor; using (new EditorGUILayout.HorizontalScope()) { int nowtype = selectPointType; for (int i = 0; i < tcnt; i++) { // カラー GUI.backgroundColor = pointTypeList[i].col; bool ret = GUILayout.Toggle(i == nowtype, pointTypeList[i].label, EditorStyles.miniButtonLeft); if (ret) { nowtype = i; } } if (nowtype != selectPointType) { selectPointType = nowtype; } } GUI.backgroundColor = bcol; // 全塗りつぶし EditorGUILayout.Space(); if (GUILayout.Button("Fill")) { foreach (var p in pointList) { p.value = pointTypeList[selectPointType].value; } change = true; } } } // リペイント if (change) { SceneView.RepaintAll(); } } } }