Buat batas-batas peta yang dibuat secara prosedural

gambar

Scott Turner terus bekerja pada gim yang dihasilkan secara prosedural dan sekarang telah memutuskan untuk mengatasi masalah merancang perbatasan peta. Untuk melakukan ini, ia harus menyelesaikan beberapa masalah sulit dan bahkan membuat bahasanya sendiri untuk menggambarkan batasan.

Borders tetap merupakan elemen penting dari kartu fantasi, yang telah ada dalam daftar saya selama beberapa waktu. Peta fungsional biasanya memiliki garis perbatasan yang sederhana, tetapi peta fantasi dan peta abad pertengahan, di mana mantan sering meminjam ide, memiliki batas yang cukup bijaksana dan artistik. Batas-batas ini memperjelas bahwa peta itu sengaja dibuat fantastis, dan memberi penonton rasa heran.

Saat ini ada beberapa cara sederhana untuk menggambar batas di gim Dragons Abound saya. Dia dapat menggambar garis tunggal atau ganda di sekeliling peta dan menambahkan elemen sederhana di sudut-sudutnya, seperti pada gambar-gambar ini:



Gim ini juga dapat menambahkan bidang di bagian bawah perbatasan untuk nama peta. Ada beberapa variasi bidang ini di Dragons Abound , termasuk elemen kompleks seperti kepala sekrup palsu:


Ada variabilitas di bidang nama ini, tetapi semuanya dibuat secara manual.

Salah satu aspek yang menarik dari batasan kartu fantasi adalah bahwa keduanya kreatif dan templat. Seringkali mereka terdiri dari sejumlah kecil elemen sederhana yang digabungkan dalam berbagai cara untuk menciptakan hasil yang unik. Seperti biasa, langkah pertama ketika bekerja dengan topik baru bagi saya adalah mempelajari kumpulan contoh peta, membuat katalog jenis elemen perbatasan, dan mempelajari penampilan mereka.

Batas paling sederhana adalah satu garis di sepanjang tepi peta dan menunjukkan batasnya. Seperti yang saya katakan di atas, ini juga disebut "garis bingkai":


Ada juga variasi dengan lokasi perbatasan dalam peta. Dalam versi ini, peta mencapai tepi gambar, tetapi perbatasan membuat batas virtual di dalam gambar:


Ini dapat dilakukan dengan semua jenis perbatasan, tetapi biasanya hanya digunakan dengan perbatasan sederhana seperti perbatasan bingkai.

Konsep desain kartu fantasi yang populer adalah untuk mensimulasikan seolah-olah mereka digambar di atas perkamen lama yang sobek. Kadang-kadang ini diwujudkan dengan menggambar perbatasan sebagai tepi kasar kertas:


Berikut ini contoh yang lebih canggih:


Dalam pengalaman saya, metode ini menjadi kurang populer karena alat digital mulai digunakan. Jika Anda ingin kartu tersebut terlihat seperti perkamen lama yang sobek, maka lebih mudah untuk menerapkan tekstur perkamen itu daripada menggambarnya dengan tangan.

Alat paling ampuh dalam membuat batas peta adalah pengulangan. Dalam kasus paling sederhana, cukup untuk mengulang satu baris untuk membuat dua baris:


Anda dapat menambahkan minat pada peta dengan memvariasikan gaya elemen yang diulang, dalam hal ini dengan menggabungkan satu garis tebal dengan satu garis tipis:


Tergantung pada elemennya, berbagai variasi gaya dimungkinkan. Dalam contoh ini, garis berulang, tetapi warnanya berubah:


Untuk membuat pola yang lebih kompleks, Anda dapat menggunakan "repeatability berulang". Perbatasan ini terdiri dari sekitar lima garis tunggal dengan lebar dan jarak yang berbeda:


Perbatasan ini mengulang garis, tetapi memisahkannya sehingga terlihat seperti dua batas tipis yang terpisah. Pada bagian posting ini saya tidak akan berbicara tentang pemrosesan sudut, tetapi sudut yang berbeda untuk kedua garis juga membantu menciptakan perbedaan ini.


Apakah ini dua baris, empat, atau enam? Saya pikir itu semua tergantung pada bagaimana Anda menggambar mereka!

Elemen stylization lainnya adalah mengisi ruang antara elemen dengan warna, pola atau tekstur. Dalam contoh ini, batas menjadi lebih menarik karena warna aksen mengisi antara dua garis:


Berikut adalah contoh bagaimana perbatasan diisi dengan pola:


Juga, elemen dapat ditata sehingga mereka terlihat tiga dimensi. Berikut adalah peta di mana perbatasan diarsir sehingga terlihat banyak:


Di peta ini, perbatasan diarsir agar terlihat tiga dimensi, dan ini dikombinasikan dengan lokasi perbatasan di dalam tepi peta:


Elemen perbatasan umum lainnya adalah skala dalam bentuk garis-garis multi-warna:


Garis-garis ini membentuk kisi ( kisi kartografi ). Pada peta nyata, skala membantu menentukan jarak, tetapi pada peta fantasi itu terutama merupakan elemen dekoratif.

Garis-garis ini biasanya digambar hitam dan putih, tetapi terkadang merah atau warna lain ditambahkan:


Elemen ini juga dapat dikombinasikan dengan yang lain, seperti dalam contoh ini dengan garis dan skala:


Contoh ini agak tidak biasa. Biasanya skala (jika ada) adalah elemen terdalam dari perbatasan.

Pada peta ini, ada skala yang berbeda dengan resolusi yang berbeda (serta catatan rahasia yang aneh!):


(Di Reddit, pengguna AbouBenAdhem memberi tahu saya bahwa tanda rahasia adalah angka 48 dan 47 ditulis dalam huruf Babel. Selain itu, "skala dengan resolusi berbeda" memiliki enam divisi yang dibagi menjadi sepuluh divisi yang lebih kecil, yang sesuai dengan sistem angka heksadesimal Babilonia. Saya menunjukkan sumber peta, tetapi ada terlalu banyak potongan kecil di posting ini, jadi saya tidak repot. Namun, peta ini dibuat oleh Thomas Ray untuk penulis S.E. Boleyn, jadi, mungkin, aksi dalam buku-bukunya terjadi di rombongan Babel.)

Selain garis dan skala, elemen yang paling umum adalah pola geometris berulang. Seringkali terdiri dari bagian-bagian seperti lingkaran, belah ketupat dan persegi panjang:


Elemen geometris, seperti garis, dapat diarsir untuk membuatnya terlihat tiga dimensi:


Batas-batas kompleks dapat dibuat dengan menggabungkan elemen-elemen ini dengan cara yang berbeda. Berikut adalah batas yang menggabungkan garis, pola geometris, dan skala:


Contoh yang ditunjukkan di atas adalah kartu digital, tetapi, tentu saja, hal yang sama dapat dilakukan dengan kartu tulisan tangan. Berikut adalah contoh pola geometris sederhana yang dibuat dengan tangan:


Elemen-elemen ini juga dapat dikombinasikan secara fleksibel dalam banyak cara. Berikut adalah pola geometris yang dikombinasikan dengan "tepi kasar":


Dalam contoh yang ditunjukkan di atas, pola geometris cukup sederhana. Tetapi Anda dapat membuat pola yang sangat kompleks dengan menggabungkan elemen geometris dasar dengan cara yang berbeda:


Elemen populer lainnya dari pola tersebut adalah tenun atau simpul Celtic:


Berikut ini adalah batas anyaman yang lebih kompleks yang mengandung warna, skala, dan elemen lainnya:


Pada peta ini, tenun dikombinasikan dengan elemen tepi yang kasar:


Selain pola geometris dan tenun, setiap pola yang berulang dapat menjadi bagian dari batas kartu. Berikut adalah contoh menggunakan bentuk menyerupai panah:


Dan berikut ini adalah contoh dengan pola gelombang berulang:


Dan akhirnya, rune atau elemen lain dari alfabet fantasi kadang-kadang ditambahkan ke tepi kartu fantasi:


