Metapost: datový typ pair a začátek typu path

12. 3. 2007
Doba čtení: 10 minut

Sdílet

Dnes se podíváme na zoubek datovým typům pair a path. Pair je první datový typ, který je spojen s nějakým konkrétním grafickým prvkem, a tím je bod. Cesta (path) definuje čáru - úsečku, lomenou čáru i různé typy křivek, definovatelných matematickými funkcemi i vytvářenými víceméně empiricky, otevřených i uzavřených.

Datový typ pair

Pair je první datový typ, který je spojen s nějakým konkrétním grafickým prvkem, a tím je bod. Skládá se ze dvou složek, datových typů numeric, v nichž je uložena souřadnice x a y.

Uvedená čísla jsou uzavřena do závorky, takže přidělení hodnoty vypadá následovně: a:=(1.11,2.75); Místo čísel mohou být v definici proměnné typu numeric i matematické vzorce odkazující na další proměnné apod. V minulém díle jsem zmínil měřítko, i to může být vloženo do definice proměnné typu pair:a:=(1.11u,2­.75u);.

Uzavřením do závorek a oddělením čárkou tedy převedeme dvě proměnné typu numeric do datového typu pair. Jeho jednotlivé složky můžeme získat zpět pomocí funkcí xpart a ypart. První nabude hodnotu složky proměnné pair před čárkou, druhá za čárkou. Použijeme-li výše definovanou proměnnou, pak příkazy:

b:=xpart(a);c:=y­part(a);

nabude proměnná b hodnotu 1.11u a proměnná c hodnotu 2.75u (metapost tyto hodnoty vyjadřuje jako jedno číslo, pokud je hodnota u známa).

Pole proměnných z

Pro pohodlnou manipulaci s body je v metapostu makry ošetřeno polez. Makro, které to zajišťuje, probereme u maker. Jestliže použijeme proměnnou z, z(libovolný sled číslic do kapacity proměnné numeric), z.(libovolný sled písmen anglické abecedy, za nímž mohou následovat číslice do kapacity proměnné numeric), potom můžeme přímo (bez volání výše uvedených funkcí) používat numerické proměnné, v jejichž názvu je počáteční z nahrazeno x nebo y. Určitým omezením je, že při prvním použití takovéto proměnné typu pair nemůžeme použít operátor „:=“, ale musíme použít prosté rovnítko. Respektive můžeme si po příslušném chybovém hlášení vynutit pokračování práce metapostu a obrázek bude vykreslen korektně. Vzhledem k tomu, že chybová hlášení (dostaneme se k nim) jsou dosti málo srozumitelná a přehledná, měli bychom se vyvarovat i takovýchto nefatálních chyb.

Při větším počtu proměnných tohoto typu mohou na sebe jejich složky odkazovat, zhruba takto:

z5=(x2,y4);

Tohoto se dá s výhodou použít, když potřebujeme mít některé body svisle či vodorovně zarovnané.

Základní manipulace s proměnnou typu pair

Proměnná pair může být chápána i jako dvourozměrný vektor spojující jí definovaný bod s počátkem souřadnic (bod(0,0)). Proto je možné proměnné tohoto typu sčítat a odečítat (ve výsledku je součet/rozdíl složek x prvního a druhého vektoru a součet/rozdíl složek y prvního a druhého vektoru). Této proměnné můžeme přiřadit záporné znaménko (totéž jako vynásobení obou složek –1). Je možné je násobit a dělit proměnnou typu numeric (zvlášť se s ní násobí/dělí složka x a složka y); není možné umocňování.

Definování bodu (jinak než zadáním hodnot proměnné pair)

Bod (a tím i jeho souřadnice) lze definovat i polohou na úsečce, spojující dva body. Syntaxe je následující:

z3=.7[z1,z2];

Místo desetinného čísla může, pochopitelně, stát proměnná typu numeric, nebo např. zlomek, u složitějšího výrazu může metapost vyžadovat závorky. Číslo před závorkami může být i větší než 1, v tom případě leží nový bod v příslušné vzdálenosti za koncem úsečky v bodě z2. Uvedené číslo může být i záporné. Pozor, v takovém případě nebude zpravidla nový bod před bodem z1, ale bude mít souřadnice o stejné absolutní hodnotě jako v případě kladného argumentu, násobené ovšem –1.

