Créer les bordures d'une carte générée de manière procédurale

image

Scott Turner continue de travailler sur son jeu généré de manière procédurale et a maintenant décidé de s'attaquer au problème de la conception des frontières des cartes. Pour ce faire, il doit résoudre plusieurs problèmes difficiles et même créer son propre langage pour décrire les frontières.

Les bordures sont restées un élément important des cartes fantaisie, qui figuraient sur ma liste depuis un certain temps. Les cartes fonctionnelles ont généralement une frontière simple, mais les cartes fantastiques et les cartes médiévales, dont les premières empruntent souvent des idées, ont des limites assez réfléchies et artistiques. Ces limites indiquent clairement que la carte est intentionnellement rendue fantastique et donnent au spectateur un sentiment d'émerveillement.

Il existe actuellement quelques façons simples de tracer des frontières dans mon jeu Dragons Abound . Elle peut tracer une ligne simple ou double autour du périmètre de la carte et ajouter des éléments simples dans les coins, comme dans ces figures:



Le jeu peut également ajouter un champ au bas de la bordure pour le nom de la carte. Il existe plusieurs variantes de ce domaine dans Dragons Abound , y compris des éléments complexes tels que les fausses têtes de vis:


Il existe une variabilité dans ces champs de nom, mais ils sont tous créés manuellement.

Un aspect intéressant des limites des cartes fantaisie est qu'elles sont à la fois créatives et modèles. Souvent, ils se composent d'un petit nombre d'éléments simples qui se combinent de différentes manières pour créer un résultat unique. Comme toujours, la première étape lorsque je travaille avec un nouveau sujet est d'étudier une collection d'exemples de carte, de créer un catalogue de types d'éléments de bordure et d'étudier leur apparence.

La bordure la plus simple est une ligne longeant les bords de la carte et indiquant ses limites. Comme je l'ai dit plus haut, on l'appelle aussi la "ligne de trame":


Il existe également une variation avec l'emplacement des bordures sur la carte. Dans cette version, la carte atteint les bords de l'image, mais la bordure crée une bordure virtuelle à l'intérieur de l'image:


Cela peut être fait avec n'importe quel type de bordure, mais il n'est généralement utilisé qu'avec des bordures simples comme la bordure d'un cadre.

Un concept de conception de cartes fantaisie populaire consiste à simuler comme si elles étaient dessinées sur un vieux parchemin déchiré. Parfois, cela est réalisé en dessinant la bordure comme le bord rugueux du papier:


Voici un exemple plus sophistiqué:


D'après mon expérience, cette méthode est devenue moins populaire parce que des outils numériques sont entrés en service. Si vous voulez que la carte ressemble à un vieux parchemin déchiré, il est plus facile de lui appliquer la texture du parchemin que de la dessiner à la main.

L'outil le plus puissant pour créer des bordures de carte est la répétabilité. Dans le cas le plus simple, il suffit de répéter une seule ligne pour créer deux lignes:


Vous pouvez ajouter de l'intérêt à la carte en variant le style de l'élément répété, dans ce cas en combinant une seule ligne épaisse avec une seule ligne mince:


Selon l'élément, différentes variantes de style sont possibles. Dans cet exemple, la ligne se répète, mais la couleur change:


Pour créer des motifs plus complexes, vous pouvez utiliser la «répétabilité répétable». Cette bordure se compose d'environ cinq lignes simples avec des largeurs et des distances différentes:


Cette bordure répète les lignes, mais les sépare de sorte qu'elles ressemblent à deux fines bordures distinctes. Dans cette partie de l'article, je ne parlerai pas du traitement des angles, mais des angles différents pour les deux lignes contribuent également à créer cette différence.


S'agit-il de deux lignes, quatre ou six? Je pense que tout dépend de la façon dont vous les dessinez!

Un autre élément de stylisation consiste à remplir l'espace entre les éléments de couleur, de motif ou de texture. Dans cet exemple, la bordure est devenue plus intéressante en raison du remplissage de couleur d'accentuation entre les deux lignes:


Voici un exemple de la façon dont la bordure est remplie d'un motif:


De plus, les éléments peuvent être stylisés de manière à avoir une apparence tridimensionnelle. Voici une carte dans laquelle la bordure est ombrée de sorte qu'elle semble volumineuse:


Dans cette carte, la bordure est ombrée pour avoir l'air en trois dimensions, et cela est combiné avec l'emplacement des bordures à l'intérieur des bords de la carte:


Un autre élément de bordure commun est l'échelle sous la forme de rayures multicolores:


Ces rayures forment une grille ( grille cartographique ). Sur les cartes réelles, l'échelle aide à déterminer les distances, mais sur les cartes fantastiques, c'est principalement un élément décoratif.

Ces rayures sont généralement dessinées en noir et blanc, mais parfois du rouge ou une autre couleur est ajoutée:


Cet élément peut également être combiné avec d'autres, comme dans cet exemple avec des lignes et une échelle:


Cet exemple est un peu inhabituel. Généralement, l'échelle (le cas échéant) est l'élément le plus interne de la bordure.