Contoh di atas diambil dari peta fantasi modern, tetapi di sini adalah contoh peta historis (abad ke-18) dengan garis dan pola yang digambar tangan:


Tentu saja, Anda dapat menemukan contoh peta dengan banyak elemen lain di perbatasan. Beberapa yang paling indah benar-benar digambar tangan dan memiliki dekorasi yang dibuat sedemikian rupa sehingga mereka dapat melampaui kartu itu sendiri ( World of Alma , Francesca Baerald):


Perlu juga sedikit dibicarakan tentang simetri . Seperti pengulangan, simetri adalah alat yang ampuh, dan batas peta biasanya simetris atau memiliki elemen simetris.

Banyak batas peta simetris dari dalam ke luar, seperti dalam contoh ini:


Di sini, perbatasan terdiri dari beberapa garis dengan dan tanpa isian, tetapi dari luar ke dalam itu idealnya diulang relatif ke pusat perbatasan.

Dalam contoh yang lebih kompleks ini, perbatasan simetris, dengan pengecualian garis-garis hitam dan putih berselang-seling:


Karena menduplikasi skala tidak masuk akal, sering dianggap sebagai elemen yang terpisah, bahkan jika sisa perbatasan simetris.

Selain simetri internal-eksternal, batas seringkali simetris sepanjang. Beberapa perbatasan bergambar mungkin memiliki desain sederhana yang membentang sepanjang panjang tepi peta, tetapi dalam kebanyakan kasus polanya cukup pendek dan berulang, mengisi perbatasan dari satu sudut ke sudut lain:


Perhatikan bahwa dalam contoh ini polanya berisi elemen yang tidak simetris (dari kiri ke kanan), tetapi pola umum simetris dan berulang:


Satu pengecualian penting untuk aturan ini adalah batas diisi dengan rune atau karakter alfabet. Seringkali mereka berubah menjadi unik, seolah-olah beberapa pesan panjang ditulis di sepanjang perbatasan:


Tentu saja, ada banyak contoh elemen peta perbatasan yang belum saya pertimbangkan di sini, tetapi kami sudah memiliki titik referensi yang baik. Di beberapa bagian selanjutnya, saya akan mengembangkan beberapa fungsi di Dragons Abound untuk mendeskripsikan, menampilkan, dan secara prosedural menghasilkan batas peta yang mirip dengan contoh-contoh ini. Pada bagian kedua, kita akan mulai dengan mengatur bahasa untuk menggambarkan perbatasan peta.

Bagian 2


Pada bagian ini, saya akan membuat versi awal Bahasa Deskripsi Perbatasan Peta (MBDL).

Mengapa menghabiskan waktu membuat bahasa deskripsi batas peta? Pertama, ini akan menjadi tujuan generasi prosedural saya. Nanti saya akan menulis algoritma untuk membuat batas peta baru, dan output dari algoritma ini akan menjadi deskripsi perbatasan baru di MBDL. Kedua, MBDL akan berfungsi sebagai representasi tekstual dari batas-batas peta. Secara khusus, saya harus bisa menyelamatkan dan menggunakan kembali batas-batas saya. Untuk melakukan ini, saya memerlukan notasi teks yang dapat ditulis dan digunakan untuk membuat ulang batas peta.

Saya akan mulai membuat MBDL dengan mendefinisikan elemen paling sederhana: baris. Garis memiliki warna dan lebar. Oleh karena itu, di MBDL saya akan menyajikan baris dalam formulir ini:

L(width, color)

Berikut beberapa contoh (maaf untuk keterampilan Photoshop saya):


Urutan elemen dirender dari luar ke dalam (*), jadi kami berasumsi bahwa ini adalah perbatasan di atas peta:


Lihatlah contoh kedua - garis dengan batas direpresentasikan sebagai tiga elemen garis terpisah.

(* Menggambar dari luar ke dalam adalah pilihan yang sewenang-wenang - sepertinya bagi saya itu lebih alami daripada merender dari dalam ke luar. Sayangnya, ternyata kemudian, ada alasan bagus untuk bekerja di arah yang berlawanan. Segera saya akan memberi tahu Anda tentang hal itu, tetapi semuanya tertinggal di pos. - lama, karena akan membutuhkan banyak waktu untuk mengulang semua ilustrasi)

Dengan mudah, ruang dapat direpresentasikan sebagai garis tanpa warna:


Tetapi akan lebih visual untuk memiliki elemen ruang vertikal spesifik:

VS (lebar)

Elemen sederhana berikut adalah bentuk geometris: garis, rhombus, dan elips. Diasumsikan bahwa garis-garis tersebut membentang di atas seluruh panjang perbatasan, sehingga tidak memiliki panjang yang ditentukan secara eksplisit. Tetapi angka geometris tidak dapat memenuhi seluruh garis, oleh karena itu, selain lebar (*), masing-masing harus memiliki panjang, warna garis besar, garis besar lebar dan warna isi:

B(width, length, outline, outline width, fill)
D(width, length, outline, outline width, fill)
E(width, length, outline, outline width, fill)

(* Saya menerima bahwa saya akan mempertimbangkan lebar dalam arah dari luar ke dalam, dan panjangnya diukur di sepanjang perbatasan.)

Berikut adalah contoh bentuk geometris sederhana:


Agar elemen-elemen ini memenuhi seluruh panjang perbatasan, mereka harus diulang. Untuk menunjukkan grup elemen yang akan diulang untuk mengisi panjang perbatasan, saya menggunakan tanda kurung:

[ element element element ... ]

Berikut adalah contoh pola berulang persegi panjang dan rhombus:


Kadang-kadang saya membutuhkan ruang (horizontal) antara elemen-elemen dari pola berulang. Meskipun Anda dapat menggunakan elemen tanpa warna untuk membuat ruang, akan lebih pintar dan nyaman untuk memiliki elemen ruang horizontal:

HS(length)

Fungsi terakhir yang diperlukan untuk iterasi pertama MBDL adalah kemampuan untuk menumpuk elemen di atas satu sama lain. Berikut ini contoh perbatasan:


Cara termudah untuk menggambarkannya adalah garis kuning lebar di bawah pola atas. Anda dapat menerapkan ini dengan cara yang berbeda (misalnya, ruang vertikal negatif), tetapi saya memutuskan untuk menggunakan kurung kurawal untuk menunjukkan urutan elemen ke dalam:

{element element element ...}

Bahkan, entri ini memberitahu Anda untuk mengingat di mana pola itu berasal dari luar ke dalam ketika memasuki tanda kurung, dan kemudian kembali ke titik ini ketika meninggalkan tanda kurung. Kurung juga dapat dianggap sebagai deskripsi elemen yang menempati ruang vertikal. Oleh karena itu, batas yang ditunjukkan di atas dapat digambarkan sebagai berikut:

L(1, black)
{L(20, yellow)}
VS(3)
[B(5, 10, black, 3, none)
D(5, 10, black,3,red)]
VS(3)
L(1, black)

Kami menggambar garis hitam, memperbaiki di mana kita berada, menggambar garis kuning, dan kemudian kembali ke posisi yang sebelumnya diperbaiki, turun sedikit ke bawah, menggambar pola persegi panjang dan belah ketupat, turun sedikit ke bawah, dan kemudian tarik garis hitam lain.

Masih banyak yang harus dilakukan di MBDL, tetapi ini cukup untuk menggambarkan banyak batasan peta. Langkah selanjutnya adalah mengubah deskripsi batas pada MBDL ke perbatasan itu sendiri. Ini mirip dengan mengubah representasi tertulis dari program komputer (seperti Javascript) ke dalam pelaksanaan program ini. Tahap pertama adalah analisis leksikal (parsing) bahasa - transformasi teks sumber menjadi perbatasan nyata peta atau menjadi semacam bentuk peralihan, yang lebih mudah dikonversi menjadi perbatasan.

