ここは Structure Layer → Exterior Layer の中でも最難関のひとつ。
でも OS 的に分解すれば、破綻なく・実装しやすく・Roblox に渡しやすい「投影アルゴリズム」が作れる。
ここでは 「room+direction → 外周セグメント」へのマッピングロジック を
完全に実装可能な形でまとめるね。
🧭 全体の考え方(OSレイヤーで整理)
外周ポリゴンはすでに得られている:
outline = [(x1,y1), (x2,y2), ...]
部屋の bounds もある:
room.bounds = {x, y, width, depth}
opening(窓・ドア)は:
{
"room": "r1",
"direction": "south",
"offset": 3.0,
"width": 2.0
}
やるべきことは:
- opening の属する部屋の「該当壁の絶対座標」を求める
- その壁が外周ポリゴンのどのセグメントと一致するか判定
- offset を外周セグメント上の座標に変換
- opening を外周セグメントにマッピングして返す
🟦 Step 1:部屋の壁を絶対座標で求める
部屋の bounds から 4 つの壁を求める:
north: (x, y+depth) → (x+width, y+depth)
south: (x, y) → (x+width, y)
east: (x+width, y) → (x+width, y+depth)
west: (x, y) → (x, y+depth)
🟧 Step 2:外周ポリゴンの各セグメントと一致するか判定
外周ポリゴンのセグメント:
[(x1,y1),(x2,y2)], [(x2,y2),(x3,y3)], ...
部屋の壁と外周セグメントが一致する条件:
- 線分が完全に重なる(colinear)
- 重なり部分が存在する(overlap)
これは Shapely を使うと簡単:
from shapely.geometry import LineString
room_wall = LineString([(x1,y1),(x2,y2)])
seg = LineString([(sx1,sy1),(sx2,sy2)])
if room_wall.intersects(seg):
# 外周に面している壁
🟨 Step 3:offset を外周セグメント上の座標に変換
opening の offset は「部屋の壁の左端からの距離」。
外周セグメントと部屋の壁が一致したら:
opening_start = seg_start + (offset / seg_length) * (seg_vector)
opening_end = opening_start + (width / seg_length) * (seg_vector)
🟪 Step 4:opening を外周セグメントにマッピングして返す
最終的に:
{
"id": "w1",
"wall_index": 3,
"from": [xA, yA],
"to": [xB, yB]
}
🧩 完全実装(Flask + Shapely)
from shapely.geometry import LineString
def get_room_wall_coords(room, direction):
b = room["bounds"]
x, y = b["x"], b["y"]
w, d = b["width"], b["depth"]
if direction == "south":
return (x, y), (x + w, y)
if direction == "north":
return (x, y + d), (x + w, y + d)
if direction == "east":
return (x + w, y), (x + w, y + d)
if direction == "west":
return (x, y), (x, y + d)
raise ValueError("invalid direction")
def project_openings_to_exterior(rooms, outline, openings):
# room_id → room データ
room_map = {r["id"]: r for r in rooms}
exterior_segments = []
n = len(outline)
for i in range(n):
p1 = outline[i]
p2 = outline[(i + 1) % n]
exterior_segments.append(LineString([p1, p2]))
projected = []
for op in openings:
room = room_map[op["room"]]
(rx1, ry1), (rx2, ry2) = get_room_wall_coords(room, op["direction"])
room_wall = LineString([(rx1, ry1), (rx2, ry2)])
# offset / width
offset = op["offset"]
width = op["width"]
for idx, seg in enumerate(exterior_segments):
if not room_wall.intersects(seg):
continue
# 外周セグメント上の位置を計算
seg_len = seg.length
if seg_len == 0:
continue
# セグメントの始点・方向ベクトル
sx, sy = seg.coords[0]
ex, ey = seg.coords[1]
dx = ex - sx
dy = ey - sy
# offset → opening start
t1 = offset / seg_len
t2 = (offset + width) / seg_len
ox1 = sx + dx * t1
oy1 = sy + dy * t1
ox2 = sx + dx * t2
oy2 = sy + dy * t2
projected.append({
"id": op["id"],
"wall_index": idx,
"from": [ox1, oy1],
"to": [ox2, oy2]
})
break
return projected
🟦 これを Flask の /generate_exterior に統合すると:
projected_openings = project_openings_to_exterior(
rooms,
outline_coords,
openings["windows"] + openings["doors"]
)
exterior = {
"outline": outline_coords,
"walls": walls,
"openings": projected_openings
}
🎯 これで完成するもの
- Structure Layer の room.bounds
- direction(north/south/east/west)
- offset / width
から、
外周ポリゴン上の正確な opening 座標が自動生成される。
Roblox 側は:
- wall_index
- from/to 座標
- height
を受け取れば、そのまま Window/ Door を配置できる。
この次はどっちを進める?
- Roblox 側の「外壁+opening を Mesh/Part に変換するコード」
- Structure Layer の JSON スキーマを正式に定義
- 3D 化のための「壁厚」「外壁 offset」アルゴリズム
- 2階以上の opening の扱い(Z 軸の導入)
どれでもいけるよ。