시각적으로 매력적이고 직관적 인 방식으로 레이블을 퍼뜨리는 알고리즘


24

짧은 버전

차량 라벨을 겹치지 않는 방식으로 배포하여 참조 차량과 최대한 가깝게 배치하기위한 디자인 패턴이 있습니까? 그렇지 않다면, 내가 제안하는 방법 중 어떤 것이 가능한가? 어떻게 이것을 직접 구현 하시겠습니까?

확장 버전

내가 쓰는 게임에서 나는 공중 차량에 대한 조감도를 가지고있다. 또한 각 차량 옆에 차량에 대한 주요 데이터가 포함 된 작은 레이블이 있습니다. 이것은 실제 스크린 샷입니다.

라벨이있는 2 대의 차량

이제 차량이 다른 고도에서 비행 할 수 있으므로 아이콘이 겹칠 수 있습니다. 그러나 나는 그들의 레이블이 겹치지 않게하고 싶습니다 (또는 차량 'A'의 레이블이 차량 'B'의 아이콘과 겹치지 않음).

현재 스프라이트 간의 충돌을 감지 할 수 있으며 겹친 스프라이트와 반대 방향으로 문제가되는 레이블을 밀어냅니다 . 이것은 대부분의 상황에서 작동하지만 영공이 붐비 면 대체 "스마트 한"대안이 있더라도 라벨 이 차량에서 아주 멀리 밀려날 수 있습니다 . 예를 들어 나는 얻는다 :

  B - label
A -----------label
  C - label

더 좋은 곳 (= 차량에 더 가까운 라벨) :

          B - label
label - A
          C - label

편집 : 또한 겹치는 차량 케이스 옆에 차량의 라벨 A이 겹칠 수있는 다른 구성이있을 수 있음을 고려해야합니다 (ASCII 예술 예는 예를 들어 3 개의 매우 가까운 차량을 보여줍니다. BC).

현재 상황을 개선하는 방법에 대한 두 가지 아이디어가 있지만 구현에 시간을 투자하기 전에 커뮤니티에 조언을 구하는 것으로 생각했습니다 (결국 디자인 패턴이 존재할 수있는 "충분히 일반적인 문제"인 것 같습니다).

가치있는 것을 위해, 내가 생각한 두 가지 아이디어는 다음과 같습니다.

라벨 공간의 슬롯 화

이 시나리오에서는 모든 화면을 레이블의 "슬롯"으로 나눕니다. 그런 다음 각 차량에는 항상 가장 가까운 빈 차량에 라벨이 붙어 있습니다 (빈 = 해당 위치에 다른 스프라이트는 없음).

스파이럴 검색

화면상의 차량 위치에서 겹치지 않는 위치를 찾을 때까지 레이블을 증가 각도로 설정 한 다음 반경을 높이려고 시도합니다. 다음과 같은 내용이 있습니다.

try 0°, 10px
try 10°, 10px
try 20°, 10px
...
try 350°, 10px
try 0°, 20px
try 10°, 20px
...

1
한 번에 몇 개의 평면이 겹칠 수 있습니까?
wangburger

1
@wangburger-이것이 관련이 있다고 생각하지 마십시오 (사고에 대해 더 알고 싶어 할 것입니다). 그러나 대답은 플레이어의 게임 전략에 달려 있습니다. 기술적으로 세계에는 24 대의 차량이 겹칠 수 있지만 대부분의 게임 조건에서 실제 수치는 3-4입니다.

3
합리적인 시간 동안 겹치지 만 정적 레이블보다 평면에 대해 움직이는 레이블을 갖는 것이 더 혼란스럽지 않습니까?
Maik Semder

3
en.wikipedia.org/wiki/…에 관심이 있으 십니까 ? 이것은 해결하기 쉬운 문제가 아닙니다. 완벽한 솔루션을 기대하지 마십시오.
Blecki

