Rovinné křivky

 

Uživatelská souřadná soustava

 

Jak již víme, DELPHI pracuje s tzv. světovými souřadnicemi, které jsou na připojeném obrázku vyznačeny modře. Práce v takové soustavě je pro uživatele značně nepříjemná. Vytvoříme si proto tzv. uživatelskou souřadnou soustavu, která je vyznačena červeně. „Délku“ osy  (interval ) jakož i „délku“ osy  (interval ) budeme moci měnit. V obrázku je  resp.  délka uživatelské jednotky na ose   resp.  ve světových souřadnicích. Zřejmě je

 

;                                                       (1)

 

Souřadnice uživatelského počátku O  ve světových souřadnicích jsou pak

 

 ;                                                      (2)

 

Je-li pak  libovolný bod na kreslicí ploše,  jeho souřadnice v uživatelské soustavě a  jeho souřadnice ve světové soustavě, pak zřejmě platí

 

;                                         (3)

 

Realizujme nyní uživatelskou souřadnou soustavu a vykresleme trojúhelník s danými vrcholy. Na formulář jsme opět umístili objekt Image, proceduru, která trojúhelník vykresluje, jsme nazvali Triangle a budeme ji aktivovat OnClick. Po těchto základních úpravách za nás DELPHI zapíše do unity, kterou jsme přejmenovali na Graph2D,  následující černé (event. modré) řádky (viz dále). Všimněme si nejdříve řádku TForm1 = class(TForm). Ten se v unitě objeví automaticky, i když do formuláře nezařadíme žádnou komponentu. Deklaruje typ TForm1 jako dědice typu TForm. Znamená to, že TForm1 automaticky přebírá vlastnosti typu TForm a navíc můžeme definovat jeho další vlastnosti. Něco za nás opět provede DELPHI. Zařadíme-li do formuláře komponentu Image, objeví se nová proměnná dědice - Image1, která je typu TImage. Spojíme-li s událostí OnClick proceduru, kterou v Object Inspectoru nazveme Triangle, pak, jak již víme, DELPHI tuto proceduru zapíše na konec zdrojového textu [procedure TForm1.Triangle (Sender: TObject);] a automaticky nás na ni navede, abychom ji mohli vypsat.

 

unit Graph2D;

interface

uses

Windows, Messages, SysUtils, Classes,

Graphics, Controls, Forms, Dialogs,ExtCtrls;

type

   TPoint = array [1..2] of Double;

   TForm1 = class(TForm)

       Image1: TImage;

   procedure Triangle(Sender:TObject); 

                                          

private

          { Private declarations }

public

          { Public declarations }

procedure Scale(x1,x2,y1,y2:Double);

function   XCoor(x:Double):integer;

function    YCoor(y:Double):integer;

end;

 

Všimněte si však, že se hlavička této procedury automaticky zapsala také jako nová vlastnost typu TForm. Tímto způsobem můžeme i my sami obohacovat typ TForm o nové proměnné, procedury a funkce, nejlépe do odstavce Public (zde budou tyto vlastnosti v případě potřeby dostupné i z jiných jednotek). Nyní se tedy dostáváme k červenému textu, který musíme dopsat sami a kterým realizujeme uživatelskou souřadnou soustavu.

 

var  Form1                    :TForm1;

     Unit_X,Unit_Y,x1,x2,y1,y2:Double;

     O                        :TPoint;

 

implementation

 

{$R *.DFM}

procedure TForm1.Scale(x1,x2,y1,y2:Double);

  begin

    Unit_X:= Image1.Width/(x2-x1);

    Unit_Y:=Image1.Height/(y2-y1);

    O[1]:= -x1*Unit_X;O[2]:=y2*Unit_Y;

  end;

 

 function  TForm1.XCoor(x:Double):integer;

    begin  XCoor:=trunc(O[1]+Unit_X*x);end;

 

  function  TForm1.YCoor(y:Double):integer;

     begin YCoor:=trunc(O[2]-Unit_Y*y);end;

 

procedure TForm1.Triangle(Sender: TObject);

  begin

  end;

end.

 

Protože budeme chtít pracovat s body v rovině, deklarujeme v části Interface  typ TPoint jako uspořádanou dvojici reálných čísel (předefinovali jsme tedy typ TPoint, který je v DELPHI deklarován. Důvody budou zřejmé později). Dále do odstavce public typu TForm1 zapíšeme hlavičku procedury Scale(x1,x2,y1,y2:Double). Parametry této procedury jsou „uživatelské“ rozměry kreslicí plochy tak, jak bylo popsáno výše. Procedura bude realizovat výpočty dle vzorců (1), (2). Dále dvě funkce Function XCoor (x:Double):Integer; Function YCoor(y:Double):Integer. Funkce jsou zvláštní typy procedur, které mají právě jeden výstup (procedury mohou mít výstupů více, nebo také žádný). Tyto funkce budou reálné uživatelské souřadnice x resp. y přepočítávat na světové celočíselné souřadnice podle vzorců (3). Dále je třeba deklarovat proměnné, které budou používány v celé jednotce (globální proměnné). Jsou to reálná čísla Unit_XUnit_Y  - délky uživatelských jednotek na jedn. osách,  dále bod O (uživatelský počátek) - proměnná námi definovaného typu TPoint a konečně x1,x2,y1,y2 - uživatelské rozměry kreslicí plochy. Do části Implemantation za zápis {$R *.DFM} (což je direktiva překladače, kterou zapsalo DELPHI) musíme nyní tyto procedury a funkce specifikovat. Všimněte si, že zde je v hlavičkách před názvem specifikován typ,  který procedura či funkce obohacuje procedure TForm1.Scale(x1,x2,y1,y2:Double);].

 

