2016-02-03 4 views
1

Как и название говорит, мне нужно увидеть, пересекаются ли две строки, прежде чем рисовать их с помощью graphics2d. Таким образом, у меня нет строк друг над другом, поэтому вы не можете их прочитать.Как проверить, пересекаются ли две строки drawString()?

Некоторые детали:

размер экрана 1000x1000 пикс. Я произвольно создаю координаты и шрифты с фиксированным интервалом в 10 миллисекунд. Затем (также каждые 10 миллисекунд) я использую g2d.drawString(), чтобы нарисовать слово «всплывающее окно»! на экран в моем методе paintComponent() со случайными шрифтами и случайными местоположениями, которые я хранил ранее. Однако, поскольку я произвольно генерирую координаты, это означает, что иногда у меня перекрываются сообщения. Как я могу гарантировать, что это не произойдет, либо не позволяя ему генерировать координаты, которые перекрываются, либо не печатать сообщения, которые перекрываются?

Код:

Font[] popups = new Font[20]; 
int[][] popupsLoc = new int[20][2]; 
Random rn = new Random(); 
public void addPopup() { //is being called every 10 miliseconds by timer 
    boolean needPopup = false; 
     int where = 0; 
     for(int i = 0; i < popups.length; i++) { 
      if(popups[i] == null) { 
       needPopup = true; 
       where = i; 
       } 
      } 
     if(needPopup == true) { 
      popups[where] = new Font("STENCIL", Font.BOLD, rn.nextInt(100) + 10); 
      popupsLoc[where][0] = rn.nextInt(800); 
      popupsLoc[where][1] = rn.nextInt(800); 
     } 
    } 
} //in paintComponent() I iterate through the popups[] array and draw the element with the font 

Paint Код:

public void paintComponent(Graphics g) { 
     super.paintComponent(g); 

     setBackground(Color.BLACK); 
     Graphics2D g2d = (Graphics2D) g; 


     for(int i = 0; i < popups.length; i++) { 
      if(popups[i] != null) { 
       g2d.setColor(popupColor); 
       g2d.setFont(popups[i]); 
       g2d.drawString("Popup!", popupsLoc[i][0], popupsLoc[i][1]); 
      } 
     } 
} 

Пример enter image description here

Как вы можете видеть, два из сообщений накладываются друг на друга здесь, в филиал ttom справа от экрана. Как я могу это предотвратить?

Редактировать: Я нашел очень простое решение.

public void addPopup() { 

      boolean needPopup = false; 
      int where = 0; 
      for (int i = 0; i < popups.length; i++) { 

       if (popups[i] == null) { 
        needPopup = true; 
        where = i; 
       } 
      } 
      if (needPopup == true) { 
       boolean doesIntersect = false; 
       popups[where] = new Font("STENCIL", Font.BOLD, rn.nextInt(100) + 10); 
       popupsLoc[where][0] = rn.nextInt(800); 
       popupsLoc[where][1] = rn.nextInt(800); 

       FontMetrics metrics = getFontMetrics(popups[where]); 
       int hgt = metrics.getHeight(); 
       int wdh = metrics.stringWidth("Popup!"); 
       popupsHitbox[where] = new Rectangle(popupsLoc[where][0], popupsLoc[where][1], wdh, hgt); 
       //System.out.println(hgt); 

       for (int i = where + 1; i < popups.length; i++) { 
        if (popupsHitbox[i] != null) { 
         if (popupsHitbox[where].intersects(popupsHitbox[i])) 
          doesIntersect = true; 

        } 
       } 
       if (doesIntersect == true) { 
        popups[where] = null; 
        popupsLoc[where][0] = 0; 
        popupsLoc[where][1] = 0; 
        popupsHitbox[where] = null; 
        addPopup(); 
       } 
      } 

    } 

Тогда, когда я рисую:

for (int i = 0; i < popups.length; i++) { 
      if (popups[i] != null) { 
       g2d.setColor(popupColor); 
       g2d.setFont(popups[i]); 
       FontMetrics metrics = getFontMetrics(popups[i]); 
       g2d.drawString("Popup!", popupsLoc[i][0], popupsLoc[i][1]+metrics.getHeight()); 
       //g2d.draw(popupsHitbox[i]); 
      } 
     } 

