using System.Collections.Generic;
using UnityEngine;
///
/// Handles socket proximity detection, highlighting, and closest-socket tracking.
///
/// Key fixes vs previous version:
/// 1. Searches from col.transform.ROOT — so if OverlapSphere hits a child mesh
/// collider, we still find SocketPoints that are siblings of that mesh.
/// 2. Uses QueryTriggerInteraction.Collide — socket colliders are usually triggers;
/// without this flag the overlap silently finds nothing when
/// Physics.queriesHitTriggers is false in Project Settings.
/// 3. Deduplicates across colliders that share a root, so a multi-collider block
/// doesn't add its sockets multiple times.
///
public class SnapSystem : MonoBehaviour
{
private static SnapSystem instance;
public static SnapSystem getInstance() => instance;
[SerializeField] private float socketDetectionRadius = 0.5f;
private readonly List nearbySockets = new List();
private readonly HashSet visitedRoots = new HashSet();
private SocketPoint closestSocket;
private void Awake()
{
if (instance == null) instance = this;
else Destroy(gameObject);
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
///
/// Re-scans for sockets around .
/// Call every frame while a ghost is being dragged.
///
///
/// World-space ray hit point on the world surface — NOT the ghost's own position.
///
///
/// The ghost's root transform. Sockets belonging to this hierarchy are ignored.
///
public void UpdateSockets(Vector3 detectionPoint, Transform ghostRoot)
{
ClearHighlights();
nearbySockets.Clear();
visitedRoots.Clear();
closestSocket = null;
// QueryTriggerInteraction.Collide ensures trigger colliders (typical for sockets) are found
Collider[] colliders = Physics.OverlapSphere(detectionPoint, socketDetectionRadius,
Physics.AllLayers, QueryTriggerInteraction.Collide);
foreach (Collider col in colliders)
{
Transform root = col.transform.root;
// Skip anything that belongs to the held ghost
if (ghostRoot != null && root == ghostRoot) continue;
// Don't process the same block twice (it may have multiple colliders)
if (visitedRoots.Contains(root)) continue;
visitedRoots.Add(root);
// Search from root so we find ALL SocketPoints on the block,
// regardless of which child collider was hit by the overlap.
foreach (SocketPoint socket in root.GetComponentsInChildren())
{
if (socket.IsOccupied()) continue;
nearbySockets.Add(socket);
socket.Highlight(HighlightState.Compatible);
Debug.Log($"[SnappingSystem] Found socket: {socket.name} on {root.name}");
}
}
if (nearbySockets.Count > 0)
{
closestSocket = FindClosest(detectionPoint, nearbySockets);
closestSocket?.Highlight(HighlightState.Active);
Debug.Log($"[SnappingSystem] Closest socket: {closestSocket?.name}");
}
}
/// Resets all highlight states without clearing the list.
public void ClearHighlights()
{
foreach (SocketPoint socket in nearbySockets)
if (socket != null)
socket.Highlight(HighlightState.Default);
}
/// Full reset — clears highlights and all cached data.
public void Clear()
{
ClearHighlights();
nearbySockets.Clear();
visitedRoots.Clear();
closestSocket = null;
}
public List GetNearbySockets() => nearbySockets;
public SocketPoint GetClosestSocket() => closestSocket;
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private static SocketPoint FindClosest(Vector3 position, List sockets)
{
SocketPoint closest = null;
float minDist = float.MaxValue;
foreach (SocketPoint socket in sockets)
{
float dist = Vector3.Distance(position, socket.GetTransform().position);
if (dist < minDist)
{
minDist = dist;
closest = socket;
}
}
return closest;
}
}