Parsing adalah bidang ilmu komputer yang cukup banyak dipelajari. Memilah bahasa tidaklah sederhana, tetapi dalam kasus kami, bagus (*) bahwa MBDL adalah tata bahasa bebas konteks . Tata bahasa bebas konteks diuraikan dengan cukup mudah, dan ada banyak alat parsing Javascript untuk mereka. Saya memilih Nearley.js , yang tampaknya cukup matang dan (lebih penting) alat yang terdokumentasi dengan baik.

(* Ini bukan hanya keberuntungan, saya memastikan bahwa bahasa itu bebas konteks.)

Saya tidak akan memperkenalkan Anda dengan tata bahasa bebas konteks, tetapi sintaks Nearley cukup sederhana dan Anda harus memahami artinya tanpa masalah. Grammar Nearley terdiri dari seperangkat aturan. Setiap aturan memiliki karakter di sebelah kiri, panah, dan bagian kanan aturan, yang dapat berupa urutan karakter dan non-karakter, serta berbagai opsi yang dipisahkan oleh "|" (atau):

border -> element | element border
element ->
L"

Masing-masing aturan mengatakan bahwa sisi kiri dapat diganti dengan salah satu opsi di sisi kanan. Artinya, aturan pertama mengatakan bahwa perbatasan adalah elemen, atau elemen, diikuti oleh perbatasan lain. Yang itu sendiri bisa menjadi elemen, atau elemen yang diikuti oleh perbatasan, dan sebagainya. Aturan kedua mengatakan bahwa elemen hanya bisa berupa string "L". Artinya, bersama-sama aturan ini sesuai dengan batas-batas tersebut:

L
LLL

dan tidak sesuai dengan batas-batas tersebut:

X
L3L

Omong-omong, jika Anda ingin bereksperimen dengan tata bahasa ini (atau yang lain) di Nearley, maka ada kotak pasir online untuk ini di sini . Anda dapat memasukkan tata bahasa dan menguji kasus untuk melihat apa yang cocok dan tidak cocok.

Berikut adalah definisi garis primitif yang lebih lengkap:

@builtin “number.ne"
@builtin “string.ne"
border -> element | element border
element -> “L(" decimal “," dqstring “)"

Nearley memiliki beberapa elemen bawaan yang umum, dan angka adalah salah satunya. Oleh karena itu, saya dapat menggunakannya untuk mengenali lebar numerik suatu garis primitif. Untuk pengenalan warna, saya menggunakan elemen bawaan lainnya dan memungkinkan penggunaan string apa pun dalam tanda kutip ganda.

Akan lebih baik untuk menambahkan spasi di antara karakter yang berbeda, jadi mari kita lakukan. Nearley mendukung kelas karakter dan RBNF untuk "nol atau lebih" sesuatu dengan ": *", jadi saya dapat menggunakan ini untuk menentukan "nol atau lebih banyak ruang" dan menempelkannya di mana saja untuk memungkinkan spasi dalam deskripsi:

@builtin "number.ne"
@builtin "string.ne"
border -> element | element border
WS -> [\s]:*
number -> WS decimal WS
color -> WS dqstring WS
element -> "L(" number "," color ")"

Namun, penggunaan WS di seluruh membuat sulit untuk membaca tata bahasa, jadi saya akan meninggalkan mereka, tetapi bayangkan bahwa mereka.

Elemen juga bisa berupa ruang vertikal:

@builtin "number.ne"
@builtin "string.ne"
border -> element | element " " border
number -> decimal
color -> dqstring
element -> "L(" number "," color ")"
element -> "VS(" number ")"

Ini sesuai dengan batas-batas tersebut

L(3.5,"black") VS(3.5)

Berikutnya adalah primitif strip, belah ketupat dan elips.

@builtin "number.ne"
@builtin "string.ne"
border -> element | element " " border
number -> decimal
color -> dqstring
element -> "L(" number "," color ")"
element -> "VS(" number ")"
geometric -> "B(" number "," number "," color "," number "," color ")"
geometric -> "E(" number "," number "," color "," number "," color ")"
geometric -> "D(" number "," number "," color "," number "," color ")"

Itu akan cocok dengan elemen tersebut

B(34, 17, "white", 3, "black")

(Perhatikan bahwa primitif geometris bukan "elemen" karena mereka tidak bisa sendirian di tingkat atas. Mereka harus dilampirkan dalam suatu pola.)

Saya juga membutuhkan ruang horizontal primitif:

@builtin "number.ne"
@builtin "string.ne"
border -> element | element " " border
number -> decimal
color -> dqstring
element -> "L(" number "," color ")"
element -> "VS(" number ")"
geometric -> "B(" number "," number "," color "," number "," color ")"
geometric -> "E(" number "," number "," color "," number "," color ")"
geometric -> "D(" number "," number "," color "," number "," color ")"
geometric -> "HS(" number ")"

Sekarang saya akan menambahkan pola (ulangi) operasi. Ini adalah urutan satu atau lebih elemen di dalam tanda kurung siku. Saya akan menggunakan operator RBNF ": +", yang di sini berarti "satu atau lebih."

@builtin "number.ne"
@builtin "string.ne"
border -> element | element " " border
number -> decimal
color -> dqstring
element -> "L(" number "," color ")"
element -> "VS(" number ")"
geometric -> "B(" number "," number "," color "," number "," color ")"
geometric -> "E(" number "," number "," color "," number "," color ")"
geometric -> "D(" number "," number "," color "," number "," color ")"
geometric -> "HS(" number ")"
element -> "[" (geometric):+ "]"

Perhatikan bahwa polanya hanya dapat diisi dengan primitif geometris. Kita tidak bisa, misalnya, menempatkan garis di dalam suatu pola. Elemen pola sekarang akan cocok dengan sesuatu seperti ini.

[B(34,17,"white",3,"black")E(13,21,"white",3,"rgb(27,0,0)")]

Bagian terakhir bahasa adalah operator overlay. Ini adalah sejumlah elemen di dalam kawat gigi.

@builtin "number.ne"
@builtin "string.ne"
border -> element | element " " border
number -> decimal
color -> dqstring
element -> "L(" number "," color ")"
element -> "VS(" number ")"
geometric -> "B(" number "," number "," color "," number "," color ")"
geometric -> "E(" number "," number "," color "," number "," color ")"
geometric -> "D(" number "," number "," color "," number "," color ")"
geometric -> "HS(" number ")"
element -> "[" (geometric ):+ "]"
element -> "{" (element ):+ "}"

yang memungkinkan kami untuk melakukan hal berikut:

{L(3.5,"rgb(98,76,15)")VS(3.5)}

(Perhatikan bahwa tidak seperti operator pengulangan, operator overlay dapat digunakan secara internal.)

Setelah membersihkan deskripsi dan menambahkan spasi ke tempat-tempat yang diperlukan, kami mendapatkan tata bahasa MBDL berikut:

@builtin "number.ne"
@builtin "string.ne"
border -> (element WS):+
WS -> [\s]:*
number -> WS decimal WS
color -> WS dqstring WS
element -> "L(" number "," color ")"
element -> "VS(" number ")"
element -> "(" WS (element WS):+ ")"
element -> "[" WS (geometric WS):+ "]"
geometric -> "B(" number "," number "," color "," number "," color ")"
geometric -> "E(" number "," number "," color "," number "," color ")"
geometric -> "D(" number "," number "," color "," number "," color ")"
geometric -> "HS(" number ")"

Jadi, MBDL sekarang didefinisikan dan kami telah membuat tata bahasa. Ini dapat digunakan dengan Nearley untuk mengenali string bahasa. Sebelum mempelajari MBDL / Nearley, saya ingin mengimplementasikan primitif yang digunakan dalam MBDL sehingga batas yang dijelaskan pada MBDL dapat ditampilkan. Ini akan kita lakukan di bagian selanjutnya.

Bagian 3


Sekarang kita akan mulai menerapkan rendering primitif sendiri. (Pada titik ini, saya tidak perlu mengikat parser ke primitif rendering. Untuk pengujian, saya hanya akan memanggil mereka secara manual.)

Mari kita mulai dengan garis primitif. Ingat bagaimana tampilannya:

L(width, color)

