COMPO · 2.10 · LINE INTERSECTIONS

Пересечения
линий.

Модуль определяет, где и как линии пересекаются: точка пересечения, угол встречи, наложение, повторные crossings, near-crossing и локальный конфликт. Он измеряет геометрию контакта, но не решает сам, «красиво» ли пересечение и какую линию следует удалить.

API-контракт

00 / ownership

CONSUMES

Line A / Line B A.pathGeometry B.pathGeometry A.geometryType B.geometryType A.strokeWidthPx? B.strokeWidthPx? A.role / B.role A.rigidity / B.rigidity geometryOwnerA.flatten(tolerancePx)? geometryOwnerB.flatten(tolerancePx)? StylePreset.intersections?

PROVIDES

IntersectionRelation[] CrossingDescriptor: pointN type angleDeg acuteAngleDeg pathPositionA pathPositionB overlapLengthPx? nearDistancePx? confidence line.intersection.count line.intersection.crossingPenalty line.intersection.overlapPenalty line.intersection.nearCrossingPenalty IntersectionCorrectionRequest[]

DOES NOT OWN

line-shape tangency // 2.11 grid alignment // 2.13 connector semantics // 2.09 line visual weight // 2.06 line color contrast // 2.16 global hierarchy global balance global eye-flow
Ключевой принцип: пересечение — факт геометрии. Конфликт — role/style-dependent интерпретация. Нельзя автоматически объявлять каждое пересечение ошибкой.

Типы line-line relation

01 / taxonomy
X
proper crossing

Линии пересекаются внутренними участками в одной точке.

T
endpoint touch

Endpoint одной линии попадает на другую линию.

collinear overlap

Линии лежат на одной оси и имеют общий участок.

near crossing

Геометрически не пересекаются, но clearance между stroke-областями мал.

IntersectionType = PROPER_CROSSING | ENDPOINT_TOUCH | COLLINEAR_OVERLAP | NEAR_CROSSING | NONE

Отрезок × отрезок

02 / exact geometry

Orientation test

orient(a,b,c) = cross(b-a, c-a) s1 = orient(A0,A1,B0) s2 = orient(A0,A1,B1) s3 = orient(B0,B1,A0) s4 = orient(B0,B1,A1) proper crossing if: sign(s1) != sign(s2) AND sign(s3) != sign(s4)

DERIVED С допуском EPS для почти коллинеарных случаев.

Intersection point

A(t) = A0 + t(A1-A0) B(u) = B0 + u(B1-B0) solve: A(t) = B(u) proper crossing: 0 < t < 1 0 < u < 1

Если t или u около 0/1 — это endpoint touch, а не proper crossing.

EPS policy: EPS задаётся относительно масштаба холста/геометрической точности и не должен использоваться как эстетический clearance. Геометрическая численная устойчивость и визуальное «слишком близко» — разные параметры.

Curve / polyline / segment

03 / canonical adapter

SEGMENT

1 primitive segment exact test

POLYLINE

iterate segment pairs skip adjacent self-segments when appropriate

CURVE

adaptive flattening or geometry-owner curve intersection API
CANONICAL INTERSECTION PIPELINE segmentsA = geometryOwnerA.flatten(tolerancePx) segmentsB = geometryOwnerB.flatten(tolerancePx) broad phase: reject segment pairs by bbox overlap narrow phase: exact segment intersection map local segment parameter back to pathPosition ∈ [0,1] deduplicate nearby hits using geometric epsilon OUTPUT: IntersectionRelation[]
Ownership: 2.10 может просить flattening, но не пересчитывает Bézier-кривую или topology polyline. Источник истины остаётся в 2.04/2.05.

Угол пересечения

04 / crossing geometry
θ
signed angle
θ = atan2( cross(Ta,Tb), dot(Ta,Tb) )

Диапазон −180°..180°.

α
acute angle
α = min( abs(θ), 180-abs(θ) ) 0°..90°

Удобно для измерения «насколько пересечение похоже на касание».

90
orthogonal

Пересечение под 90° — геометрически однозначное crossing, но не автоматически эстетически предпочтительное.

Малый acuteAngleDeg может быть precursor к визуальной tangency-like ambiguity, но именно line-line crossing остаётся ownership 2.10. Контакт линии с формой — 2.11.

Near-crossing без пересечения

05 / stroke-aware clearance

Centerline distance

dCenter = minDistance(pathA,pathB) strokeClearance = dCenter - 0.5*widthA - 0.5*widthB

Если ширина неизвестна, можно вычислить только centerline distance и снизить confidence.

Near threshold

if 0 < strokeClearance < nearThresholdPx: type = NEAR_CROSSING nearCrossingPenalty = 1 - clamp( strokeClearance / nearThresholdPx, 0,1 )

HEURISTIC Порог зависит от масштаба, width и style preset.

Если strokeClearance ≤ 0, stroke-области уже соприкасаются/перекрываются даже при отсутствии centerline crossing. Это важно для толстых линий.

Local conflict scoring

06 / role-aware

OWNS

line.intersection.count line.intersection.properCrossingCount line.intersection.endpointTouchCount line.intersection.overlapCount line.intersection.nearCrossingCount line.intersection.crossingPenalty line.intersection.overlapPenalty line.intersection.nearCrossingPenalty line.intersection.localConflictScore

DEFERRED

semantic necessity connector priority global hierarchy global eye-flow global balance color/contrast z-order policy

Например, пересечение двух texture-линий и двух semantic connector-линий — разные случаи.

