COMPO · 2.11 · LINE × SHAPE CONTACT

Касания
линии
и формы.

Модуль формализует отношения между линией и границей объекта: пересечение, касание, near-tangency, вход внутрь формы, attachment и случайный «почти контакт». Цель — отделить точную геометрию контакта от визуального конфликта и не смешивать её с line-line intersections.

API-контракт

00 / ownership

CONSUMES

Line Line.pathGeometry Line.strokeWidthPx? Line.role Line.rigidity SceneObject Object.boundsN Object.shapeGeometry? Object.shapeSDF? Object.anchorHints? OPTIONAL: Compo2.09.AttachmentDescriptor Compo2.09.ConnectionIntent StylePreset.contacts?

PROVIDES

LineShapeContactRelation[] ContactDescriptor: objectId pathPosition pointN type signedDistancePx tangentAngleDeg? penetrationDepthPx? contactLengthPx? confidence line.shapeContact.count line.shapeContact.penalty line.shapeContact.tangencyPenalty line.shapeContact.penetrationPenalty line.shapeContact.nearContactPenalty ContactCorrectionRequest[]

DOES NOT OWN

line-line intersections // 2.10 semantic attachment intent // 2.09 line visual weight // 2.06 line color contrast // 2.16 grid alignment // 2.13 global hierarchy global balance global eye-flow
Boundary: 2.10 — линия против линии. 2.11 — линия против границы формы/объекта. Если connector специально прикреплён к объекту, 2.11 анализирует геометрию контакта, но semantic correctness attachment остаётся у 2.09.

Типы line-shape relation

01 / taxonomy
×
crossing

Линия проходит через границу формы и входит/выходит из interior.

tangent touch

Линия касается boundary без перехода внутрь.

near tangency

Линия почти касается формы, но между stroke и границей остаётся малый зазор.

attachment

Endpoint намеренно закреплён на boundary/port объекта.

LineShapeContactType = BOUNDARY_CROSSING | TANGENT_TOUCH | NEAR_TANGENCY | ATTACHMENT | INTERIOR_RUN | NONE

Универсальная signed-distance модель

02 / preferred representation

Shape SDF

d(p) = signedDistanceToShape(p) d > 0 → outside d = 0 → boundary d < 0 → inside

Если объект предоставляет SDF или эквивалентный shape-distance API, это лучший канонический способ классифицировать контакт.

Path sampling

for q in arc fractions: p = samplePath(q) d[q] = shapeSDF(p) sign change: boundary crossing local minimum |d|: tangent / near-contact candidate

Для точного crossing после coarse sampling выполняется root refinement.

Fallback: если shape geometry неизвестна, можно анализировать bounds, но relation помечается confidence < 1. Нельзя считать bbox истинной формой объекта.

Точное касание

03 / tangent geometry

Boundary normal

n = normalize( ∇SDF(contactPoint) )

Нормаль направлена наружу из формы.

Line tangent

t = normalize( pathTangent(contactQ) )

Берётся у владельца path geometry.

Tangency angle

normalAngle = angleBetween(t,n) tangentError = abs(90° - normalAngle) ideal tangency: tangentError = 0

DERIVED Геометрическое касание означает, что tangent линии ортогонален normal границы.

Для визуальной оценки near-tangency важны два независимых признака: малый gap и малый tangentError. Один только маленький gap ещё не делает контакт похожим на касание.

Проникновение внутрь формы

04 / interior analysis

Penetration depth

penetrationDepthPx = max_q( max(0,-SDF(P(q))) )

Максимальная глубина центра линии внутри формы.

Interior run

interiorArcLength = ∫ I[SDF(P(s)) < 0] ds interiorFraction = interiorArcLength / pathLength

Показывает, насколько значительная часть path проходит внутри объекта.

Stroke-aware

effectiveDistance = SDF(centerline) - 0.5*strokeWidthPx effectiveDistance < 0 → stroke overlaps shape

Толстая линия может визуально заходить на форму, даже если её centerline остаётся снаружи.

Не путать с attachment: connector может специально входить на несколько пикселей внутрь объекта из-за rendering cap/arrowhead. Если 2.09 помечает attachment как намеренный, 2.11 применяет другой policy, а не автоматически штрафует.

