メインコンテンツまでスキップ

凸片 — 割付から領域へ

空間の領域は矩形の合併として書かれる。書かれた割付は形ではない。形は、割付から導かれた凸片の列である。

form.spaces[0].outline
// [ [ {x:0,y:0}, {x:3600,y:0}, {x:3600,y:4500}, {x:0,y:4500} ] ]

面積も、壁の位置も、屋根の輪郭も、柱の立地も、投影も — 形を読むすべての導出がこの一つの入口を通る。

割付から凸片へ

導出はまず各矩形を反時計回りの四頂点へ写す。

{x1,y1,x2,y2}  →  [(x1,y1), (x2,y1), (x2,y2), (x1,y2)]

次に、境界に描かれた線があれば、その半平面で切り直す。

線が無い    :  凸片 = 割付の写し
線がある    :  凸片 = 窓の外 + 窓の中を半平面で切った残り

切り直しは合成の出口で一度だけ効く。冪等ではない — 先に効いた線が縮めた領域が、後の線の窓を縮める。

切る順序は、宣言順ではない

線の切り分けは正準の境界順に効く。並びの規則は正準 JSON と同じで、between の辞書順、同じ between は直列化順である。合成で境界が挟まっても並びは動かない。

宣言順で切ってはならない理由は、正準形が宣言順を捨てるからである。捨てられる情報で切ると、同じ正準形から違う面積が出る — 交差する二本の線を持つ実測で、同じバイト列のモデルから 27.00 ㎡ と 22.50 ㎡ が出た。形は正準形の関数でなければならない。

線の及ぶ窓

線は無限直線ではない。線に沿っては限られた区間を、線を横切っては相手を含む範囲を切る。この非対称が要である — 無限直線として扱うと離れた翼を巻き込んで室が消え、線分の外接矩形として扱うと軸平行の線で退化して何も切れない。どちらの誤りも実際に起きた。

線の向きは |Δy| ≥ |Δx| なら縦向きである(ちょうど 45 度と退化した点も縦向きに数える)。縦向きなら「沿う」軸が y、「横切る」軸が x で、横向きならその逆になる。

場合沿う軸横切る軸
二空間を分け直す(両側が領域を持つ)二つの領域の外接矩形の二つの外接矩形の
外皮を切る(片側が領域を持たない)線分自身の区間領域を持つ側の外接矩形全体

窓は二辺とも許容 EPS を超えていなければ実体を持たず、その線は何も切らない(LIN01)。

残す側

線のどちら側を残すかは書かない。窓に触れる凸片を丸ごと測って決める。窓に触れるかは外接矩形どうしの重なりで見る。各片を線で二分し、左の面積の合計と右の面積の合計を比べ、差が AREA_EPS 未満なら「偏りなし」とする。

切るのは窓の中だけ、決めるのは片の全体である。全ての凸片を測ると線が届かない翼が符号を支配して室が消え、窓の中だけを測ると隅から隅への線が「二等分」に見える。

  • 外皮を切る場合 — 領域を持つ側の偏りが、そのまま残す側である。偏りなしなら切らない
  • 二空間の場合 — 両側の偏りを取り、片方だけが 0 なら他方の反対を当てる。両方 0 でも、同じ側でも切らない

a/b のどちらを先に書いたかは、残す側に効かない。boundary /L1/room /outboundary /out /L1/room は同じ関係の二つの綴りであり、残す側は領域を持つ側そのものが決める。a/b の向きが意味を持つのは edgeswing — 「どちらから見た関係か」を必要とする二つ — だけである。この規則を持たなかったとき、書き順を逆にしただけで 26 ㎡ と 34 ㎡ に割れ、check は緑のままだった。

分け直しの操作

窓の内と外へ割り、窓の中の割付を合併してから線の両側へ分け直す

  • 二空間の分け直しでは合計面積が保存される — 一方が失う三角形を他方が得る
  • 外皮を切る場合は領域を持つ側だけが減る — 相手は面積を得ない

半平面で切った結果が三頂点未満か、面積が AREA_EPS 以下なら、その片は無かったことにする。この閾値が無ければ、隅切りの端に髪の毛のような破片が残り、それが辺として読まれて幽霊の壁を生む。

線は向きを持たない

同じ二点を結ぶ線は、どちらの端から書いても同じ線である。導出は切り分けの前に、各線の端点を解決座標の (x, 次いで y) 昇順へ揃える。正準 JSON が端点の対に使う規則と同じものである。綴り(通り参照)も一緒に入れ替わるので、診断が引用する綴りは書かれたとおりのまま順だけが入れ替わる。

この正準の始端が、線に載る開口の at: の起点である。揃えなければ、正準 JSON がバイト同一のまま扉が別の位置に出る — 実測で line X1,Y1+2000 X2,Y1+4000line X2,Y1+4000 X1,Y1+2000 が同じハッシュのまま扉を (1500, 2500) と (4500, 3500) に置いていた。

切り分けの帰結

線が実際に何をしたかは、導出したその場で記録される。公開型 DrawnLineeffect がそれを持つ。

意味診断
"cut"実際に形を切った
"nothing"何も切らなかった — 既定の隣接線と同じか、線の及ぶ範囲に割付が無いLIN03(warning)
"undetermined"残す側が決まらない — 両側の偏りが同じか、割付をちょうど二等分したLIN01(error)

後から計算し直さないのは、そのときには既に切られた形が相手になっていて、母集団が食い違うからである。effect は導出の帰結であって書かれた構成ではないので、正準 JSON には出ない。

一つ書いてみる

koyu 1.0
unit mm
grid X 0 6000
grid Y 0 6000
level L1 0 h:2700 slab:200

space /L1/a room X1..X2 Y1..Y2 name:隅切りの室
space /out exterior name:外部

boundary /L1/a /out t:150 spec:RC
  line X1,Y2-3000 X1+3000,Y2

6000 × 6000 の割付から北西の三角形 (3000 × 3000 ÷ 2 = 4.5 ㎡) が落ちる。

npx tsx src/cli.ts stats corner.muro
L1
  /L1/a	隅切りの室	room	31.50 m2
  Subtotal 31.50 m2
Total 31.50 m2 (indoor floor area)
  room: 31.50 m2

割付は 36 ㎡ のままだが、面積は凸片から出る

隣り合う頁

  • derive(model) と四つの約束
  • 境界線分 — 凸片の辺から壁が立つ
  • line — 線をどう書くか
  • space — 割付をどう書くか
  • LIN の診断 — 切れなかったときに何が出るか