282 lines
10 KiB
C#
282 lines
10 KiB
C#
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using Ashwild.Harvesting;
|
|
|
|
namespace Ashwild.EditorTools
|
|
{
|
|
[CustomEditor(typeof(ScatterZone))]
|
|
public class ScatterZoneEditor : Editor
|
|
{
|
|
private enum EditMode { Resize, Brush }
|
|
|
|
private EditMode mode = EditMode.Resize;
|
|
|
|
// Outil Pinceau (layer)
|
|
private int activeLayer;
|
|
private bool brushDelete;
|
|
|
|
// État de drag
|
|
private Vector3 lastPaint;
|
|
private bool hasLastPaint;
|
|
|
|
private readonly BoxBoundsHandle boxHandle = new BoxBoundsHandle();
|
|
|
|
// ---------- Création via le menu ----------
|
|
|
|
[MenuItem("GameObject/Tools/Scatter Zone", false, 10)]
|
|
private static void CreateZone(MenuCommand cmd)
|
|
{
|
|
GameObject go = new GameObject("ScatterZone");
|
|
go.AddComponent<ScatterZone>();
|
|
GameObjectUtility.SetParentAndAlign(go, cmd.context as GameObject);
|
|
|
|
if (SceneView.lastActiveSceneView != null)
|
|
go.transform.position = SceneView.lastActiveSceneView.pivot;
|
|
|
|
Undo.RegisterCreatedObjectUndo(go, "Create Scatter Zone");
|
|
Selection.activeGameObject = go;
|
|
}
|
|
|
|
// ---------- Inspector ----------
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
ScatterZone zone = (ScatterZone)target;
|
|
serializedObject.Update();
|
|
|
|
EditorGUILayout.PropertyField(serializedObject.FindProperty("size"));
|
|
EditorGUILayout.PropertyField(serializedObject.FindProperty("layers"), true);
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
EditorGUILayout.Space();
|
|
DrawLayerActions(zone);
|
|
|
|
EditorGUILayout.Space();
|
|
if (GUILayout.Button("Ajouter un volume d'exclusion"))
|
|
AddExclusionVolume(zone);
|
|
|
|
EditorGUILayout.Space();
|
|
DrawModeBar();
|
|
|
|
EditorGUILayout.Space();
|
|
if (mode == EditMode.Brush) DrawBrushControls(zone);
|
|
else EditorGUILayout.HelpBox("Mode Déplacer/Redim : poignées de la boîte dans la Scene View.", MessageType.None);
|
|
}
|
|
|
|
private static string LayerLabel(ScatterZone.Layer layer, int index)
|
|
=> layer.profile != null ? layer.profile.name : $"Layer {index} (aucun profil)";
|
|
|
|
private void DrawLayerActions(ScatterZone zone)
|
|
{
|
|
EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel);
|
|
|
|
for (int i = 0; i < zone.layers.Count; i++)
|
|
{
|
|
ScatterZone.Layer layer = zone.layers[i];
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
EditorGUILayout.LabelField(LayerLabel(layer, i), GUILayout.MinWidth(60));
|
|
|
|
using (new EditorGUI.DisabledScope(layer.profile == null))
|
|
if (GUILayout.Button("Scatter", GUILayout.Width(70)))
|
|
RunAndNotify(() => ScatterRunner.ScatterLayer(zone, i), "placées");
|
|
|
|
if (GUILayout.Button("Clear", GUILayout.Width(60)))
|
|
RunAndNotify(() => ScatterRunner.ClearLayer(zone, i), "supprimées");
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.Space(2);
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
if (GUILayout.Button("Scatter tout", GUILayout.Height(26)))
|
|
RunAndNotify(() => ScatterRunner.ScatterAll(zone), "placées");
|
|
|
|
GUI.backgroundColor = new Color(1f, 0.6f, 0.6f);
|
|
if (GUILayout.Button("Clear tout", GUILayout.Height(26), GUILayout.Width(100)))
|
|
RunAndNotify(() => ScatterRunner.ClearAll(zone), "supprimées");
|
|
GUI.backgroundColor = Color.white;
|
|
}
|
|
}
|
|
|
|
private void AddExclusionVolume(ScatterZone zone)
|
|
{
|
|
GameObject go = new GameObject("Exclusion");
|
|
Undo.RegisterCreatedObjectUndo(go, "Add Exclusion Volume");
|
|
ScatterExclusionVolume vol = go.AddComponent<ScatterExclusionVolume>();
|
|
Undo.SetTransformParent(go.transform, zone.transform, "Add Exclusion Volume");
|
|
go.transform.position = zone.transform.position;
|
|
go.transform.localScale = Vector3.one;
|
|
vol.size = new Vector3(10f, 10f, 10f);
|
|
Selection.activeGameObject = go;
|
|
}
|
|
|
|
private void DrawModeBar()
|
|
{
|
|
EditMode newMode = (EditMode)GUILayout.Toolbar((int)mode,
|
|
new[] { "Déplacer/Redim", "Pinceau" });
|
|
if (newMode != mode)
|
|
{
|
|
mode = newMode;
|
|
hasLastPaint = false;
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
private void DrawBrushControls(ScatterZone zone)
|
|
{
|
|
if (zone.layers.Count == 0)
|
|
{
|
|
EditorGUILayout.HelpBox("Ajoute au moins un layer pour utiliser le pinceau.", MessageType.Warning);
|
|
return;
|
|
}
|
|
|
|
string[] names = new string[zone.layers.Count];
|
|
for (int i = 0; i < names.Length; i++)
|
|
names[i] = LayerLabel(zone.layers[i], i);
|
|
|
|
activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1);
|
|
activeLayer = EditorGUILayout.Popup("Layer actif", activeLayer, names);
|
|
|
|
brushDelete = GUILayout.Toolbar(brushDelete ? 1 : 0, new[] { "Add", "Delete" }) == 1;
|
|
|
|
ScatterZone.Layer layer = zone.layers[activeLayer];
|
|
EditorGUI.BeginChangeCheck();
|
|
float r = EditorGUILayout.Slider("Rayon", layer.brushRadius, 0.5f, 50f);
|
|
float s = EditorGUILayout.Slider("Densité", layer.brushStrength, 0.01f, 1f);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(zone, "Brush Settings");
|
|
layer.brushRadius = r;
|
|
layer.brushStrength = s;
|
|
}
|
|
|
|
EditorGUILayout.HelpBox("Clique-glisse sur le terrain : Add pose, Delete supprime (dans le rayon).", MessageType.None);
|
|
}
|
|
|
|
// ---------- Scene View ----------
|
|
|
|
private void OnSceneGUI()
|
|
{
|
|
ScatterZone zone = (ScatterZone)target;
|
|
|
|
if (mode == EditMode.Resize)
|
|
{
|
|
DrawBoxHandle(zone);
|
|
return;
|
|
}
|
|
|
|
HandleBrush(zone);
|
|
}
|
|
|
|
private void DrawBoxHandle(ScatterZone zone)
|
|
{
|
|
boxHandle.center = zone.transform.position;
|
|
boxHandle.size = zone.size;
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
boxHandle.DrawHandle();
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(zone, "Resize Scatter Zone");
|
|
Undo.RecordObject(zone.transform, "Move Scatter Zone");
|
|
zone.transform.position = boxHandle.center;
|
|
zone.size = new Vector3(
|
|
Mathf.Max(1f, boxHandle.size.x),
|
|
Mathf.Max(0.1f, boxHandle.size.y),
|
|
Mathf.Max(1f, boxHandle.size.z));
|
|
}
|
|
}
|
|
|
|
private void HandleBrush(ScatterZone zone)
|
|
{
|
|
if (zone.layers.Count == 0) return;
|
|
|
|
Event e = Event.current;
|
|
int id = GUIUtility.GetControlID(FocusType.Passive);
|
|
if (e.type == EventType.Layout)
|
|
HandleUtility.AddDefaultControl(id);
|
|
|
|
Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
|
|
if (!Physics.Raycast(ray, out RaycastHit hit, 5000f))
|
|
return;
|
|
|
|
Vector3 point = hit.point;
|
|
activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1);
|
|
float radius = zone.layers[activeLayer].brushRadius;
|
|
|
|
Handles.color = brushDelete ? new Color(1f, 0.25f, 0.2f) : new Color(0.2f, 1f, 0.3f);
|
|
Handles.DrawWireDisc(point, Vector3.up, radius);
|
|
Color fill = Handles.color; fill.a = 0.08f;
|
|
Handles.color = fill;
|
|
Handles.DrawSolidDisc(point, Vector3.up, radius);
|
|
|
|
bool paintEvent = (e.type == EventType.MouseDown || e.type == EventType.MouseDrag)
|
|
&& e.button == 0 && !e.alt;
|
|
if (paintEvent)
|
|
{
|
|
float step = radius * 0.25f;
|
|
if (!hasLastPaint || e.type == EventType.MouseDown
|
|
|| (point - lastPaint).sqrMagnitude >= step * step)
|
|
{
|
|
ApplyBrush(zone, point);
|
|
lastPaint = point;
|
|
hasLastPaint = true;
|
|
}
|
|
e.Use();
|
|
}
|
|
|
|
if (e.type == EventType.MouseUp)
|
|
hasLastPaint = false;
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private void ApplyBrush(ScatterZone zone, Vector3 point)
|
|
{
|
|
activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1);
|
|
ScatterZone.Layer layer = zone.layers[activeLayer];
|
|
|
|
Undo.IncrementCurrentGroup();
|
|
int group = Undo.GetCurrentGroup();
|
|
|
|
if (brushDelete)
|
|
ScatterRunner.PaintDelete(zone, activeLayer, point, layer.brushRadius);
|
|
else
|
|
ScatterRunner.PaintAdd(zone, activeLayer, point, layer.brushRadius, layer.brushStrength);
|
|
|
|
Undo.CollapseUndoOperations(group);
|
|
MarkDirty(zone);
|
|
}
|
|
|
|
// ---------- Utilitaires ----------
|
|
|
|
private void RunAndNotify(System.Func<int> action, string verb)
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
int group = Undo.GetCurrentGroup();
|
|
|
|
int n = action();
|
|
|
|
Undo.CollapseUndoOperations(group);
|
|
MarkDirty((ScatterZone)target);
|
|
ShowNotification($"{n} instances {verb}");
|
|
}
|
|
|
|
private static void ShowNotification(string msg)
|
|
{
|
|
if (SceneView.lastActiveSceneView != null)
|
|
SceneView.lastActiveSceneView.ShowNotification(new GUIContent(msg));
|
|
}
|
|
|
|
private static void MarkDirty(ScatterZone zone)
|
|
{
|
|
EditorUtility.SetDirty(zone);
|
|
if (!Application.isPlaying)
|
|
EditorSceneManager.MarkSceneDirty(zone.gameObject.scene);
|
|
}
|
|
}
|
|
}
|