Near-tangency и случайный зазор

05 / accidental contact detector

Clearance

centerClearance = min_q SDF(P(q)) strokeClearance = centerClearance - 0.5*strokeWidthPx

Для внешнего path положительное значение означает реальный зазор между stroke и shape.

Near-tangency penalty

gapTerm = 1 - clamp( strokeClearance / nearGapThresholdPx, 0,1 ) angleTerm = 1 - clamp( tangentError / angleThresholdDeg, 0,1 ) nearTangencyPenalty = gapTerm * angleTerm

HEURISTIC Сильный penalty возникает только когда линия одновременно близка и почти параллельна boundary tangent.

Если strokeWidthPx неизвестен, near-contact может считаться по centerline distance только как диагностическая метрика с пониженной confidence.

Local contact scoring

06 / role-aware

OWNS

line.shapeContact.contactCount line.shapeContact.boundaryCrossingCount line.shapeContact.tangentTouchCount line.shapeContact.nearTangencyCount line.shapeContact.interiorRunFraction line.shapeContact.penetrationPenalty line.shapeContact.tangencyPenalty line.shapeContact.nearContactPenalty line.shapeContact.localContactScore

DEFERRED / FOREIGN

2.09 attachmentSemanticFit 2.10 lineIntersectionPenalty 2.06 visualWeight 2.16 contrast global hierarchy global balance global eye-flow object importance
if contact.type == ATTACHMENT and attachmentIntent is valid: use attachmentContactProfile else: localContactScore = 1 - clamp( wP*penetrationPenalty + wT*tangencyPenalty + wN*nearContactPenalty, 0,1 ) // role pair / object policy comes from STYLE_PRESET // measurement stays deterministic
Composite-first: будущий 2.20 использует line.shapeContact.localContactScore как основной vote. Penetration/tangency/nearContact остаются evidence-компонентами, чтобы не задвоить один и тот же контакт.

Role-aware policies

07 / contact meaning
Line roleBoundary crossingAttachmentNear tangency
connectorобычно нежелательно кроме owner endpointчасто ожидаетсяштрафовать случайные почти-касания
annotationобычно избегатьожидается у target/sourceвысокая чувствительность
trajectoryможет пересекать объект только по контекстуне обязательносредняя чувствительность
accentможет быть намеренноредко семантическоеSTYLE_PRESET
textureможет проходить под/через объектыне применимообычно низкая чувствительность
dividerобычно не режет semantic objectsне применимосредняя чувствительность
INITIAL HEURISTIC Таблица задаёт стартовые policy-направления, а не универсальные правила дизайна.

Correction requests

08 / no foreign mutation

MOVE

Сдвинуть path или endpoint от случайного контакта.

REROUTE

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

SNAP

Если почти-contact должен быть attachment — предложить 2.09 привязать к валидному anchor.

ALLOW

Зафиксировать intentional overlap/crossing reason.

ContactCorrectionRequest { lineId, objectId, relationId, reasonCode: ACCIDENTAL_NEAR_TANGENCY | UNINTENTIONAL_PENETRATION | IMMEDIATE_REENTRY | MISALIGNED_ATTACHMENT | INTENTIONAL_CONTACT, targetOwner: "Compo2.04" | "Compo2.05" | "Compo2.09" | "GLOBAL_SOLVER", requestedChange, bounds?, confidence }

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

09 / contact cases
TANGENT TOUCH: линия касается окружности в одной точке и не проходит внутрь.
BOUNDARY CROSSING: path входит в interior и выходит обратно; penetration > 0.
NEAR TANGENCY: маленький gap + почти параллельное boundary-направление.
ATTACHMENT: endpoint на boundary target. 2.11 подтверждает contact geometry; 2.09 подтверждает semantic ownership.
Кривая может войти внутрь формы после почти tangent entry. Модуль должен различать точное attachment и subsequent re-entry/interior run.

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

10 / regression

TEST 01 · circle tangent

circle c=(0,0), r=10 line y=10 expected: TANGENT_TOUCH min signed distance=0 no interior run