localConflictScore = 1 - clamp( wX * crossingPenalty + wO * overlapPenalty + wN * nearCrossingPenalty, 0,1) // weights depend on role pair: // connector×connector > texture×texture // STYLE_PRESET / HEURISTIC
Не дублировать: 2.10 владеет геометрией crossing и локальным line-line conflict. 2.09 может потреблять этот penalty для connector candidate, но не считать его повторно.

Correction requests

07 / no foreign mutation

MOVE

Сместить endpoint / whole path.

REROUTE

Попросить 2.04/2.05 изменить маршрут.

REORDER

Попросить z-order owner изменить визуальное перекрытие.

ALLOW

Оставить intentional crossing и зафиксировать reason code.

IntersectionCorrectionRequest { relationId, lineIds:[A,B], reasonCode: UNINTENTIONAL_CROSSING | LOW_ANGLE_CROSSING | COLLINEAR_OVERLAP | NEAR_CROSSING | EXCESSIVE_CROSSING_COUNT, targetOwner: "Compo2.04" | "Compo2.05" | "Compo2.09" | "GLOBAL_SOLVER" | "Z_ORDER_OWNER", requestedChange, bounds?, costEstimate? }

Визуальная лаборатория

08 / measurable cases
PROPER CROSSING: одна точка, угол близок к 56.8°.
ENDPOINT TOUCH: T-связь, не proper crossing.
COLLINEAR OVERLAP: общий участок должен хранить длину overlap, не одну «точку пересечения».
Centerlines не пересекаются, но толстые stroke почти соприкасаются: near-crossing определяется через stroke clearance.
Curve × segment может давать несколько crossings. 2.10 хранит каждый hit с pathPosition A/B.

Детерминированные тесты

09 / regression

TEST 01 · X

A:(0,0)→(10,10) B:(0,10)→(10,0) expected: type=PROPER_CROSSING point=(5,5)

TEST 02 · T

A:(0,0)→(10,0) B:(5,-5)→(5,0) expected: ENDPOINT_TOUCH point=(5,0)

TEST 03 · parallel

A:y=0 B:y=3 expected: NONE centerlineDistance=3

TEST 04 · overlap

A:(0,0)→(10,0) B:(4,0)→(14,0) expected: COLLINEAR_OVERLAP overlapLength=6

TEST 05 · angle

Ta=(1,0) Tb=(0,1) expected: acuteAngle=90°

TEST 06 · no leak

change only: global.balanceScore expected: intersection relations UNCHANGED

TEST 07 · width missing

stroke width = N/A expected: centerline geometry valid near-crossing confidence reduced must not invent width

TEST 08 · multiplicity

curve crosses line twice expected: 2 distinct relations after deduplication

TEST 09 · ownership

connector conflict found 2.10 MAY: emit penalty/request 2.10 MUST NOT: reroute connector itself

LIVE REFERENCE TESTS

IDInputComputedStatus

DATA FOR LAYOUT ENGINE

10 / machine contract
{ "module": "Compo 2.10", "name": "line_intersections", "version": "1.0", "scope": "LOCAL_LINE_LINE_CONTACT", "consumes": [ "Compo2.00.Line", "Compo2.00.LinePathGeometry", "GeometryOwner.flatten(tolerancePx)?", "Compo2.06.Line.strokeWidthPx?", "StylePreset.intersections?" ], "provides": [ "IntersectionRelation[]", "CrossingDescriptor", "IntersectionCorrectionRequest[]" ], "intersection_types": [ "PROPER_CROSSING", "ENDPOINT_TOUCH", "COLLINEAR_OVERLAP", "NEAR_CROSSING", "NONE" ], "owns_metrics": [ "line.intersection.count", "line.intersection.properCrossingCount", "line.intersection.endpointTouchCount", "line.intersection.overlapCount", "line.intersection.nearCrossingCount", "line.intersection.crossingPenalty", "line.intersection.overlapPenalty", "line.intersection.nearCrossingPenalty", "line.intersection.localConflictScore" ], "hard_rules": [ "geometric_intersection_must_be_deterministic_with_numeric_epsilon", "geometric_epsilon_must_not_equal_visual_clearance_threshold", "curve_polyline_geometry_must_come_from_geometry_owner", "multiple_distinct_crossings_must_not_be_collapsed_to_one", "missing_width_must_not_be_invented", "must_not_own_line_shape_tangency_metrics", "must_not_mutate_foreign_geometry_directly" ], "initial_heuristics": { "flatten_tolerance_px": {"value":0.75,"source":"IMPLEMENTATION_HEURISTIC"}, "intersection_dedupe_px": {"value":1.0,"source":"IMPLEMENTATION_HEURISTIC"}, "near_threshold_width_multiplier": {"value":1.5,"source":"INITIAL_HEURISTIC"} }, "deferred_inputs": [ "Compo2.09.semanticConnectionFit", "Compo2.11.lineShapeTangencyPenalty", "global.eyeFlowScore", "global.hierarchyScore", "global.balanceScore", "zOrder.policy" ], "recompute_when": [ "LinePathGeometry A changes", "LinePathGeometry B changes", "stroke width changes", "role/rigidity changes", "StylePreset.intersections changes" ], "finality": "LOCAL_CONTACT_ANALYSIS_ONLY" }
ARCHITECTURE CHECKPOINT: 2.10 — единственный владелец line-line crossing geometry и локального crossing conflict. 2.09, 2.13 и global solver только потребляют эти результаты; 2.11 начинается там, где линия взаимодействует уже не с другой линией, а с границей формы/объекта.