placementScore function

double placementScore({
  1. required Offset candidate,
  2. required Offset previous,
  3. required Offset outward,
  4. Offset? connectorStart,
  5. Offset? connectorEnd,
  6. Rect? candidateRect,
  7. List<Rect> slots = const [],
  8. List<Rect> obstacles = const [],
  9. double previousWeight = 0.65,
  10. double originWeight = 0.2,
  11. double directionWeight = 16.0,
  12. double lengthWeight = 0.18,
  13. double clearanceWeight = 8.0,
  14. double coverageWeight = 16.0,
  15. double slotCenterWeight = 18.0,
  16. double connectorIntersectWeight = 160.0,
  17. double connectorProximityWeight = 24.0,
  18. double connectorAvoidDistance = 10.0,
})

Implementation

double placementScore({
  required Offset candidate,
  required Offset previous,
  required Offset outward,
  Offset? connectorStart,
  Offset? connectorEnd,
  Rect? candidateRect,
  List<Rect> slots = const [],
  List<Rect> obstacles = const [],
  double previousWeight = 0.65,
  double originWeight = 0.2,
  double directionWeight = 16.0,
  double lengthWeight = 0.18,
  double clearanceWeight = 8.0,
  double coverageWeight = 16.0,
  double slotCenterWeight = 18.0,
  double connectorIntersectWeight = 160.0,
  double connectorProximityWeight = 24.0,
  double connectorAvoidDistance = 10.0,
}) {
  final prevDistance = (candidate - previous).distance;
  final originDistance = candidate.distance;
  final connectorLength = candidate.distance;

  final preferredDir = dirOrFallback(previous, outward);
  final preferredNorm = preferredDir.distance > 1e-6
      ? preferredDir / preferredDir.distance
      : const Offset(1, 0);
  final candidateNorm =
      candidate.distance > 1e-6 ? candidate / candidate.distance : Offset.zero;
  final directionPenalty = (1 -
          (candidateNorm.dx * preferredNorm.dx +
              candidateNorm.dy * preferredNorm.dy)) *
      directionWeight;

  final clearancePenalty = switch (candidateRect) {
    final Rect rect when slots.isNotEmpty =>
      math.max(0.0, _kClearanceTarget - bestSlotClearance(rect, slots)) *
          clearanceWeight,
    _ => 0.0,
  };

  final coveragePenalty = switch (candidateRect) {
    final Rect rect when slots.isNotEmpty =>
      (1 - slotCoverageRatio(rect, slots)) * coverageWeight,
    _ => 0.0,
  };

  final slotCenterPenalty = switch (candidateRect) {
    final Rect rect when slots.isNotEmpty =>
      bestSlotCenterDistanceRatio(rect, slots) * slotCenterWeight,
    _ => 0.0,
  };

  final connectorPenalty = switch ((connectorStart, connectorEnd)) {
    (final Offset start, final Offset end) when obstacles.isNotEmpty =>
      connectorObstaclePenalty(
        start,
        end,
        obstacles,
        avoidDistance: connectorAvoidDistance,
        intersectWeight: connectorIntersectWeight,
        proximityWeight: connectorProximityWeight,
      ),
    _ => 0.0,
  };

  return prevDistance * previousWeight +
      originDistance * originWeight +
      connectorLength * lengthWeight +
      directionPenalty +
      clearancePenalty +
      coveragePenalty +
      slotCenterPenalty +
      connectorPenalty;
}