Объяснение заключается в следующем: Когда я создаю всплывающее окно шрифта/Корд расположение, я также создать прямоугольник «хитбокс», используя Корд расположение и FontMetrics, чтобы получить размер сообщения будет в пикселях, тогда я сохраняю этот прямоугольник в массиве. После этого у меня есть логический флаг с именем doesIntersect, который инициализируется false. Я прокручиваю все хиты и проверяю, пересекается ли текущий() с другими. Если это так, я устанавливаю флаг в true. Затем, после проверки, если флаг является истинным, он сбрасывает это местоположение в массиве до нуля и вызывает addPopup(). (Здесь может быть какая-то рекурсия) Наконец, когда я рисую, я просто рисую строку в месте координат (с y + height, так как строки рисуют слева внизу). Не может быть очень чистым, но он работает.

+0

Помогает ли это? https://docs.oracle.com/javase/tutorial/2d/text/measuringtext.html – ajb

+0

@ajb на самом деле да, он действительно благодарит. Но он все равно не отвечает на него полностью. Я подумал о том, чтобы указывать прямоугольники hitboxes, когда я создаю случайные шрифты и координаты, чтобы это позволяло мне это делать, но тогда даже с помощью метода .intersects у меня нет возможности проверить все остальные шрифты, если есть перекрытия. (извините, если это звучало очень запутанно. Короткий ответ: да, это помогает, но все равно нужна дополнительная помощь!) –

+0

