A sofa can sit 17 inches from a coffee table while the route from the entry to the rest of the room is too narrow. The 17 inches measures a gap between two pieces. The route check asks whether a person can get from one point to another while maintaining clearance all the way.
The runnable example below uses axis-aligned furniture in a rectangular room. It illustrates the geometry independently of Roomture’s production code.
Represent the furniture as boxes
Use inches throughout. A box is a tuple (left, bottom, right, top). For each axis, find the gap between the two intervals. The distance is the hypotenuse of those two gaps. Touching and overlapping boxes have zero distance, including when one box fully contains another.
Search for a continuous route
For a 36-inch target, model the moving cross-section as a circle with an 18-inch radius. Its center must stay at least 18 inches from each wall and every obstacle. Start and goal are points inside the room. To model crossing a doorway you’d need to include the actual opening and space on both sides.
The search below moves along a two-inch grid with horizontal and vertical steps. Each step is checked over its full length, including the space between nodes. A clear route on this grid is feasible for the model. A failed search can also mean the grid missed a route, so try a finer grid for borderline cases.
from collections import deque
from math import hypot
def box_gap(a, b):
dx = max(a[0] - b[2], b[0] - a[2], 0)
dy = max(a[1] - b[3], b[1] - a[3], 0)
return hypot(dx, dy)
def route_exists(room, obstacles, start, goal, width, step=2):
"""Search on the grid anchored at start. Dimensions are in inches."""
if width <= 0 or step <= 0:
raise ValueError("width and step must be positive")
if any((end - origin) % step for origin, end in zip(start, goal)):
raise ValueError("goal must lie on the grid anchored at start")
radius = width / 2 left, bottom, right, top = room def inside_inset(point): x, y = point return left + radius <= x <= right - radius and ( bottom + radius <= y <= top - radius ) def clear_segment(a, b): x1, x2 = sorted((a[0], b[0])) y1, y2 = sorted((a[1], b[1])) segment = (x1, y1, x2, y2) return all(box_gap(segment, item) >= radius for item in obstacles) for point in (start, goal): if not inside_inset(point) or not clear_segment(point, point): return False queue = deque([start]) seen = {start} while queue: x, y = current = queue.popleft() if current == goal: return True for point in ((x + step, y), (x - step, y), (x, y + step), (x, y - step)): if point in seen or not inside_inset(point): continue if clear_segment(current, point): seen.add(point) queue.append(point) return False if name == "main":
sofa = (14, 58, 98, 90)
table = (33, 17, 79, 41)
assert box_gap(sofa, table) == 17
assert box_gap(sofa, (20, 60, 30, 70)) == 0
room = (0, 0, 140, 100) start, goal = (24, 48), (116, 48) lower = (50, 0, 90, 30) wide = [lower, (50, 66, 90, 100)] narrow = [lower, (50, 64, 90, 100)] assert route_exists(room, wide, start, goal, width=36) assert not route_exists(room, narrow, start, goal, width=36) assert not route_exists(room, [], (16, 48), goal, width=36) print(f"Sofa-to-table gap: {box_gap(sofa, table):.0f} in") print("36 in opening: route found") print("34 in opening: no 36 in route found") The two barriers in the example touch the top and bottom walls, so there’s no path around their ends. With a 36-inch opening, the center can pass at y = 48, exactly 18 inches from each obstacle. Narrow the opening to 34 inches and that route closes. The 36-inch target is a parameter for the example.
For angled furniture or concave rooms, use polygon footprints, test full containment, and search against the inward-offset room and outward-offset obstacles. Door swings can be represented as swept regions. In an actual room, use measured room and furniture dimensions when a few inches could change the result.
See how furniture layout planning works in Roomture.


