Files
2026-07-21 08:56:10 +03:00

221 lines
8.0 KiB
C#

using System.Collections;
using System.Collections.Generic;
//using Codice.Utils;
using UnityEngine;
public class JointPlacementHandler : MonoBehaviour
{
private static JointPlacementHandler instance;
[SerializeField] private float socketSearchRadius = 1f;
[SerializeField] private LayerMask blockLayerMask;
public static JointPlacementHandler getInstance() => instance;
void Awake()
{
if (instance == null) instance = this;
else Destroy(gameObject);
}
/// <summary>
/// Finds all sockets located on the line segment between two specific sockets.
/// Useful for identifying all "holes" a pivot passes through in a stack of links.
/// </summary>
public List<SocketPoint> FindAllSocketsAlignedBetween(SocketPoint start, SocketPoint end)
{
List<SocketPoint> alignedSockets = new List<SocketPoint>();
Vector3 startPos = start.GetTransform().position;
Vector3 endPos = end.GetTransform().position;
Collider[] candidates;
if (startPos == endPos)
{
// Use a Sphere search to find everything around the pos
candidates = Physics.OverlapSphere(startPos, socketSearchRadius, blockLayerMask);
}
else
{
// Use a Capsule search to find everything in the "tunnel" between the two ends
candidates = Physics.OverlapCapsule(startPos, endPos, socketSearchRadius, blockLayerMask);
}
foreach (Collider col in candidates)
{
// Don't detect sockets on the joint itself
if (col.transform.root == transform.root) continue;
SocketPoint[] socketsInBlock = col.GetComponentsInChildren<SocketPoint>();
foreach (SocketPoint p in socketsInBlock)
{
// Skip the start and end points themselves
if (p == start || p == end) continue;
// 2. Mathematically check if the socket lies on the line segment
if (IsPointOnSegment(startPos, endPos, p.GetTransform().position, 0.1f))
{
alignedSockets.Add(p);
}
}
}
// remove start and end from it
if (alignedSockets.Contains(start))
alignedSockets.Remove(start);
if (alignedSockets.Contains(end))
alignedSockets.Remove(end);
return alignedSockets;
}
/// <summary>
/// Helper to check if a point P is on the line segment AB within a certain tolerance
/// </summary>
private bool IsPointOnSegment(Vector3 A, Vector3 B, Vector3 P, float tolerance)
{
Vector3 ab = B - A;
Vector3 ap = P - A;
// Project point P onto the line AB to find its "position" along the line (t)
float t = Vector3.Dot(ap, ab) / ab.sqrMagnitude;
// Check if the projection is within the segment (between 0 and 1)
if (t < 0 || t > 1) return false;
// Calculate the distance from the point to the line
Vector3 nearestPointOnSegment = A + t * ab;
float distToLine = Vector3.Distance(P, nearestPointOnSegment);
return distToLine <= tolerance;
}
/// <summary>
/// Finds compatible sockets for a joint block to connect to
/// </summary>
public List<SocketPoint> FindTargetSocketsForJoint(JointBlock jointBlock, Vector3 searchPosition)
{
List<SocketPoint> validSockets = new List<SocketPoint>();
SocketPoint end1 = jointBlock.GetSocketEnd1();
SocketPoint end2 = jointBlock.GetSocketEnd2();
if (end1 == null)
{
Debug.LogWarning("Joint block has no socket ends!");
return validSockets;
}
// Find all nearby sockets
Collider[] nearbyColliders = Physics.OverlapSphere(searchPosition, socketSearchRadius, blockLayerMask);
List<SocketPoint> nearbySockets = new List<SocketPoint>();
foreach (Collider col in nearbyColliders)
{
// Don't detect sockets on the joint itself
if (col.transform.root == jointBlock.transform.root) continue;
SocketPoint[] sockets = col.GetComponentsInChildren<SocketPoint>();
foreach (SocketPoint socket in sockets)
{
if (socket.CanAccept(end1, true))
{
nearbySockets.Add(socket);
}
}
}
if (nearbySockets.Count == 0)
{
Debug.Log("No compatible sockets found nearby");
return validSockets;
}
SocketPoint nearestSocket = FindClosestSocket(searchPosition, nearbySockets);
if (nearestSocket != null) validSockets.Add(nearestSocket);
// For a two-ended joint (like a pivot/axle)
if (end2 != null)
{
// Find the two closest compatible sockets
SocketPoint socket2 = FindSocketAtJointEnd(end1, end1, end2);
if (socket2 != null) validSockets.Add(socket2);
}
else
{
// Single-ended joint - just find closest socket
SocketPoint closest = FindClosestSocket(end1.GetTransform().position, nearbySockets);
if (closest != null) validSockets.Add(closest);
}
return validSockets;
}
private SocketPoint FindSocketAtJointEnd(SocketPoint firstSocket, SocketPoint end1, SocketPoint end2)
{
// 1. Calculate the required distance (the physical length of the joint)
float jointLength = Vector3.Distance(end1.GetTransform().position, end2.GetTransform().position);
// 2. Define the search direction (the negative normal of the first socket)
Vector3 searchDirection = -firstSocket.GetNormal();
// 3. Calculate exactly where the second end of the joint should land
Vector3 targetPosition = firstSocket.GetTransform().position + (searchDirection * jointLength);
// 4. Look for sockets near that target position
Collider[] candidates = Physics.OverlapSphere(targetPosition, socketSearchRadius, blockLayerMask);
SocketPoint bestMatch = null;
float closestDistToTarget = float.MaxValue;
foreach (Collider col in candidates)
{
// Don't connect the second end to the same block as the first end
// (Unless your design specifically allows 180-degree internal loops)
if (col.transform.root == firstSocket.GetTransform().root) continue;
SocketPoint[] sockets = col.GetComponentsInChildren<SocketPoint>();
foreach (SocketPoint socket in sockets)
{
// Skip the first socket and occupied ones
if (socket == firstSocket || socket.IsOccupied()) continue;
// Check if this socket can accept the 'end2' type
if (!socket.CanAccept(end2, true)) continue;
// Optional: Check if the socket's normal is facing the right way
// (e.g., the second socket should face the SAME way as the first for a pass-through)
float angleMatch = Vector3.Dot(socket.GetNormal(), firstSocket.GetNormal());
if (angleMatch < 0.9f) continue; // Only accept if normals are roughly aligned
float distToTarget = Vector3.Distance(socket.GetTransform().position, targetPosition);
if (distToTarget < closestDistToTarget)
{
closestDistToTarget = distToTarget;
bestMatch = socket;
}
}
}
return bestMatch;
}
private SocketPoint FindClosestSocket(Vector3 position, List<SocketPoint> sockets)
{
SocketPoint closest = null;
float minDistance = float.MaxValue;
foreach (SocketPoint socket in sockets)
{
float distance = Vector3.Distance(position, socket.GetTransform().position);
if (distance < minDistance)
{
minDistance = distance;
closest = socket;
}
}
return closest;
}
}