Další možností definování bodu je průsečík dvou přímek (úseček). Pro definování průsečíku úseček z1–z2 a z3–z4 použijeme následující konstrukci:

z5=whatever[z1,z2]=w­hatever[z3,z4];

z5 bude definován i v případě, že se přímky vymezené uvedenými úsečkami protínají za jejich vymezením.

V případě křivek (a a b) se používá konstrukce:

z=a intersectionpoint b;

Problém může nastat:

  • Cesty se neprotínají. V tom případě skončí běh programu chybovým hlášením. Tomu můžeme předejít testováním pomocí funkce intersectiontimes: z=a intersectiontimes b; která pro neprotínající se křivky vrací pair o „nemožné“ hodnotě (-1,–1). (Podrobněji bude probráno u programování metapostových skriptů.)
  • Cesty se protínají více než jedenkrát. Uvedený problém je v originální dokumentaci řešen odkazem na Knuthův metafontbook. U křivek si ukážeme, jak uvedený problém alespoň částečně řešit i bez toho.

Dále může být bod definován časem na cestě. Syntaxe je:

z=point t of a;

z je proměnná typu pair (souřadnice bodu), a je proměnná typu path a t je čas na cestě (tento pojem bude vysvětlen v příštím díle).

Vykreslení bodu

K vykreslení bodu lze použít dva (resp. tři) příkazy:

  • drawdot z;
  • draw z; vykreslí v místě bodu dotyk aktuálního pera. Je-li pero definováno pomocí měřítka (u), bude velikost otisku pera na něm závislá. Příkazy draw i drawdot umožňují definovat barvu bodu.

    příkaz undrav s identickou syntaxí v místě bodu vymaže případně existující kresbu a nahradí ji barvou pozadí.

  • Pokud je bod definován jako „speciální proměnná“ začínající z, je možné bod vykreslit příkazem dotlabels();, kdy v závorce je seznam bodů (jejich názvů bez písmene z, případně bez „z.“). Tento příkaz vykreslí v místě bodu kroužek a u něj vytiskne jako popis text příslušné položky ze seznamu. Velikost kroužku, popisu i jejich barva nejsou ovlivnitelné běžnými příkazy (zcela určitě by se dalo provést předefinováním příslušných maker plain metapostu, ale to už je „vyšší dívčí“, nikoli pro práci běžného uživatele). Zato lze příkazy bot, lft, rt a top ovivnit polohu popisu vzhledem k vyznačení bodu (pod, vlevo, vpravo, nad).

Funkce a konstanty typu pair

Typ pair má jednu pojmenovanou konstantu, a tou je origin, bod, ležící na souřadnicích (0,0).

  • Výše bylo zmíněno sčítání a odčítání proměnných typu pair a jejich násobení proměnnou typu numeric (protože můžeme násobit i číslem menším než 1, můžeme je proměnnou numeric i dělit operátorem /)
  • Dvě proměnné typu pair můžeme porovnávat booleovskými operátory (viz předchozí díl)
  • Operátory lft, rt, top, bot ovlivní umístění popisu vzhledem k bodu při použití funkce dotlabels
  • Operátor dotprod vynásobí první a druhé složky dvou proměnných pair a násobky sečte do proměnné numeric, funkce xpart vrátí hodnotu první složky proměnné pair jako proměnnou typu numeric, obdobně funkce
  • round zaokrouhlí obě složky proměnné typu pair
  • scaled vynásobí hodnotu obou složek argumentem (bude podrobněji probráno u transformací); transformací se týkají i operátory rotated, shifted, slanted, transformed, xscaled, yscaled a zscaled
  • proměnná pair je výsledkem funkcí llcorner, lrcorner, ulcorner a urcorner, které vracejí pozice rohů rovnoběžníku opsaného proměnným typu path, picture nebo pen