1) Получите «форму» для текста, как показано в [этом ответе] (http://stackoverflow.com/ а/6296381/418556). 2) Проверьте, пересекаются ли фигуры, как показано в [этом ответе] (http://stackoverflow.com/a/14575043/418556). –

ответ

0

Я создал статический класс полезности для генерации точных Shape экземпляров для заданных String и Graphics2D поверхностей рендеринга, которые будут эффективно вычислять обнаружение пересечения без ошибки, связанной только с ограничительными рамками.

/** 
* Provides methods for generating accurate shapes describing the area a particular {@link String} will occupy when 
* drawn alongside methods which can calculate the intersection of those shapes efficiently and accurately. 
* 
* @author Emily Mabrey ([email protected]) 
*/ 

public class TextShapeIntersectionCalculator { 

    /** 
    * An {@link AffineTransform} which returns the given {@link Area} unchanged. 
    */ 
    private static final AffineTransform NEW_AREA_COPY = new AffineTransform(); 

    /** 
    * Calculates the delta between two single coordinate values. 
    * 
    * @param coordinateA 
    *  The origination coordinate which we are calculating from 
    * @param coordinateB 
    *  The destination coordinate which the delta takes us to 
    * @return A coordinate value delta which expresses the change from A to B 
    */ 
    private static int getCoordinateDelta(final int coordinateA, final int coordinateB) { 

    return coordinateB - coordinateA; 
    } 

    /** 
    * Calls {@link #getTextShape(TextLayout)} using a {@link TextLayout} generated from the provided objects and 
    * returns the generated {@link Shape}. 
    * 
    * @param graphicsContext 
    *  A non-null {@link Graphics2D} object with the configuration of the desired drawing surface 
    * @param string 
    *  An {@link AttributedString} containing the data describing which characters to draw alongside the 
    *  {@link Attribute Attributes} describing how those characters should be drawn. 
    * @return A {@link Shape} generated via {@link #getTextShape(TextLayout)} 
    */ 
    public static Shape getTextShape(final Graphics2D graphicsContext, final AttributedString string) { 

    final FontRenderContext fontContext = graphicsContext.getFontRenderContext(); 

    final TextLayout textLayout = new TextLayout(string.getIterator(), fontContext); 

    return getTextShape(textLayout); 
    } 

    /** 
    * Calls {@link #getTextShape(TextLayout)} using a {@link TextLayout} generated from the provided objects and 
    * returns the generated {@link Shape}. 
    * 
    * @param graphicsContext 
    *  A non-null {@link Graphics2D} object with the configuration of the desired drawing surface 
    * @param attributes 
    *  A non-null {@link Map} object populated with {@link Attribute} objects which will be used to determine the 
    *  glyphs and styles for rendering the character data 
    * @param string 
    *  A {@link String} containing the character data which is to be drawn 
    * @return A {@link Shape} generated via {@link #getTextShape(TextLayout)} 
    */ 
    public static Shape getTextShape(final Graphics2D graphicsContext, final Map<? extends Attribute, ?> attributes, 
    final String string) { 

    final FontRenderContext fontContext = graphicsContext.getFontRenderContext(); 

    final TextLayout textLayout = new TextLayout(string, attributes, fontContext); 

    return getTextShape(textLayout); 
    } 

    /** 
    * Calls {@link #getTextShape(TextLayout)} using a {@link TextLayout} generated from the provided objects and 
    * returns the generated {@link Shape}. 
    * 
    * @param graphicsContext 
    *  A non-null {@link Graphics2D} object with the configuration of the desired drawing surface 
    * @param outputFont 
    *  A non-null {@link Font} object used to determine the glyphs and styles for rendering the character data 
    * @param string 
    *  A {@link String} containing the character data which is to be drawn 
    * @return A {@link Shape} generated via {@link #getTextShape(TextLayout)} 
    */ 
    public static Shape getTextShape(final Graphics2D graphicsContext, final Font outputFont, final String string) { 

    final FontRenderContext fontContext = graphicsContext.getFontRenderContext(); 

    final TextLayout textLayout = new TextLayout(string, outputFont, fontContext); 

    return getTextShape(textLayout); 
    } 

    /** 
    * Determines the {@link Shape} which should be generated by rendering the given {@link TextLayout} object using the 
    * internal {@link Graphics2D} rendering state alongside the internal {@link String} and {@link Font}. The returned 
    * {@link Shape} is a potentially disjoint union of all the glyph shapes generated from the character data. Note that 
    * the states of the mutable contents of the {@link TextLayout}, such as {@link Graphics2D}, will not be modified. 
    * 
    * @param textLayout 
    *  A {@link TextLayout} with an available {@link Graphics2D} object 
    * @return A {@link Shape} which is likely a series of disjoint polygons 
    */ 
    public static Shape getTextShape(final TextLayout textLayout) { 

    final int firstSequenceEndpoint = 0, secondSequenceEndpoint = textLayout.getCharacterCount(); 
    final Shape generatedCollisionShape = textLayout.getBlackBoxBounds(firstSequenceEndpoint, secondSequenceEndpoint); 

    return generatedCollisionShape; 

    } 

    /** 
    * Converts the absolute coordinates of {@link Shape Shapes} a and b into relative coordinates and uses the converted 
    * coordinates to call and return the result of {@link #checkForIntersection(Shape, Shape, int, int)}. 
    * 
    * @param a 
    *  A shape located with a user space location 
    * @param aX 
    *  The x coordinate of {@link Shape} a 
    * @param aY 
    *  The y coordinate of {@link Shape} a 
    * @param b 
    *  A shape located with a user space location 
    * @param bX 
    *  The x coordinate of {@link Shape} b 
    * @param bY 
    *  The x coordinate of {@link Shape} b 
    * @return True if the two shapes at the given locations intersect, false if they do not intersect. 
    */ 
    public static boolean checkForIntersection(final Shape a, final int aX, final int aY, final Shape b, final int bX, 
    final int bY) { 

    return checkForIntersection(a, b, getCoordinateDelta(aX, bX), getCoordinateDelta(aY, bY)); 
    } 

    /** 
    * Detects if two shapes with relative user space locations intersect. The intersection is checked in a way which 
    * fails quickly if there is no intersection and which succeeds using the least amount of calculation required to 
    * determine there is an intersection. The location of {@link Shape} a is considered to be the origin and the position 
    * of {@link Shape} b is defined relative to the position of {@link Shape} a using the provided coordinate deltas. 
    * 
    * @param a 
    *  The shape placed at what is considered the origin 
    * @param b 
    *  The shape placed in the position relative to a 
    * @param relativeDeltaX 
    *  The delta x coordinate of {@link Shape} b compared to the x coordination of {@link Shape} a (which is always 
    *  0). 
    * @param relativeDeltaY 
    *  The delta y coordinate of {@link Shape} b compared to the y coordination of {@link Shape} a (which is always 
    *  0). 
    * @return True to indicate the provided {@link Shape Shapes} intersect when placed in the indicated locations. 
    */ 
    public static boolean checkForIntersection(final Shape a, final Shape b, int relativeDeltaX, int relativeDeltaY) { 

    return isIntersectionUsingSimpleBounds(a, b, relativeDeltaX, relativeDeltaY) 
     && isIntersectionUsingAdvancedBounds(a, b, relativeDeltaX, relativeDeltaY) 
     && isIntersectionUsingExactAreas(a, b, relativeDeltaX, relativeDeltaY); 
    } 

    /** 
    * Detects if two shapes with relative user space locations intersect. The intersection is checked using a fast but 
    * extremely simplified bounding box calculation. The location of {@link Shape} a is considered to be the origin and 
    * the position of {@link Shape} b is defined relative to the position of {@link Shape} a using the provided 
    * coordinate deltas. 
    * 
    * @param a 
    *  The shape placed at what is considered the origin 
    * @param b 
    *  The shape placed in the position relative to a 
    * @param relativeDeltaX 
    *  The delta x coordinate of {@link Shape} b compared to the x coordination of {@link Shape} a (which is always 
    *  0). 
    * @param relativeDeltaY 
    *  The delta y coordinate of {@link Shape} b compared to the y coordination of {@link Shape} a (which is always 
    *  0). 
    * @return True to indicate the provided {@link Shape Shapes} intersect when placed in the indicated locations. 
    */ 
    private static boolean isIntersectionUsingSimpleBounds(final Shape a, final Shape b, int relativeDeltaX, 
    int relativeDeltaY) { 

    final Rectangle rectA = a.getBounds(); 
    final Rectangle rectB = b.getBounds(); 

    rectB.setLocation(rectA.getLocation()); 
    rectB.translate(relativeDeltaX, relativeDeltaY); 

    return rectA.contains(rectB); 
    } 

    /** 
    * Detects if two shapes with relative user space locations intersect. The intersection is checked using a slightly 
    * simplified bounding box calculation. The location of {@link Shape} a is considered to be the origin and the 
    * position of {@link Shape} b is defined relative to the position of {@link Shape} a using the provided coordinate 
    * deltas. 
    * 
    * @param a 
    *  The shape placed at what is considered the origin 
    * @param b 
    *  The shape placed in the position relative to a 
    * @param relativeDeltaX 
    *  The delta x coordinate of {@link Shape} b compared to the x coordination of {@link Shape} a (which is always 
    *  0). 
    * @param relativeDeltaY 
    *  The delta y coordinate of {@link Shape} b compared to the y coordination of {@link Shape} a (which is always 
    *  0). 
    * @return True to indicate the provided {@link Shape Shapes} intersect when placed in the indicated locations. 
    */ 
    private static boolean isIntersectionUsingAdvancedBounds(final Shape a, final Shape b, int relativeDeltaX, 
    int relativeDeltaY) { 

    final Rectangle2D rectA = a.getBounds(); 
    final Rectangle2D rectB = b.getBounds(); 

    rectB.setRect(rectA.getX() + relativeDeltaX, rectA.getY() + relativeDeltaY, rectB.getWidth(), rectB.getHeight()); 

    return rectA.contains(rectB); 
    } 

    /** 
    * Detects if two shapes with relative user space locations intersect. The intersection is checked using a slow but 
    * perfectly accurate calculation. The location of {@link Shape} a is considered to be the origin and the position of 
    * {@link Shape} b is defined relative to the position of {@link Shape} a using the provided coordinate deltas. 
    * 
    * @param a 
    *  The shape placed at what is considered the origin 
    * @param b 
    *  The shape placed in the position relative to a 
    * @param relativeDeltaX 
    *  The delta x coordinate of {@link Shape} b compared to the x coordination of {@link Shape} a (which is always 
    *  0). 
    * @param relativeDeltaY 
    *  The delta y coordinate of {@link Shape} b compared to the y coordination of {@link Shape} a (which is always 
    *  0). 
    * @return True to indicate the provided {@link Shape Shapes} intersect when placed in the indicated locations. 
    */ 
    private static boolean isIntersectionUsingExactAreas(final Shape a, final Shape b, int relativeDeltaX, 
    int relativeDeltaY) { 

    final Area aClone = new Area(a).createTransformedArea(NEW_AREA_COPY); 
    final Area bClone = new Area(b).createTransformedArea(NEW_AREA_COPY); 

    bClone.transform(AffineTransform.getTranslateInstance(relativeDeltaX, relativeDeltaY)); 
    aClone.intersect(bClone); 

    return !aClone.isEmpty(); 
    } 

} 

Используя этот класс, вы должны быть в состоянии нарисовать String где-нибудь фактический символ глиф не является, даже если место, которое вы хотите нарисовать внутри ограничивающего прямоугольника для другого String.

Я переписал код, который вы мне дали, чтобы использовать мое новое обнаружение пересечения, но, переписывая его, я очистил его и добавил несколько новых классов, чтобы улучшить его.Эти два класса просто структуры данных, и они необходимы вместе с моим переписывание кода:

class StringDrawInformation { 

    public StringDrawInformation(final String s, final Font f, final Color c, final int x, final int y) { 
     this.text = s; 
     this.font = f; 
     this.color = c; 
     this.x = x; 
     this.y = y; 
    } 

    public final String text; 

    public final Font font; 

    public final Color color; 

    public int x, y; 
    } 

class DrawShape { 

    public DrawShape(final Shape s, final StringDrawInformation drawInfo) { 
     this.shape = s; 
     this.drawInfo = drawInfo; 
    } 

    public final Shape shape; 

    public StringDrawInformation drawInfo; 
    } 

Используя мои три новых класса я переписал код, чтобы выглядеть следующим образом:

private static final Random random = new Random(); 

    public static final List<StringDrawInformation> generateRandomDrawInformation(int newCount) { 

    ArrayList<StringDrawInformation> newInfos = new ArrayList<>(); 

    for (int i = 0; newCount > i; i++) { 
     String s = "Popup!"; 
     Font f = new Font("STENCIL", Font.BOLD, random.nextInt(100) + 10); 
     Color c = Color.WHITE; 
     int x = random.nextInt(800); 
     int y = random.nextInt(800); 
     newInfos.add(new StringDrawInformation(s, f, c, x, y)); 
    } 

    return newInfos; 
    } 

    public static List<DrawShape> generateRenderablePopups(final List<StringDrawInformation> in, Graphics2D g2d) { 

    List<DrawShape> outShapes = new ArrayList<>(); 

    for (StringDrawInformation currentInfo : in) { 
     Shape currentShape = TextShapeIntersectionCalculator.getTextShape(g2d, currentInfo.font, currentInfo.text); 
     boolean placeIntoOut = true; 

     for (DrawShape nextOutShape : outShapes) { 
     if (TextShapeIntersectionCalculator.checkForIntersection(nextOutShape.shape, nextOutShape.drawInfo.x, 
      nextOutShape.drawInfo.y, currentShape, currentInfo.x, currentInfo.y)) { 
      // we found an intersection so we dont place into out and we stop verifying 
      placeIntoOut = false; 
      break; 
     } 
     } 

     if (placeIntoOut) { 
     outShapes.add(new DrawShape(currentShape, currentInfo)); 
     } 
    } 

    return outShapes; 

    } 

    private List<StringDrawInformation> popups = generateRandomDrawInformation(20); 

    public void paintComponent(Graphics g) { 

    super.paintComponent(g); 
    Graphics2D g2d = (Graphics2D) g; 
    g2d.setBackground(Color.BLACK); 

    for (DrawShape renderablePopup : generateRenderablePopups(popups, g2d)) { 
     g2d.setColor(renderablePopup.drawInfo.color); 
     g2d.setFont(renderablePopup.drawInfo.font); 
     g2d.drawString(renderablePopup.drawInfo.text, renderablePopup.drawInfo.x, renderablePopup.drawInfo.y); 
    } 
    } 

Переписанный код легко модифицируется, чтобы использовать больше фигур, разных шрифтов, разных цветов и т. д., а не очень трудно модифицировать. Я обернул различные данные в супертипы, которые инкапсулировали меньшие типы данных, чтобы упростить их использование. Моя переписка не идеальна, но, надеюсь, это помогает.

Я еще не протестировал этот код, просто написал его вручную. Надеюсь, он работает по назначению. В конце концов, я смогу проверить его, достаточно было найти время, чтобы написать то, что я уже сделал. Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать их. Извините, мне потребовалось столько времени, чтобы ответить!

Редактировать: небольшая запоздалая мысль - порядок StringDrawInformationList принят к generateRenderabePopups(...) в порядке очереди. Каждый элемент списка сравнивается со всеми проверенными в настоящее время элементами. Первый неконтролируемый элемент всегда успешно проверяется, потому что нет сопоставлений. Второй непроверенный элемент проверяется на 1-й, поскольку 1-й был проверен. Третий непроверенный может быть проверен до двух других элементов: от 4 до 3. В принципе, элемент в позиции i может быть проверен на i-1 других элементах. Итак, если это имеет значение, поместите более важный текст ранее в список и наименее важный текст позже в списке.

+0

Хорошо, я добавил свой код краски в вопрос тела. Здесь уже поздно, поэтому мне нужно заснуть, но я вернусь здесь утром в утро 6:50. –

+0

Размер пикселя текста основан на контексте «Font» и «Graphics», на который он должен быть нарисован. Большая часть функциональности содержится в самом API-интерфейсе Graphics, более подробно см. [Работа с текстовыми API-интерфейсами] (https://docs.oracle.com/javase/tutorial/2d/text/index.html) – MadProgrammer

+0

Попытка написать экземпляр 'Composite' будет наиболее эффективным способом реализации этого кода, но как JDK использует этот класс вместе с CompositeContext', Blit' и другие очень сложно. Я попытаюсь получить рабочую реализацию моей оригинальной идеи, но если я потерплю неудачу/это слишком сложно, я дам ответ, включающий менее эффективную и более грубую реализацию, основанную на обнаружении пересечения. –