Příklad 1: Výše uvedená část kódu by samozřejmě ještě trojúhelník nevykreslila. Doplňme jej tak, abychom na výstupu trojúhelník dostali. V řešeném příkladu jsou podobně jako výše uvedené tři procedury a funkce doplněny další:

 

          procedure PutPixel(X:TPoint;Color:DWord);

          procedure MoveTo(X:TPoint);

          procedure LineTo(X:TPoint;Color:DWord);

          procedure Line(X,Y:TPoint;Color:DWord);

          procedure XAxis(x1,x2,y:Double;Color:DWord);

          procedure YAxis(y1,y2,x:Double;Color:DWord);

 

Procedury PutPixel, MoveTo LineTo fungují analogicky, jako metody obsažené v TCanvas ve standardním DELPHI, pracují však v uživatelských souřadnicích (v argumentu jsou proměnné typu TPoint) a dále proměnná definující barvu, kterou se má kreslit. Dále je zde procedura Line, která spojí dva uživatelské body X, Y úsečkou. Konečně procedury XAxis resp. YAxis sestrojují souřadné osy, přesněji řečeno vodorovné resp. svislé přímky. Např. u XAxis určují parametry x1,x2 x ové souřadnice krajních bodů, parametr y pak y ovou souřadnici průsečíku s osou y (YAxis analogicky). Všechny tyto procedury využívají standardních metod objektu TCanvas. Například procedura LineTo vypadá takto:

 

procedure TForm1.LineTo(X:TPoint;Color:DWord);

  begin

   With Image1.Canvas do

     Begin

      Pen.Color:=Color;LineTo(XCoor(X[1]),YCoor(X[2]));

     end;

  end;

 

V těchto procedurách tedy není využit přístup do bitmapy přes obrazové řádky tak, jak bylo uvedeno v matematických podrobnostech předchozí kapitoly. V dalším textu budeme prozatím používat osmibitové barvy, resp. předdeklarované konstanty - např. slRed, slGreen atd., které byly popsány v matematických podrobnostech předchozí kapitoly, a vlastní proceduru Line pro úsečku, která je popsána v matematických podrobnostech této kapitoly  .

 

Jsou-li k dispozici výše uvedené procedury, pak trojúhelník vykreslíme velmi jednoduše:

 

procedure TForm1.Triangle(Sender: TObject);

var A,B,C:TPoint;

begin

    A[1]:=-2;A[2]:=6; B[1]:= 4;B[2]:=-1; C[1]:= 5;C[2]:=5;             {definice vrcholů}

    x1:=-4;x2:=7;y1:=-3;y2:=8;

    Scale(x1,x2,y1,y2);        {definice uživatelské kreslicí plochy}

    Line(A,B,slRed);Line(B,C,slRed);Line(A,C,slRed);          {vlastní konstrukce}

end;

 

Zde najdete kompletní              zdrojový kód         a zde               spustitelný kód

 

Příklad 2:  Předchozí příklad vylepšíme o cejchování souřadných os, který obstarávají procedury XGauge (x1,x2,y:Double;Color:Byte); resp. YGauge (y1,y2,x:Double; Color: Byte). Podle velikosti měřítka procedury zvolí vhodnou velikost jednotky na popis, k popisu připojí vzdálenost sousedních „fousů“. Fungují následujícím způsobem (jako příklad uveďme popis osy x):

 

procedure TForm1.XScale(x1,x2,y:Double;Color:DWord);

var   AxisUnit       :Double;

      Text           :String;

      Lx,Exponent    :Integer;

begin

Image1.Canvas.Font.Color:=Color;

AxisUnit:=1E30;Lx:=0;Exponent:=30;

While (x2-x1)/AxisUnit<1 do

begin

  AxisUnit:=AxisUnit/10;Exponent:=Exponent-1;

end;                                                         {nalezení vhodné velikosti jednotky}

if x1<0                                                                              {nalezení nejmenší popisky}

     then Repeat Lx:=Lx-1 Until AxisUnit*(Lx-1)<x1                

     else Repeat Lx:=Lx+1 Until AxisUnit*(Lx+1)>x1;

With Image1.Canvas do

  repeat

      MoveTo(XCoor(AxisUnit*Lx),YCoor(y)-3);     {cejchování osy}

      LineTo(XCoor(AxisUnit*Lx),YCoor(y)+3);

Str(Lx,Text);TextOut(XCoor(AxisUnit*Lx),YCoor(y)+5,Text);Lx:=Lx+1;                                                                                                                                {popisky}

  until AxisUnit*Lx>x2;

Str(Exponent,Text);                {funkce Str převádí číselnou hodnotu na řetězec}          