Cesta

Cesta (datový typ path) definuje čáru – úsečku, lomenou čáru i různé typy křivek, definovatelných matematickými funkcemi i vytvářenými více-méně empiricky, otevřených i uzavřených.

Metapost má dva typy operátoru provádějícího proložení cesty body: „–“ vytváří úsečku mezi dvěma body. V případě řady bodů vytvoří tento operátor vložený mezi nimi klikatou čáru, respektive řadu úseček. „..“ prokládá mezi řadou bodů Beziérovy křivky, které mohou být ovlivněny dalšími parametry (protože tato problematika je velice rozsáhlá, odložil jsem ji do následujících dílů). Vložení dalšího příkazu cycle za poslední bod značí, že cesta je uzavřená a tento poslední bod je propojen s bodem prvním. Tato konstrukce je velice důležitá, protože i v případě, že první a poslední bod cesty budou mít identické souřadnice, nebo dokonce půjde o jeden a tentýž bod, bude metapost bez cycle považovat křivku za neuzavřenou.

Křivky je možné, stejně jako body, vykreslovat příkazem draw, respektive undraw, jejichž parametrem v tomto případě není bod, ale cesta. Stejně jako u bodu je chování tohoto příkazu závislé na nastavení barvy, tvaru a velikosti aktuálního pera. Na uzavřené křivky je možné aplikovat příkaz fill, resp. unfill. První vyplní plochu vymezenou křivkou aktuální barvou, druhý tuto plochu přemaže barvou pozadí (takže má smysl jen tam, kde už nějaká kresba je). Pokud křivkou vymezenou plochu vyplňujeme jednou barvou a samotnou křivku vykreslujeme barvou jinou (např. černě konturovaný modrý čtverec), je nanejvýš doporučeníhodné nejprve použít příkaz fill a poté draw, protože v opačném pořadí dojde k odmazání vnitřní poloviny kontury. Zejména u tenkých čar to může vést k nežádoucím efektům jako je periodické ztenčování kontury v závislosti na její orientaci a kvalitě zobrazovacího zařízení. Kombinovaný příkaz filldraw udělá totéž, jako bychom plochu vymezenou křivkou vyplnili a následně křivku obtáhli stejnou barvou a aktuálním perem.

Cesty je možné navazovat operátorem &. To nemá význam u cest složených z úseček, ale zásadní význam to má u křivek (operátor „..“), které se v navazujícím bodě mohou libovolně lomit a přitom tvoří jeden celek (lze jimi vymezit plochu).

Další možností spojení cest je vytvoření uzavřené cesty z několika navazujících cest příkazem buildcycle, jehož syntaxe je následující:

a=buildcycle(b,c,d … q);

kdy všechny zúčastněné proměnné jsou typu path.

To by bylo jako uvedení do cest vše; v osnově, podle které tento seriál píšu, má datový typ path asi trojnásobek stran než datové typy boolean, pen, numeric a pair dohromady. Přitom je to typ velice důležitý, protože tvoří jak základ samotného kreslení, tak i datového typu picture. Proto tomuto typu věnuji více dílů.

Příklady

Uvádím zde několik příkladů kreslení bodů a jednoduchých čar. Záměrně používám primitivní rozepsání sledu příkazů, protože prvky řízení programu, jako podmínky, cykly a makra teprve budou probrány (považuji za nepedagogické vysvětlovat něco pomocí dosud neznámých a nedefinovaných pojmů).

Vykreslení bodů

Následující program ukáže vykreslení bodů různými způsoby.

Začátek programu a první obrázek:

prologues:=1;
u=1cm;
pair a[];
a0=origin; a1=(0,5u);a2=(5u,0);a3=(3u,3u);
pen p;
p=pencircle scaled .2u;
beginfig(1);
pickup p;
drawdot a0;drawdot a1;drawdot a2;drawdot a3;
endfig;

Vykreslený obrázek vypadá následovně:

Body1

Program pokračuje definováním dalšího obrázku:

beginfig(2);
pickup p;
draw a0;draw a1;draw a2;draw a3;
endfig;

