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