Image1.Canvas.TextOut(XCoor(x2)+15,YCoor(y)+5,'*10');

                                                                                                          {jednotka a exponent}  

Image1.Canvas.TextOut(XCoor(x2)+35,YCoor(y),Text);                

end;

 

Zde najdete kompletní              zdrojový kód         a zde               spustitelný kód

 

Předchozí příklady neumožňují měnit parametry za běhu programu. Tento nedostatek napravíme v následujícím příkladu.

 

Příklad 3: K ovládání našeho formuláře Form1 založíme celý nový formulář. V menu File klikneme na New Form. Objeví se nám nový formulář Form2 spolu s novou jednotkou Unit2. Tomuto formuláři změníme titulek na Control Panel. a umístíme na něj tři tlačítka, která vybereme ze stránky Standard vícestránkové lišty. Těmto tlačítkům změníme titulky na „Start, Clear ImageExit. Dále několik objektů Static Text, tento typ najdeme v liště Additional, jejichž titulky budou Triangle, Scale, další budou sloužit pro popis vrcholů - A=, B=, C= a uživatelských rozměrů kreslicí plochy x, y. Dále zde umístíme Check Box (lišta Standard) s titulkem Show Axses, s jehož pomocí bude uživatel vybírat možnost zobrazení souřadných os. O tom, zda se souřadné osy mají či nemají zobrazit, rozhoduje vlastnost Checked typu Boolean.  Konečně přidáme potřebný počet objektů  Edit (opět v liště Standard), do kterých bude uživatel zadávat hodnoty. Změníme jejich jména (vlastnost Name v Object Inspectoru) na EditA1, EditA2, …, resp EditX1, EditX2…, a Text, tak, aby při spuštění programu tato okna obsahovala výchozí parametry. Náš formulář vypadá tak, jak vidíme na přiloženém obrázku. Procedury týkající se rovinné uživatelské souřadné soustavy jsou v samostatné jednotce - původní Unit1 přejmenujeme na Graph2D pomocí File/SaveAs. Do této jednotky jsme také umístili objekt typu TImage, který jsme nazvali Draw2D a všechny konstrukce probíhají v tomto objektu. Např. procedura pro mazání kreslicí plochy kreslí přes celou tuto plochu bílý obdélník:

 

procedure TDraw2D.ClearImage;

begin

  with Image1.Canvas do

    begin Pen.Color:=clWhite;Brush.Color:=clWhite;end;

    with Image1 do Canvas.Rectangle(0,0,Width-1,Height-1);

end;

 

V Control Panelu nejdříve zařídíme čtení parametrů z Edit-okének, a to procedurou Setting:

 

Procedure TControl_Panel.Setting;

var Code:Integer;

begin

   With Draw2D do

   begin

      Val(EditA1.Text,A[1],Code);

      Val(EditA2.Text,A[2],Code);

      Val(EditY2.Text,y2,Code);

      Scale(x1,x2,y1,y2);

  end;

end;

 

Tlačítka v Control Panelu spojíme přes událost OnClick pořadě s procedurami Triangle, ClearImage a Exit:

 

procedure TControl_Panel.Triangle(Sender: TObject);

begin

    With Draw2D do

      begin

      ClearImage;Setting;

      if CheckBox1.Checked then CoorSystem(slRed);

      Line(A,B,slBlue);Line(B,C,slBlue);Line(A,C,slBlue);

     end;

end;

 

procedure TControl_Panel.ClearImage(Sender: TObject);

begin  Draw2D.ClearImage;end;

 

procedure TControl_Panel.Exit(Sender: TObject);

begin Halt; end;

 

V příkladu je navíc řešeno zadávání vrcholů trojúhelníka myší

 

Zde najdete kompletní              zdrojový kód         a zde               spustitelný kód

 

Křivky typu

 

Z hlediska konstrukce se jedná o nejjednodušší křivky. Lze je konstruovat jako lomené čáry složené z úseček dle připojeného obrázku:

 

