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
Детерминированные тесты
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
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 начинается там, где линия взаимодействует уже не с другой линией, а с границей формы/объекта.