Sur cette carte, il existe différentes échelles avec différentes résolutions (ainsi que d'étranges notes runiques!):


(Sur Reddit, l'utilisateur AbouBenAdhem m'a informé que les marques runiques sont des nombres 48 et 47 écrits en cunéiforme babylonien. De plus, les «échelles avec différentes résolutions» ont six divisions divisées en dix divisions plus petites, ce qui correspond au système de nombres hexadécimal babylonien. Habituellement J'indique les sources des cartes, mais il y a trop de petites pièces dans ce post, donc je n'ai pas pris la peine. Cependant, cette carte a été créée par Thomas Ray pour l'auteur S.E.Boleyn, donc, peut-être, l'action dans ses livres se déroule dans l'entourage de Babylone.)

En plus des lignes et de l'échelle, l'élément le plus courant est un motif géométrique répétitif. Il se compose souvent de parties telles que des cercles, des losanges et des rectangles:


Les éléments géométriques, comme les lignes, peuvent être ombrés pour leur donner un aspect tridimensionnel:


Des frontières complexes peuvent être créées en combinant ces éléments de différentes manières. Voici la bordure qui combine les lignes, les motifs géométriques et l'échelle:


Les exemples ci-dessus étaient des cartes numériques, mais, bien sûr, la même chose peut être faite avec des cartes manuscrites. Voici un exemple d'un motif géométrique simple créé à la main:


Ces éléments peuvent également être combinés de manière flexible de nombreuses manières. Voici un motif géométrique combiné avec un «bord déchiqueté»:


Dans les exemples ci-dessus, le motif géométrique est assez simple. Mais vous pouvez créer des motifs très complexes en combinant de manière différente les éléments géométriques de base:


Un autre élément populaire du motif est le tissage ou le nœud celtique:


Voici une bordure en osier plus complexe contenant la couleur, l'échelle et d'autres éléments:


Sur cette carte, le tissage est combiné avec un élément de bord déchiqueté:


En plus des motifs géométriques et du tissage, tout motif répétitif peut faire partie de la bordure de la carte. Voici un exemple utilisant des formes ressemblant à des pointes de flèches:


Et voici un exemple avec un motif d'onde répétitif:


Et enfin, des runes ou d'autres éléments de l'alphabet fantastique sont parfois ajoutés aux bords des cartes fantaisie:


Les exemples ci-dessus sont tirés de cartes fantastiques modernes, mais voici un exemple de carte historique (XVIIIe siècle) avec des lignes et un motif dessiné à la main:


Bien sûr, vous pouvez trouver des exemples de cartes avec de nombreux autres éléments sur les bordures. Certaines des plus belles sont entièrement dessinées à la main et ont des décorations si soigneusement conçues qu'elles peuvent surpasser la carte elle-même ( World of Alma , Francesca Baerald):


Cela vaut également la peine de parler de symétrie . Comme la répétabilité, la symétrie est un outil puissant et les bordures de carte sont généralement symétriques ou comportent des éléments symétriques.

De nombreuses bordures de carte sont symétriques de l'intérieur vers l'extérieur, comme dans cet exemple:


Ici, la bordure est composée de plusieurs lignes avec et sans remplissage, mais de l'extérieur vers l'intérieur, elle se répète idéalement par rapport au centre de la bordure.

Dans cet exemple plus complexe, la bordure est symétrique, à l'exception de l'alignement des bandes noires et blanches d'échelle:


Étant donné que la duplication de l'échelle n'a pas de sens, elle est souvent considérée comme un élément distinct, même si le reste de la bordure est symétrique.

En plus de la symétrie interne-externe, les bordures sont souvent resymétriques sur toute leur longueur. Certaines bordures illustrées peuvent avoir un design simple qui s'étend sur toute la longueur du bord de la carte, mais dans la plupart des cas, le motif est assez court et se répète, remplissant la bordure d'un coin à l'autre:


Notez que dans cet exemple, le motif contient un élément qui n'est pas symétrique (de gauche à droite), mais le motif général est symétrique et se répète:


Une exception notable à cette règle est les bordures remplies de runes ou de caractères alphabétiques. Souvent, ils se révèlent uniques, comme si un long message était écrit le long de la frontière:


Bien sûr, il existe de nombreux autres exemples d'éléments de bordure de carte que je n'ai pas examinés ici, mais nous avons déjà un bon point de référence. Dans les prochaines parties, je développerai plusieurs fonctions dans Dragons Abound pour décrire, afficher et générer des bordures de carte de manière similaire à ces exemples. Dans la deuxième partie, nous commencerons par définir le langage de description des bordures des cartes.

2e partie


Dans cette partie, je vais créer la version initiale du Map Border Description Language (MBDL).

Pourquoi passer du temps à créer un langage de description de limites de carte? Premièrement, ce sera l'objectif de ma génération procédurale. Plus tard, j'écrirai un algorithme pour créer de nouvelles bordures de carte, et la sortie de cet algorithme sera une description de la nouvelle bordure sur MBDL. Deuxièmement, MBDL servira de représentation textuelle des limites de la carte. En particulier, je dois pouvoir sauvegarder et réutiliser mes limites. Pour ce faire, j'ai besoin d'une notation de texte qui peut être écrite et utilisée pour recréer la bordure de la carte.

Je vais commencer à créer MBDL en définissant l'élément le plus simple: la ligne. La ligne a une couleur et une largeur. Par conséquent, dans MBDL, je présenterai la ligne sous cette forme:

L(width, color)

Voici quelques exemples (désolé pour mes compétences Photoshop):


La séquence d'éléments est rendue de l'extérieur vers l'intérieur (*), nous supposons donc que c'est la bordure en haut de la carte:


Regardez le deuxième exemple - une ligne avec des bordures est représentée comme trois éléments de ligne distincts.

(* Dessiner de l'extérieur vers l'intérieur était un choix arbitraire - il me semblait juste que c'était plus naturel que de rendre de l'intérieur vers l'extérieur. Malheureusement, comme cela s'est avéré beaucoup plus tard, il y avait une bonne raison de travailler dans la direction opposée. Bientôt je vais vous en parler, mais tout est laissé dans le post - ancien, car il faudrait beaucoup de temps pour refaire toutes les illustrations)

De manière pratique, les espaces peuvent être représentés sous forme de lignes sans couleur:


Mais il serait plus visuel d'avoir un élément d'espace vertical spécifique:

VS (largeur)

Les éléments simples suivants sont des formes géométriques: rayures, losanges et ellipses. Il est supposé que les lignes sont étirées sur toute la longueur de la bordure, de sorte qu'elles n'ont pas de longueur explicitement spécifiée. Mais les figures géométriques ne peuvent pas remplir toute la ligne.Par conséquent, en plus de la largeur (*), chacune doit avoir une longueur, une couleur de contour, une largeur de contour et une couleur de remplissage:

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

(* J'ai accepté de considérer la largeur dans la direction de l'extérieur vers l'intérieur, et la longueur est mesurée le long de la bordure.)

Voici des exemples de formes géométriques simples:


Pour que ces éléments remplissent toute la longueur de la bordure, ils doivent être répétés. Pour indiquer le groupe d'éléments qui seront répétés pour remplir la longueur de la bordure, j'utilise des crochets:

[ element element element ... ]

Voici un exemple d'un motif répétitif de rectangles et de losanges:


Parfois, j'ai besoin d'un espace (horizontal) entre les éléments d'un motif répétitif. Bien que vous puissiez utiliser un élément sans couleurs pour créer un espace, il sera plus intelligent et plus pratique d'avoir un élément d'espace horizontal:

HS(length)

La dernière fonction requise pour cette première itération de MBDL est la possibilité d'empiler des éléments les uns sur les autres. Voici un exemple de bordure:


La façon la plus simple de le décrire est une large ligne jaune sous le motif supérieur. Vous pouvez implémenter cela de différentes manières (par exemple, un espace vertical négatif), mais j'ai décidé d'utiliser des accolades pour indiquer l'ordre des éléments vers l'intérieur:

{element element element ...}

En fait, cette entrée vous indique de vous rappeler où se trouvait le motif de l'extérieur vers l'intérieur lorsque vous entrez les crochets, puis de revenir à ce point lorsque vous quittez les crochets. Les parenthèses peuvent également être considérées comme une description des éléments occupant un espace vertical. Par conséquent, la bordure illustrée ci-dessus peut être décrite comme suit:

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)

Nous dessinons une ligne noire, fixons où nous sommes, dessinons une ligne jaune, puis revenons à la position précédemment fixée, descendons un peu, dessinons un motif de rectangles et de losanges, descendons un peu, puis dessinons une autre ligne noire.

Il y a beaucoup plus à faire dans MBDL, mais cela suffit pour décrire les nombreuses limites des cartes. L'étape suivante consiste à convertir la description des limites du MBDL en bordure elle-même. Cela revient à convertir une représentation écrite d'un programme informatique (tel que Javascript) en exécution de ce programme. La première étape est l' analyse lexicale (analyse) de la langue - la transformation du texte source en une véritable bordure de la carte ou en une sorte de forme intermédiaire, qui est plus facile à convertir en bordure.

L'analyse est un domaine assez bien étudié de l'informatique. L'analyse d'un langage n'est pas très simple, mais dans notre cas, il est bon (*) que MBDL soit une grammaire sans contexte. Les grammaires sans contexte sont analysées assez facilement, et il existe de nombreux outils d'analyse Javascript pour elles. Je me suis installé sur Nearley.js , qui semble être assez mature et (plus important encore) un outil bien documenté.

(* Ce n'est pas seulement de la chance, je me suis assuré que la langue était hors contexte.)

Je ne vais pas vous présenter les grammaires sans contexte, mais la syntaxe Nearley est assez simple et vous devez comprendre le sens sans aucun problème. La grammaire Nearley consiste en un ensemble de règles. Chaque règle a un caractère à gauche, une flèche et la partie droite de la règle, qui peut être une séquence de caractères et de non-caractères, ainsi que diverses options séparées par le "|" (ou):

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

Chacune des règles stipule que le côté gauche peut être remplacé par l'une des options du côté droit. Autrement dit, la première règle dit qu'une bordure est un élément, ou un élément, suivie d'une autre bordure. Qui lui-même peut être un élément, ou un élément suivi d'une bordure, etc. La deuxième règle dit qu'un élément ne peut être qu'une chaîne "L". Autrement dit, ensemble, ces règles correspondent à ces limites:

L
LLL

et ne correspondent pas à de telles limites:

X
L3L

Soit dit en passant, si vous souhaitez expérimenter cette grammaire (ou toute autre) dans Nearley, il existe un bac à sable en ligne ici . Vous pouvez entrer des cas de grammaire et de test pour voir ce qui correspond et ne correspond pas.

Voici une définition plus complète d'une primitive de ligne:

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

Nearley a plusieurs éléments intégrés communs, et le nombre est l'un d'entre eux. Par conséquent, je peux l'utiliser pour reconnaître la largeur numérique d'une primitive de ligne. Pour la reconnaissance des couleurs, j'utilise un autre élément intégré et autorise l'utilisation de n'importe quelle chaîne entre guillemets.

Ce serait bien d'ajouter des espaces entre différents personnages, alors faisons-le. Nearley prend en charge les classes de caractères et RBNF pour «zéro ou plus» de quelque chose avec «: *», donc je peux l'utiliser pour spécifier «zéro ou plusieurs espaces» et le coller n'importe où pour autoriser les espaces dans les descriptions:

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

Cependant, l'utilisation de WS partout rend la lecture de la grammaire difficile, donc je vais les abandonner, mais imaginez qu'ils le sont.

Un élément peut également être un espace vertical:

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

Cela correspond à ces limites

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

Viennent ensuite les primitives de bande, losange et ellipse.

@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 ")"

Il correspondra à ces éléments

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

(Notez que les primitives géométriques ne sont pas des «éléments» car elles ne peuvent pas être seules au niveau supérieur. Elles doivent être entourées d'un motif.)

J'ai aussi besoin d'une primitive d'espace horizontal:

@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 ")"

Maintenant, je vais ajouter une opération de répétition de motif. Il s'agit d'une séquence d'un ou plusieurs éléments entre crochets. J'utiliserai l'opérateur RBNF ": +", qui signifie ici "un ou plusieurs".

@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):+ "]"

Notez que le motif ne peut être rempli qu'avec des primitives géométriques. Nous ne pouvons pas, par exemple, placer une ligne à l'intérieur d'un motif. L'élément pattern va maintenant correspondre à quelque chose comme ça.

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

La dernière partie du langage est l'opérateur de superposition. Il s'agit d'un nombre quelconque d'éléments entre accolades.

@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 ):+ "}"

ce qui nous permet de faire ce qui suit:

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

(Notez que contrairement à l'opérateur de répétition, l'opérateur de superposition peut être utilisé en interne.)

Après avoir nettoyé la description et ajouté des espaces aux endroits nécessaires, nous obtenons la grammaire MBDL suivante:

@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 ")"

Ainsi, MBDL est maintenant défini et nous avons créé une grammaire du langage. Il peut être utilisé avec Nearley pour reconnaître les chaînes de langue. Avant de plonger dans MBDL / Nearley, je voudrais implémenter les primitives utilisées dans MBDL afin que la frontière décrite sur MBDL puisse être affichée. C'est ce que nous ferons dans la partie suivante.

3e partie


Nous allons maintenant commencer à implémenter les primitives de rendu elles-mêmes. (À ce stade, je n'ai pas encore besoin de lier l'analyseur aux primitives de rendu. Pour les tests, je vais simplement les appeler manuellement.)

Commençons par la ligne primitive. Rappelez-vous à quoi ça ressemble:

L(width, color)

En plus de la largeur et de la couleur, il existe un paramètre implicite ici - la distance par rapport au bord extérieur de la carte. (Je dessine les bordures du bord de la carte vers l'extérieur. Notez que nous sommes partis d'une autre!) Il ne doit pas pointer vers le MBDL, car cela peut être suivi par l'interpréteur qui exécute le MBDL pour dessiner la frontière. Cependant, cela doit être entré pour toutes les primitives de rendu afin qu'elles sachent où les dessiner. J'appellerai ce paramètre offset.

Si je n'avais besoin que de tracer une bordure en haut de la carte, la primitive de ligne serait très simple à mettre en œuvre. Cependant, en fait, je devrai tirer d'en haut. en bas, à gauche et à droite. (Peut-être qu'un jour je réaliserai des bordures obliques ou courbes, mais pour l'instant nous respecterons les bordures rectangulaires standard.) De plus, la longueur et l'emplacement de l'élément de ligne dépendent de la taille de la carte (ainsi que du décalage). Par conséquent, en tant que paramètres, j'ai besoin de toutes ces données.

Après avoir défini tous ces paramètres, il suffit de créer simplement une primitive de ligne et de l'utiliser pour tracer une ligne autour de la carte:


(Notez que j'utilise diverses fonctions de Dragons Abound pour tracer la ligne «manuscrite».) Essayons de créer une bordure plus complexe:

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

Cela ressemble à ceci:


Assez bien. Notez qu'il existe des endroits où les lignes noires et la ligne dorée ne sont pas tout à fait alignées en raison des fluctuations. Si je veux me débarrasser de ces taches, vous pouvez simplement réduire la quantité d'oscillation.

L'implémentation d'une primitive d'espace vertical est assez simple; il effectue juste un incrément de décalage. Ajoutons un peu d'espace:

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


Lorsque vous dessinez des lignes, des angles peuvent être obtenus en dessinant entre le décalage et le dessin le long de la carte dans le sens horaire. Mais en général, je dois implémenter la troncature de chaque côté de la bordure de la carte pour créer une connexion angulaire avec un biseau . Cela sera nécessaire pour créer des bordures avec des motifs correctement joints aux coins et, dans le cas général, éliminera la nécessité de dessiner des éléments avec des bords à un angle qui serait autrement requis. (*)

(Remarque: comme cela sera dit dans les parties suivantes, au fil du temps, j'ai refusé d'utiliser des régions de troncature lors de la mise en œuvre des angles. La principale raison est que pour créer des angles complexes, par exemple, des décalages carrés:


des zones de troncature de plus en plus complexes sont nécessaires. De plus, au fil du temps, j'ai trouvé une meilleure façon de travailler avec des motifs dans les coins. Au lieu de retourner et de réécrire cette partie de l'article, j'ai décidé de la laisser pour illustrer le processus de «créativité».)

L'idée principale est de tronquer chaque bordure en diagonale et de créer quatre zones tronquées dans lesquelles chaque côté de la bordure sera tracé:


Lors de la troncature, tout ce qui est dessiné dans la zone correspondante sera coupé à l'angle souhaité.


Malheureusement, cela crée de petits espaces le long des lignes diagonales, probablement parce que le navigateur effectue imparfaitement le lissage le long du bord tronqué. Le test a montré qu'un arrière-plan brille à travers l'espace entre les deux bords. Il était possible de résoudre ce problème en développant un peu un des masques (la moitié du pixel semble être suffisant), mais cela ne résout pas toujours le problème.

Ensuite, vous devez implémenter des formes géométriques. Contrairement aux lignes, elles sont répétées dans le motif, remplissant le côté de la bordure de la carte:


Une personne dessine ce motif de gauche à droite, dessine un rectangle, un losange, puis répète la même chose jusqu'à ce que toute la bordure soit remplie. Par conséquent, cela peut également être implémenté dans le programme en dessinant un motif le long de la bordure. Cependant, il sera plus facile de dessiner d'abord tous les rectangles, puis tous les losanges. Il suffira de dessiner le long de la bordure la même figure géométrique à intervalles. Et il est très pratique que chaque élément ait le même intervalle. Bien sûr, une personne ne le ferait pas, car il est trop difficile de ranger les éléments aux bons endroits, mais ce n'est pas un problème pour le programme.

En d'autres termes, la procédure de dessin de formes géométriques simples nécessite des paramètres dans lesquels toutes les tailles et couleurs de la figure sont transmises (c'est-à-dire largeur, longueur, épaisseur de ligne, couleur de ligne et remplissage), ainsi que la position de départ (qui, pour des raisons qui deviendront claires bientôt, Je considérerai le centre de la figure), l'intervalle d'espace horizontal pour la transition entre les répétitions et le nombre de répétitions. Il sera également pratique d'indiquer la direction de répétition sous la forme du vecteur [dx, dy], afin que nous puissions effectuer des répétitions de gauche à droite, de droite à gauche, de haut en bas, en changeant simplement le vecteur et le point de départ. Assemblez le tout et obtenez une bande de formes répétitives:


En utilisant ce code plusieurs fois et en effectuant le rendu avec le même décalage, je peux combiner les bandes noires et blanches pour créer l'échelle de la carte:


Avant de commencer à comprendre comment appliquer tout cela à la bordure réelle de la carte, implémentons d'abord la même fonctionnalité pour les ellipses et les losanges.

Les losanges ne sont que des rectangles avec des sommets pivotés, vous n'avez donc qu'à apporter une petite modification au code. Il s'est avéré que je n'ai toujours pas de code prêt à l'emploi pour le rendu de l'ellipse, mais il est très facile de prendre la vue paramétrique de l'ellipse et de créer une fonction me donnant les points de l'ellipse:


Voici un exemple (créé manuellement) qui utilise les fonctionnalités implémentées ci-dessus:


Pour une si petite quantité de code, ça a l'air plutôt bien!

Résolvons maintenant le cas complexe des bordures avec des éléments répétitifs: les coins.

S'il existe une bordure avec des éléments répétitifs, il existe plusieurs façons de résoudre le problème des coins. La première consiste à ajuster les répétitions afin qu'elles soient exécutées dans les coins sans mariage notable:


Une autre option consiste à arrêter la répétition quelque part près du coin des deux côtés. Cela est souvent fait si le motif ne peut pas être facilement «tourné» dans le coin:


La dernière option consiste à fermer le motif avec une décoration d'angle:


Un jour, j'arriverai aux décorations d'angle, mais pour l'instant, nous utiliserons la première option. Comment faire tourner un motif de rayures ou de cercles dans les coins de la carte sans espaces?

L'idée principale est de placer l'élément de motif exactement dans le coin de sorte que la moitié de celui-ci se trouve sur un bord de la carte et l'autre sur celui adjacent. Dans cet exemple, le cercle est exactement dans le coin et peut être tracé dans n'importe quelle direction:


Dans d'autres cas, l'élément est à moitié dessiné dans une direction et à moitié dans l'autre, mais les bords coïncident:


Dans ce cas, une bande blanche est dessinée des deux côtés, mais est connectée dans le coin sans espaces.

Il y a deux aspects à considérer lors du placement d'un élément dans un coin.

Tout d'abord, l'élément d'angle sera divisé et mis en miroir par rapport à la diagonale passant par le centre de l'élément. Les éléments à symétrie radiale, par exemple les carrés, les cercles et les étoiles, ne changeront pas de forme. Les éléments sans symétrie radiale, par exemple les rectangles et les losanges, changeront de forme lors de la symétrie par rapport à la diagonale.

Deuxièmement, pour que les éléments d'angle des deux côtés se connectent correctement, il doit y avoir un nombre entier d'éléments (*) le long des deux côtés de la carte. Ils ne doivent pas nécessairement être le même nombre, mais il doit y avoir un nombre entier d'éléments des deux côtés. Si un nombre fractionnaire de motifs est contenu sur un côté, alors d'un côté le motif ne coïncidera pas avec le côté adjacent.

(* Dans certains cas, par exemple, avec de longues rayures, une répétition partielle peut se produire avec une répétition complète et les éléments seront toujours alignés. Cependant, l'élément d'angle résultant sera asymétrique et différera en longueur du même élément sur les côtés de la carte. Un exemple de cela peut être vu ici:


Une barre d'échelle blanche apparaît avec différentes répétitions partielles et, par conséquent, un élément décalé par rapport au centre est obtenu. Pour l'échelle de la carte, ce n'est pas toujours le cas, car elle montre la distance absolue et n'a pas besoin d'être symétrique. Mais pour un motif décoratif, cela semble généralement mauvais.)

Voici un exemple montrant comment un nombre entier de répétitions est coupé exactement dans le coin:


Si vous faites la même chose des quatre côtés, les coins coïncideront et le motif sera parfaitement situé sur toute la longueur de la bordure:


Après un examen attentif, vous remarquerez que le motif ne se produit pas exactement dans les coins. La moitié du cercle dans chaque coin est prise de chaque côté, et ces deux moitiés sont dessinées indépendamment à la main, elles ne sont donc pas parfaites. Mais maintenant, ils sont assez proches de cela.

Ainsi, nous pouvons réaliser une connexion parfaite du motif dans les coins, en choisissant un nombre entier de répétitions pour chaque bord. Cependant, la solution à ce problème n'est pas anodine.

Supposons d'abord que nous savons que le côté mesure 866 pixels et que nous voulons répéter l'élément 43 fois. Ensuite, l'élément doit être répété tous les 20,14 pixels. Comment définir la longueur spécifique d'un élément (et dans le cas général, un modèle d'éléments)? Dans l'exemple ci-dessus, j'ai ajouté un espace supplémentaire entre les cercles. Mais si les cercles se touchaient initialement, cela changera le schéma. Peut-être vaut-il la peine d'étirer les cercles pour qu'ils continuent à se toucher?


Maintenant, les éléments se touchent, mais les cercles se sont transformés en ellipses et les coins ont une forme étrange. (Rappelez-vous, j'ai dit que les éléments sans symétrie radiale changent de forme lorsqu'ils sont réfléchis par rapport à un angle? Pour les rayures, ce ne sera pas un gros problème.) Ou, peut-être, cela vaut la peine de compresser tous les éléments afin qu'ils se touchent et s'adaptent dans une longueur appropriée:


Mais pour réaliser cela, nous devons rendre les éléments beaucoup plus petits qu'ils ne l'étaient à l'origine. Aucune de ces options ne semble parfaite.

Le deuxième problème se produit lorsque les côtés de la carte sont de tailles différentes. Maintenant, nous devons résoudre le problème de trouver un nombre entier de répétitions adapté aux deux côtés. Il serait idéal de trouver une solution adaptée aux deux côtés. Mais je ne veux pas faire cela au prix d'un changement de modèle trop important. Il peut être préférable de créer des motifs légèrement différents des deux côtés s'ils sont tous les deux suffisamment proches du motif d'origine.

Et enfin, le troisième problème se pose lorsque j'utilise la fonction de superposition de plusieurs éléments les uns sur les autres:


Je ne veux pas apporter de modifications au modèle qui détruirait la relation entre les éléments. Je pense qu'avec une mise à l'échelle correcte, les ratios dans leur ensemble resteront, mais je dois tester cela.

Tâche intéressante, non? Jusqu'à présent, je n'ai pas de solutions de haute qualité pour elle. Peut-être qu'ils apparaîtront plus tard!

Partie 4


Nous avons donc implémenté des primitives pour dessiner des lignes et des formes géométriques. J'ai commencé à travailler sur l'utilisation de formes répétitives pour remplir les bordures et j'ai parlé des difficultés à placer des motifs arbitraires sur la bordure de la carte afin qu'ils s'adaptent parfaitement dans les coins. Le problème principal est que dans le cas général, vous devez allonger (ou raccourcir) le motif pour qu'il s'adapte latéralement. Les options pour changer la longueur du motif - ajouter ou supprimer des espaces, changer la longueur des éléments des motifs - entraînent divers changements dans le motif lui-même. Il semble que la tâche de sélectionner un motif à partir de plusieurs éléments soit très difficile!

Lorsque je rencontre des tâches apparemment sans compromis, j'aime commencer par implémenter une version simple. Les tâches infructueuses peuvent souvent être résolues en résolvant à plusieurs reprises des problèmes «simples» jusqu'à ce que le résultat soit suffisamment bon. Et parfois, la mise en œuvre d'une version simple donne une certaine compréhension qui simplifie la solution d'un problème plus complexe. Si cela ne s'améliore pas et que le problème reste inconfortable, alors nous aurons au moins une version simplifiée qui peut toujours être utile, bien que pas tout à fait comme il se doit.

Le moyen le plus simple consiste à modifier la longueur du motif en ajoutant des longueurs sans rien changer au motif. Essentiellement, cela ajoute un espace vide à la fin du motif. (Remarque: il est préférable de répartir l'espace vide entre tous les éléments du motif.) Il convient de considérer qu'une telle solution ne peut que rallonger le motif. Nous pouvons toujours ajouter de l'espace vide au motif, mais ne pouvons pas le prendre si nécessaire - peut-être qu'il n'y aura plus d'espace vide dans le motif!

Avec cette approche, l'algorithme de localisation des motifs sur le côté de la carte sera très simple:

  • Divisez la longueur du côté de la carte par la longueur du motif et arrondissez-la pour déterminer le nombre de répétitions du motif qui correspondent à ce côté.
  • La distance entre les éléments dans ce cas sera égale à la longueur du côté divisée par le nombre de répétitions. (C'est le plus proche de l'emplacement d'origine, étant donné que nous ne pouvons ajouter que de l'espace.)
  • Tracez un motif sur le côté, en tenant compte de la distance calculée.

Il était difficile de mettre en œuvre ce système. Les virages tenaces ne voulaient pas coïncider. Il m'a fallu trop de temps pour réaliser que lorsque la carte n'est pas carrée, je ne peux pas dessiner des zones de troncature sur quatre côtés à partir du centre de la carte, car cela crée des angles de troncature qui ne sont pas égaux à 45 degrés. En fait, les zones de troncature devraient ressembler à l'arrière d'une enveloppe:


Quand j'ai compris cela, l'algorithme a commencé à fonctionner sans problème.

(Mais n'oubliez pas la note précédente qu'au fil du temps j'ai abandonné les zones de troncature!)

Voici un exemple avec un ratio d'environ 2: 1:

Sur cette échelle, c'est assez difficile à remarquer, mais les coins se connectent correctement et il n'y a qu'une légère différence visuelle entre les côtés. Dans ce cas, l'algorithme d'alignement des motifs n'a besoin que d'insérer des pixels fractionnaires, il est donc invisible à l'œil, notamment parce que les contours des cercles se chevauchent d'un pixel.

Voici un autre exemple avec des rayures:


Ceci est le haut de la bordure carrée. Voici la même bordure sur une carte plus rectangulaire:


Ici, vous pouvez voir que sur le côté de la carte il y a un écart visuellement plus grand entre les bandes. L'algorithme ne doit pas insérer plus d'espace que la longueur d'un élément complet; par conséquent, le pire des cas se produit lorsque nous avons des éléments longs et un côté court légèrement différent d'une taille appropriée. Mais dans la plupart des cas pratiques, l'alignement n'est pas très nocif.

Voici un exemple avec un motif de plusieurs éléments:


Ici, les rayures chevauchent les rayures:


Vous pouvez voir que puisque le même alignement est effectué pour chaque élément, les bandes restent centrées les unes par rapport aux autres.

J'ai suggéré qu'une bonne solution pour placer le motif sur le côté de la carte serait difficile, mais une approche très simple avec une distribution uniforme de l'élément de motif pour remplir l'espace souhaité fonctionne assez bien pour de nombreux motifs. C'est un rappel pour nous tous: il n'est pas nécessaire de supposer que la décision doit être compliquée; cela peut être plus facile que vous ne le pensez!

Cependant, cette solution ne fonctionne pas pour les motifs avec des éléments en contact, par exemple, pour l'échelle de la carte. Dans ce cas, l'ajout d'espace décale les éléments:


Une autre option pour allonger un motif, que j'ai mentionné ci-dessus, consiste à étirer les éléments individuels du motif. Il convient à quelque chose comme un motif d'échelle, mais il aura l'air mauvais dans un motif avec des éléments symétriques, car l'étirement les rendra asymétriques.

La mise en œuvre de l'option avec étirement s'est avérée plus difficile que prévu, principalement parce que j'ai dû étirer les éléments à différents bords de la carte de différentes tailles (car la carte n'est peut-être pas carrée mais rectangulaire), et également changer dynamiquement la disposition des éléments en fonction des nouveaux étirés. tailles. Mais après quelques heures, j'ai réussi à y parvenir:


J'ai maintenant toutes les fonctionnalités nécessaires pour dessiner la bordure de la carte (bien que les éléments de bordure eux-mêmes soient créés manuellement):


J'ai converti l'image en niveaux de gris, car je ne voulais pas me soucier de la sélection des couleurs, et la carte elle-même est plutôt ennuyeuse, mais comme preuve de concept, les bordures sont plutôt jolies.

Partie 5


Dans la partie 2, j'ai développé la grammaire MBDL (Map Border Description Language) et dans les parties 3 et 4, j'ai implémenté des procédures pour exécuter toutes les primitives de langage. Je vais maintenant travailler sur la connexion de ces parties afin de pouvoir décrire la bordure sur MBDL et la dessiner sur la carte.

Dans la partie 3, j'ai écrit la grammaire MBDL pour qu'elle fonctionne avec l' outil d'analyse Javascript Nearley . La grammaire terminée ressemble à ceci:

@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 " )"

Par défaut, lors de l'analyse réussie d'une règle à l'aide de Nearley, la règle renvoie un tableau contenant tous les éléments qui correspondent au côté droit de la règle. Par exemple, si la règle

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

assorti avec de la ficelle

A

alors Nearley reviendra

[ " A" ]

Un tableau avec une seule valeur est la chaîne «A» correspondant au côté droit de la règle.

Que retourne Nearley lorsqu'un élément est analysé à l'aide de cette règle?

number -> WS decimal WS

Il y a trois parties à droite de la règle, elle renverra donc un tableau avec trois valeurs. La première valeur sera celle qui renvoie la règle pour WS, la deuxième valeur sera celle qui renverra la règle pour la décimale et la troisième valeur sera celle qui renverra la règle pour WS. Si, en utilisant la règle ci-dessus, je pars "57", alors le résultat sera le suivant:

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

Le résultat final de l'analyse de Nearley sera un tableau imbriqué de tableaux, qui est un arbre de syntaxe . Dans certains cas, l'arbre de syntaxe est une représentation très utile; dans d'autres cas, pas tout à fait. Dans Dragons Abound , par exemple, un tel arbre n'est pas particulièrement utile.

Heureusement, les règles Nearley peuvent remplacer le comportement standard et retourner tout ce qu'elles veulent. En fait, la règle (intégrée) pour la décimale ne renvoie pas une liste de nombres, elle renvoie le nombre Javascript équivalent, ce qui dans la plupart des cas est beaucoup plus utile, c'est-à-dire que la valeur de retour de la règle numérique a la forme:

[
[ " " ],
57,
[ ]
]

Les règles Nearley redéfinissent le comportement standard en ajoutant un post-processeur à la règle, en prenant un tableau standard et en le remplaçant par ce dont vous avez besoin. Un postprocesseur est juste du code Javascript entre crochets spéciaux à la fin d'une règle. Par exemple, dans la règle des nombres , je ne suis jamais intéressé par les espaces possibles de chaque côté du nombre. Par conséquent, il serait pratique que la règle renvoie simplement un nombre et non un tableau de trois éléments. Voici un post-processeur qui effectue cette tâche:

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

Ce post-processeur prend le résultat standard (le tableau à trois éléments illustré ci-dessus) et le remplace par le deuxième élément du tableau, qui est le numéro Javascript de la règle décimale . Alors maintenant, la règle numérique renvoie le nombre réel.

Cette fonctionnalité peut être utilisée pour traiter une langue entrante dans une langue intermédiaire, ce qui est plus facile à utiliser. Par exemple, je peux utiliser la grammaire Nearley pour transformer une chaîne MBDL en un tableau de structures Javascript, chacune représentant une primitive identifiée par un champ «op». La règle pour la primitive de ligne ressemblera à ceci:

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

Autrement dit, le résultat de l'analyse de «L (13, noir)» sera la structure Javascript:

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

Après avoir ajouté le post-traitement approprié, le résultat renvoyé par la grammaire peut être une séquence (tableau) de structures d'opération pour la ligne entrante. Autrement dit, le résultat de l'analyse de la chaîne

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

sera

[
{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"}]}
]

qui est beaucoup plus facile à traiter pour créer une bordure de carte.

À ce stade, vous pouvez avoir une question - si la phase de post-traitement de la règle Nearley peut contenir du Javascript, alors pourquoi ne pas ignorer la vue intermédiaire et simplement dessiner la bordure de la carte à droite pendant le post-traitement? Pour de nombreuses tâches, cette approche serait idéale. J'ai décidé de ne pas l'utiliser pour plusieurs raisons.

Premièrement, dans MBDL, il y a quelques (*) composants qui ne peuvent pas être exécutés pendant le processus d'analyse. Par exemple, nous ne pouvons pas dessiner des éléments géométriques répétitifs (bande ou losange) pendant le processus d'analyse, car nous devons connaître les informations d'autres éléments dans le même modèle. En particulier, nous devons connaître la longueur totale du motif afin de comprendre jusqu'où nous devons organiser les répétitions de chaque élément individuel. Autrement dit, l'élément du motif doit toujours créer une représentation intermédiaire de tous les éléments géométriques.

(* Il y a d'autres composants avec des limitations similaires dont je n'ai pas encore parlé.)

Deuxièmement, Javascript dans Nearley est intégré dans les règles, nous ne pourrons donc pas transmettre d'informations supplémentaires à Javascript, à l'exception des variables globales. Par exemple, pour dessiner la frontière, j'ai besoin de connaître la taille de la carte, les quatre zones de troncature utilisées, etc. Bien que je puisse ajouter du code qui rend ces informations disponibles pour les post-processeurs Nearley, ce sera un peu compliqué et il pourrait être difficile de maintenir un tel code.

Pour ces raisons, j'analyse une représentation intermédiaire, qui est ensuite exécutée pour créer la bordure de la carte elle-même.

La prochaine étape consistera à développer un interpréteur qui reçoit une représentation intermédiaire de MBDL et l'exécute pour générer des limites de carte. Ce n'est pas très difficile à faire. Fondamentalement, le travail consiste à définir les conditions initiales (par exemple, générer des régions de troncature pour les quatre côtés de la carte) et itérer sur la séquence de structures de la représentation intermédiaire à chaque exécution.

Il y a quelques moments glissants.

Tout d'abord, je dois passer du rendu de l'intérieur au dessin de l'intérieur vers l'extérieur. La raison en est que je veux que la plupart des bordures ne chevauchent pas la carte, j'ai donc besoin de dessiner les bordures de sorte que les lignes du bord intérieur coïncident avec les bords de la carte. Si je dessine de l'extérieur vers l'intérieur, j'ai besoin de connaître la largeur de la bordure avant de commencer à dessiner afin que la bordure ne chevauche pas la carte. Si je dessine de l'intérieur vers l'extérieur, je commence simplement par le bord de la carte et je dessine. Il vous permet également d'imposer éventuellement une bordure sur la carte; il suffit de commencer la bordure avec un espace vertical négatif (VS).

Un autre point difficile est la répétition des schémas. Pour dessiner des motifs répétitifs, je dois regarder tous les éléments du motif et déterminer le plus large, car cela définira la largeur de l'ensemble du motif. Je dois également regarder et suivre la longueur du motif afin de savoir quelle distance il me reste avant chaque répétition.

Voici un exemple de bordure assez complexe que j'ai utilisé pour tester l'interpréteur:


Je pense qu'il était possible (nécessaire?) De le joindre pour le tester à l'analyseur, mais pour cette bordure, je viens de créer une vue intermédiaire manuellement:

[
{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},
]},
]

J'ai créé cette vue par essais et erreurs. Quoi qu'il en soit, l'interprète fonctionne!

Comme dernière étape, permettez-moi d'utiliser l'analyseur pour créer une vue intermédiaire à partir de la version MBDL. Il n'y a rien de spécial à me montrer ici: j'ai dû corriger plusieurs noms de champs, mais sinon le code fonctionnait bien. Pour la frontière, j'ai utilisé une version légèrement différente de MBDL:

[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")

Elle dessine la même bordure, mais d'une manière légèrement différente. J'ai également changé la syntaxe de la superposition, en remplaçant les parenthèses par des accolades pour qu'elle soit plus différente de l'autre syntaxe.

Pour montrer pourquoi je voulais dessiner de l'intérieur vers l'extérieur, et pas seulement placer automatiquement la bordure à l'extérieur de la carte, je peux ajouter un espace vertical négatif au début de cette bordure pour déplacer l'échelle de la carte à l'intérieur du bord de la carte:


J'ai maintenant la plupart des infrastructures nécessaires à la génération procédurale des bordures de carte: un langage de description des limites, un analyseur de langage et des procédures pour effectuer une représentation intermédiaire. Il ne reste plus qu'à gérer la partie difficile - la génération procédurale!

Partie 6


Maintenant que l'intégralité du MBDL a été implémentée, j'avais l'intention de procéder à la génération procédurale des bordures de carte, mais je ne sais pas encore comment je veux le faire, car je vais m'attarder un peu et implémenter quelques autres fonctionnalités de MBDL.

Dans la première discussion sur le traitement des coins avec des motifs, j'ai parlé de deux approches différentes. À la fin, j'ai réalisé les coins biseautés, mais il y avait une deuxième option: arrêter le motif près du coin, comme dans ces exemples:



Une telle solution est souvent utilisée lorsque le motif de bordure est une sorte de figure asymétrique, de runes ou autre chose qui ne peut pas être tourné de 90 degrés, tout en maintenant l'alignement. Mais il est évident que cela fonctionnera avec des formes géométriques.

C'est peut-être l'option que vous choisissez avant de générer la bordure, mais vous pouvez ajouter un peu de flexibilité si vous l'activez à partir d'une partie de la bordure et utilisez le coin biseauté de l'autre. Pour ce faire, je dois ajouter une nouvelle commande à MBDL. Je soupçonne que d'autres options peuvent survenir pour différentes parties de la frontière, donc j'ajouterai une commande d'options générales:

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

(Ici encore, pour plus de clarté, nous omettons les espaces et certains autres détails.) Jusqu'à présent, les seules options sont «MITRE» pour les coins biseautés et «ARRÊTÉ» pour l'arrêt près des coins. Si aucune valeur n'est transmise STOPPED, le programme arrête le motif à une distance raisonnable du coin. Si la valeur est transmise, le motif s'arrête à cette distance du coin.

Si des coins ARRÊTÉS sont utilisés, alors j'arrête de dessiner le motif de coin loin des coins. Voici à quoi ça ressemble:


Ici, j'ai utilisé l'option MITRE pour le modèle d'échelle noir et blanc, de sorte qu'il reflète en fonction de l'angle. Pour un motif de cercles rouges et de carrés noirs à l'intérieur d'une ligne dorée (et pour un motif de cercles à l'extérieur de la bordure), j'ai utilisé STOPPED. Vous pouvez voir que ces deux motifs se terminent près du coin.

Cependant, il y a quelques problèmes. Tout d'abord, nous voyons qu'à gauche, l'élément le plus proche du coin est un carré noir et en haut un cercle rouge. Cela s'est produit parce que le coin est près du début de la répétition d'un côté et près de la fin de la répétition de l'autre. Mais ça a l'air bizarre. Il serait préférable que les coins soient symétriques, même si pour cela nous devions ajouter un autre élément à la fin du motif. Deuxièmement, vous pouvez voir que le motif à l'extérieur de la bordure (demi-cercles et points noirs) se termine également par une répétition dans le coin. Mais comme la longueur de cette répétition est bien inférieure à la longueur des cercles rouges / carrés noirs, ils se retrouvent à des endroits différents. Il serait probablement préférable que tous les motifs s'arrêtent à la même distance du coin.

Pour résoudre le premier problème, vous devez ajouter une autre répétition du premier élément du motif à la fin de chaque côté de la bordure. Mais en fait, c'est un peu plus compliqué, car je pourrais utiliser un décalage horizontal négatif à l'intérieur du motif pour chevaucher plusieurs éléments (comme ici). Vous devez également ajouter une autre répétition à tout élément du motif qui a le même point de départ que le premier élément.


Maintenant, le motif est symétrique par rapport à l'angle et semble beaucoup mieux.

Ensuite, je dois suivre le motif ARRÊT le plus long et arrêter chaque motif ARRÊT à cette distance:


Maintenant, le motif des cercles blancs est plus mis de côté, mais il n'est toujours pas aligné avec le motif des cercles rouges. Pourquoi?Cela est dû au fait que le motif de cercle blanc est plus éloigné du bord de la carte et que la bordure est plus longue que celle où le motif de cercle rouge est tracé. Pour résoudre ce problème, vous devez déplacer les motifs de manière uniforme et en tenant compte de leur décalage par rapport au bord de la carte.


Maintenant, tout est magnifiquement aligné.

La deuxième option pour les angles est les décalages carrés dans les coins, par exemple ceux-ci:


Ce sera beaucoup plus difficile à mettre en œuvre!

Cependant, la grammaire de cette option est simple et utilise l'opcode Option:

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

Le nombre indique la taille du déplacement carré pour l'élément sur le bord de la carte; Les éléments avec différents décalages doivent être alignés en conséquence. S'il n'y a pas de nombre, le programme sélectionne la taille de décalage appropriée. La mise à zéro du nombre désactive le décalage carré. Cela vous permet de créer des bordures dans lesquelles certains éléments utilisent des décalages carrés, tandis que d'autres ne le font pas, comme dans cette bordure:


La première chose que j'ai réalisée est que j'aurais besoin de zones de troncature supplémentaires car j'utilise la troncature pour traiter les endroits où la bordure change de direction. SQOFFSET nécessitera des zones de troncature plus complexes; Vous aurez également besoin de zones distinctes pour différents éléments lorsque vous activez et désactivez SQOFFSET. Étant donné que les zones de troncature ajoutent de toute façon des artefacts indésirables, cela semble être trop de travail.

Lorsque j'ai travaillé sur les modèles pouvant être arrêtés ci-dessus, j'ai implémenté le remplissage d'un modèle asymétrique pour ajouter une autre répétition à une extrémité du modèle. J'ai également réalisé que cela éliminerait le besoin de coins biseautés. Je vais simplement dessiner tous les motifs le long de la bordure dans le sens horaire, en commençant le motif dans un coin et en terminant près du coin suivant. Cela me permettra de me débarrasser des zones de troncature.

Le plus important dans cette nouvelle façon de travailler avec les coins est que le premier élément du motif n'est plus «divisé» en deux côtés. Si vous regardez les motifs d'échelle en noir et blanc sur les cartes ci-dessus, vous pouvez voir qu'il y a un rectangle blanc passant dans le coin. Maintenant, le rectangle blanc jouxte le coin:


Les cartes sont dessinées dans les deux sens, mais ce n'est pas un très gros problème.

Pour commencer, j'ai implémenté des décalages pour les lignes. Pour ce faire, il suffisait de tourner la ligne par rapport aux angles correspondants:


Comme vous pouvez le comprendre, je peux combiner des angles avec des décalages et des angles réguliers, comme dans la carte ci-dessus:


Bien sûr, il est plus difficile de tourner les motifs au coin de la rue. L'idée générale est de dessiner d'un coin à presque l'autre, et ainsi de suite le long de la frontière, jusqu'à revenir au début. Il suffit théoriquement de dessiner uniquement des motifs horizontaux et verticaux, et tout doit être magnifiquement aligné; en fait, suivre tout cela est assez morne. En fait, j'ai dû réécrire complètement le code deux fois et écrire un tas de papier, mais je n'en parlerai pas en détail. Montrez simplement le résultat:


Une illusion d'optique gênante apparaît dans les coins - l'élément d'angle semble non centré plus près de l'extérieur du coin. En fait, ce n'est pas vrai, mais il semble que oui, car plus près de l'intérieur du coin, il y a visuellement plus d'espace vide.

Étant donné que les segments des angles de décalage sont assez courts, il est très facile de créer un motif de non-équilibre dans le coin:


Parfois, ça a l'air assez moche. Cela m'a rappelé une vieille blague:

Patient: "Docteur, quand je fais ça, ça me fait mal."
Docteur: "Alors ne fais pas ça!"

Je vais donc essayer de ne pas le faire.

Habituellement, je ne dessinerai pas l'échelle de la carte le long de l'angle de décalage, mais si j'en ai besoin, je devrai utiliser l'option qui étire le motif afin que l'échelle de la carte s'insère dans le coin sans espaces entre les rectangles:


Vous pouvez voir qu'en conséquence, la taille des rectangles d'échelle varie considérablement. Autrement dit, ce n'est pas une très bonne option. (Soit dit en passant, les angles de décalage ont également un bug dans le motif des cercles. Plus tard, je l'ai corrigé, mais comme je l'ai dit, il est très difficile de le faire.)

Si le motif est trop grand pour tenir sur le segment de l'angle de décalage, l'algorithme se rend simplement:


Ce qui est loin d'être idéal, mais, comme je l'ai dit plus haut, "alors ne le faites pas". (Il n'est en fait pas très difficile d'ajouter une fonction de compression ou d'étirement si j'en ai besoin.)

Que se passe-t-il si j'utilise les deux coins décalés et l'option qui arrête les motifs devant les coins? Dans ce cas, je m'arrête juste non loin des coins décalés:


Il me semble que c'est une décision logique.

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


All Articles