227 lines
6.5 KiB
C#

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<double>? position,
IReadOnlyList<double>? normal,
double depth)
{
if (boundingBox?.Min?.Length != 3 || boundingBox.Max?.Length != 3)
{
return Array.Empty<string>();
}
var center = ToVector3(position);
var axis = Normalize(ToVector3(normal));
if (center == null || axis == null)
{
return Array.Empty<string>();
}
var faces = new List<string>();
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<string> faces, string? face)
{
if (string.IsNullOrWhiteSpace(face))
{
return;
}
faces.Add(face!.Trim().ToLowerInvariant());
}
private static double[]? ToVector3(IReadOnlyList<double>? 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<double> point)
{
return ResolveNearestSurfaceFace(box, point, null, null, SurfaceFaceToleranceMm);
}
private static string? ResolveNearestSurfaceFace(
BoundingBox3D box,
IReadOnlyList<double> 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<double> 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;
}
}