Příklad 1: Sestrojme proceduru Explicit_Curve, která sestrojí graf funkce   (viz připojený výpis). Nejdříve opět sestavíme ovládací panel ze známých komponent (v panelu NumberOfSegments bude uživatel zadávat počet segmentů, z kterých má být křivka sestrojena. Stejně jako v předchozí kapitole sestrojíme uživatelskou kreslicí plochu, souřadné osy a definujeme barvu sestrojované křivky. Vidíme, že funkce   je definována uvnitř procedury.  Vlastní konstrukce spočívá v nastavení kroku hx a v postupné konstrukci úseček :

 

 

Procedure  TControl_Panel.Explicit_Curve(Sender:TObject);

var x,hx:Double;

        A,B :TPoint;

function f(x:Double):Double;

begin if x=0 then f:=0

                           else f:=x+10*x*x*sin(1/x); end;

begin

  With Draw2D do                                                            {Uživatelská kreslicí plocha}

  begin

    if CheckBox1.Checked then

begin                                   {souřadné osy a jejich popis}

 XScale(x1,x2,0,clRed);             {sestrojí stupnici rovnoběžnou s osou x, resp. y.}

 YScale(y1,y2,0,clRed);     { Pro určení barvy je použita standardní konstanta }

end;                    { kvůli snazšímu popisu os pomocí procedury TextOut}

hx:=(x2-x1)/NumberOfSegments;x:=x1;A[1]:=x;A[2]:=f(x);    

Repeat                            {konstrukce křivky jako lomené čáry}

  B[1]:=x+hx;B[2]:=f(x+hx); Line(A,B,slBlue);A:=B;x:=x+hx

Until x>x2

end;

end;

 

 

Zde najdete kompletní              zdrojový kód         a zde               spustitelný kód

 

V případě křivek bývá výhodné posloupnost bodů, kterými křivka prochází, nejdříve celou spočítat a teprve potom proložit křivku.  Delphi má sice metodu PolyLine, která umožňuje proložit polygon posloupností bodů. Tato metoda však pracuje jen se světovými souřadnicemi, kdežto my pracujeme již v souřadnicích uživatelských. Tuto metodu si tedy opět předefinujeme.

 

Příklad 2: Předefinujme metodu PolyLine a funkci z př. 1. sestrojme pomocí této nové metody. Naše metoda bude pracovat s posloupností bodů, musíme tedy nejdříve deklarovat nový typ, nejlépe hned za TPoint:

 

TPoint            = array [1..2] of Double;

TArrayOfPoints    =  array [0..800] of TPoint;

 

TPoint je tedy jeden bod - uspořádaná dvojice čísel, TArrayOfPoints je posloupnost nejvýše osmi set bodů. Do proměnné tohoto typu se budou ukládat body, kterými má procházet naše křivka. S nástroji, které již máme k dispozici, je její konstrukce jednoduchá:

procedure Draw2D.PolyLine(Q:TArrayOfPoints; n:Word;Color:Byte);

var i:Word;

begin

   MoveTo(Q[1]);

   for i:=1 to n do LineTo(Q[i],Color);

end;

 

Všimněme si nejdříve hlavičky: Q je posloupnost bodů, n definuje počet bodů, které mají být spojeny, Color pak barvu, kterou má být křivka sestrojena. Procedura nejdříve umístí pero do prvního bodu pomocí naší metody MoveTo, do následujících pak kreslí úsečky naší metodou LineTo. Samozřejmě je třeba upravit i naši proceduru Explicit_Curve (uvádím pouze část kódu, která se od předchozího příkladu liší).

 

procedure TControl_Panel.Explicit_Curve(Sender:TObject);

var x,hx:Double;     Q     :TArrayOfPoints;     i      :Word;

function f.....

begin

   .......

  hx:=(x2-x1)/NumberOfSegments;i:=0;x:=x1;

  Repeat

   Q[i,1]:=x;Q[i,2]:=f(x);       {konstrukce křivky jako lomené čáry}

   x:=x+hx;i:=succ(i);

  Until x>x2+hx;

  PolyLine(Q,i,Color);

end;

 

Zde najdete kompletní              zdrojový kód         a zde               spustitelný kód

 

Je zde jedna poněkud nepříjemná věc: V proměnné typu TArrayOfPoints nám přibyl jeden index, který udává pořadí bodu v posloupnosti. Ten je třeba uvádět vždy jako první. Zápis Q [i,2] tedy znamená, že se jedná o druhou souřadnici  i - tého bodu v posloupnosti Q. Naši metodu PolyLine použijeme i při konstrukci křivek zadaných parametricky a polárně. Zde bude poněkud odlišný pouze způsob naplnění pole Q.

 

Z hlediska uživatele zůstává jiná nepříjemnost věc: z ovládacího panelu není možné měnit zadání funkce.

 

příkladu 3, jehož                 zdrojový kód je zde                 a          spustitelný kód je zde,

 

 

je tento nedostatek odstraněn. Řetezec zadávaný do okna EditFunction je vyčíslován v jednotce Graph2D_256 funkcí Calc(Text:String; ErrorReport:String). V zadávaném řetězci je možno zadávat všechny běžné funkce včetně cyklometrických, k označení proměnných lze použít x, y, t, k. ErrorReport vrací chybové hlášení. Máme-li k dispozici tuto proceduru, odpadá zadávání funkce f ve zdrojovém kódu. Proměnnou f deklarujeme jako řetězec a příkaz A[i,2]:=f(x) nahradíme příkazem A[i,2]:=Calc(f, ErrorReport). Řetězec ErrorReport je při korektním výpočtu správný. Při chybném vyhodnocení je vrácen popis chyby, čehož je možno využít k chybovému hlášení a přerušení procedury, např:

 

A[i,2]:=Calc(f, ErrorReport)

if ErrorReport<>’’

     then begin

         ShowMessage(ErrorReport);

         exit;

         end;

 

Samotné vyčíslení řetezce (funkce Calc) však není jednoduchou záležitostí, bezprostředně nesouvisí přímo s grafickými problémy a nebudeme se jím proto podrobněji zabývat.

 

Dříve, než postoupíme dále, je třeba učinit ještě jednu poznámku. Barvy zadáváme v Delphi pomocí předdeklarovaných konstant (clRed, clYellow…). Těchto konstant je však k dispozici pouze šestnáct přestože jsou čtyřiadvacetibitové. Máme tedy k dispozici 2563 barev, které lze zadat číselnou hodnotou. Tyto barvy pracují v systému RGB popsanému dříve. Prozatím jsme používali jednotku Graph2D_256.pas, která pracuje v režimu 256 barev, které jsou deklarovány pomocí osmibitových konstant - slRed, slYellow…(scanlineRed…) - Přístup k ostatním barvám v prostoru RGB zajišťuje jednotka Graph2D.pas, která s barvami zachází poněkud jinak. Grafickým procedurám je třeba barvu zadávat pomocí složek Red, Green Blue. Další odlišnost spočívá v tom, že tyto procedury pracují buď v objektu Canvas, anebo přímo v bitmapě. Procedury pracující z Canvasem jsou odlišeny zkratkou cv – např. procedura cvPolyLine(Q,i,255,0,0) sestrojí polygon napočítaný v poli Q.  Procedury přistupující přímo do bitmapy tuto „předponu“ nemají, před jejich použitím je však třeba bitmapu vytvořit. To lze provést metodou InitImage, která je v Graph2D k dispozici. Bitmapu vytvoříme nejlépe na událost onActivate ovládacího panelu. Sestrojení grafu funkce přímým přístupem do bitmapy pak vypadá takto:

 

procedure TControl_Panel.FormActivate(Sender:TObject);

begin

   Draw2D.InitImage(Image1.Width,Image1.Height);

end;

 

procedure TControl_Panel.Explicit_Curve(Sender:TObject);

var x,hx:Double;     Q     :TArrayOfPoints;     i      :Word;

function f.....

begin

   .......

  hx:=(x2-x1)/NumberOfSegments;i:=0;x:=x1;

  Repeat

   Q[i,1]:=x;Q[i,2]:=f(x);       {konstrukce křivky jako lomené čáry}

   x:=x+hx;i:=succ(i);

  Until x>x2+hx;

  PolyLine(Q,i,Color);

end;

 

Odkazy na kódy pracující v True Color režimu budeme uvádět takto:

 

True Color:                  zdrojový kód                                      spustitelný kód

 

 

Křivky zadané parametricky a polárně

 

Konstrukce těchto křivek je velmi podobná, ukážeme si ji na konkrétním příkladu:

 

Příklad 1: Sestrojte křivku určenou obecně parametrickými  rovnicemi ;  (jedná se o tzv. Lissajousovy křivky, které opisuje kyvadlo rozkmitané ve dvou navzájem kolmých rovinách a amplitudami ,  a úhlovými frekvencemi ;). Náš úkol řeší procedura Param_Curve. Nejprve jsou deklarovány konstanty t1, t2, které definují rozsah parametru. Místo proměnných x, hx je deklarován parametr t a jeho krok ht. Na deklaraci parametrických rovnic ve zdrojovém kódu musíme místo funkce použít proceduru, neboť jako výstup potřebujeme dvě proměnné - x, y.

 

procedure TControl_Panel.Param_Curve (Sender: TObject);

var i:Word;

procedure Lissajous(t:double;var x,y:double);

  begin

        x:=3*sin(3*t);  y:=3*cos(5*t);

     end;

begin

With Draw2D do

begin

  ClearImage(slWhite);          {vyplnění plochy bílou barvou}

  Scale(x1,x2,y1,y2);

  ht:=(t2-t1)/NumberOfSegments;

  t:=t1;i:=0;

  Repeat

     Lissajous(t,Q[i,1],Q[i,2]);

     t:=t+ht;i:=succ(i);

  Until t>t2+ht;

  PolyLine(Q,i,slBlue);

  end;

end;

 

Výstupní parametry od vstupních je třeba v hlavičce procedury oddělit klíčovým slovem var. Následují parametrické rovnice (s konkrétními volbami za a, b, j, y). Ve vlastní proceduře jsem se omezil opět výhradně na konstrukci křivky. Nejdříve je potřeba nastavit krok parametru ht a jeho počáteční hodnotu. V cyklu pak naplňujeme pole Q  parametrickými rovnicemi, dokud parametr t  krokem ht neproběhne celý interval át1;t2ñ (indexem i  počítáme body v posloupnosti Q). Nakonec křivku vykreslíme opět pomocí PolyLine.

 

 

Zde najdete kompletní  zdrojový kód               a zde                spustitelný kód

 

 

Příklad 2: opět poskytuje možnost měnit rovnici křivky za běhu programu.

 

Zde najdete kompletní  zdrojový kód               a zde                spustitelný kód

 

True Color:                              zdrojový kód                                       spustitelný kód

 

Příklad 3: Sestrojme křivku zadanou v polárních souřadnicích. Postupujeme zcela analogicky. Základem je procedura Polar_Curve, kam zadáváme rovnici v polárních souřadnicíchpřepočet do souřadnic kartézských:

 

procedure TControl_Panel.Polar_Curve (Sender: TObject);

var i:Word;

 

procedure Spiral(fi:real;var x,y:real); var rho:Real;

begin

    rho:=0.1*fi; x:=rho*cos(fi); y:=rho*sin(fi);

end;

 

begin

With Draw2D do

begin

  Scale(x1,x2,y1,y2); fi:=fi1;i:=0;  

  hfi:=(fi2-fi1)/NumberOfSegments;

Repeat

      Spiral(fi,Q[i,1],Q[i,2]);

      fi:=fi+hfi;i:=succ(i);

  Until fi>fi2+hfi;

  PolyLine(Q,i,slBlue);

 end;

end;

 

 

Zde najdete kompletní  zdrojový kód   a zde                spustitelný kód

True Color:                              zdrojový kód                           spustitelný kód

 

Příklad 4: Je známo, že tlumené kmity jsou určeny rovnicí . Znázorněme tyto kmity za předpokladu, že . Příslušnou funkci nyní definujeme jako funkci dvou proměnných:

 

function f(K,x:real):real;

 begin f:=K*exp(-x)*sin(10*x); end;

  

Konstrukce systému křivek probíhá nyní ve dvou do sebe vnořených cyklech: Ve vnějším probíhá   krokem  interval , ve vnitřním pak  krokem  interval .

 

 

Najdete zde opět kompletní     zdrojový kód               i kompletní                   spustitelný kód.

True Color:                              zdrojový kód                                                  spustitelný kód

 

Příklad 5: Zajímavý parametrický systém funkcí získáme předpisem

 

.

 

Na připojeném obrázku je sestrojen pro  krokem . Zeleně je sestrojen systém  (se znaménkem plus před odmocninou), modře pak systém  (se znaménkem mínus).

 

 

Příklad 6: Sestavme program, který bude sestrojovat částečné součty funkčních řad - . Program se příliš neliší od př. 5. Místo výrazů  vyčíslujeme výrazy  a neustále je přičítáme. Tento příklad si však přesto zaslouží několik poznámek. Především má uživatel opět možnost příslušnou řadu zadávat z ovládacího panelu: Zadaný výraz je opět vyčíslován procedurou Calc.

 

 

Aby tato procedura byla použitelná na Taylorovy řady, je do ní zařazena funkce Fa, která počítá faltoriál z celočíselného nezáporného argumentu. Zobrazený panel je připraven na sestrojení prvních dvaceti částečných součtů Taylorovy řady funkce sinus:

 

.

 

Dále je třeba zřejmě jednotlivé částečné součty od sebe barevně odlišit. To lze zajistit mnoha způsoby. V případě námi používaných osmibitových barev je možné nastavit počáteční barvu na jedničku a po každé křivce  toto číslo o jedničku zvýšit. Po dosažení určitého počtu barev se vrátit zpět na jedničku. V řešeném příkladu je použito pět barev, které se pravidelně střídají:

 

Konstrukce funkčních řad si zaslouží poznámku. Výběr barev byl v osmibitových barvách jednoduchý - stačilo postupovat podle jejich pořadí. Zde je třeba si barvy i jejich pořadí nadefinovat. Definujeme celkem pět barev, které se pravidelně střídají, a udává číslo křivky a tím i barvy (je využito funkce mod, která vrací hodnoty zbytku po celočíselném dělení):

 

Procedure Colors;

var Modulo:Byte;

begin

   Modulo:= n mod 5; 

   With Draw2D do

   Case Modulo of

     0:begin Red:=  0;Green:=  0;Blue:=150;end;

     1:begin Red:=  0;Green:=150;Blue:=  0;end;

     2:begin Red:=150;Green:=  0;Blue:=  0;end;

     3:begin Red:=120;Green:=  0;Blue:= 50;end;

     4:begin Red:= 50;Green:=  0;Blue:=120;end;

  end;

 end;

 

Ovládací panel je oproti připojenému obrázku v řešeném příkladu vylepšen tak, že pro zadávání řad jsou k dispozici dva boxy - jeden pro řady Taylorovy, jeden pro Fourierovy. Poslední poznámka se týká připojených výstupů z tohoto příkladu: Je zde zobrazeno prvních 100 částečných součtů Taylorovy řady

 

 

 

na hranici oboru konvergence a prvních deset částečných součtů Fourierovy řady

 

 

 

Zde najdete kompletní  zdrojový kód               a zde                spustitelný kód

True Color:                              zdrojový kód                                       spustitelný kód

 

Křivky zadané rovnicí

 

Nejjednodušší algoritmus pro sestrojování těchto křivek využívá rozdílu mezi fyzickými a logickými pixely (viz kpt. 2.1.). Křivku  je možno vykreslit samozřejmě pouze fyzickými pixely (viz světle modrá „křivka“ na přiložené zvětšenině). Každý takový pixel je však logicky obdélník o stranách  (reálná velikost stran závisí na rozměrech uživatelské kreslicí plochy). Je zřejmé, že fyzický pixel má být vykreslen právě tehdy, protíná-li křivka hranice tohoto pixelu. Je tedy třeba nastavit kroky   tak, aby „logickéobdélníky přesně pokrývaly „fyzicképixely. Kriteriem pro zobrazení fyzického pixelu jsou pak různá znaménka funkce  alespoň ve dvou vrcholech obdélníka. V algoritmu je tedy tedy nejdříve definovat funkci ,  výpis vlastního algoritmu následuje.

function f(x,y:double):double;        {definována kružnice x2+y2-16=0}

begin f:=x*x+y*y-16; end;

begin

hx:=(x2-x1)/Image1.Width;

hy:=(y2-y1)/Image1.Height;

x:=x1;i:=0;

Repeat

  y:=y1;

  Repeat

  if (f(x,y)*f(x,y+hy)<0) or (f(x,y+hy)*f(x+hx,y+hy)<0)

      then begin A[1]:=x;A[2]:=y;PutPoint(A,slBlue); end;

        y:=y+hy;

  Until y>y2;

  x:=x+hx;i:=succ(i);

until x>x2;

end;

 

Příklad 1: V tomto příkladu jsou sestrojeny grafy křivek

 

                      x(x2+y2)-2(x2-y2)=0

              x((x+1)2+y2)-2(x+1)2=0

 

 

Zde najdete kompletní  zdrojový kód   a zde    spustitelný kód

 

Příklad 2 je opět doplněn o možnost zadávat rovnice za běhu programu z ovládacího panelu.

 

Zde najdete kompletní  zdrojový kód   a zde    spustitelný kód

 

True Color:                              zdrojový kód               spustitelný kód

 

 

Podobně jako jsme v předcházející kapitole sestrojovali systémy funkcí Kf(x), můžeme nyní sestrojovat systémy f(x,y)=K, což jsou vlastně vrstevnice plochy z= f(x,y). Přibývá zde opět cyklus pro parametr K.

 

Příklad 3: Sestrojte vrstevnice plochy

 

V tomto řešeném příkladu je voleno celkem 40 vrstevnic.

 

 

Zde najdete kompletní  zdrojový kód               a zde                           spustitelný kód

 

V příkladu 4 je předchozí příklad upraven tak, že uživatel má možnost volit rovnici, kreslicí plochu, interval parametru a počet křivek na panelu. Podmínku

 

if (f(x,y)*f(x,y+hy)<0) or (f(x,y+hy)*f(x+hx,y+hy)<0)

 

(viz fragment kódu před př. 1) je třeba nahradit voláním funkce Calc, např.

 

h1:=Calc(f,ErrorReport);y:=y+hy;

h2:=Calc(f,ErrorReport);x:=x+hx;

h3:=Calc(f,ErrorReport);x:=x-hx;y:=y-hy;

if (h1*h2<0) or (h2*h3<0) then...

(předpokládáme, že f je vyhodnocovaný řetězec a h1,h2,h3 jsou proměnné deklarované jakou double).

 

Výhoda měnit rovnici křivky za běhu programu je ovšem zaplacena zpomalením výpočtu. Je to dáno tím, že procházíme statisíce (někdy i miliony bodů a v každém z nich třikrát voláme funkci Calc. Ta musí vyhodnocovaný řetězec vždy kompletně zpracovat, i když se předpis v cyklu vůbec nemění a je do něj potřeba pouze dosazovat různé hodnoty. Celý proces lze značně urychlit tím, že si řetězec před cyklem „předzpracujeme” funkcí PreCalc a v cyklu pak již pouze dosazujeme pomocí funkce PostCalc. Obě funkce jsou opět součástí knihovny Graph2D. Funkce Precalc(f:String;var ErrorReport):TExpression předzpracuje řetězec f a vrátí chybové hlášení (v případě chyby) a předzpracovaný výraz, který je možno uložit do proměnné Expression, která je deklarována opět v Graph2D. Funkce PostCalc pak proměnnou Expression vyhodnotí pro různé hodnoty proměnných. Příslušný fragment kódu pak vypadá takto:

 

Expression:=PreCalc(f,ErrorReport);

if ErrorReport<>’’ then     begin ShowMessage(ErrorReport);Exit;end;

Repeat

y:=y1;

Repeat

     h1:=PostCalc(Expression,ErrorReport);y:=y+hy;

if ErrorReport<>’’ then     begin ShowMessage(ErrorReport);Exit;end;

h2:=PostCalc(Expression,ErrorReport);x:=x+hx;

if ErrorReport<>’’ then     begin ShowMessage(ErrorReport);Exit;end;

h3:=PostCalc(Expression,ErrorReport);x:=x-hx;y:=y-hy;

if ErrorReport<>’’ then     begin ShowMessage(ErrorReport);Exit;end;

if (h1*h2)<0) or (h2*h3)<0) then