Selain lebar dan warna, ada parameter implisit di sini - jarak dari tepi luar peta. (Saya menarik batas dari tepi peta ke luar. Perhatikan bahwa kita mulai dari yang berbeda!) Seharusnya tidak menunjuk ke MBDL, karena ini dapat dilacak oleh penerjemah yang menjalankan MBDL untuk menggambar perbatasan. Namun, ini harus menjadi input untuk semua rendering primitif sehingga mereka tahu di mana menggambarnya. Saya akan memanggil parameter ini diimbangi.

Jika saya hanya perlu menggambar perbatasan di bagian atas peta, maka garis primitif akan sangat mudah diterapkan. Namun, pada kenyataannya, saya perlu menggambar dari atas. bawah, kiri dan kanan. (Mungkin suatu hari nanti saya akan menyadari batas miring atau melengkung, tetapi untuk sekarang kita akan mematuhi batas persegi panjang standar.) Selain itu, panjang dan lokasi elemen garis tergantung pada ukuran peta (serta pada offset). Karena itu, sebagai parameter, saya memerlukan semua data ini.

Setelah menetapkan semua parameter ini, cukup membuat garis primitif dan menggunakannya untuk menggambar garis di sekitar peta:


(Perhatikan bahwa saya menggunakan berbagai fungsi Dragons Abound untuk menggambar garis "tulisan tangan".) Mari kita coba membuat perbatasan yang lebih kompleks:

L(3, black) L(10, gold) L(3, black)

Ini terlihat seperti ini:


Cukup bagus. Perhatikan bahwa ada tempat-tempat di mana garis-garis hitam dan garis emas tidak cukup sejajar karena fluktuasi. Jika saya ingin menghilangkan bintik-bintik ini, maka Anda cukup mengurangi jumlah osilasi.

Menerapkan primitif ruang vertikal cukup sederhana; itu hanya melakukan kenaikan offset. Mari tambahkan sedikit ruang:

L(3, black) L(10, gold) L(3, black)
VS(5)
L(3, black) L(10, red) L(3, black)


Saat menggambar garis, sudut bisa diwujudkan dengan menggambar antara offset dan gambar sepanjang peta searah jarum jam. Tetapi secara umum, saya perlu menerapkan pemotongan di setiap sisi perbatasan peta untuk membuat koneksi sudut dengan bevel . Ini akan diperlukan untuk membuat batas dengan pola yang disatukan dengan benar di sudut, dan dalam kasus umum akan menghilangkan kebutuhan untuk menggambar elemen dengan tepi pada sudut yang seharusnya diperlukan. (*)

(Catatan: seperti yang akan dikatakan di bagian berikut, seiring waktu saya menolak untuk menggunakan daerah pemotongan ketika menerapkan sudut. Alasan utama adalah untuk membuat sudut yang kompleks, misalnya, offset persegi:


area pemotongan semakin kompleks diperlukan. Juga, seiring waktu, saya menemukan cara yang lebih baik untuk bekerja dengan pola di sudut-sudut. Alih-alih mengembalikan dan menulis ulang bagian artikel ini, saya memutuskan untuk meninggalkannya untuk menggambarkan proses “kreativitas”.)

Gagasan utamanya adalah memotong setiap perbatasan secara diagonal dan membuat empat area terpotong di mana masing-masing sisi perbatasan akan digambar:


Saat memotong, semua yang digambar di area terkait akan terpotong pada sudut yang diinginkan.


Sayangnya, ini menciptakan celah kecil di sepanjang garis diagonal, mungkin karena browser berperforma tidak sempurna di sepanjang tepi yang terpotong. Tes menunjukkan bahwa latar belakang bersinar melalui celah antara kedua tepi. Itu mungkin untuk memperbaikinya dengan sedikit memperluas salah satu topeng (setengah pixel tampaknya sudah cukup), tetapi ini kadang-kadang tidak menyelesaikan masalah.

Selanjutnya Anda perlu menerapkan bentuk geometris. Tidak seperti garis, mereka diulang dalam pola, mengisi sisi perbatasan peta:


Seseorang akan menggambar pola ini dari kiri ke kanan, menggambar persegi panjang, belah ketupat, dan kemudian mengulangi yang sama sampai seluruh perbatasan dipenuhi. Oleh karena itu, ini juga dapat diterapkan dalam program dengan menggambar pola di sepanjang perbatasan. Namun, akan lebih mudah untuk menggambar semua persegi panjang, dan kemudian semua belah ketupat. Ini akan cukup hanya dengan menggambar di sepanjang perbatasan sosok geometris yang sama pada interval. Dan sangat nyaman bahwa setiap elemen memiliki interval yang sama. Tentu saja, seseorang tidak akan melakukan itu, karena terlalu sulit untuk mengatur unsur-unsur di tempat yang tepat, tetapi ini bukan masalah bagi program.

Artinya, prosedur untuk menggambar bentuk geometris sederhana memerlukan parameter di mana semua dimensi dan warna gambar ditransfer (yaitu lebar, panjang, ketebalan garis, warna garis dan isi), serta posisi awal (yang karena alasan yang akan segera menjadi jelas, Saya akan mempertimbangkan pusat gambar), interval ruang horizontal untuk transisi antara pengulangan, dan jumlah pengulangan. Juga akan mudah untuk menunjukkan arah pengulangan dalam bentuk vektor [dx, dy], sehingga kita dapat melakukan pengulangan dari kiri ke kanan, dari kanan ke kiri, atas atau bawah, cukup mengubah vektor dan titik awal. Gabungkan semuanya dan dapatkan potongan bentuk berulang:


Menggunakan kode ini beberapa kali dan rendering dengan offset yang sama, saya dapat menggabungkan garis-garis hitam dan putih untuk membuat skala peta:


Sebelum saya mulai mencari tahu cara menerapkan semua ini pada batas sebenarnya peta, mari kita terapkan fungsi yang sama untuk elips dan belah ketupat.

Belah ketupat hanya persegi panjang dengan simpul yang diputar, jadi Anda hanya perlu melakukan sedikit perubahan pada kode. Ternyata saya masih belum memiliki kode siap pakai untuk merender elips, tetapi sangat mudah untuk mengambil tampilan parametrik elips dan membuat fungsi yang memberi saya poin elips:


Berikut adalah contoh (dibuat secara manual) yang menggunakan fitur yang diimplementasikan di atas:


Untuk sejumlah kecil kode, itu terlihat cukup bagus!

Sekarang mari kita pecahkan kasus perbatasan yang rumit dengan elemen berulang: sudut.

Jika ada perbatasan dengan elemen berulang, ada beberapa cara untuk menyelesaikan masalah dengan sudut. Yang pertama adalah menyesuaikan pengulangan sehingga mereka dieksekusi di sudut tanpa pernikahan yang nyata:


Pilihan lain adalah menghentikan pengulangan di suatu tempat dekat sudut di kedua sisi. Ini sering dilakukan jika polanya tidak mudah "diputar" di sudut:


Opsi terakhir adalah menutup pola dengan beberapa hiasan sudut:


Suatu hari nanti saya akan sampai ke dekorasi sudut, tetapi untuk sekarang kita akan menggunakan opsi pertama. Bagaimana membuat pola garis-garis atau lingkaran “berputar” di sudut-sudut peta tanpa celah?

Gagasan utamanya adalah menempatkan elemen pola tepat di sudut sehingga setengahnya berada di satu sisi peta dan yang lainnya berada di sisi yang berdekatan. Dalam contoh ini, lingkaran tepat di sudut dan dapat ditarik dari segala arah:


Dalam kasus lain, elemen setengah ditarik ke satu arah, dan setengah di sisi lain, tetapi ujungnya bertepatan:


Dalam hal ini, garis putih digambar di kedua sisi, tetapi terhubung di sudut tanpa celah.

Ada dua aspek yang perlu dipertimbangkan ketika menempatkan elemen di sudut.

