grid-apps-cmms/src/void/sketch/constraints_tangent.js
2026-02-14 19:00:48 -05:00

172 lines
5.9 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
function applyTangentConstraint(constraint, ctx) {
const {
points,
lines,
arcs,
fixed,
dragged = new Set(),
draggedArcs = new Set(),
tangentAggressive = false,
deps
} = ctx;
const {
getLineEndpoints,
getLineEndpointId,
isFixed,
setPoint,
getArcCircleData,
moveArcCenterBy
} = deps;
const refs = Array.isArray(constraint?.refs) ? constraint.refs : [];
if (refs.length < 2) return false;
const line = lines.get(lines.has(refs[0]) ? refs[0] : (lines.has(refs[1]) ? refs[1] : null));
const arc = arcs.get(arcs.has(refs[0]) ? refs[0] : (arcs.has(refs[1]) ? refs[1] : null));
if (!line && !arc) return false;
if (!line && arc) {
const a1 = arcs.get(refs[0]);
const a2 = arcs.get(refs[1]);
if (!a1 || !a2) return false;
return applyArcArcTangent(constraint, a1, a2, ctx, deps);
}
if (!line || !arc) return false;
const [a, b] = getLineEndpoints(line, points);
if (!a || !b) return false;
const circ = getArcCircleData(arc, points);
if (!circ) return false;
const x1 = a.x || 0;
const y1 = a.y || 0;
const x2 = b.x || 0;
const y2 = b.y || 0;
const dx = x2 - x1;
const dy = y2 - y1;
const len = Math.hypot(dx, dy);
if (len < 1e-9) return false;
const nx = -dy / len;
const ny = dx / len;
const dist = ((circ.cx - x1) * nx + (circ.cy - y1) * ny);
const sign = dist >= 0 ? 1 : -1;
const target = sign * circ.radius;
const err = dist - target;
if (Math.abs(err) < 1e-5) return false;
const relax = 0.35;
const maxStep = Math.max(0.25, len * 0.2);
const corr = Math.max(-maxStep, Math.min(maxStep, err * relax));
const aId = getLineEndpointId(line, 'a');
const bId = getLineEndpointId(line, 'b');
const fa = isFixed(aId, fixed);
const fb = isFixed(bId, fixed);
if (fa && fb) return false;
if (!fa && !fb) {
const mx = corr * nx;
const my = corr * ny;
let changed = false;
changed = setPoint(a, x1 + mx, y1 + my) || changed;
changed = setPoint(b, x2 + mx, y2 + my) || changed;
return changed;
}
if (!fa) {
return setPoint(a, x1 + corr * nx, y1 + corr * ny);
}
return setPoint(b, x2 + corr * nx, y2 + corr * ny);
}
function applyArcArcTangent(constraint, arc1, arc2, ctx, deps) {
const {
points,
fixed,
dragged = new Set(),
draggedArcs = new Set(),
tangentAggressive = false
} = ctx;
const {
getLineEndpointId,
isFixed,
getArcCircleData,
moveArcCenterBy
} = deps;
const c1 = getArcCircleData(arc1, points);
const c2 = getArcCircleData(arc2, points);
if (!c1 || !c2) return false;
let dx = c2.cx - c1.cx;
let dy = c2.cy - c1.cy;
let dist = Math.hypot(dx, dy);
if (!Number.isFinite(dist) || dist < 1e-9) {
dx = 1;
dy = 0;
dist = 1;
}
const ux = dx / dist;
const uy = dy / dist;
const ext = c1.radius + c2.radius;
const intl = Math.abs(c1.radius - c2.radius);
const mode = resolveArcArcTangentMode(constraint, dist, ext, intl);
const target = mode === 'internal' ? intl : ext;
const err = dist - target;
if (Math.abs(err) < 1e-6) return false;
const a2id = getLineEndpointId(arc2, 'a');
const b2id = getLineEndpointId(arc2, 'b');
const a2f = isFixed(a2id, fixed);
const b2f = isFixed(b2id, fixed);
const a1id = getLineEndpointId(arc1, 'a');
const b1id = getLineEndpointId(arc1, 'b');
const a1f = isFixed(a1id, fixed);
const b1f = isFixed(b1id, fixed);
const arc1Dragged = draggedArcs?.has?.(arc1.id);
const arc2Dragged = draggedArcs?.has?.(arc2.id);
const a1Dragged = dragged?.has?.(a1id) || dragged?.has?.(b1id);
const a2Dragged = dragged?.has?.(a2id) || dragged?.has?.(b2id);
if (arc1Dragged && !arc2Dragged && !(a2f && b2f)) {
return moveArcCenterTowardTarget(c1, c2, target, ux, uy, arc2, points, fixed, tangentAggressive, moveArcCenterBy);
}
if (arc2Dragged && !arc1Dragged && !(a1f && b1f)) {
return moveArcCenterTowardTarget(c2, c1, target, -ux, -uy, arc1, points, fixed, tangentAggressive, moveArcCenterBy);
}
if (a1Dragged && !a2Dragged && !(a2f && b2f)) {
return moveArcCenterTowardTarget(c1, c2, target, ux, uy, arc2, points, fixed, tangentAggressive, moveArcCenterBy);
}
if (a2Dragged && !a1Dragged && !(a1f && b1f)) {
return moveArcCenterTowardTarget(c2, c1, target, -ux, -uy, arc1, points, fixed, tangentAggressive, moveArcCenterBy);
}
if (!(a2f && b2f)) {
return moveArcCenterTowardTarget(c1, c2, target, ux, uy, arc2, points, fixed, tangentAggressive, moveArcCenterBy);
}
if (!(a1f && b1f)) {
return moveArcCenterTowardTarget(c2, c1, target, -ux, -uy, arc1, points, fixed, tangentAggressive, moveArcCenterBy);
}
return false;
}
function resolveArcArcTangentMode(constraint, dist, ext, intl) {
const data = (constraint && typeof constraint === 'object') ? (constraint.data || (constraint.data = {})) : {};
if (data.arc_arc_mode === 'external' || data.arc_arc_mode === 'internal') {
return data.arc_arc_mode;
}
const mode = Math.abs(dist - ext) <= Math.abs(dist - intl) ? 'external' : 'internal';
data.arc_arc_mode = mode;
return mode;
}
function moveArcCenterTowardTarget(anchor, moving, targetDist, ux, uy, moveArc, points, fixed, aggressive = false, moveArcCenterBy) {
const tx = anchor.cx + ux * targetDist;
const ty = anchor.cy + uy * targetDist;
let dx = tx - moving.cx;
let dy = ty - moving.cy;
if (!aggressive) {
dx *= 0.4;
dy *= 0.4;
}
return moveArcCenterBy(moveArc, points, fixed, dx, dy);
}
export { applyTangentConstraint };