begin A[1]:=x;A[2]:=y;PutPoint(A,slBlue); end;

     y:=y+hy;

Until y>y2;

x:=x+hx;i:=succ(i);

until x>x2;

 

Kompletní kód zpracovaný pro True Color:    

 

                        zdrojový kód               a zde                           spustitelný kód

 

 

Síťová konstrukce: Výše uvedený algoritmus je sice poměrně jednoduchý a nejpřesnější, jakého lze na daném výstupním zařízení dosáhnout, jeho rychlost je však přes uvedené urychlení mnohdy nedostatečná. Konstrukci lze urychlit za cenu poněkud menší přesnosti následujícím způsobem: Na kreslicí plochu „položíme“ čtvercovou síť, přitom strana čtverce je celočíselným násobkem strany fyzického pixelu (ve světových souřadnicích je to tedy čtvercová síť, v uživatelských to však obecně jsou obdélníky o stranách hx, hy, neboť uživatel může mít procedurou Scale nastaveny na osách různé jednotky). Kreslicí plochu nyní nebudeme procházet bod po bodu, ale po těchto obdélnících. Testováním znamének funkce  ve vrcholech obdélníka budeme zjišťovat, zda křivka protíná jeho obvod. Najdeme-li dvě takové strany, pak metodou půlení intervalu zjistíme průsečíky A B s obvodem s přesností na jeden fyzický pixel. Těmito body pak proložíme úsečku, čímž vlastně křivku v tomto čtverci interpolujeme. Při realizaci programu musíme rozlišit, zda půlíme svislou či vodorovnou stranu obdélníka. Uveďme příklad „vodorovného“ půlení:

 