Pertama, elemen sudut akan dibagi dan dicerminkan relatif terhadap melewati diagonal melalui pusat elemen. Elemen dengan simetri radial, misalnya, kuadrat, lingkaran, dan bintang, tidak akan mengubah bentuknya. Elemen tanpa simetri radial, misalnya, persegi panjang dan belah ketupat, akan berubah bentuk ketika mirroring relatif terhadap diagonal.

Kedua, agar elemen sudut dari kedua sisi terhubung dengan benar, harus ada jumlah elemen (*) integer di kedua sisi peta. Mereka tidak harus memiliki angka yang sama, tetapi harus ada bilangan bulat elemen di kedua sisi. Jika sejumlah pola fraksional terkandung di satu sisi, maka dari satu sisi pola tidak akan bertepatan dengan sisi yang berdekatan.

(* Dalam beberapa kasus, misalnya, dengan garis-garis panjang, pengulangan parsial dapat terjadi dengan pengulangan penuh dan elemen-elemen akan tetap selaras. Namun, elemen sudut yang dihasilkan akan asimetris dan panjangnya berbeda dari elemen yang sama di sisi peta. Contohnya bisa dilihat di sini:


Balok putih skala terjadi dengan pengulangan parsial yang berbeda dan sebagai hasilnya elemen bergeser relatif ke pusat diperoleh. Untuk skala peta, ini tidak selalu terjadi, karena menunjukkan jarak absolut dan tidak harus simetris. Tetapi untuk pola dekoratif, ini biasanya terlihat buruk.)

Berikut adalah contoh yang menunjukkan bagaimana jumlah pengulangan integer dipangkas tepat di sudut:


Jika Anda melakukan hal yang sama dari keempat sisi, maka sudut-sudutnya akan bertepatan dan polanya akan terletak di sepanjang garis perbatasan:


Setelah pemeriksaan cermat, Anda akan melihat bahwa pola tidak terjadi persis di sudut-sudut. Setengah lingkaran di setiap sudut diambil dari masing-masing sisi, dan dua bagian ini ditarik secara terpisah dengan tangan, oleh karena itu mereka tidak sempurna. Tapi sekarang mereka cukup dekat dengan ini.

Jadi, kita dapat mewujudkan koneksi sempurna dari pola di sudut-sudut, memilih jumlah pengulangan integer untuk setiap sisi. Namun, solusi untuk masalah ini adalah nontrivial.

Pertama, misalkan kita tahu bahwa sisi adalah 866 piksel, dan kami ingin mengulang elemen 43 kali. Maka elemen harus diulang setiap 20,14 piksel. Bagaimana kita menetapkan panjang elemen tertentu (dan dalam kasus umum, pola elemen)? Pada contoh di atas, saya menambahkan ruang ekstra di antara lingkaran. Tetapi jika pada awalnya lingkaran saling bersentuhan, maka ini akan mengubah polanya. Mungkin ada baiknya merentangkan lingkaran sehingga mereka terus saling menyentuh?


Sekarang elemen-elemennya bersentuhan, tetapi lingkarannya telah berubah menjadi elips dan sudut-sudutnya memiliki bentuk yang aneh. (Ingat, saya katakan bahwa elemen tanpa simetri radial berubah bentuk ketika dipantulkan relatif ke sudut? Untuk garis, ini tidak akan menjadi masalah besar.) Atau, mungkin, ada baiknya mengompresi semua elemen sehingga mereka saling menyentuh dan pas dalam panjang yang sesuai:


Tetapi untuk menyadari hal ini, kita perlu membuat elemen-elemennya jauh lebih kecil dari aslinya. Tidak satu pun dari opsi ini yang tampak sempurna.

Masalah kedua terjadi ketika sisi-sisi kartu memiliki ukuran yang berbeda. Sekarang kita perlu memecahkan masalah menemukan jumlah pengulangan integer yang cocok untuk kedua belah pihak. Akan ideal untuk menemukan satu solusi yang cocok untuk kedua sisi. Tetapi saya tidak ingin melakukan ini dengan mengorbankan terlalu banyak perubahan pola. Mungkin lebih baik untuk membuat pola yang sedikit berbeda di kedua sisi jika keduanya cukup dekat dengan pola aslinya.

Dan akhirnya, masalah ketiga muncul ketika saya menggunakan fungsi menumpangkan beberapa elemen satu sama lain:


Saya tidak ingin membuat perubahan pada pola yang akan menghancurkan hubungan antar elemen. Saya pikir dengan penskalaan yang tepat, rasio secara keseluruhan akan tetap ada, tetapi saya perlu menguji ini.

Tugas yang menarik, bukan? Sejauh ini saya tidak punya solusi berkualitas tinggi untuknya. Mungkin mereka akan muncul nanti!

Bagian 4


Jadi, kami telah menerapkan primitif untuk menggambar garis dan bentuk geometris. Saya mulai bekerja menggunakan bentuk berulang untuk mengisi perbatasan dan berbicara tentang kesulitan menempatkan pola sewenang-wenang di perbatasan peta sehingga mereka pas di sudut-sudut. Masalah utama adalah bahwa dalam kasus umum, Anda harus membuat polanya lebih panjang (atau lebih pendek) agar pas. Pilihan untuk mengubah panjang pola - menambah atau menghapus spasi, mengubah panjang elemen pola - menyebabkan berbagai perubahan dalam pola itu sendiri. Tampaknya tugas memilih pola dari beberapa elemen sangat sulit!

Ketika saya menemukan tugas-tugas yang tampaknya tanpa kompromi, saya ingin memulai dengan menerapkan versi sederhana. Tugas yang gagal seringkali dapat diselesaikan dengan berulang kali memecahkan masalah "sederhana" sampai hasilnya cukup baik. Dan kadang-kadang implementasi versi sederhana memberikan beberapa pemahaman yang menyederhanakan solusi dari masalah yang lebih kompleks. Jika tidak menjadi lebih baik dan masalahnya tetap tidak stabil, maka setidaknya kita akan memiliki versi yang disederhanakan yang masih bisa berguna, meskipun tidak cukup seperti seharusnya.

Cara termudah adalah mengubah panjang pola dengan menambahkan panjang tanpa mengubah apa pun dalam pola. Pada dasarnya, ini menambahkan ruang kosong ke ujung pola. (Catatan: lebih baik mendistribusikan ruang kosong di antara semua elemen dalam pola.) Perlu mempertimbangkan bahwa solusi seperti itu hanya dapat memperpanjang pola. Kami selalu dapat menambahkan ruang kosong ke pola, tetapi tidak dapat menerimanya jika perlu - mungkin tidak akan ada lagi ruang kosong di dalam pola!

Dengan pendekatan ini, algoritma lokasi pola di sisi kartu akan sangat sederhana:

  • Bagilah panjang sisi kartu dengan panjang pola dan bulatkan ke bawah untuk menentukan jumlah pengulangan pola yang sesuai di sisi itu.
  • Jarak antara elemen dalam kasus ini akan sama dengan panjang sisi dibagi dengan jumlah pengulangan. (Ini adalah yang terdekat dengan lokasi asli, mengingat kami hanya dapat menambah ruang.)
  • Gambarlah pola di sepanjang sisi, dengan mempertimbangkan jarak yang dihitung.

Sulit untuk menerapkan sistem ini. Sudut-sudut yang keras kepala tidak mau bertepatan. Butuh waktu terlalu lama bagi saya untuk menyadari bahwa ketika peta tidak persegi, saya tidak dapat menggambar area pemotongan untuk empat sisi dari pusat peta, karena ini menciptakan sudut pemotongan yang tidak sama dengan 45 derajat. Faktanya, area pemotongan harus menyerupai bagian belakang amplop:


Ketika saya menemukan ini, algoritma mulai bekerja tanpa masalah.

(Tapi jangan lupa catatan sebelumnya bahwa seiring waktu saya meninggalkan area pemotongan!)

Berikut adalah contoh dengan rasio sekitar 2: 1:

Pada skala ini, cukup sulit untuk diperhatikan, tetapi sudut-sudutnya terhubung dengan benar dan hanya ada sedikit perbedaan visual antara kedua sisi. Dalam hal ini, algoritma untuk menyelaraskan pola hanya perlu memasukkan piksel fraksional, sehingga tidak terlihat oleh mata, terutama karena kontur lingkaran tumpang tindih oleh piksel.