2
GraphViz 는 라벨이 겹치지 않도록 그래프를 시각적으로 기쁘게 배치하는 도구 모음입니다. 직접 사용할 수는 없지만, 그래프를 레이아웃하는 데 사용하는 알고리즘 종류에 대한 설명서 나 소스 코드에서 일부 정보를 수집 할 수 있습니다. 예를 들어 에너지 기반 모델과 스프링 기반 모델이 모두있는 것 같습니다.
Lars Viklund

답변:


14

본질적으로이 문제는 충돌 방지 문제와 유사합니다. 예, 비행기는 다른 고도에서 비행 할 수 있지만 레이블은 모두 "고도"입니다.

정렬되지 않은 충돌 방지 와 같은 알고리즘이 있습니다 이 있으며 올바른 방향으로 나아가는 단계입니다. 물론 상황에 따라 레이블이 평면에 "연결되어"있으므로 이동 범위가 제한되어 있습니다.

Flocking Behavior 를 보면 의 첫 번째 규칙 인 단거리 반발을 구현하려고합니다. 그러나 가장 가까운 이웃에서 멀어지는 방향으로 "조향"하는 대신 "멀리"벡터를 레이블의 배치 위치로 사용합니다.

예를 들면 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

큰 검은 색 원은 영향을받는 영역을 나타내고 녹색 원은 레이블의 유효한 배치를 나타내고 중앙 녹색 점은 현재 고려중인 평면이며 작은 녹색 점은 레이블 배치를 위해 선택한 원의 점입니다.

이제 검은 점은 다른 레이블 또는 다른 평면을 나타낼 수 있습니다. 어느 것이 가장 잘 작동하는지 잘 모르겠습니다. 다른 레이블 인 경우 더 잘 피할 수 있지만 확실하지 않습니다. 분명히 "힘"화살표는 현재 평면과 "영향 대상"사이의 방향 벡터입니다. 마지막으로 상자는 레이블입니다.

따라서 위의 예를 사용하면 다음과 같이 생성됩니다.

            - label
           / 
          B 
label - A
          C 
           \
            - label

이 방법을 사용하면 세로로 정렬 된 3 개의 평면과 같은 특별한 상황을 만들어야하는 경우가 있습니다.

          - label       label -
          |                   |
          B                   B
  label - A                   A - label
          C                   C
          |                   |
          - label       label -

레이블 모서리 정의 방법에 따라 세 개의 레이블이 모두 오른쪽에서 왼쪽으로 퍼질 수 있습니다. 기본적으로 0, 90, 180, 270에서 레이블이 그려지는 모서리를 전환 할 수있는 원 주위의 각도를주의해야합니다.

여기에 이미지 설명을 입력하십시오

결국에는 레이블이 서로 피하는 것을 보면서 깔끔하게 보입니다. 너무주의가 산만 해지는 경우, 덜 자주 움직이기 위해 가장 가까운 10도까지 반올림 할 수 있습니다.

이상한 세부 사항에 대해 유감스럽게 생각합니다. 내 게임을 위해 방사형 메뉴를 만들 때 생각했던 대부분의 것들이지만,이 "동적"형식에서는 꽤 잘 작동한다고 생각합니다.


1
실제로 내 예제는 x 및 z 좌표가 정확히 일치하는 것처럼 서로 직접 위에있는 평면을 고려하지 않았습니다 (그러나 플로트를 사용하는 경우에는 발생하지 않을 것입니다). 내가 준 예제는 서로 가까운 비행기입니다. 또한 위에서 말했듯이 위 이미지의 검은 점을 다른 레이블로 생각할 수 있습니다.
MichaelHouse

1
아, 댓글을 삭제 한 것 같습니다.
MichaelHouse

1
이전의 [불완전하게 공식화되어 현재 제거 된] 의견에서 의미 한 바는 다른 비 이동식 (즉, 차량) 스프라이트에 의해 레이블이 "트랩 / 서라운드"되는 시나리오가있을 수 있다는 것입니다. 이 경우 레이블은 둘러싸는 원 외부에서 "점프"해야하지만 어떻게 발생해야하는지 명확하지 않습니다. BTW : 원래 사진의 녹색 점이 여러 개의 비행기가 다른 비행기 위에 쌓여 있었지만 의견을 보낸 후에는 내가 틀렸다는 것을 깨달았습니다 ...).

