int21h

Geometrie přímky

V tomto krátkém textu si probereme počítačovou podobu několika postupů z analytické geometrie.
Pascal bohužel žádnou knihovnu pro tyto operace neobsahuje a i na internetu se k tomu nic rozumného sehnat nedá, takže se s tím budeme muset poprat sami.
Nebude to nic moc - matematické schopnosti lékařů jsou myslím všeobecně známé a ani já nejsem výjimkou...

Napřed dvě poznámky:
- používejte videomód s poměrem stran 4:3 (např. 640x480 nebo 800x600, popř. 320x240). V těchto módech má totiž pixel čtvercový tvar. Pokud byste použili třeba 320x200, tak ten má jiný poměr stran a pixel je obdélníček postavený na výšku.
Problémy vznikají u kružnic, protože ty, jestliže mají vypadat kulatě, a ne šišatě, jsou ve skutečnosti elipsami, jsou vykresleny pomocí jiných vzorců a geometrie kružnice selhává :-(

- na obrázcích používám osy grafů shodně jako ve škole. Tzn. X stoupá směrem doprava a Y směrem nahoru. U počítačů se používá jiná souřadnicová síť (Y stoupá směrem dolů), ale ve skutečnosti na tom vůbec nezáleží a ve výpočtech nás to nemusí zajímat. Fungovat to bude.


Rovnice přímky se dá zapsat několika způsoby. Pro nás zajímavé jsou tvary obecná rovnice přímky,směrnicový tvar a parametrický tvar.
Obecná rovnice přímky zní:
a*x+b*y+c=0

směrnicový tvar vznikne z obecné rovnice vyjádřením Y:
Y=(a*x-c)/b (většinou se ale píše jako Y=k*x+t)

parametrický tvar je trochu odlišný:
x=x1+p*t
y=y1+q*t

x1 a y1 jsou souřadnice jakéhokoliv bodu ležícího na přímce.


Všimněte si, že ve směrnicovém tvaru se vyskytuje dělení. Jak víme nulou dělit nelze, takže pro některé přímky nelze tento tvar použít. Týká se to svislých přímek.
Na svislé přímky pozor!
Pro úlohy na počítači je bohužel právě směrnicový tvar nejpříhodnější.

Na obrázku vidíte přímku s rovnicí Y=X+2. Vidíte, že směrnice vlastně popisuje úhel mezi přímkou a osou X (a potažmo Y). V tomto případě se Směrnice (k) rovná 1. (A třeba vodorovné přímky mají směrnici 0).
Posun (t) je roven 2. Vidíte, že posun označuje místo průsečíku s osou Y.
Z uvedeného vyplývá, že
Rovnoběžky mají shodné K (a rozdílné T).
A kolmice?

Pokud máme přímku se směrnicí K1, tak platí, pro směrnici (K2) její kolmice platí, že
K1*K2=-1.
Tedy:
K2=-1/K1

Uff...
Bavíme se tu o směrnicích, a ještě jsme si ani neřekli jak se taková směrnice vypočítá.
Máme-li dva body A[x1,y1] a B[x2,y2] určující přímku, tak směrnici spočítáme takto:
K:=(y2-y1)/(x2-x1);


A posun?
T:=((x2*y1)-(x1*y2))/(x2-x1);


Tyhle vzorečky vám prostě vylezou, když řešíte soustavu rovnic o dvou neznámých (K a L) s tím, že dosadíte právě body A[x1,y1] a B[x2,y2]
y1=k*x1+t
y2=k*x2+t

Ještě jednou si všimněte, že pokud X2=X1, pak je dělitel nula a víme, co to znamená.
Je zajímavé, že Turbo pascal by to ale v režimu $N+ asi vzal. Ne, že by uměl dělit nulou (to umí jenom Bůh), ale X2-X1 pro matematický koprocesor není nula, ale jakási ezoterická strašlivě malinká, nicméně nenulová hodnota. Je rozumné to ale nepokoušet.


Jako poslední si definujeme funkci vzdálenost. Vypočítá vzdálenost dvou bodů.
Vzdalenost:=sqrt(sqr(x1-x2)+sqr(y1-y2));

Jak vidíte, obyčejná Pythagorova věta.

Než se dále ponoříme do zdrojáků v pascalu, tak si definujeme typ BOD.
Bod = record
x,y:longint;
end;  

Začneme něčím méně praktickým, ale přesto užitečným. Funkce JeTrojuhelnik zjisti, zda
všechny tři zadané body leží na jedné přímce. Zjistíme to pomocí směrnice.
Pokud K(ab)=K(ac), pak je jasné, že jsou na přímce. Pokud se nerovnají, na přímce nejsou a je to trojúhelník.
Function JeTrojuhelnik(a,b,c:bod):boolean;
var p1,p2:real;
begin
if a.x=b.x then
   if a.x=c.x then JeTrojuhelnik:=false else JeTrojuhelnik:=true
           else
   if a.x=c.x then JeTrojuhelnik:=true else
      begin
      p1:=Smernice(a,b);
      p2:=Smernice(a,c);
      JeTrojuhelnik:=p1<>p2;
      end;
end;  
Zase se tu setkáváme s problémem svislých přímek. Pro ně směrnici počítat nemůžeme, a proto je musíme ohlídat zvlášť.

Z podobného soudku je funkce JeKolma. Zjistí zda je přímka zadaná body A1 a A2 kolmá na druhou přímku zadanou body B1 a B2. Testuje se, zda je součin jejich směrnic roven -1.
Function JeKolma(a1,a2,b1,b2:bod):boolean;
var p1,p2:real;
begin
if a1.x=a2.x then
   begin
   JeKolma:=b1.y=b2.y;
   Exit;
   end else
if b1.x=b2.x then
   begin
   JeKolma:=a1.y=a2.y;
   Exit;
   end;
p1:=Smernice(a1,a2);
p2:=Smernice(b1,b2);
JeKolma:=p1*p2=-1;
end;  

Poněkud jiná je procedura Bod_ze_smeru.
Máme přímku určenou body A1 a A2 a na ní ležící bod ODKUD. ODKUD může být shodný s A1 či A2.
Procedura vypočítá polohu bodu taktéž ležícího na této přímce, který je ve vzdálenosti DELKA od bodu ODKUD.
Tato procedura je jiná proto, že se směrnicí nepočítá, ale provádí banální přepočet pomocí trojčlenky.
Procedure Bod_ze_smeru(a1,a2,odkud:bod;delka:longint;var b:bod);
var p1,p2:real;
    dx,dy:longint;
begin
if a1.x=a2.x then begin b.x:=odkud.x;b.y:=odkud.y+delka;Exit;end;
if a1.y=a2.y then begin b.y:=odkud.y;b.x:=odkud.x+delka;Exit;end;
dx:=a2.x-a1.x;
dy:=a2.y-a1.y;
p2:=vzdalenost(a1,a2);
p1:=dx*delka/p2;
p2:=dy*delka/p2;
b.x:=odkud.x+round(p1);
b.y:=odkud.y+round(p2);
end;  

Teď veledůležitá věc: určení průsečíku dvou přímek.
Tady pouze se směrnicí nevystačíme - je třeba počítat i s posunem. Vinou ošetření svislých přímek vypadá procedura dost hrozivě, ale v jádru je to zase řešení soustavy rovnic. Y=K*X+T
Tahle funkce je doopravdy užitečná, tak jsem ji ještě rozšířil o určení, zda průsečík náleží úsečkám A1A2 a B1B2, nebo zda je až za nimi. Tento výpočet ale funkci dost zpomaluje, takže se provede jenom pokud je parametr OREZ nastaven na TRUE.

Function Prusecik(a1,a2,b1,b2:bod;orez:boolean;var b:bod):byte;
{Vyznam konstant ROVNOBEZKY a RUZNOBEZKY je jasny. NEDOSAHNOU znamena, ze jde o
ruznobezky, ale prusecik nelezi na zadanych useckach}
   Procedure Prohod(var mensi,vetsi:longint);
   var j:longint;
   begin
   if mensi>vetsi then begin j:=mensi;mensi:=vetsi;vetsi:=j;end;
   end;
var p1,p2,q1,q2:real;
    _x,_y:real;
    v:longint;
begin


if a1.x<>a2.x then
   begin
   p1:=Smernice(a1,a2);
   q1:=Posun(a1,a2);
   end;


if b1.x<>b2.x then
   begin
   p2:=Smernice(b1,b2);
   q2:=Posun(b1,b2);
   end;


if b1.x=b2.x then
   if a1.x=a2.x then begin Prusecik:=ROVNOBEZKY;Exit;end
      else begin
      _x:=b1.x;
      _y:=p1*_x+q1;
      end else
      if a1.x=a2.x then
         begin
         _x:=a1.x;
         _y:=p2*_x+q2;
         end else
         if p1=p2 then begin Prusecik:=ROVNOBEZKY;Exit;end else
            begin
            _x:=(q1-q2) / (p2-p1);
            _y:=p1*_x+q1;
            end;
b.x:=round(_x);
b.y:=round(_y);


if orez then
   begin
   if (a1.x>a2.x) then Prohod(a1.x,a2.x);
   if (a1.y>a2.y) then Prohod(a1.y,a2.y);
   if (b1.x>b2.x) then Prohod(b1.x,b2.x);
   if (b1.y>b2.y) then Prohod(b1.y,b2.y);
   if (b.x>=a1.x) and (b.x>=b1.x) and (b.x<=a2.x) and (b.x<=b2.x) and
      (b.y>=a1.y) and (b.y>=b1.y) and (b.y<=a2.y) and (b.y<=b2.y) then
      Prusecik:=RUZNOBEZKY else Prusecik:=NEDOSAHNOU;
   end else Prusecik:=RUZNOBEZKY;
end;  

Touhle funkcí můžeme třeba vyzdobit výpočet kolmice. Máme vodicí přímku a na ni budeme chtít sestrojit kolmici. K tomu ještě potřebujeme nějaký bod ležící mimo přímku. To je první určující bod kolmice. (Přímky jsou určeny dvěma body) My chceme dostat druhý. Co kdyby ležel druhý bod právě na vodicí přímce? Nebylo by to roztomilé? Bylo.
Procedure SestrojKolmici(a1,a2,b1:bod;var b2:bod);
{druhy bod bude lezet na primce}
var p1,p2,_x,_y:real;
    n:bod;
begin
if a1.x=a2.x then begin b2.x:=a1.x;b2.y:=b1.y;Exit;end;
if a1.y=a2.y then begin b2.y:=a1.y;b2.x:=b1.x;Exit;end;
p1:=Smernice(a1,a2);
p2:=-1/p1;
_y:=b1.y-p2*b1.x;
n.x:=0;
n.y:=round(_y);
Prusecik(a1,a2,b1,n,false,b2);
end;  

A na závěr si dáme řešení programátorského evergreenu. Kružnice zadaná třemi body.
Přestože je to klasická úloha, tak jsem na internetu nenašel řešení. Správné řešení podotýkám. (jedna verze vracela nesmyslná desetinná čísla a druhá čísla vcelku rozumná, ale chybná)
Máme dva způsoby, jak úlohu řešit. Buďto přes rovnici kružnice a řešením soustavy rovnic o třech neznámých, nebo reprodukci postupu z papírové geometrie pomocí kružítka a pravítka. Já jsem použil druhý způsob. Když máme funkci Prusecik, tak proč ji nevyužít, že jo?
Abyste se ale necítili ošizeni, tak napíšu rovnici kružnice:

sqr(x-m)+sqr(y-n)=sqr(r)

M a N jsou x-ová a y-ová souřadnice středu.
Obdobně jako u přímky existuje ještě parametrický tvar, který je mezi programátory možná známější:
x=r*cos(t)
y=r*sin(t)


A zpátky k naší úloze!
Z obrázku vidíte, že provádíme konstrukci kružnice opsané. Zadané body chápeme jako trojúhelník (můžeme i zavolat funkci JeTrojuhelnik).
Najdeme středy stran a vedeme na ně kolmice. Přiznávám, že tady jsem to lehce odflák. Pro sestrojení kolmice je třeba vodicí přímka na kterou budeme kolmici tvořit a bod, který je přímku a skrz který kolmice povede.
Já jsem v zájmu jednoduchosti dosadil bod s x-ovou souřadnicí 0 (y se právě dopočítá). Kdyby byla půle vodicí přímky zrovna na ose Y, tak by nastala shoda bodů a sestrojili bychom prd. Tahle pravděpodobnost je ale dostatečně malá.
Ještě omluvte, že názvy bodů na obrázku a v programu se neshodují. Je to věc srozumitelnosti. Na obrázku je srozumitelnější to a v programu ono.
Function Kruznice3(a1,a2,a3:bod;var b:bod;var r:longint):boolean;
var b3,b2,c3,c2:bod;
    p1,p2:real;
    _y1,_y2:real;
    j:byte;
begin
if JeTrojuhelnik(a1,a2,a3)=false then begin Kruznice3:=false;Exit;end;
b3.x:=(a1.x+a2.x) div 2;
b3.y:=(a1.y+a2.y) div 2;
b2.x:=(a1.x+a3.x) div 2;
b2.y:=(a1.y+a3.y) div 2;
c3.x:=0;             {nepekne "osetreno". musime doufat, ze c3.x<>b3.x}
c2.x:=0;             {to same}
if a1.x=a2.x then
   c3.y:=b3.y else
if a1.y=a2.y then
   begin
   c3.x:=b3.x;c3.y:=0;end else
   begin
   p1:=-1/Smernice(a1,a2);
   _y1:=b3.y-p1*b3.x;
   c3.y:=round(_y1);
   end;


if a1.x=a3.x then
   c2.y:=b2.y else
if a1.y=a3.y then
   begin
   c2.x:=b2.x;c2.y:=0;end else
   begin
   p2:=-1/Smernice(a1,a3);
   _y2:=b2.y-p2*b2.x;
   c2.y:=round(_y2);
   end;
j:=Prusecik(b3,c3,b2,c2,false,b);
r:=round(Vzdalenost(b,a1));
end;  
2006-11-30 | Laaca
Reklamy: