Thuật toán để truyền bá nhãn theo cách trực quan và hấp dẫn


24

Phiên bản ngắn

Có một mẫu thiết kế để phân phối nhãn xe theo kiểu không chồng chéo, đặt chúng càng gần càng tốt với chiếc xe mà chúng đề cập đến? Nếu không, có bất kỳ phương pháp tôi đề nghị khả thi? Làm thế nào bạn sẽ thực hiện điều này cho mình?

Phiên bản mở rộng

Trong trò chơi tôi đang viết tôi có một tầm nhìn mắt chim về các phương tiện bay của mình. Tôi cũng có bên cạnh mỗi chiếc xe một nhãn nhỏ với dữ liệu chính về chiếc xe. Đây là một ảnh chụp màn hình thực tế:

Hai chiếc xe có nhãn của họ

Bây giờ, vì các phương tiện có thể bay ở các độ cao khác nhau, các biểu tượng của chúng có thể trùng nhau. Tuy nhiên, tôi muốn không bao giờ có nhãn của chúng trùng nhau (hoặc nhãn từ xe 'A' chồng lên biểu tượng của xe 'B').

Hiện tại, tôi có thể phát hiện sự va chạm giữa các sprite và tôi chỉ cần đẩy nhãn vi phạm theo hướng ngược lại với sprite chồng chéo . Điều này hoạt động trong hầu hết các tình huống, nhưng khi không phận trở nên đông đúc, nhãn có thể bị đẩy ra rất xa khỏi phương tiện của nó, ngay cả khi có một sự thay thế "thông minh hơn" khác. Ví dụ tôi nhận được:

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

nơi nào sẽ tốt hơn (= nhãn gần xe hơn) để có được:

          B - label
label - A
          C - label

EDIT: Cũng phải xem xét rằng bên cạnh trường hợp xe bị chồng chéo, có thể có các cấu hình khác trong đó các phương tiện có thể trùng nhau (ví dụ nghệ thuật ASCII cho thấy ba phương tiện rất gần trong đó nhãn của Anó sẽ chồng lên biểu tượng của BC).

Tôi có hai ý tưởng về cách cải thiện tình hình hiện tại, nhưng trước khi dành thời gian thực hiện chúng, tôi đã nghĩ đến việc nhờ cộng đồng tư vấn (sau tất cả có vẻ như là một "vấn đề đủ phổ biến" mà một mẫu thiết kế cho nó có thể tồn tại).

Để biết giá trị của nó, đây là hai ý tưởng tôi đã nghĩ đến:

Khe cắm của không gian nhãn

Trong kịch bản này, tôi sẽ chia tất cả màn hình thành các "khe" cho nhãn. Sau đó, mỗi chiếc xe sẽ luôn có nhãn của nó được đặt ở vị trí trống gần nhất (trống = không có các họa tiết khác tại vị trí đó.

Tìm kiếm xoắn ốc

Từ vị trí của chiếc xe trên màn hình, tôi sẽ cố gắng đặt nhãn ở các góc tăng và sau đó tại các bán kính tăng dần, cho đến khi tìm thấy một vị trí không chồng chéo. Một cái gì đó xuống dòng:

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

1
Có bao nhiêu mặt phẳng có thể chồng lên nhau cùng một lúc?
wangburger

1
@wangburger - Không bao giờ nghĩ rằng điều này sẽ có liên quan (sẽ được quan tâm để biết thêm về dòng suy nghĩ của bạn), nhưng câu trả lời là: nó phụ thuộc vào chiến lược trò chơi của người chơi. Về mặt kỹ thuật, thế giới có thể có 24 xe chồng chéo, nhưng một con số thực tế trong hầu hết các điều kiện trò chơi là 3-4.
mac

3
Không có gì khó hiểu hơn khi có các nhãn di chuyển so với mặt phẳng so với các nhãn chồng lấp nhưng tĩnh trong một khoảng thời gian hợp lý?
Maik

3
Bạn có thể quan tâm đến en.wikipedia.org/wiki/ trộm - đây không phải là vấn đề đơn giản để giải quyết. Đừng mong đợi để tìm một giải pháp hoàn hảo.
Blecki

2
GraphViz là một bộ công cụ để sắp xếp các biểu đồ theo cách trực quan dễ chịu, với xu hướng tránh sự chồng chéo trong nhãn. Mặc dù có thể không sử dụng được trực tiếp, nhưng bạn có thể thu thập được một số thông tin từ tài liệu hoặc mã nguồn của họ về loại thuật toán họ sử dụng để bố trí biểu đồ của họ. Họ dường như có cả mô hình dựa trên năng lượng và mô hình dựa trên mùa xuân, ví dụ.
Lars Viklund

Câu trả lời:


14

Về cơ bản vấn đề này tương tự như một vấn đề tránh va chạm. Vâng, các máy bay có thể bay ở các độ cao khác nhau, nhưng nhãn của chúng đều ở cùng một "độ cao".

Có những thuật toán như Unalign Collision Tránh , đó sẽ là một bước đi đúng hướng cho bạn. Tất nhiên đối với tình huống của bạn, các nhãn được "buộc" vào các mặt phẳng của chúng, vì vậy chúng có phạm vi di chuyển hạn chế.

Nếu bạn nhìn vào Hành vi đổ xô , bạn muốn thực hiện "quy tắc" đầu tiên của việc đổ xô: lực đẩy tầm ngắn. Tuy nhiên, thay vì "lái" theo hướng cách xa hàng xóm gần nhất, bạn sẽ sử dụng vectơ "đi" làm vị trí đặt cho nhãn của mình.

Ví dụ:

nhập mô tả hình ảnh ở đây

Vòng tròn lớn màu đen biểu thị vùng ảnh hưởng của bạn, vòng tròn màu xanh lá cây đại diện cho các vị trí hợp lệ cho nhãn, chấm màu xanh lá cây trung tâm là mặt phẳng mà bạn hiện đang xem xét, chấm nhỏ màu xanh lá cây là điểm trên vòng tròn được chọn để đặt nhãn.

Bây giờ các chấm đen có thể đại diện cho các nhãn khác hoặc các mặt phẳng khác. Tôi không chắc cái nào sẽ hoạt động tốt nhất, bạn có thể tránh được tốt hơn nếu chúng là nhãn khác, nhưng tôi không chắc. Rõ ràng các mũi tên "lực" là các vectơ chỉ hướng giữa mặt phẳng hiện tại của bạn và "đối tượng ảnh hưởng". Cuối cùng, hộp là nhãn.

Vì vậy, sử dụng ví dụ của bạn ở trên, tôi nghĩ rằng điều này sẽ tạo ra một cái gì đó như:

            - label
           / 
          B 
label - A
          C 
           \
            - label

Sử dụng phương pháp này, có một số tình huống bạn sẽ phải thực hiện các trường hợp đặc biệt, như ba mặt phẳng thẳng hàng:

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

Tất cả ba nhãn có thể lật từ phải sang trái, tùy thuộc vào cách xác định góc nhãn của bạn. Về cơ bản, bạn sẽ chỉ cần để ý các góc xung quanh vòng tròn nơi nhãn của bạn có thể chuyển đổi góc được vẽ từ: 0, 90, 180, 270.

nhập mô tả hình ảnh ở đây

Tôi nghĩ rằng cuối cùng, điều này sẽ trông gọn gàng, xem các nhãn tránh nhau. Nếu nó quá mất tập trung, có lẽ bạn có thể làm tròn đến 10 độ gần nhất để di chuyển ít thường xuyên hơn.

Xin lỗi vì những chi tiết kỳ lạ, hầu hết những thứ này tôi nghĩ đến khi tôi làm một menu radial cho trò chơi của mình, nhưng tôi nghĩ ở dạng "động" này, nó sẽ hoạt động khá tốt.


1
Trên thực tế, ví dụ của tôi thậm chí không tính đến các mặt phẳng trực tiếp với nhau, vì trong tọa độ x và z của chúng khớp chính xác (nhưng nếu bạn sử dụng phao thì điều này có thể sẽ không xảy ra). Các ví dụ tôi đưa ra là của các mặt phẳng gần nhau. Ngoài ra, như tôi đã nói, bạn có thể nghĩ về các chấm đen trong hình trên như các nhãn khác.
MichaelHouse

1
Ồ, có vẻ như bạn đã xóa bình luận của bạn?
MichaelHouse

1
Điều tôi muốn nói với nhận xét [công thức kém và do đó đã bị xóa] trước đây của tôi, là bạn có thể có một kịch bản trong đó một nhãn bị "kẹt / bao quanh" bởi các sprite không di chuyển (tức là phương tiện) khác. Trong trường hợp này, nhãn có lẽ sẽ "nhảy" ra khỏi vòng tròn kèm theo, nhưng đối với tôi điều này sẽ không rõ ràng như thế nào. BTW: Ban đầu tôi mặc dù chấm màu xanh lá cây trong ảnh của bạn là một số máy bay xếp chồng lên nhau, nhưng sau khi nhận xét của bạn, tôi nhận ra mình đã sai ... xin lỗi!).
mac

1
Ah tôi thấy. Vì vậy, bạn sẽ coi các chấm đen là cả hai mặt phẳng VÀ nhãn. Nếu vị trí được tìm thấy trong lần lặp đầu tiên không tốt, hãy nhân đôi bán kính và kiểm tra lại. Hoặc bạn đã có một vectơ chỉ ra khỏi phần lớn đám đông, bạn có thể theo dõi nó cho đến khi bạn tìm thấy một nơi nào đó hoạt động. Tuy nhiên tôi nghĩ phương pháp đầu tiên sẽ có kết quả tốt hơn.
MichaelHouse

9

Sau một số suy nghĩ, cuối cùng tôi đã quyết định thực hiện phương pháp tìm kiếm xoắn ốc mà tôi đã mô tả ngắn gọn trong câu hỏi ban đầu.

Lý do là phương pháp của Byte56 cần được đối xử đặc biệt đối với một số điều kiện nhất định, trong khi tìm kiếm xoắn ốc thì không, và nó mã hóa theo một cách thực sự nhỏ gọn . Ngoài ra, tìm kiếm mở rộng nhấn mạnh việc tìm vị trí gần hơn với phương tiện để đặt nhãn, mà IMO là yếu tố chính giúp bản đồ có thể đọc được.

Tuy nhiên, hãy tiếp tục nêu lên câu trả lời của anh ấy, vì nó không chỉ hữu ích, nó còn được viết rất tốt!

Dưới đây là ảnh chụp màn hình kết quả đạt được với mã xoắn ốc:

nhập mô tả hình ảnh ở đây

Và đây là mã - mặc dù không khép kín - nó đưa ra ý tưởng về cách thực hiện đơn giản:

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

Lưu ý 1 - tag.place()trả về Đúng nếu thẻ hoàn toàn nằm trên vùng hiển thị của màn hình / radar. Vì vậy, dòng đó có nội dung như "tiếp tục lặp nếu thẻ nằm ngoài radar hoặc nó chồng lên thứ khác ..."

Lưu ý 2 - tag.connector.updatelà phương pháp vẽ đường nối biểu tượng máy bay với nhãn / thẻ với thông tin văn bản.


Làm tốt lắm, điều đó thực sự rất nhỏ gọn. Cảm ơn lời khen ngợi. Cũng cảm ơn vì đã đăng bài về những gì bạn đã làm, điều đó luôn hữu ích cho những người tìm kiếm câu trả lời sau này. Từ ảnh chụp màn hình có vẻ như nó hoạt động rất tốt! Hãy chắc chắn chấp nhận câu trả lời của bạn, vì đó là những gì bạn đã kết thúc.
MichaelHouse

@ Byte56 - Cảm ơn bạn đã "khen ngợi retro";) Tôi đang chờ chọn câu trả lời là được chấp nhận vì tôi vẫn muốn mã hóa giải pháp của bạn và so sánh kết quả. Đối với một người, tôi nghi ngờ rằng giải pháp của bạn có thể dẫn đến mã dài hơn nhưng cũng nhanh hơn để thực thi. Ngoài ra, tôi muốn xem làm thế nào hai so sánh trong không gian rất chật chội ... vì vậy vẫn có cơ hội tôi có thể phát hành mã với thuật toán của bạn. Xem không gian này! ;)
mac

@ Byte56 - Tôi đã bắt đầu thực hiện thuật toán của bạn. Nó hoạt động tốt và nhanh với mật độ thấp, nhưng ngay khi không gian bị nhồi nhét, tôi gặp vấn đề trong việc tìm kiếm một triển khai đơn giản để quản lý các tình huống cụ thể trong đó một nhãn sẽ "nhảy" một khối nhãn khác hoặc bị kẹt bởi không phải di chuyển (tức là biểu tượng máy bay) spites. Tôi đang đánh dấu câu trả lời này khi được chọn, nhưng một lần nữa: cảm ơn rất nhiều về thời gian và sự đóng góp! :)
mac
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.