From 52a1bb3d0d3d4a191b83df350fa3f054082659cb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:58:22 +0000 Subject: [PATCH 1/2] Fix plan filtering to support rotated geometries Replaced the conservative AABB check in `PlanLoader.ApplyConstraints` with a two-stage process: 1. Fast check using `plan.Bounds` (AABB). 2. Precise check verifying that all individual geometries are covered by the bounding polygon. This resolves an issue where rotated plans were incorrectly rejected because their axis-aligned bounding box exceeded the lot boundaries, even if the plan itself fit perfectly. Also exposed `ResPlan.Library` internals to `ResPlan.Tests` to allow testing the validation logic. --- ResPlan.Library/AssemblyInfo.cs | 3 ++ ResPlan.Library/PlanLoader.cs | 47 ++++++++++++++++++----- ResPlan.Tests/ConstraintTests.cs | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 ResPlan.Library/AssemblyInfo.cs create mode 100644 ResPlan.Tests/ConstraintTests.cs diff --git a/ResPlan.Library/AssemblyInfo.cs b/ResPlan.Library/AssemblyInfo.cs new file mode 100644 index 0000000..a4991f7 --- /dev/null +++ b/ResPlan.Library/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ResPlan.Tests")] diff --git a/ResPlan.Library/PlanLoader.cs b/ResPlan.Library/PlanLoader.cs index 6b281c0..6036012 100644 --- a/ResPlan.Library/PlanLoader.cs +++ b/ResPlan.Library/PlanLoader.cs @@ -175,17 +175,9 @@ private static List ApplyConstraints(List plans, PlanGenerationConst } // Apply Bounding Constraint - if (constraints.BoundingPolygon != null) + if (!IsPlanCompatible(plan, constraints.BoundingPolygon)) { - // Check if plan bounds are within polygon - // Since plan.Bounds is an Envelope, we convert to geometry - var geometryFactory = new GeometryFactory(); - var planGeom = geometryFactory.ToGeometry(plan.Bounds); - - if (!constraints.BoundingPolygon.Contains(planGeom)) - { - continue; // Filter out - } + continue; } result.Add(plan); @@ -193,6 +185,41 @@ private static List ApplyConstraints(List plans, PlanGenerationConst return result; } + internal static bool IsPlanCompatible(Plan plan, Polygon boundingPolygon) + { + if (boundingPolygon == null) return true; + + // Check if plan bounds are within polygon + // Since plan.Bounds is an Envelope, we convert to geometry + var geometryFactory = new GeometryFactory(); + var planGeom = geometryFactory.ToGeometry(plan.Bounds); + + // Fast check: if AABB is contained, we are good. + if (boundingPolygon.Contains(planGeom)) + { + return true; + } + + // Fallback: Precise check using actual geometries + // If the AABB doesn't fit (e.g. rotated plan), check if all individual geometries fit + bool allInside = true; + foreach (var kvp in plan.Geometries) + { + foreach (var geom in kvp.Value) + { + // Use Covers instead of Contains to allow boundary contact + if (!boundingPolygon.Covers(geom)) + { + allInside = false; + break; + } + } + if (!allInside) break; + } + + return allInside; + } + private static Plan ConvertDataToPlan(ResPlanData data) { var plan = new Plan diff --git a/ResPlan.Tests/ConstraintTests.cs b/ResPlan.Tests/ConstraintTests.cs new file mode 100644 index 0000000..72abd53 --- /dev/null +++ b/ResPlan.Tests/ConstraintTests.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using NetTopologySuite.Geometries; +using ResPlan.Library; +using Xunit; + +namespace ResPlan.Tests +{ + public class ConstraintTests + { + [Fact] + public void IsPlanCompatible_RotatedPlanInRotatedLot_ReturnsTrue() + { + // Arrange + var geometryFactory = new GeometryFactory(); + + // Create a Plan with a 10x10 square at (0,0) + // Centered at 5,5 + var coords = new Coordinate[] + { + new Coordinate(0, 0), + new Coordinate(10, 0), + new Coordinate(10, 10), + new Coordinate(0, 10), + new Coordinate(0, 0) + }; + var shell = geometryFactory.CreateLinearRing(coords); + var poly = geometryFactory.CreatePolygon(shell); + + var plan = new Plan + { + Id = 1, + Geometries = new Dictionary> + { + { "room", new List { poly } } + } + }; + // Initial bounds (0,0) to (10,10) + plan.Bounds = new Envelope(0, 10, 0, 10); + + // Rotate the plan by 45 degrees around its center (5,5) + plan.Rotate(System.Math.PI / 4.0, new Coordinate(5, 5)); + + // Now create a bounding polygon that matches the rotated plan geometry exactly + // We can cheat by using the plan's geometry itself (the rotated poly) + // But to be rigorous, let's use the rotated poly from the plan + var rotatedGeom = plan.Geometries["room"][0]; + Assert.True(rotatedGeom is Polygon); + var boundingPolygon = (Polygon)rotatedGeom.Copy(); + + // The bounding polygon now matches the plan's geometry. + // However, plan.Bounds (AABB) will be larger than the rotated geometry. + // AABB of a 45-degree rotated 10x10 square is roughly 14.14x14.14. + // This AABB will NOT be contained in the 10x10 rotated square (which is the boundingPolygon). + + // Act + bool isCompatible = PlanLoader.IsPlanCompatible(plan, boundingPolygon); + + // Assert + // This should be true if we are checking geometry containment correctly. + // It will be false if we are only checking AABB containment. + Assert.True(isCompatible, "Plan should be compatible with a bounding polygon that exactly matches its geometry."); + } + } +} From d2366f943a34f594ae2de66406d34482b786e9f0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:03:44 +0000 Subject: [PATCH 2/2] Fix plan filtering to support rotated geometries Replaced the conservative AABB check in `PlanLoader.ApplyConstraints` with a two-stage process: 1. Fast check using `plan.Bounds` (AABB). 2. Precise check verifying that all individual geometries are covered by the bounding polygon. This resolves an issue where rotated plans were incorrectly rejected because their axis-aligned bounding box exceeded the lot boundaries, even if the plan itself fit perfectly. Also exposed `ResPlan.Library` internals to `ResPlan.Tests` to allow testing the validation logic. --- DOCUMENTATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index bf43abd..c4ed46a 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -160,7 +160,7 @@ Handles the loading of plan data. * **maxItems**: Optional limit on the number of plans to load. * **logger**: Optional callback to receive real-time progress updates (e.g., dependency installation logs, download progress). * **constraints**: Optional `PlanGenerationConstraints` to filter or orient plans. - * **BoundingPolygon**: If set, only plans whose bounds are completely contained within this polygon are returned. + * **BoundingPolygon**: If set, only plans whose geometry is completely contained within this polygon are returned. The check first validates the plan's Axis-Aligned Bounding Box (AABB) for performance, and if that fails, performs a precise geometry-by-geometry containment check to support rotated plans. * **FrontDoorFacing**: If set, plans are rotated so their front door (vector from center to door) aligns with this vector. * **GarageFacing**: Currently unsupported; will log a warning if set. * **Returns**: A list of `Plan` objects.