Vykreslený obrázek vypadá následovně: (vypadá ale stejně jako ten předchozí)

Body2

Program definuje třetí obrázek a končí:

beginfig(3);
z0=a0;z1=a1;z2=a2;z3=a3;
z4=(1.5x3,(5/4)*y3);
pickup p;
dotlabels.lft(0);
dotlabels.rt(1);
dotlabels.top(2);
dotlabels.bot(3);
dotlabels(4);
endfig;
end;

Povšiměte si, že pro použití příkazu dotlabels bylo nutno překopírovat pole a do pole z. Byl navíc přičiněn ještě jeden bod, z4, aby bylo možné následně demonstrovat všechny varianty tohoto příkazu. Výsledný obrázek vypadá následovně:

Body3

Nadefinování bodů a vykreslení čar

Následující program definuje body v rozích čtverce, poté na spojnici horních bodů nadefinuje dva nové, skrze ně vede spojnice s dolními rohy bodů a najde jejich průsečík. Nadefinuje body na příslušných přímkách za tímto průsečíkem a vykreslí jejich spojnice s dolními rohy čtverce. Vyznačí jinak body tvořící rohy základního čtverce a jinak body, které vznikly uvedenými operacemi.

prologues:=1;
u=1cm;
beginfig(1);
z0=origin; z1=(5u,0); z2=(5u,5u); z3=(0,5u);
% definování bodů představujících rohy čtverce

z4=.4[z2,z3]; z5=.3[z3,z2];
% definice nových bodů na spojnici z2 a z3
% povšiměte si změny pořadí bodů v definici

z6=whatever[z0,z5]=whatever[z1,z4];
% definování průsečíku bodů

z7=1.25[z0,z6]; z8=1.5[z1,z6];
% definujeme body za průsečíkem

draw z0--z7; draw z1--z8;
% vykreslíme úsečky jdoucí přes takto získaný
% průsečík implicitním perem

pickup pencircle scaled .2u;
draw z0; draw z1; draw z2; draw z3;
% vykreslíme body původně definovaného čtverce
% jako nepopsaná kolečka

dotlabels.lft(4,5,6,7,8);
% ostatní body vyznačíme čísly

endfig;
end;

Co bude vykresleno, vidíme na následujícím obrázku:

BodDef

Vykreslení jednoduché uzavřené cesty

Funkci programu doufám dostatečně vysvětlují komentáře. Unitsquare je vlastně makro, vykreslující cestu (0,0)–(1,0)–(1,1)–(0,1)–cycle

bitcoin_skoleni

prologues:=1;
u=1cm;
beginfig(1);
path ctverec[];
ctverec0=unitsquare scaled 3u;
% jednotkový čtverec s levým dolním rohem na (0,0)
% o straně dlouhé 3u

% Další čtverce získáme posuny; bude probráno dál,
% tady jen stručný komentář, "co ty divné příkazy dělají"

ctverec1=ctverec0 shifted(4u,0);
% stejný jako předchozí, ale posunutý o 4u doprava

ctverec2=ctverec0 shifted(0,-4u);
% stejný jako ctverec0, ale posunutý o 4u dolů

ctverec3=ctverec1 shifted(0,-4u);
% tohle už si domyslíte :-)

% Tady máme čtyři čtverce, teď nás zajímá jejich
% vykreslení

pickup pencircle scaled .2u;
% kvůli názornosti škaredě tlusté pero

draw ctverec0;
fill ctverec1;
filldraw ctverec2;
draw ctverec3;unfill ctverec3;

% standardní ukončení obrázku a programu:
endfig; end;

Povšimněte si rozdílů ve vykreslení čtverců; pokud by bylo použito čtvercové pero (pensquare), byly by rohy čtverců 0, 2 a 3 ostré, nikoli zaoblené.

Ctverce

Příště

Příště se už pustíme do složitějších konstrukcí, ale úplně nejdříve se naučíme vykreslovat čáry různě čárkované a tečkované, abychom mohli vytvářet přehlednější obrázky.

Autor článku