using System; using System.Collections.Generic; using System.Linq; namespace FurnitureExporter; public static class PanelFaceSemanticResolver { private const double SurfaceFaceToleranceMm = 3.0; private const double InteriorFaceToleranceMm = 0.8; public static string[] ResolveSurfaceFaces( BoundingBox3D? boundingBox, IReadOnlyList? position, IReadOnlyList? normal, double depth) { if (boundingBox?.Min?.Length != 3 || boundingBox.Max?.Length != 3) { return Array.Empty(); } var center = ToVector3(position); var axis = Normalize(ToVector3(normal)); if (center == null || axis == null) { return Array.Empty(); } var faces = new List(); var halfDepth = Math.Max(0, depth) / 2.0; var dominantFaces = ResolveDominantAxisFaces(axis); if (halfDepth > 0.01) { AddFace( faces, ResolveNearestSurfaceFace( boundingBox, Offset(center, axis, halfDepth), dominantFaces.Positive, dominantFaces.Negative, SurfaceFaceToleranceMm)); AddFace( faces, ResolveNearestSurfaceFace( boundingBox, Offset(center, axis, -halfDepth), dominantFaces.Negative, dominantFaces.Positive, SurfaceFaceToleranceMm)); } if (faces.Count == 0) { AddFace( faces, ResolveNearestSurfaceFace( boundingBox, center, dominantFaces.Positive, dominantFaces.Negative, SurfaceFaceToleranceMm + InteriorFaceToleranceMm)); } return faces .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); } public static string OppositeFace(string? face) { return face?.Trim().ToLowerInvariant() switch { "left" => "right", "right" => "left", "front" => "back", "back" => "front", "top" => "bottom", "bottom" => "top", _ => "unknown", }; } private static void AddFace(ICollection faces, string? face) { if (string.IsNullOrWhiteSpace(face)) { return; } faces.Add(face!.Trim().ToLowerInvariant()); } private static double[]? ToVector3(IReadOnlyList? value) { if (value == null || value.Count < 3) { return null; } return new[] { value[0], value[1], value[2] }; } private static double[]? Normalize(double[]? value) { if (value == null) { return null; } var length = Math.Sqrt(value[0] * value[0] + value[1] * value[1] + value[2] * value[2]); if (length < 1e-9) { return null; } return new[] { value[0] / length, value[1] / length, value[2] / length, }; } private static double[] Offset(double[] origin, double[] direction, double distance) { return new[] { origin[0] + direction[0] * distance, origin[1] + direction[1] * distance, origin[2] + direction[2] * distance, }; } private static string? ResolveNearestSurfaceFace(BoundingBox3D box, IReadOnlyList point) { return ResolveNearestSurfaceFace(box, point, null, null, SurfaceFaceToleranceMm); } private static string? ResolveNearestSurfaceFace( BoundingBox3D box, IReadOnlyList point, string? preferredFace, string? secondaryFace, double tolerance) { var candidates = new List<(string Face, double Distance)>(); if (IsWithinRange(point[1], box.Min[1], box.Max[1], tolerance) && IsWithinRange(point[2], box.Min[2], box.Max[2], tolerance)) { candidates.Add(("left", Math.Abs(point[0] - box.Min[0]))); candidates.Add(("right", Math.Abs(point[0] - box.Max[0]))); } if (IsWithinRange(point[0], box.Min[0], box.Max[0], tolerance) && IsWithinRange(point[2], box.Min[2], box.Max[2], tolerance)) { candidates.Add(("front", Math.Abs(point[1] - box.Min[1]))); candidates.Add(("back", Math.Abs(point[1] - box.Max[1]))); } if (IsWithinRange(point[0], box.Min[0], box.Max[0], tolerance) && IsWithinRange(point[1], box.Min[1], box.Max[1], tolerance)) { candidates.Add(("bottom", Math.Abs(point[2] - box.Min[2]))); candidates.Add(("top", Math.Abs(point[2] - box.Max[2]))); } var best = candidates .Where(item => item.Distance <= tolerance) .OrderBy(item => GetFacePriority(item.Face, preferredFace, secondaryFace)) .ThenBy(item => item.Distance) .FirstOrDefault(); return string.IsNullOrWhiteSpace(best.Face) ? null : best.Face; } private static (string Positive, string Negative) ResolveDominantAxisFaces(IReadOnlyList axis) { var absX = Math.Abs(axis[0]); var absY = Math.Abs(axis[1]); var absZ = Math.Abs(axis[2]); if (absX >= absY && absX >= absZ) { return axis[0] >= 0 ? ("right", "left") : ("left", "right"); } if (absY >= absX && absY >= absZ) { return axis[1] >= 0 ? ("back", "front") : ("front", "back"); } return axis[2] >= 0 ? ("top", "bottom") : ("bottom", "top"); } private static int GetFacePriority(string face, string? preferredFace, string? secondaryFace) { if (!string.IsNullOrWhiteSpace(preferredFace) && string.Equals(face, preferredFace, StringComparison.OrdinalIgnoreCase)) { return 0; } if (!string.IsNullOrWhiteSpace(secondaryFace) && string.Equals(face, secondaryFace, StringComparison.OrdinalIgnoreCase)) { return 1; } return 2; } private static bool IsWithinRange(double value, double min, double max, double tolerance) { return value >= min - tolerance && value <= max + tolerance; } }