129 lines
5.4 KiB
C#
129 lines
5.4 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
public class PlacementValidator : MonoBehaviour, IValidator
|
|
{
|
|
[SerializeField] private LayerMask collisionLayerMask;
|
|
[SerializeField] private float toleranceSkin = 0.05f; // Shrinks the check box slightly
|
|
|
|
public void SetLayerMask(LayerMask cLayerMask) => collisionLayerMask = cLayerMask;
|
|
|
|
public bool IsValidPlacement(GameObject objToPlace)
|
|
{
|
|
Collider objCollider = objToPlace.GetComponent<Collider>();
|
|
if (objCollider == null) return true;
|
|
if (objCollider.isTrigger) return true;
|
|
if (objCollider.gameObject.layer == LayerMask.NameToLayer("Gizmos")) return true;
|
|
|
|
return CheckCollider(objCollider, objToPlace.transform.root);
|
|
}
|
|
|
|
private bool CheckCollider(Collider objCollider, Transform root)
|
|
{
|
|
Collider[] hits = new Collider[0];
|
|
|
|
if (objCollider is BoxCollider box)
|
|
{
|
|
Vector3 center = objCollider.transform.TransformPoint(box.center);
|
|
Vector3 lossyScale = objCollider.transform.lossyScale;
|
|
Vector3 scale = new Vector3(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z));
|
|
Vector3 size = Vector3.Scale(box.size, scale);
|
|
|
|
// Apply tolerance (shrink on both sides of each axis), but never shrink below 80% of original size
|
|
Vector3 overlapSize = new Vector3(
|
|
Mathf.Max(size.x - toleranceSkin * 2f, size.x * 0.8f),
|
|
Mathf.Max(size.y - toleranceSkin * 2f, size.y * 0.8f),
|
|
Mathf.Max(size.z - toleranceSkin * 2f, size.z * 0.8f)
|
|
);
|
|
|
|
hits = Physics.OverlapBox(center, overlapSize * 0.5f, objCollider.transform.rotation, collisionLayerMask);
|
|
}
|
|
else if (objCollider is SphereCollider sphere)
|
|
{
|
|
Vector3 center = objCollider.transform.TransformPoint(sphere.center);
|
|
Vector3 lossyScale = objCollider.transform.lossyScale;
|
|
float maxScale = Mathf.Max(Mathf.Abs(lossyScale.x), Mathf.Max(Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z)));
|
|
float radius = sphere.radius * maxScale;
|
|
float overlapRadius = Mathf.Max(0f, radius - toleranceSkin);
|
|
|
|
hits = Physics.OverlapSphere(center, overlapRadius, collisionLayerMask);
|
|
}
|
|
else if (objCollider is CapsuleCollider capsule)
|
|
{
|
|
Vector3 center = objCollider.transform.TransformPoint(capsule.center);
|
|
Vector3 lossyScale = objCollider.transform.lossyScale;
|
|
float scaleX = Mathf.Abs(lossyScale.x);
|
|
float scaleY = Mathf.Abs(lossyScale.y);
|
|
float scaleZ = Mathf.Abs(lossyScale.z);
|
|
|
|
float radiusScale = Mathf.Max(scaleX, scaleZ);
|
|
float heightScale = scaleY;
|
|
Vector3 dir = objCollider.transform.up;
|
|
|
|
if (capsule.direction == 0) // X-axis
|
|
{
|
|
radiusScale = Mathf.Max(scaleY, scaleZ);
|
|
heightScale = scaleX;
|
|
dir = objCollider.transform.right;
|
|
}
|
|
else if (capsule.direction == 2) // Z-axis
|
|
{
|
|
radiusScale = Mathf.Max(scaleX, scaleY);
|
|
heightScale = scaleZ;
|
|
dir = objCollider.transform.forward;
|
|
}
|
|
|
|
float radius = capsule.radius * radiusScale;
|
|
float height = capsule.height * heightScale;
|
|
|
|
float overlapRadius = Mathf.Max(0f, radius - toleranceSkin);
|
|
float overlapHeight = Mathf.Max(0f, height - (toleranceSkin * 2f));
|
|
|
|
float pointOffset = Mathf.Max(0f, (overlapHeight * 0.5f) - overlapRadius);
|
|
|
|
Vector3 point0 = center + dir * pointOffset;
|
|
Vector3 point1 = center - dir * pointOffset;
|
|
|
|
hits = Physics.OverlapCapsule(point0, point1, overlapRadius, collisionLayerMask);
|
|
}
|
|
else if (objCollider is MeshCollider mc && mc.sharedMesh != null)
|
|
{
|
|
Bounds localBounds = mc.sharedMesh.bounds;
|
|
Vector3 center = objCollider.transform.TransformPoint(localBounds.center);
|
|
Vector3 lossyScale = objCollider.transform.lossyScale;
|
|
Vector3 scale = new Vector3(Mathf.Abs(lossyScale.x), Mathf.Abs(lossyScale.y), Mathf.Abs(lossyScale.z));
|
|
Vector3 size = Vector3.Scale(localBounds.size, scale);
|
|
|
|
Vector3 overlapSize = new Vector3(
|
|
Mathf.Max(size.x - toleranceSkin * 2f, size.x * 0.8f),
|
|
Mathf.Max(size.y - toleranceSkin * 2f, size.y * 0.8f),
|
|
Mathf.Max(size.z - toleranceSkin * 2f, size.z * 0.8f)
|
|
);
|
|
|
|
hits = Physics.OverlapBox(center, overlapSize * 0.5f, objCollider.transform.rotation, collisionLayerMask);
|
|
}
|
|
else
|
|
{
|
|
// Fallback: AABB check
|
|
Vector3 center = objCollider.bounds.center;
|
|
Vector3 size = objCollider.bounds.size;
|
|
|
|
Vector3 overlapSize = new Vector3(
|
|
Mathf.Max(size.x - toleranceSkin * 2f, size.x * 0.8f),
|
|
Mathf.Max(size.y - toleranceSkin * 2f, size.y * 0.8f),
|
|
Mathf.Max(size.z - toleranceSkin * 2f, size.z * 0.8f)
|
|
);
|
|
|
|
hits = Physics.OverlapBox(center, overlapSize * 0.5f, Quaternion.identity, collisionLayerMask);
|
|
}
|
|
|
|
foreach (Collider hit in hits)
|
|
{
|
|
if (hit.transform.root == root) continue;
|
|
if (hit.isTrigger) continue;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |