226 lines
8.1 KiB
C#
226 lines
8.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
#endif
|
|
using UnityEngine;
|
|
|
|
namespace Obi
|
|
{
|
|
[ExecuteAlways]
|
|
public class ObiActorEditorSelectionHandler
|
|
{
|
|
private static HashSet<ObiSolver> solvers = new HashSet<ObiSolver>();
|
|
private static ObiSolver clickedSolver;
|
|
private static int particleIndex;
|
|
|
|
#if UNITY_EDITOR
|
|
internal static void SolverInitialized(ObiSolver solver)
|
|
{
|
|
if (solver != null)
|
|
{
|
|
if (solvers.Count == 0)
|
|
Init();
|
|
|
|
if (solver != null)
|
|
solvers.Add(solver);
|
|
}
|
|
|
|
}
|
|
|
|
internal static void SolverTeardown(ObiSolver solver)
|
|
{
|
|
if (solver != null)
|
|
{
|
|
solvers.Remove(solver);
|
|
|
|
if (solvers.Count == 0)
|
|
Dispose();
|
|
}
|
|
}
|
|
|
|
internal static void Init()
|
|
{
|
|
SceneView.duringSceneGui += SceneGui;
|
|
}
|
|
|
|
internal static void Dispose()
|
|
{
|
|
SceneView.duringSceneGui -= SceneGui;
|
|
}
|
|
|
|
private static bool RaycastAgainstSolver(ObiSolver solver, Ray ray, float thickness, out int pIndex, out float distance)
|
|
{
|
|
float closestDistanceToRay = float.MaxValue;
|
|
pIndex = -1;
|
|
distance = float.MaxValue;
|
|
|
|
Matrix4x4 solver2World = solver.transform.localToWorldMatrix;
|
|
|
|
// Find the closest particle hit by the ray:
|
|
for (int i = 0; i < solver.activeParticleCount; ++i)
|
|
{
|
|
int p = solver.activeParticles[i];
|
|
|
|
Vector3 worldPos = solver2World.MultiplyPoint3x4(solver.positions[p]);
|
|
Vector3 projected = ObiUtils.ProjectPointLine(ray.origin, ray.origin + ray.direction, worldPos, out float mu, false);
|
|
|
|
// Disregard particles behind the camera:
|
|
if (mu < 0)
|
|
continue;
|
|
|
|
float radius = solver.principalRadii[p][0] + thickness;
|
|
float distanceToRay = Vector3.SqrMagnitude(worldPos - projected);
|
|
|
|
if (distanceToRay <= radius * radius &&
|
|
distanceToRay < closestDistanceToRay &&
|
|
mu < closestDistanceToRay)
|
|
{
|
|
distance = mu;
|
|
closestDistanceToRay = distanceToRay;
|
|
pIndex = p;
|
|
}
|
|
}
|
|
|
|
return pIndex >= 0;
|
|
}
|
|
|
|
private static ObiSolver RaycastAllSolvers(Ray ray, out int pIndex, out float distance)
|
|
{
|
|
distance = float.MaxValue;
|
|
pIndex = -1;
|
|
|
|
ObiSolver hitSolver = null;
|
|
|
|
foreach (ObiSolver s in solvers)
|
|
{
|
|
if (s == null || !s.bounds.IntersectRay(ray))
|
|
continue;
|
|
|
|
if (RaycastAgainstSolver(s, ray, 0, out int p, out float d))
|
|
{
|
|
if (d < distance)
|
|
{
|
|
distance = d;
|
|
hitSolver = s;
|
|
pIndex = p;
|
|
}
|
|
}
|
|
}
|
|
return hitSolver;
|
|
}
|
|
|
|
private static void SceneGui(SceneView sceneView)
|
|
{
|
|
if (EditorApplication.isPaused || !ObiEditorSettings.GetOrCreateSettings().sceneViewParticlePicking)
|
|
return;
|
|
|
|
// only do this in the main stage or prefab stage, if we're in any other stage don't raycast against particles.
|
|
// This will prevent selecting stuff when in the blueprint editor, avatar editor, or other stages.
|
|
var stage = StageUtility.GetCurrentStage();
|
|
if (!(stage is MainStage) && !(stage is PrefabStage))
|
|
return;
|
|
|
|
var evt = Event.current;
|
|
|
|
if (evt.alt)
|
|
return;
|
|
|
|
float ppp = EditorGUIUtility.pixelsPerPoint;
|
|
int mouseScreenX = (int)(evt.mousePosition.x * ppp);
|
|
int mouseScreenY = (int)(evt.mousePosition.y * ppp);
|
|
|
|
if (mouseScreenX < 0 || mouseScreenX >= sceneView.camera.pixelWidth ||
|
|
mouseScreenY < 0 || mouseScreenY >= sceneView.camera.pixelHeight)
|
|
return;
|
|
|
|
int controlID = GUIUtility.GetControlID(FocusType.Passive);
|
|
|
|
switch (evt.type)
|
|
{
|
|
case EventType.Layout:
|
|
case EventType.MouseMove:
|
|
|
|
if (!Tools.viewToolActive)
|
|
{
|
|
// Raycast against all solvers:
|
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
|
clickedSolver = RaycastAllSolvers(ray, out particleIndex, out float closest);
|
|
|
|
// check whether we hit a collider before the closest particle in the solver:
|
|
RaycastHit hit;
|
|
ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
|
if (Physics.Raycast(ray, out hit, closest) && hit.collider != null)
|
|
clickedSolver = null;
|
|
|
|
// If we hit something, register our control ID and the distance to the particle:
|
|
if (clickedSolver != null && Camera.current != null)
|
|
{
|
|
var worldPos = clickedSolver.transform.TransformPoint(clickedSolver.positions[particleIndex]);
|
|
var screenCenter = HandleUtility.WorldToGUIPoint(worldPos);
|
|
|
|
var distance = (Event.current.mousePosition - screenCenter).magnitude;
|
|
|
|
HandleUtility.AddControl(controlID, distance);
|
|
|
|
// AddDefaultControl means that if no other control is selected, this will be chosen as the fallback.
|
|
// This allows things like the translate handle and buttons to function.
|
|
HandleUtility.AddDefaultControl(controlID);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case EventType.MouseDown:
|
|
|
|
if (evt.button == 0 && HandleUtility.nearestControl == controlID && clickedSolver != null)
|
|
{
|
|
// Setting the hotControl tells the Scene View that this mouse down/up event cannot be considered
|
|
// a picking action because the event is in use.
|
|
GUIUtility.hotControl = controlID;
|
|
evt.Use();
|
|
}
|
|
break;
|
|
|
|
case EventType.MouseUp:
|
|
|
|
if (!Tools.viewToolActive && GUIUtility.hotControl == controlID)
|
|
{
|
|
// In case we hit some actor, select the actor it belongs to:
|
|
if (clickedSolver != null)
|
|
{
|
|
var clickedActor = clickedSolver.particleToActor[particleIndex].actor;
|
|
if (clickedActor != null)
|
|
{
|
|
var selection = Selection.objects.ToList();
|
|
|
|
if (evt.shift || evt.control)
|
|
{
|
|
if (selection.Contains(clickedActor.gameObject))
|
|
selection.Remove(clickedActor.gameObject);
|
|
else
|
|
selection.Add(clickedActor.gameObject);
|
|
}
|
|
else
|
|
{
|
|
selection.Clear();
|
|
selection.Add(clickedActor.gameObject);
|
|
}
|
|
|
|
Selection.objects = selection.ToArray();
|
|
}
|
|
|
|
GUIUtility.hotControl = 0;
|
|
evt.Use();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|