#if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace AutomaticDoorSystem.Editor { /// /// The panel-collider authoring checks, shared by the DoorAuthoring inspector and the Setup /// Validator so both surfaces always agree. Mirrors the baker's collider handling /// (DoorColliderBake): the bake is agnostic to where the BoxCollider lives - child offsets, /// right-angle rotations, scale and mirrored bake-time transforms all fold into the panel /// frame - so these checks only flag what genuinely cannot be reproduced (oblique rotations /// bake bloated, mirrored panel nodes break the runtime pose) plus the setups that tell on /// stale pre-2.14 bakes. Nothing is silent, and findings that can be repaired locally carry /// a one-click, undoable . /// internal static class DoorColliderChecks { internal enum Level { Info, Warning } internal struct Finding { public Level Level; public string Message; public Object Context; /// Optional one-click repair (undoable). Null when only re-authoring can fix it. public System.Action Fix; public string FixLabel; } /// Runs every panel-collider check for one door, appending to . internal static void Analyze(DoorAuthoring door, List results) { if (door == null || door.doorConfig == null) return; if (door.doorConfig.doorCount == DoorConfig.DoorCountEnum.Double) { AnalyzePanel(door.leftDoorMesh, "left", results); AnalyzePanel(door.rightDoorMesh, "right", results); } else { AnalyzePanel(door.doorMesh, "door", results); } } private static void AnalyzePanel(Transform panel, string which, List results) { if (panel == null) return; // A mirrored panel node breaks the runtime pose itself: the pooled proxy takes the // panel entity's LocalToWorld rotation, which is undefined for a negative-scale // matrix - no bake math can compensate. Authoring change required, so no auto-fix. var panelScale = panel.lossyScale; if (panelScale.x < 0f || panelScale.y < 0f || panelScale.z < 0f) { results.Add(new Finding { Level = Level.Warning, Message = $"The {which} panel '{panel.name}' has a NEGATIVE scale ({panelScale}) - the runtime " + "collider proxy derives its pose from the panel's LocalToWorld rotation, which cannot " + "represent a mirror, so the pooled collider will sit wrong. Re-author the panel without " + "negative scale (mirror via a 180° rotation instead).", Context = panel }); } var box = panel.GetComponent(); if (box == null) box = panel.GetComponentInChildren(); if (box == null) { var fixablePanel = panel; var hasRenderers = panel.GetComponentInChildren() != null; results.Add(new Finding { Level = Level.Warning, Message = $"The {which} panel '{panel.name}' has no BoxCollider. Baking copies its size onto " + "the pooled collider, so without one the panel falls back to a generic 1 x 2.5 x 0.1 box.", Context = panel, Fix = hasRenderers ? () => AddBoxFromRendererBounds(fixablePanel) : (System.Action)null, FixLabel = "Add BoxCollider from mesh bounds" }); return; } var worldSize = DoorColliderBake.WorldSize(box); DoorColliderBake.DescribeInPanelFrame( panel.position, panel.rotation, DoorColliderBake.WorldCenter(box), box.transform.rotation, worldSize, out _, out var bakedSize, out var relativeAngle); // Right-angle rotations reproduce exactly and stay silent; only oblique ones bloat. if (DoorColliderBake.BloatRatio(bakedSize, worldSize) > DoorColliderBake.BloatWarnRatio) { var fixablePanel = panel; var fixableBox = box; results.Add(new Finding { Level = Level.Warning, Message = $"The {which} panel's BoxCollider (on '{box.name}') is rotated {relativeAngle:0}° " + "relative to the panel pivot. The pooled runtime collider is an axis-aligned box in " + "the panel's frame, so it bakes as the enclosing (larger) box. The fix moves an " + "equivalent box onto the panel pivot (same enclosing size the bake would use, but " + "then edit mode and runtime match exactly).", Context = box, Fix = () => MoveBoxOntoPanelPivot(fixablePanel, fixableBox), FixLabel = "Move box onto panel pivot" }); } if (box.transform != panel) { var childOffset = panel.InverseTransformPoint(box.transform.position); if (childOffset.magnitude > 0.05f) { results.Add(new Finding { Level = Level.Info, Message = $"The {which} panel's BoxCollider lives on child '{box.name}' at local offset " + $"{childOffset}. Handled correctly since package 2.14.0 - but a subscene baked " + "with an older version places this collider on the wrong side of the hinge. If " + "the Door Doctor reports a misaligned collider for this door, re-import (re-bake) " + "its subscene.", Context = box }); } } } // --------------------------------------------------------------------------------------- // One-click fixes. Both express the box in the panel node's own local space (position via // InverseTransformPoint, size divided by |lossyScale|), so the authored component matches // what the bake computes - edit mode and runtime become identical by construction. /// Adds a BoxCollider on the panel pivot enclosing everything the panel renders. private static void AddBoxFromRendererBounds(Transform panel) { var renderers = panel.GetComponentsInChildren(); if (renderers.Length == 0) return; var min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); var max = new Vector3(float.MinValue, float.MinValue, float.MinValue); foreach (var renderer in renderers) { var b = renderer.bounds; // All 8 corners of the world AABB into panel space - conservative but correct // for any panel orientation. for (var i = 0; i < 8; i++) { var corner = b.center + Vector3.Scale(b.extents, new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1)); var local = panel.InverseTransformPoint(corner); min = Vector3.Min(min, local); max = Vector3.Max(max, local); } } var box = Undo.AddComponent(panel.gameObject); box.center = (min + max) * 0.5f; box.size = max - min; } /// /// Replaces an off-pivot (rotated) BoxCollider with an equivalent one on the panel pivot: /// the same enclosing box the bake computes, so nothing changes at runtime - but edit-mode /// physics now matches it exactly and the warning goes away. /// private static void MoveBoxOntoPanelPivot(Transform panel, BoxCollider oldBox) { var worldSize = DoorColliderBake.WorldSize(oldBox); var worldCenter = DoorColliderBake.WorldCenter(oldBox); DoorColliderBake.DescribeInPanelFrame( panel.position, panel.rotation, worldCenter, oldBox.transform.rotation, worldSize, out _, out var bakedSize, out _); var lossy = panel.lossyScale; var localSize = new Vector3( bakedSize.x / Mathf.Max(Mathf.Abs(lossy.x), 1e-6f), bakedSize.y / Mathf.Max(Mathf.Abs(lossy.y), 1e-6f), bakedSize.z / Mathf.Max(Mathf.Abs(lossy.z), 1e-6f)); Undo.DestroyObjectImmediate(oldBox); var box = Undo.AddComponent(panel.gameObject); box.center = panel.InverseTransformPoint(worldCenter); box.size = localSize; } } } #endif