Berikut adalah contoh lain dengan garis-garis:


Ini adalah bagian atas dari batas bujur sangkar. Berikut adalah perbatasan yang sama pada peta yang lebih persegi:


Di sini Anda dapat melihat bahwa di sisi kartu terdapat celah visual yang lebih besar di antara kedua band. Algoritme tidak boleh memasukkan ruang lebih dari panjang satu elemen penuh; oleh karena itu, kasus terburuk terjadi ketika kita memiliki elemen panjang dan sisi pendek yang sedikit berbeda dari ukuran yang sesuai. Tetapi dalam kebanyakan kasus praktis, penyelarasan tidak terlalu berbahaya.

Berikut ini adalah contoh dengan pola beberapa elemen:


Di sini garis-garis saling bertumpuk:


Anda dapat melihat bahwa karena penyelarasan yang sama dilakukan untuk setiap elemen, garis-garis tetap terpusat relatif satu sama lain.

Saya menyarankan bahwa solusi yang baik untuk menempatkan pola di sisi peta akan sulit, tetapi pendekatan yang sangat sederhana dengan mendistribusikan elemen pola secara merata untuk mengisi ruang yang diinginkan bekerja dengan baik untuk banyak pola. Ini adalah pengingat bagi kita semua: tidak perlu berasumsi bahwa keputusan harus rumit; mungkin lebih mudah dari yang Anda pikirkan!

Namun, solusi ini tidak berfungsi untuk pola dengan elemen sentuh, misalnya untuk skala peta. Dalam hal ini, menambahkan ruang menggeser elemen:


Pilihan lain untuk memperpanjang pola, yang saya sebutkan di atas, adalah meregangkan elemen individu dari pola. Ini cocok untuk sesuatu seperti pola skala, tetapi akan terlihat buruk dalam pola dengan elemen simetris, karena peregangan akan membuatnya asimetris.

Implementasi opsi dengan peregangan ternyata lebih sulit daripada yang saya harapkan, terutama karena saya harus meregangkan elemen pada tepi yang berbeda dari peta dengan ukuran yang berbeda (karena peta mungkin bukan persegi tetapi persegi panjang), dan juga secara dinamis mengubah pengaturan elemen berdasarkan yang baru diregangkan. ukuran. Tetapi setelah beberapa jam saya berhasil mencapai ini:


Sekarang saya memiliki semua fitur yang diperlukan untuk menggambar perbatasan peta (walaupun elemen perbatasan itu sendiri dibuat secara manual):


Saya mengubah gambar menjadi skala abu-abu, karena saya tidak ingin repot dengan pemilihan warna, dan kartu itu sendiri agak membosankan, tetapi sebagai bukti konsep, perbatasan terlihat cukup cantik.

Bagian 5


Di bagian 2, saya mengembangkan tata bahasa Map Border Description Language (MBDL), dan di bagian 3 dan 4, saya menerapkan prosedur untuk mengeksekusi semua primitif bahasa. Sekarang saya akan bekerja untuk menghubungkan bagian-bagian ini sehingga saya dapat menggambarkan perbatasan pada MBDL dan menggambarnya di peta.

Di Bagian 3, saya menulis tata bahasa MBDL sehingga berfungsi dengan Nearing Javascript Parsing Tool . Tata bahasa yang sudah selesai terlihat seperti ini:

@builtin " number.ne"
@builtin " string.ne"
border -> (element WS):+
WS -> [\s]:*
number -> WS decimal WS
color -> WS dqstring WS
element ->
" L(" number " ," color " )"
element -> " VS(" number " )"
element -> " (" WS (element WS):+ " )"
element -> " [" WS (geometric WS):+ " ]"
geometric -> " B(" number " ," number " ," color " ," number " ," color " )"
geometric -> " E(" number " ," number " ," color " ," number " ," color " )"
geometric -> " D(" number " ," number " ," color " ," number " ," color " )"
geometric -> " HS(" number " )"

Secara default, saat berhasil mengurai aturan menggunakan Nearley, aturan mengembalikan array yang berisi semua elemen yang sesuai dengan sisi kanan aturan. Misalnya kalau aturan

test -> " A" | " B" | " C"

cocok dengan string

A

maka Nearley akan kembali

[ " A" ]

Array dengan nilai tunggal adalah string "A" yang sesuai dengan sisi kanan aturan.

Apa yang dikembalikan Nearley ketika elemen diuraikan menggunakan aturan ini?

number -> WS decimal WS

Ada tiga bagian di sisi kanan aturan, sehingga akan mengembalikan array dengan tiga nilai. Nilai pertama akan menjadi yang mengembalikan aturan untuk WS, nilai kedua akan menjadi yang akan mengembalikan aturan untuk desimal, dan nilai ketiga akan menjadi yang akan mengembalikan aturan untuk WS. Jika, menggunakan aturan di atas, saya akan menguraikan "57", maka hasilnya adalah sebagai berikut:

[
[ " " ],
[ "5", "7" ],
[ ]
]

Hasil akhir parsing Nearley adalah array array yang bersarang, yang merupakan pohon sintaksis . Dalam beberapa kasus, pohon sintaks adalah representasi yang sangat berguna, dalam kasus lain, tidak cukup. Di Dragons Abound , misalnya, pohon seperti itu tidak terlalu berguna.

Untungnya, aturan Nearley dapat mengesampingkan perilaku standar dan mengembalikan apa pun yang mereka inginkan. Faktanya, aturan desimal (built-in) tidak mengembalikan daftar digit, ia mengembalikan nomor Javascript yang setara, yang dalam banyak kasus jauh lebih berguna, yaitu, nilai balik aturan angka memiliki bentuk:

[
[ " " ],
57,
[ ]
]

Aturan Nearley mendefinisikan kembali perilaku standar dengan menambahkan post-prosesor ke aturan, mengambil array standar dan menggantinya dengan apa yang Anda butuhkan. Postprocessor hanyalah kode Javascript di dalam tanda kurung khusus di akhir aturan. Misalnya, dalam aturan angka , saya tidak pernah tertarik pada ruang yang memungkinkan di kedua sisi nomor. Oleh karena itu, akan lebih mudah jika aturan hanya mengembalikan angka, dan bukan array dari tiga elemen. Berikut adalah post-prosesor yang melakukan tugas ini:

number -> WS decimal WS {% default => default[1] %}

Post-prosesor ini mengambil hasil standar (array tiga elemen yang ditunjukkan di atas) dan menggantinya dengan elemen kedua array, yang merupakan nomor Javascript dari aturan desimal . Jadi sekarang aturan bilangan mengembalikan bilangan real.

Fungsi ini dapat digunakan untuk memproses bahasa yang masuk menjadi bahasa perantara, yang lebih mudah untuk digunakan. Sebagai contoh, saya bisa menggunakan tata bahasa Nearley untuk mengubah string MBDL menjadi array struktur Javascript, yang masing-masing mewakili primitif yang diidentifikasi oleh bidang "op". Aturan untuk garis primitif akan terlihat seperti ini:

element -> " L(" number " ," color " )" {% data=> {op: " L", width: data[1], color: data[3]} %}

Artinya, hasil parsing "L (13, black)" akan menjadi struktur Javascript:

{op: " L", width: 13, color: " black"}

Setelah menambahkan post-processing yang sesuai, hasilnya dikembalikan dari tata bahasa dapat menjadi urutan (array) dari struktur operasi untuk baris yang masuk. Artinya, hasil parsing string