Procedure HorizHalf(x1,x2,y:Double; var x:Double);

{vstupní parametry v hlavičce mají stejný význam, jako při konstrukci osy x, výstupní parametr udává x- ovou souřadnici nalezeného průsečíku.}  

var i      :Integer;

        a,b,c:Double;                                                                                                                       

begin

  a:=x1;b:=x2;                                                             

  For i:=1 to Trunc(Step) do                                        

  begin                     {půlení intervalu a výběr intervalu s průsečíkem}

     c:=(a+b)/2;if f(a,y)*f(c,y)<0 then b:=c else a:=c;                                                         

  end;

  x:=c;                                                                                          {nastavení výstupní hodnoty}

end;

 

Dále je třeba zjistit, jak (a zda vůbec) křivka testovaný čtverec protíná. Křivka  protíná testovaný čtverec zřejmě tehdy,  když v jeho vrcholech má funkce  různá znaménka. Ohodnoťme vrcholy čtverce  postupně hodnotami ; ; ; , a to právě tehdy, když v příslušném vrcholu je , jinak vrcholu přiřaďme nulu. Celý čtverec tak může být ohodnocen hodnotami   - z hlediska naší konstrukce existuje právě šestnáct způsobů, jak může sestrojovaná křivka testovaný čtverec protínat. Čtverec na obrázku vlevo je ohodnocen číslem , čtverec vpravo pak číslem . Součet těchto ohodnocení je  a je zřejmé, že postup konstrukce bude v obou případech stejný - v obou případech je třeba hledat průsečíky na úsečkách , . Ačkoli je tedy celkový počet případů, které je třeba řešit, čtrnáct (čtverce s ohodnoceními 0 resp.15 křivka neprotíná), program stačí větvit pouze na sedm větví. Zajímavé vyjádření ohodnocení je ve dvojkové soustavě, kdy bod je ohodnocen pouze nulou, či jedničkou. Např. pro levý obrázek máme:

       

 