TEST 02 · circle crossing

circle r=10 line y=0 expected: 2 boundary crossings penetrationDepth=10

TEST 03 · near tangent

circle r=10 line y=11 strokeWidth=0 expected: clearance=1 nearTangency if threshold > 1

TEST 04 · stroke overlap

circle r=10 line y=11 strokeWidth=4 center clearance=1 effective clearance=-1 expected: stroke overlaps shape

TEST 05 · attachment

endpoint=(10,0) circle r=10 2.09 says valid attachment expected: contact type=ATTACHMENT not generic penalty

TEST 06 · fallback

shape missing bounds present expected: bounds relation allowed confidence reduced

TEST 07 · no leak

change: global.balanceScore expected: contact geometry UNCHANGED

TEST 08 · ownership

near tangency found 2.11 MAY: emit correction request MUST NOT: move Bézier controls itself

TEST 09 · angle

boundary normal=(1,0) line tangent=(0,1) expected: normalAngle=90° tangentError=0

LIVE REFERENCE TESTS

IDInputComputedStatus

DATA FOR LAYOUT ENGINE

11 / machine contract
{ "module": "Compo 2.11", "name": "line_shape_tangency_and_contact", "version": "1.0", "scope": "LOCAL_LINE_SHAPE_CONTACT", "consumes": [ "Compo2.00.Line", "Compo2.00.LinePathGeometry", "Compo0.SceneObject", "Object.boundsN", "Object.shapeGeometry?", "Object.shapeSDF?", "Compo2.06.Line.strokeWidthPx?", "Compo2.09.AttachmentDescriptor?", "Compo2.09.ConnectionIntent?", "StylePreset.contacts?" ], "provides": [ "LineShapeContactRelation[]", "ContactDescriptor", "ContactCorrectionRequest[]" ], "contact_types": [ "BOUNDARY_CROSSING", "TANGENT_TOUCH", "NEAR_TANGENCY", "ATTACHMENT", "INTERIOR_RUN", "NONE" ], "owns_metrics": [ "line.shapeContact.contactCount", "line.shapeContact.boundaryCrossingCount", "line.shapeContact.tangentTouchCount", "line.shapeContact.nearTangencyCount", "line.shapeContact.interiorRunFraction", "line.shapeContact.penetrationPenalty", "line.shapeContact.tangencyPenalty", "line.shapeContact.nearContactPenalty", "line.shapeContact.localContactScore" ], "hard_rules": [ "line_shape_contact_must_not_be_mixed_with_line_line_intersection", "semantic_attachment_status_must_come_from_2.09", "exact_shape_geometry_preferred_over_bounds_fallback", "missing_shape_geometry_must_reduce_confidence", "missing_stroke_width_must_not_be_invented", "path_geometry_must_come_from_geometry_owner", "foreign_geometry_changes_must_be_emitted_as_requests" ], "initial_heuristics": { "near_gap_threshold_width_multiplier": {"value":1.5,"source":"INITIAL_HEURISTIC"}, "near_tangent_angle_threshold_deg": {"value":12,"source":"INITIAL_HEURISTIC"}, "sdf_root_refine_iterations": {"value":12,"source":"IMPLEMENTATION_HEURISTIC"} }, "deferred_inputs": [ "Compo2.09.attachmentSemanticFit", "Compo2.10.lineIntersectionPenalty", "Compo2.16.lineColorContrast", "global.eyeFlowScore", "global.hierarchyScore", "global.balanceScore" ], "metric_policy": { "primary_vote": "line.shapeContact.localContactScore", "components": "diagnostic_only_by_default" }, "recompute_when": [ "LinePathGeometry changes", "Object shape/bounds changes", "stroke width changes", "2.09 attachment intent changes", "Line.role or rigidity changes", "StylePreset.contacts changes" ], "finality": "LOCAL_CONTACT_ANALYSIS_ONLY" }
ARCHITECTURE CHECKPOINT: 2.11 замыкает базовое разделение контактов: 2.10 владеет line-line relations, 2.11 — line-shape relations, 2.09 — semantic attachment intent. Эти три слоя связаны, но не дублируют ownership друг друга.