L( 415, “black")
VS(5)
[B(1, 2, “black", 3, “white") HS(5) E(1, 2, “black", 3, “white")]

akan

[
{op: "L", width: 415, color: "black"},
{op: "VS", width: 5},
{op: "P",
elements: [{op: "B", width: 1, length: 2,
olColor: "black", olWidth: 3,
fill: "white"},
{op: "HS", width: 5},
{op: "E", width: 1, length: 2,
olColor: "black", olWidth: 3,
fill: "white"}]}
]

yang jauh lebih mudah untuk diproses untuk membuat perbatasan peta.

Pada titik ini, Anda mungkin memiliki pertanyaan - jika fase pasca-pemrosesan aturan Nearley dapat berisi Javascript apa pun, lalu mengapa tidak melewati tampilan perantara dan cukup menggambar batas peta tepat selama pasca-pemrosesan? Untuk banyak tugas, pendekatan ini ideal. Saya memutuskan untuk tidak menggunakannya karena beberapa alasan.

Pertama, di MBDL ada beberapa (*) komponen yang tidak dapat dieksekusi selama proses parsing. Sebagai contoh, kita tidak dapat menggambar elemen geometris berulang (strip atau belah ketupat) selama proses parsing, karena kita perlu mengetahui informasi dari elemen lain dalam pola yang sama. Secara khusus, kita perlu mengetahui panjang total pola untuk memahami sejauh mana kita perlu mengatur pengulangan setiap elemen individu. Artinya, elemen pola masih harus membuat representasi menengah dari semua elemen geometris.

(* Ada komponen lain dengan keterbatasan serupa yang belum saya bicarakan.)

Kedua, Javascript di Nearley tertanam dalam aturan, jadi kami tidak akan dapat meneruskan informasi tambahan ke Javascript, kecuali untuk variabel global. Misalnya, untuk menggambar perbatasan, saya perlu tahu ukuran peta, empat area pemotongan yang digunakan, dll. Walaupun saya dapat menambahkan kode yang membuat informasi ini tersedia untuk pasca-prosesor Nearley, itu akan sedikit berantakan dan mungkin sulit untuk mempertahankan kode tersebut.

Karena alasan ini, saya menguraikan representasi perantara, yang kemudian dieksekusi untuk membuat perbatasan peta itu sendiri.

Langkah selanjutnya adalah mengembangkan juru bahasa yang menerima representasi perantara dari MBDL dan menjalankannya untuk menghasilkan batas peta. Ini tidak terlalu sulit untuk dilakukan. Pada dasarnya, tugasnya adalah mengatur kondisi awal (misalnya, menghasilkan daerah pemotongan untuk empat sisi peta) dan beralih pada urutan struktur representasi perantara dengan masing-masing yang mengeksekusi.

Ada beberapa momen yang licin.

Pertama, saya harus beralih dari rendering dari dalam ke menggambar dari dalam ke luar. Alasannya adalah karena saya ingin sebagian besar perbatasan tidak tumpang tindih peta, jadi saya perlu menggambar perbatasan sehingga garis tepi bagian dalam bertepatan dengan tepi peta. Jika saya menggambar dari luar ke dalam, maka saya perlu tahu lebar perbatasan sebelum saya mulai menggambar sehingga perbatasan tidak tumpang tindih peta. Jika saya menggambar dari dalam ke luar, saya baru mulai dari tepi peta dan menggambar. Ini juga memungkinkan Anda untuk memaksakan batas pada peta secara opsional; mulai saja perbatasan dengan ruang vertikal negatif (VS).

Poin sulit lainnya adalah pola berulang. Untuk menggambar pola berulang, saya perlu melihat semua elemen pola dan menentukan yang terluas, karena akan mengatur lebar keseluruhan pola. Saya juga perlu melihat dan melacak panjang pola sehingga saya tahu berapa jauh jarak yang tersisa sebelum setiap pengulangan.

Berikut adalah contoh perbatasan yang agak rumit yang saya gunakan untuk menguji juru bahasa:


Saya pikir itu mungkin (perlu?) Untuk melampirkannya untuk pengujian ke parser, tetapi untuk perbatasan ini saya baru saja membuat tampilan perantara secara manual:

[
{op:'P', elements: [
{op:'B', width: 10, length: 37, lineWidth: 2, color: 'black', fill: 'white'},
{op:'B', width: 10, length: 37, lineWidth: 2, color: 'black', fill: 'black'},
]},
{op:'VS', width: 2},
{op:'L', width:3, color: 'black'},
{op:'PUSH'},
{op:'L', width:10, color: 'rgb(222,183,64)'},
{op:'POP'},
{op:'PUSH'},
{op:'P', elements: [
{op:'E', width: 5, length: 5, lineWidth: 1, color: 'black', fill: 'red'},
{op:'HS', length: 10},
]},
{op:'L', width:3, color: 'black'},
{op:'POP'},
{op:'VS', width: 2},
{op:'P', elements: [
{op:'E', width: 2, length: 2, lineWidth: 0, color: 'black', fill: 'white'},
{op:'HS', length: 13},
]},
]

Saya membuat tampilan ini melalui coba-coba. Bagaimanapun, interpreter bekerja!

Sebagai langkah terakhir, izinkan saya menggunakan pengurai untuk membuat tampilan perantara dari versi MBDL. Tidak ada banyak yang bisa saya tunjukkan di sini: Saya harus memperbaiki beberapa nama bidang, tetapi jika tidak, kodenya berfungsi dengan baik. Untuk perbatasan, saya menggunakan versi MBDL yang sedikit berbeda:

[B(5,37,"black",2,"white") B(5,37,"black",2,"black")]
VS(3)
L(3,"black")
{L(10,"rgb(222,183,64)")}
[E(5,5,"black",1,"red") HS(-5) E(2,2,"none",0,"white") HS(10)]
L(3,"black")

Dia menggambar perbatasan yang sama, tetapi dengan cara yang sedikit berbeda. Saya juga mengubah sintaks untuk overlay, menggantikan tanda kurung dengan kurung kurawal sehingga lebih berbeda dari sintaks lainnya.

Untuk menunjukkan mengapa saya ingin menggambar dari dalam ke luar, dan tidak hanya secara otomatis menempatkan perbatasan di luar peta, saya dapat menambahkan ruang vertikal negatif ke awal perbatasan ini untuk memindahkan skala peta di dalam tepi peta:


Sekarang saya memiliki sebagian besar infrastruktur yang diperlukan untuk pembuatan batas peta prosedural: bahasa deskripsi batas, pengurai bahasa, dan prosedur untuk melakukan representasi perantara. Tetap hanya berurusan dengan bagian yang sulit - generasi prosedural!

Bagian 6


Sekarang seluruh MBDL telah diimplementasikan, saya bermaksud melanjutkan ke generasi prosedural perbatasan peta, tetapi saya belum yakin bagaimana saya ingin melakukan ini, karena saya akan sedikit berlama-lama dan menerapkan beberapa fitur MBDL lebih banyak.

Dalam diskusi pertama tentang pemrosesan sudut dengan pola, saya berbicara tentang beberapa pendekatan berbeda. Pada akhirnya, saya menyadari sudut miring, tetapi ada pilihan kedua: hentikan pola di dekat sudut, seperti dalam contoh berikut:



Solusi semacam itu sering digunakan ketika pola perbatasan adalah semacam angka asimetris, rune, atau sesuatu yang tidak dapat diputar 90 derajat, sambil mempertahankan perataan. Tetapi jelas bahwa ini akan bekerja dengan bentuk geometris.

Ini mungkin opsi yang Anda pilih sebelum membuat perbatasan, tetapi Anda dapat menambahkan sedikit fleksibilitas jika Anda mengaktifkannya dari satu bagian perbatasan dan menggunakan sudut miring di sisi lainnya. Untuk melakukan ini, saya harus menambahkan perintah baru ke MBDL. Saya menduga bahwa opsi lain mungkin muncul untuk bagian perbatasan yang berbeda, jadi saya akan menambahkan perintah opsi umum:

element -> "O(MITER)"
element -> "O(STOPPED)"
element -> "O(STOPPED," number ")"

(Di sini sekali lagi, untuk kejelasan, kami menghilangkan spasi dan beberapa detail lainnya.) Sejauh ini, satu-satunya pilihan adalah "MITER" untuk sudut miring dan "STOPPED" untuk berhenti di dekat sudut. Jika tidak ada nilai yang dikirimkan STOPPED, maka program menghentikan pola pada jarak yang masuk akal dari sudut. Jika nilai ditransmisikan, pola berhenti pada jarak itu dari sudut.

Jika sudut STOPPED digunakan, maka saya berhenti menggambar pola sudut jauh dari sudut. Begini tampilannya:


Di sini, saya menggunakan opsi MITER untuk pola skala hitam dan putih, sehingga mencerminkan sehubungan dengan sudut. Untuk pola lingkaran merah dan kotak hitam di dalam garis emas (dan untuk pola lingkaran di luar perbatasan), saya menggunakan STOPPED. Anda dapat melihat bahwa kedua pola ini berakhir dekat sudut.

Namun, ada beberapa masalah. Pertama, kita melihat bahwa di sebelah kiri elemen terdekat ke sudut adalah kotak hitam, dan di atas adalah lingkaran merah. Itu terjadi karena sudut dekat awal pengulangan di satu sisi dan di dekat akhir pengulangan di sisi lain. Tapi itu terlihat aneh. Akan lebih baik jika sudutnya simetris, bahkan jika untuk ini kita harus menambahkan elemen lain ke akhir pola. Kedua, Anda dapat melihat bahwa pola di luar perbatasan (setengah lingkaran dan titik-titik hitam) juga berakhir dalam satu pengulangan ke sudut. Tetapi karena panjang pengulangan ini jauh lebih kecil dari panjang lingkaran merah / kotak hitam, mereka berakhir di tempat yang berbeda. Mungkin akan lebih baik jika semua pola berhenti pada jarak yang sama dari sudut.

Untuk memperbaiki masalah pertama, Anda perlu menambahkan pengulangan lain dari elemen pertama dari pola di akhir setiap sisi perbatasan. Tapi sebenarnya ini sedikit lebih rumit, karena saya bisa menggunakan offset horizontal negatif di dalam pola untuk tumpang tindih beberapa elemen (seperti yang dilakukan di sini). Anda juga perlu menambahkan pengulangan ke elemen pola mana pun yang memiliki titik awal yang sama dengan elemen pertama.


Sekarang polanya simetris dengan memperhatikan sudut dan terlihat jauh lebih baik.

Selanjutnya, saya perlu melacak pola STOPPED terpanjang dan menghentikan setiap pola STOPPED pada jarak ini:


Sekarang pola lingkaran putih lebih disisihkan, tetapi masih belum selaras dengan pola lingkaran merah. MengapaIni terjadi karena pola lingkaran putih lebih jauh dari tepi peta, dan perbatasan lebih panjang daripada di mana pola lingkaran merah digambar. Untuk memperbaiki masalah ini, Anda perlu memindahkan pola juga dan mempertimbangkan offsetnya relatif ke tepi peta.


Sekarang semuanya tertata dengan indah.

Pilihan kedua untuk sudut adalah offset kuadrat di sudut, misalnya ini:


Akan jauh lebih sulit untuk menerapkan ini!

Namun, tata bahasa opsi ini sederhana dan menggunakan opcode Opsi:

element -> "O(SQOFFSET)"
element -> "O(SQOFFSET," number ")"

Angka menunjukkan ukuran perpindahan kuadrat untuk elemen di tepi peta; Elemen dengan offset yang berbeda harus disejajarkan. Jika tidak ada angka, program memilih ukuran offset yang sesuai. Menekan angka akan menonaktifkan offset persegi. Ini memungkinkan Anda untuk membuat batas di mana beberapa elemen menggunakan offset kotak, sementara yang lain tidak, seperti di perbatasan ini:


Hal pertama yang saya sadari adalah bahwa saya akan membutuhkan area pemotongan tambahan karena saya menggunakan pemotongan untuk memproses tempat-tempat di mana perbatasan berubah arah. SQOFFSET akan membutuhkan area pemotongan yang lebih kompleks; Anda juga akan membutuhkan area terpisah untuk item yang berbeda saat Anda mengaktifkan dan menonaktifkan SQOFFSET. Mengingat bahwa area pemotongan menambah artefak yang tidak diinginkan, sepertinya ini terlalu banyak pekerjaan.

Ketika saya mengerjakan pola yang dapat dihentikan di atas, saya menerapkan pengisian pola asimetris untuk menambahkan pengulangan dari satu ujung pola. Saya juga menyadari bahwa ini akan menghilangkan kebutuhan sudut miring. Saya hanya akan menggambar semua pola di sepanjang perbatasan searah jarum jam, mulai pola di satu sudut dan berakhir di dekat sudut berikutnya. Ini akan memungkinkan saya untuk menyingkirkan area pemotongan.

Hal terpenting dalam cara baru bekerja dengan sudut ini adalah bahwa elemen pertama dari pola tidak lagi “dibagi” menjadi dua sisi. Jika Anda melihat pola skala hitam dan putih pada peta di atas, Anda dapat melihat bahwa ada persegi panjang putih yang melewati sudut. Sekarang persegi panjang putih akan berbatasan dengan sudut:


Peta digambar dengan dua cara, tetapi ini bukan masalah yang sangat besar.

Sebagai permulaan, saya menerapkan offset untuk garis. Untuk melakukan ini, cukup mengubah garis relatif ke sudut yang sesuai:


Seperti yang dapat Anda pahami, saya dapat menggabungkan sudut dengan offset dan sudut reguler, seperti pada peta di atas:


Tentu saja, memutar pola di tikungan lebih sulit. Gagasan umum adalah menggambar dari satu sudut ke sudut yang lain, dan seterusnya sepanjang perbatasan, sampai kita kembali ke awal. Secara teoritis cukup untuk hanya menggambar pola horizontal dan vertikal, dan semuanya harus selaras indah; sebenarnya melacak semua ini cukup suram. Sebenarnya, saya harus menulis ulang kode sepenuhnya dua kali dan menulis banyak kertas, tetapi saya tidak akan membicarakan hal ini secara terperinci. Tunjukkan saja hasilnya:


Ilusi optik yang menjengkelkan muncul di sudut-sudut - elemen sudut tampaknya tidak terpusat lebih dekat ke bagian luar sudut. Sebenarnya, ini tidak benar, tetapi tampaknya begitu, karena lebih dekat ke bagian dalam sudut ada ruang yang lebih kosong secara visual.

Karena segmen sudut offset cukup pendek, sangat mudah untuk membuat pola nonequilibrium di sudut:


Terkadang terlihat sangat jelek. Itu mengingatkan saya pada lelucon lama:

Pasien: "Dokter, ketika saya melakukan ini, itu menyakitkan saya."
Dokter: "Kalau begitu jangan lakukan itu!"

Karena itu, saya akan berusaha untuk tidak melakukannya.

Biasanya saya tidak akan menggambar skala peta di sepanjang sudut offset, tetapi jika saya membutuhkannya, saya akan perlu menggunakan opsi yang merentangkan pola sehingga skala peta cocok ke sudut tanpa kesenjangan antara persegi panjang:


Anda dapat melihat bahwa sebagai hasilnya, ukuran persegi panjang skala sangat bervariasi. Artinya, ini bukan pilihan yang sangat baik. (Ngomong-ngomong, sudut offset juga memiliki bug dalam pola lingkaran. Kemudian saya memperbaikinya, tetapi seperti yang saya katakan, sangat sulit untuk melakukannya.)

Jika polanya terlalu besar untuk masuk ke segmen sudut offset, maka algoritme menyerah begitu saja:


Yang jauh dari ideal, tetapi, seperti yang saya katakan di atas, "Kalau begitu jangan lakukan itu." (Sebenarnya tidak terlalu sulit untuk menambahkan fungsi kompresi atau peregangan jika saya membutuhkannya.)

Apa yang terjadi jika saya menggunakan kedua sudut offset dan opsi yang menghentikan pola di depan sudut? Dalam hal ini, saya hanya berhenti tidak jauh dari sudut offset:


Menurut saya ini adalah keputusan yang logis.

Source: https://habr.com/ru/post/id444190/


All Articles