Konečně je třeba si uvědomit, že v případě  resp.  nelze jednoznačně rozhodnout, jak interpolační úsečky vlastně sestrojit (viz možnosti 5, 10 v připojené sekvenci kódu - obrázek na další straně). V tom případě je třeba vybrat jen jednu z obou možností.

 

begin

hx:=Step*(x2-x1)/Image1.Width;      {po obvyklém přečtení hodnot z Control}

hy:=Step*(y2-y1)/Image1.Height;   {Panelu a nastavení měřítka nastavíme }

x:=x1;           {kroky hx, hy tak,aby přesně odpovídaly uživatelským rozměrům}

Repeat                         {fyzického pixelu a začínáme procházet síť}

y:=y1;                                                                     {Proměnná h bude určovat, na kterých}

Repeat                         {stranách čtverce byly nalezeny průsečíky}

  h:=0;

  if f(x,y)      >0 then h:=h+8;

  if f(x+hx,y)   >0 then h:=h+4;

  if f(x+hx,y+hy)>0 then h:=h+2;

  if f(x   ,y+hy)>0 then h:=h+1;

  Case h of

    1,14:begin

           K[1]:=x;VertHalf(y,y+hy,x,K[2]);

           HorizHalf(x,x+hx,y+hy,L[1]);L[2]:=y+hy;

           Line(K,L,Red,Green,Blue);

         end;

    5,10:begin

           K[1]:=x   ;VertHalf(y,y+hy,x+hx,K[2]);

           HorizHalf(x,x+hx,y,L[1]);L[2]:=y;

           Line(K,L,Red,Green,Blue);

           K[1]:=x   ;VertHalf(y,y+hy,x,K[2]);

           HorizHalf(x,x+hx,y+hy,L[1]);L[2]:=y+hy;

           Line(K,L,Red,Green,Blue);

          end;

    ………………………………………………………………..          {ostatní případy analogicky}

      end;

  y:=y+hy;

Until y>y2;

x:=x+hx;

until x>x2;

 

          

 

V příkladu 5 je předchozí algoritmus zpracován.

 

Zde najdete kompletní  zdrojový kód               a zde                spustitelný kód

 

Podobným způsobem lze konstruovat plochy určené rovnicí  v prostoru. Zde se podobným způsoben hledají průsečíky plochy s hranami krychle a plocha se interpoluje pomocí trojúhelníků. Různých možností však není čtrnáct resp. sedm, ale 254 resp. 127. Tímto problémem se budeme zabývat dále