function connectItem()

in public/bubblesets.js [966:1097]


  function connectItem(nonMembers, item, visited) {
    var scannedLines = [];
    var linesToCheck = [];

    var itemCenter = new Point(item.centerX(), item.centerY());
    var closestNeighbour = null;
    var minLengthSq = Number.POSITIVE_INFINITY;
    // discover the nearest neighbour with minimal interference items
    visited.forEach(function(neighbourItem) {
      var nCenter = new Point(neighbourItem.centerX(), neighbourItem.centerY());
      var distanceSq = itemCenter.distanceSq(nCenter);

      var completeLine = new Line(itemCenter.x(), itemCenter.y(), nCenter.x(), nCenter.y());
      // augment distance by number of interfering items
      var numberInterferenceItems = countInterferenceItems(nonMembers, completeLine);

      // TODO is there a better function to consider interference in nearest-neighbour checking? This is hacky
      if(distanceSq * (numberInterferenceItems + 1) * (numberInterferenceItems + 1) < minLengthSq) {
        closestNeighbour = neighbourItem;
        minLengthSq = distanceSq * (numberInterferenceItems + 1) * (numberInterferenceItems + 1);
      }
    });

    // if there is a visited closest neighbour, add straight line between
    // them to the positive energy to ensure connected clusters
    if(closestNeighbour) {
      var completeLine = new Line(itemCenter.x(), itemCenter.y(), closestNeighbour.centerX(), closestNeighbour.centerY());
      // route the edge around intersecting nodes not in set
      linesToCheck.push(completeLine);

      var hasIntersection = true;
      var iterations = 0;
      var intersections = [];
      intersections.length = 4;
      var numIntersections = 0;
      while(hasIntersection && iterations < maxRoutingIterations) {
        hasIntersection = false;
        while(!hasIntersection && linesToCheck.length) {
          var line = linesToCheck.pop();
          // resolve intersections in order along edge
          var closestItem = getCenterItem(nonMembers, line);
          
          if(closestItem) {
            numIntersections = Intersection.testIntersection(line, closestItem, intersections);
            // 2 intersections = line passes through item
            if(numIntersections === 2) {
              var tempMorphBuffer = morphBuffer;
              var movePoint = rerouteLine(closestItem, tempMorphBuffer, intersections, true);
              // test the movePoint already exists
              var foundFirst = pointExists(movePoint, linesToCheck) || pointExists(movePoint, scannedLines);
              var pointInside = isPointInsideNonMember(movePoint, nonMembers);
              // prefer first corner, even if buffer becomes very small
              while(!foundFirst && pointInside && (tempMorphBuffer >= 1)) {
                // try a smaller buffer
                tempMorphBuffer /= 1.5;
                movePoint = rerouteLine(closestItem, tempMorphBuffer, intersections, true);
                foundFirst = pointExists(movePoint, linesToCheck) || pointExists(movePoint, scannedLines);
                pointInside = isPointInsideNonMember(movePoint, nonMembers);
              }

              if(movePoint && (!foundFirst) && (!pointInside)) {
                // add 2 rerouted lines to check
                linesToCheck.push(new Line(line.x1(), line.y1(), movePoint.x(), movePoint.y()));
                linesToCheck.push(new Line(movePoint.x(), movePoint.y(), line.x2(), line.y2()));
                // indicate intersection found
                hasIntersection = true;
              }

              // if we didn't find a valid point around the
              // first corner, try the second
              if(!hasIntersection) {
                tempMorphBuffer = morphBuffer;
                movePoint = rerouteLine(closestItem, tempMorphBuffer, intersections, false);
                var foundSecond = pointExists(movePoint, linesToCheck) || pointExists(movePoint, scannedLines);
                pointInside = isPointInsideNonMember(movePoint, nonMembers);
                // if both corners have been used, stop; otherwise gradually reduce buffer and try second corner
                while(!foundSecond && pointInside && (tempMorphBuffer >= 1)) {
                  // try a smaller buffer
                  tempMorphBuffer /= 1.5;
                  movePoint = rerouteLine(closestItem, tempMorphBuffer, intersections, false);
                  foundSecond = pointExists(movePoint, linesToCheck) || pointExists(movePoint, scannedLines);
                  pointInside = isPointInsideNonMember(movePoint, nonMembers);
                }

                if(movePoint && (!foundSecond)) {
                  // add 2 rerouted lines to check
                  linesToCheck.push(new Line(line.x1(), line.y1(), movePoint.x(), movePoint.y()));
                  linesToCheck.push(new Line(movePoint.x(), movePoint.y(), line.x2(), line.y2()));
                  // indicate intersection found
                  hasIntersection = true;
                }
              }
            }
          } // end check of closest item

          // no intersection found, mark this line as completed
          if(!hasIntersection) {
            scannedLines.push(line);
          }
          iterations += 1;
        } // end inner loop - out of lines or found an intersection
      } // end outer loop - no more intersections or out of iterations

      // finalize any that were not rerouted (due to running out of
      // iterations) or if we aren't morphing
      while(linesToCheck.length) {
        scannedLines.push(linesToCheck.pop());
      }

      // try to merge consecutive lines if possible
      while(scannedLines.length) {
        var line1 = scannedLines.pop();
        if(scannedLines.length) {
          var line2 = scannedLines.pop();
          var mergeLine = new Line(line1.x1(), line1.y1(), line2.x2(), line2.y2());
          // resolve intersections in order along edge
          var closestItem = getCenterItem(nonMembers, mergeLine);
          // merge most recent line and previous line
          if(!closestItem) {
            scannedLines.push(mergeLine);
          } else {
            linesToCheck.push(line1);
            scannedLines.push(line2);
          }
        } else {
          linesToCheck.push(line1);
        }
      }
      scannedLines = linesToCheck;
    }
    return scannedLines;
  }