Files
2026-06-22 16:18:34 +02:00

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);
}
}
}