1
아, 알겠습니다 따라서 검은 점을 두 평면과 레이블로 취급합니다. 첫 번째 반복에서 찾은 위치가 좋지 않으면 반경을 두 배로 늘리고 다시 확인하십시오. 또는 이미 군중의 대다수를 가리키는 벡터가 있다면, 작동하는 곳을 찾을 때까지 그 벡터를 따를 수 있습니다. 그러나 첫 번째 방법은 더 나은 결과를 얻을 것이라고 생각합니다.
MichaelHouse

9

약간의 생각 후에, 나는 원래의 질문에서 간략하게 설명한 나선형 검색 방법 을 구현하기로 결정했습니다 .

이론적으로는 Byte56 의 방법은 특정 조건에 대해 특별한 처리가 필요하지만 나선형 검색 은 필요하지 않으며 매우 컴팩트 한 방식으로 코딩됩니다 . 또한, Spriralling 검색 은 차량더 가까운 지점을 찾아 레이블을 배치하는 것을 강조 합니다 .이 IMO는지도를 읽기 쉽게 만드는 주요 요소입니다.

그러나 그의 답변은 유용 할뿐만 아니라 매우 잘 쓰여져 있기 때문에 계속 답하십시오.

나선형 코드로 얻은 결과의 스크린 샷은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

그리고 여기에 자체 포함 된 것은 아니지만 구현이 얼마나 간단한 지에 대한 아이디어가 있습니다.

def place_tags(self):
    for tag in self.tags:
        start_angle = tag.angle
        while not tag.place() or is_colliding(tag):  #See note n.1
            tag.angle = (tag.angle + angle_step) % 360
            if tag.angle == start_angle:
                tag.radius += radius_step
        tag.connector.update()                       #See note n.2

주 1 - tag.place()태그가 화면 / 레이더의 가시 영역에 전적으로 경우 True를 반환합니다. 따라서 해당 태그는 "태그가 레이더 외부에 있거나 다른 것과 겹치는 경우 계속 루프를 유지합니다."

주 2 - tag.connector.update텍스트 정보로 라벨 / 태그에 비행기 아이콘을 연결하는 선을 그리는 방법이다.


잘하셨습니다. 실제로 매우 컴팩트합니다. 칭찬 주셔서 감사합니다. 또한 내가 한 일에 대해 게시 해 주셔서 감사합니다. 나중에 답변을 찾는 사람들에게 항상 유용합니다. 스크린 샷에서 잘 작동하는 것처럼 보입니다! 당신이 끝낸 것에 따라 당신의 대답을 받아들이십시오.
MichaelHouse

@ Byte56- "retro-praise"에 감사드립니다;) 여전히 솔루션을 코딩하고 결과를 비교하고 싶기 때문에 수락 된 답변을 선택하기를 기다리고 있습니다. 우선, 귀하의 솔루션이 더 길지만 더 빠른 코드 실행을 초래할 수 있다고 생각합니다 . 또한, 두 개의 공간이 매우 좁은 공간에서 어떻게 비교되는지 알고 싶습니다 ... 그래서 알고리즘으로 코드를 해제 할 가능성이 여전히 있습니다. 이 공간을보십시오! ;)
mac

@ Byte56-알고리즘을 구현했습니다. 저밀도에서는 훌륭하고 빠르게 작동했지만 공간이 꽉 차 자마자 레이블이 다른 레이블 블록을 "점프"하거나 비 레이블에 갇히게되는 특정 상황을 관리하는 간단한 구현을 찾는 데 문제가있었습니다. 움직일 수있는 (예 : 평면 아이콘) 스프라이트. 나는이 답변을 그때 선택된 것으로 표시하고 있지만 다시 한번 : 시간과 기여에 감사드립니다! :)
mac
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.