Ve třetím článku o fyzikálním enginu, který je součástí knihovny LÖVE, si ukážeme, jakým způsobem je možné v tomto systému vytvořit takzvané pružné (či elastické) vazby mezi několika tělesy. Taktéž si vysvětlíme, jak tyto vazby ovlivní vzájemnou interakci těles v simulovaném světě.
Obsah
1. Fyzikální engine implementovaný v knihovně LÖVE (3.část)
2. Systémy několika těles propojených s využitím elastických vazeb
3. Vytvoření simulovaného světa s tělesy spojenými elastickými vazbami
5. Vytvoření pružné vazby mezi dvojicí těles
6. Zobrazení pružné vazby v simulovaném světě
7. První demonstrační příklad – vytvoření pružné vazby mezi dvěma míčky
9. Druhý demonstrační příklad – 25 volně se pohybujících míčků
10. Programové vytvoření elastických vazeb
11. Třetí demonstrační příklad – vazby mezi míčky v mřížce 25×25
12. Čtvrtý demonstrační příklad – vytvoření horizontálních i vertikálních vazeb
13. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
1. Fyzikální engine implementovaný v knihovně LÖVE (3.část)
V úvodním článku o fyzikálním enginu love.physics, který je součástí knihovny LÖVE, jsme se seznámili s tím, k čemu se vlastně tento engine využívá a jakým způsobem je ho možné zařadit do vyvíjených aplikací napsaných v programovacím jazyku Lua. Současně jsme se seznámili se základními typy objektů, s nimiž tento engine pracuje a z nichž se vytváří simulovaný (například herní) svět. Na díl první velmi úzce navázal druhý díl věnovaný konceptu takzvaných obalových těles používaných při detekci kolizí v simulovaném světě a taktéž se způsobem řešení některých problémů plynoucích z nepřesných výpočtů těchto kolizí. Připomeňme si, že kvůli přesnějšímu výpočtu kolizí je nutné některá tělesa označit příznakem „střela“.
Obrázek 1: Obalová tělesa (bounding boxes) zobrazená pro všechny objekty v simulovaném světě. Princip obalových těles jsme si vysvětlili v předchozí části seriálu.
Fyzikální engine love.physics dokáže mezi tělesy vytvořit i přímé vazby, z nichž nejpoužívanější je elastická vazba (můžeme si zjednodušeně představit, že tělesa jsou spojena pružinkami). Z hlediska simulace objektů s elastickými vazbami je nejdůležitější silou ovlivňující trajektorii těchto těles pružná (elastická) síla. Jedná se, na rozdíl od globálně působící gravitační síly či odporu okolního prostředí, o sílu lokální, protože působí vždy mezi dvojicí těles, která jsou explicitně spojena elastickou vazbou. Tato síla může mít vliv na pohyb hmotného středu těles (těžiště pevných těles), popř. i na jejich otáčení v případě, že je konec elastické vazby umístěn mimo těžiště. Směr a velikost této síly je vypočten s využitím Hookova zákona. Nejdůležitější vlastností elastické vazby, kterou si můžeme představit jako neviditelnou pružinu nataženou mezi dvojici těles, je klidová délka, které se pružina při svém natažení či stlačení snaží dosáhnout a dále konstanty odpovídající síle pružiny a míře jejího tlumení (čím větší je tlumení, k tím menším zákmitům dochází a naopak).
Obrázek 2: Při kolizi těles dochází k protnutí jejich bounding boxů (podrobnější informace byly opět uvedeny minule).
2. Systémy několika těles propojených s využitím elastických vazeb
Pružné (elastické) vazby je možné použít pro vytvoření takzvaného systému vázaných těles. Jedná se o takové systémy, ve kterých jsou mezi jednotlivými tělesy vytvořeny pevné nebo elastické vazby. Pevné vazby nedovolují změnit vzdálenost mezi tělesy ve vazbě a řešení vzájemné polohy těles je v tomto případě zajištěno pomocí poměrně složité inverzní kinematiky, která však není v současné verzi systému LÖVE implementována. Naproti tomu u elastických vazeb se vzdálenost těchto těles může v určitém intervalu hodnot měnit. V převážné většině případů se jedná o vazby vytvořené mezi dvojicí těles (vazby totiž simulují pružiny natažené mezi dvojicí těles). Složitější vazby se vytváří složením těchto jednoduchých vazeb, což si ukážeme později na demonstračních příkladech.
Obrázek 3: Začátek prvního demonstračního příkladu. Vidíme zde dvojici míčků spojených elastickou vazbou (ta je naznačena úsečkou).
Vazby mezi tělesy jsou většinou zadávány už při vytváření celého modelu, ovšem v systému LÖVE je možné počet vazeb i jejich vlastnosti měnit i v průběhu simulace, což povoluje například simulovat i rozpad celého objektu či naopak jeho složení z několika částí. Mimochodem, v enginu love.physics je dostupných hned několik typů vazeb, nejenom vazby elastické. Ostatně stačí se podívat na následující tabulku, v níž jsou podporované vazby vypsány:
# | Objekt představující vazbu mezi tělesy |
---|---|
1 | DistanceJoint |
2 | FrictionJoint |
3 | GearJoint |
4 | MouseJoint |
5 | PrismaticJoint |
6 | PulleyJoint |
7 | RevoluteJoint |
8 | RopeJoint |
9 | WeldJoint |
10 | WheelJoint |
Obrázek 4: První demonstrační příklad: kvádr se nachází těsně před dopadem na podložku. To stejné platí pro dvojici míčků propojených elastickými vazbami.
3. Vytvoření simulovaného světa s tělesy spojenými elastickými vazbami
Při inicializaci aplikace, ve které je počítán stav simulovaného dvourozměrného světa, se musí nastavit hmotnosti, počáteční pozice, rychlosti a samozřejmě taktéž tvary těles, což je téma, které již důvěrně známe z obou předchozích částí tohoto seriálu. Následně jsou vytvořeny vazby mezi tělesy, přičemž v závislosti na potřebách aplikace je možné tyto vazby vygenerovat buď ručně nebo automaticky (obojí způsob si ukážeme v demonstračních příkladech). Manuální i automatická tvorba musí brát v úvahu geometrickou stabilitu celého systému. Této stability se dosáhne vytvořením vazeb na hranách zadaných těles a dále vytvořením pomocných vazeb po úhlopříčkách, což si taktéž ukážeme v jednom demonstračním příkladu (bude se samozřejmě jednat pouze o dvojrozměrný případ). Systémy s elastickými vazbami jsou vhodné pro použití především u těch modelů, které v průběhu simulace (a s ní souvisejících deformací modelu) nemění svoji topologii.
Obrázek 5: První demonstrační příklad: nyní došlo k odrazu kvádru od podložky a následně i k odrazu obou spojených míčků (od kvádru).
V některých případech však může být žádoucí, aby bylo možné modelovaný objekt rozdělit, tj. programově přerušit některé elastické vazby (a to kdykoli během simulace). Této vlastnosti lze dosáhnout specifikací maximální možné síly, která může působit mezi dvěma tělesy spojenými elastickou vazbou. V případě, že skutečná síla překročí tuto mez, dojde k „přetržení“ pružiny a elastická vazba je zrušena. Pokud vznikne nutnost modelovat objekt, u nějž se topologie mění zásadnějším způsobem, je vhodnější použít dynamicky vázané systémy. Tyto pokročilejší techniky sice systém LÖVE (a engine love.physics) ve své současné verzi nepodporuje, ovšem lze je napodobit programově, neboť informace o stavu každé vazby (nejenom elastické) je možné poměrně jednoduše získat v kterémkoli okamžiku simulace.
Obrázek 6: První demonstrační příklad: dopad kvádru zpět na podložku.
4. Objekt typu DistanceJoint
Elastická vazba, která je v systému LÖVE (resp. přesněji řečeno v modulu love.physics) představovaná objektem typu DistanceJoint, se vytvoří voláním konstruktoru love.physics.newDistanceJoint(). Tento konstruktor musí být zavolaný se šesti či nově dokonce se sedmi parametry. První dva parametry představují dvojici těles, které mají být spojeny pomocí elastické vazby (musí se tedy jednat o objekty typu Body). Další dvojice parametrů udává souřadnice prvního konce vazby, tj. místo, kde je pružina ukotvena v prvním tělese, a následující dvojice parametrů pak specifikuje souřadnice druhého konce vazby, tj. bod nalézající se ve druhém tělese (většinou oba koncové body leží v těžišti těles, viz všechny následující demonstrační příklady, ve skutečnosti to však není nutné). Posledním parametrem je možné specifikovat, zda se mají obě tělesa spojená pružnou vazbou vzájemně odrážet či nikoli.
Obrázek 7: První demonstrační příklad: kvádr již pouze klouže po podložce.
Konstruktor love.physics.newDistanceJoint() vytvoří objekt, nad nímž je možné volat metody vypsané v následující tabulce:
# | Metoda objektu DistanceJoint | Popis metody | Poznámka |
---|---|---|---|
1 | getAnchors() | získání bodů, ve kterých je pružina k tělesům připojena. | |
2 | getBodies() | získání obou těles, ke kterým je pružina. | přidáno ve verzi 0.9.2 |
3 | getLength() | získání klidové vzdálenosti mezi oběma tělesy. | |
4 | getFrequency() | získání rychlosti reakce pružiny na její natažení či stlačení. | |
5 | getDamping() | získání míry tlumení pružiny (eliminace zákmitů). | odstraněno ve verzi 0.8.0 |
6 | getDampingRatio() | získání míry tlumení pružiny (eliminace zákmitů). | přidáno do verze 0.8.0 |
7 | setLength() | nastavení klidové vzdálenosti mezi oběma tělesy. | |
8 | setFrequency() | nastavení rychlosti reakce pružiny na její natažení či stlačení. | |
9 | setDamping() | nastavení míry tlumení. | odstraněno ve verzi 0.8.0 |
10 | setDampingRatio() | nastavení míry tlumení. | přidáno do verze 0.8.0 |
11 | getReactionForce() | aktuálně působící síla mezi tělesy. | přidáno do verze 0.8.0 |
12 | destroy() | zrušení elastické vazby působící mezi dvěma tělesy. |
Obrázek 8: První demonstrační příklad: kvádr již pouze klouže po podložce.
5. Vytvoření pružné vazby mezi dvojicí těles
V prvním demonstračním příkladu, jehož úplný zdrojový kód bude uveden v sedmé kapitole, je ukázáno, jak je možné s využitím pružných (elastických) vazeb navzájem propojit dvojici míčků. Mezi dva míčky je vložena neviditelná „pružina“ představující elastickou vazbu. Oba konce pružiny jsou umístěny do středu míčků – jedná se o třetí až šestý parametr funkce love.physics.newDistanceJoint(), které se předávají jak objekty představující obě tělesa spojená vazbami, tak i souřadnice koncových bodů pružin (sami si můžete vyzkoušet, jak se simulace bude lišit v případě, že elastické vazby nebudou ukončeny v geometrických středech míčků). Následně je s využitím metody joint:setLength() nastavena klidová délka pružiny a dále metodou joint:setFrequency() rychlost odezvy pružiny na její natažení či naopak stlačení.
Vytvoření těles (body), jejich tvarů (shape) i elastické vazby (joint) je realizováno v callback funkci love.load():
-- -- Funkce volaná při inicializaci aplikace. -- function love.load() ... ... ... -- těleso představující první míček objects.ball1 = {} -- počáteční umístění tělesa objects.ball1.body = -- tvar a rozměry objects.ball1.shape = -- propojení tvaru s tělesem objects.ball1.fixture = -- těleso představující druhý míček objects.ball2 = {} -- počáteční umístění tělesa objects.ball2.body = -- tvar a rozměry objects.ball2.shape = -- propojení tvaru s tělesem objects.ball2.fixture = -- vytvoření pružné vazby mezi míčky local b1 = objects.ball1.body local b2 = objects.ball2.body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) -- klidová délka pružiny joint:setLength(100) -- rychlost odezvy pružiny na její natažení či naopak stlačení joint:setFrequency(10) ... ... ... end
Obrázek 9: První demonstrační příklad: pokračování animace.
6. Zobrazení pružné vazby v simulovaném světě
Pozice pružin (elastických vazeb) je při vykreslování simulovaného světa naznačena s využitím úseček, které spojují středy obou míčků. Vzhledem k tomu, že nemáme přímé informace o pozici „pružiny“ v ploše simulovaného světa, použijeme namísto toho známé informace – víme totiž, že oba konce pružin jsou navázány na středy obou míčků a ty dokážeme zjistit snadno, jelikož středy míčků přesně odpovídají jejich těžištím. O vykreslení scény se stará zdrojový kód umístěný do callback funkce love.draw(), takže si nyní ukažme tu část těla této funkce, která vykreslení elastické vazby zajišťuje:
-- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() ... ... ... -- vykreslení pružné vazby love.graphics.setColor(250, 150, 250) -- zjistíme těžiště obou míčků a propojíme je úsečkou love.graphics.line(objects.ball1.body:getX(), objects.ball1.body:getY(), objects.ball2.body:getX(), objects.ball2.body:getY()) ... ... ... end
Obrázek 10: První demonstrační příklad: pokračování animace.
7. První demonstrační příklad – vytvoření pružné vazby mezi dvěma míčky
Všechny důležité informace o způsobu vytvoření elastické vazby mezi dvojicí míčků jsme si již řekli v předchozích dvou kapitolách, takže nám již pouze zbývá si ukázat úplný zdrojový kód dnešního prvního demonstračního příkladu. V něm jsou vytvořena čtyři tělesa: nehybná zešikmená podlaha, kvádr, který na tuto podlahu dopadá a dvojice míčků. Ovládání tohoto příkladu je velmi jednoduché – klávesou [Esc] je ho možné ukončit a klávesou [R] simulaci znovu nastartovat (v podstatě se znovu zavolá callback funkce love.load()):
-- -- Knihovna LÖVE -- -- Dvacátý čtvrtý demonstrační příklad -- -- Vytvoření pružné vazby mezi dvěma tělesy. -- -- rozměry okna, do něhož se bude provádět vykreslování width = 600 height = 600 ground_height = 50 -- tíhové zrychlení G = 9.81 -- počet pixelů odpovídající jednomu metru v simulovaném světě pixels_per_meter = 64 -- -- Funkce volaná při inicializaci aplikace. -- function love.load() -- načtení standardního fontu a nastavení grafického režimu local font = love.graphics.newFont(love.default_font, 40) -- inicializace grafického režimu love.graphics.setMode(width, height, false, false, 0) -- nastavení rozměrů simulovaného světa -- (mapování fyzikálních jednotek na pixely) love.physics.setMeter(pixels_per_meter) -- vytvoření simulovaného světa world = love.physics.newWorld(0, G * pixels_per_meter, true) -- tabulka s tělesy objects = {} -- těleso představující zemi objects.ground = {} -- počáteční umístění tělesa objects.ground.body = love.physics.newBody(world, width/2, height-ground_height/2-40) -- tvar a rozměry objects.ground.shape = love.physics.newRectangleShape(0, 0, width, ground_height, -0.15) -- propojení tvaru s tělesem objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape, 5) objects.ground.fixture:setRestitution(0.7) -- těleso představující první míček objects.ball1 = {} -- počáteční umístění tělesa objects.ball1.body = love.physics.newBody(world, 390, 50, "dynamic") -- tvar a rozměry objects.ball1.shape = love.physics.newCircleShape(30) -- propojení tvaru s tělesem objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5) objects.ball1.fixture:setRestitution(0.9) -- těleso představující druhý míček objects.ball2 = {} -- počáteční umístění tělesa objects.ball2.body = love.physics.newBody(world, 510, 50, "dynamic") -- tvar a rozměry objects.ball2.shape = love.physics.newCircleShape(30) -- propojení tvaru s tělesem objects.ball2.fixture = love.physics.newFixture(objects.ball2.body, objects.ball2.shape, 5) objects.ball2.fixture:setRestitution(0.9) -- vytvoření pružné vazby mezi míčky local b1 = objects.ball1.body local b2 = objects.ball2.body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) joint:setLength(100) joint:setFrequency(10) -- těleso představující kvádr objects.block3 = {} -- počáteční umístění tělesa objects.block3.body = love.physics.newBody(world, 450, 150, "dynamic") -- tvar a rozměry objects.block3.shape = love.physics.newRectangleShape(200, 100) -- propojení tvaru s tělesem objects.block3.fixture = love.physics.newFixture(objects.block3.body, objects.block3.shape, 5) objects.block3.fixture:setRestitution(0.0) end -- -- Funkce volaná cca 30x za sekundu -- function love.update(dt) -- přepočítat parametry celého "světa" world:update(dt) local delay = 1/30 if dt < delay then love.timer.sleep(delay - dt) end end -- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() -- vykreslení země love.graphics.setColor(72, 160, 14) love.graphics.polygon("line", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- vykreslení prvního tělesa (míčku) love.graphics.setColor(250, 150, 150) love.graphics.circle("line", objects.ball1.body:getX(), objects.ball1.body:getY(), objects.ball1.shape:getRadius()) -- vykreslení druhého tělesa (míčku) love.graphics.setColor(150, 150, 250) love.graphics.circle("line", objects.ball2.body:getX(), objects.ball2.body:getY(), objects.ball2.shape:getRadius()) -- vykreslení pružné vazby love.graphics.setColor(250, 150, 250) love.graphics.line(objects.ball1.body:getX(), objects.ball1.body:getY(), objects.ball2.body:getX(), objects.ball2.body:getY()) -- vykreslení třetího tělesa (kvádru) love.graphics.setColor(150, 250, 150) love.graphics.polygon("line", objects.block3.body:getWorldPoints(objects.block3.shape:getPoints())) -- výpis zprávy love.graphics.setColor(250, 250, 250) love.graphics.print("Escape: to exit.", 20, 615) end -- -- Callback funkce zavolaná při stisku klávesy. -- function love.keypressed(k) if k == 'escape' then -- klávesou "Escape" se aplikace ukončí love.event.quit() elseif k == 'r' then -- restart celé simulace po stisku klávesy [R] love.load() end end -- -- finito --
Obrázek 11: První demonstrační příklad: simulace je téměř u konce.
8. Složitější topologie vazeb
V úvodních kapitolách jsme si mj. řekli, že pro zajištění stability celého systému těles, které jsou navzájem propojeny elastickými vazbami, je většinou nutné mezi tato tělesa umístit větší množství vazeb. V případě trojrozměrných těles se většinou jedná o stěnové a tělesové úhlopříčky, ovšem v dvojrozměrném prostoru je situace poněkud jednodušší – většinou totiž postačuje vytvořit vazby takovým způsobem, aby každá trojice nejbližších těles (myšleno samozřejmě v klidovém stavu celého systému) byla propojena trojicí elastických vazeb. Například ve třetím demonstračním příkladu, jehož popis následuje níže, je celý systém složen z 25 míčků tvořících mřížku 5×5 míčků.
Obrázek 12: Druhý demonstrační příklad – začátek simulace.
Zdánlivě by mohlo postačovat propojit tělesa pouze horizontálními a vertikálními vazbami (takže se při zobrazení vazeb skutečně použije pravidelná mřížka), ovšem takto vytvořený systém po nárazu na podložku samovolně „splaskne“, protože neexistuje žádná vazba, která by zabránila vzájemnému přiblížení či naopak vzdálení dvou protilehlých vrcholů čtverců. Aby k tomuto jevu nedocházelo, postačuje většinou vytvořit i úhlopříčné vazby v každém čtverci mřížky. Taktéž je možné vytvořit úhlopříčné vazby mezi dvojicí či trojicí čtverců atd. – výsledný systém potom klade při pokusu o ohyb mnohem větší odpor.
Obrázek 13: Druhý demonstrační příklad – změna vzdáleností míčků na začátku simulace.
Obrázek 14: Druhý demonstrační příklad – pokračování simulace.
9. Druhý demonstrační příklad – 25 volně se pohybujících míčků
Vliv elastických vazeb na pohyb těles, která jsou těmito vazbami svázána, nelze v žádném případě podceňovat. Ostatně se můžeme podívat na další demonstrační příklad, v němž elastické vazby nejsou použity. Tento příklad bude sloužit jako základ pro oba příklady následující. Při inicializaci je kromě již obligátní podlahy v simulovaném světě vytvořeno i dvacet pět míčků, které ve výchozím stavu (na začátku simulace) tvoří pravidelnou mřížku 5×5 míčků. Po spuštění programu míčky dopadnou na podlahu a začnou se od ní i od sebe odrážet různými směry, přičemž na ně nepůsobí žádná elastická vazba. Podívejme se nyní na několik snímků z této simulace (další tři snímky již byly uvedeny v předchozí kapitole):
Obrázek 15: Druhý demonstrační příklad – pokračování simulace.
Obrázek 16: Druhý demonstrační příklad – pokračování simulace.
Obrázek 17: Druhý demonstrační příklad – pokračování simulace.
Obrázek 18: Druhý demonstrační příklad – pokračování simulace.
Obrázek 19: Druhý demonstrační příklad – pokračování simulace.
Obrázek 20: Druhý demonstrační příklad – pokračování simulace.
Následuje úplný zdrojový kód druhého demonstračního příkladu:
-- -- Knihovna LÖVE -- -- Dvacátý pátý demonstrační příklad -- -- 25 míčků v mřížce 5x5 -- -- rozměry okna, do něhož se bude provádět vykreslování width = 600 height = 600 ground_height = 50 -- tíhové zrychlení G = 9.81 -- počet pixelů odpovídající jednomu metru v simulovaném světě pixels_per_meter = 64 -- -- Funkce volaná při inicializaci aplikace. -- function love.load() -- načtení standardního fontu a nastavení grafického režimu local font = love.graphics.newFont(love.default_font, 40) -- inicializace grafického režimu love.graphics.setMode(width, height, false, false, 0) -- nastavení rozměrů simulovaného světa -- (mapování fyzikálních jednotek na pixely) love.physics.setMeter(pixels_per_meter) -- vytvoření simulovaného světa world = love.physics.newWorld(0, G * pixels_per_meter, true) -- tabulka s tělesy objects = {} -- těleso představující zemi objects.ground = {} -- počáteční umístění tělesa objects.ground.body = love.physics.newBody(world, width/2, height-ground_height/2-40) -- tvar a rozměry objects.ground.shape = love.physics.newRectangleShape(0, 0, width, ground_height, -0.15) -- propojení tvaru s tělesem objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape, 5) objects.ground.fixture:setRestitution(0.7) objects.mesh = {} for i = 1, 5 do objects.mesh[i] = {} for j = 1, 5 do -- těleso představující míček local obj = {} -- počáteční umístění tělesa obj.body = love.physics.newBody(world, 300 + i * 30, 50 + j * 30, "dynamic") -- tvar a rozměry obj.shape = love.physics.newCircleShape(10) -- propojení tvaru s tělesem obj.fixture = love.physics.newFixture(obj.body, obj.shape, 5) obj.fixture:setRestitution(0.9) objects.mesh[i][j] = obj end end end -- -- Funkce volaná cca 30x za sekundu -- function love.update(dt) -- přepočítat parametry celého "světa" world:update(dt) local delay = 1/30 if dt < delay then love.timer.sleep(delay - dt) end end -- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() -- vykreslení země love.graphics.setColor(72, 160, 14) love.graphics.polygon("line", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- vykreslení všech míčků love.graphics.setColor(250, 150, 150) for i = 1, 5 do for j = 1, 5 do local obj = objects.mesh[i][j] love.graphics.circle("line", obj.body:getX(), obj.body:getY(), obj.shape:getRadius()) end end -- výpis zprávy love.graphics.setColor(250, 250, 250) love.graphics.print("Escape: to exit.", 20, 615) end -- -- Callback funkce zavolaná při stisku klávesy. -- function love.keypressed(k) if k == 'escape' then -- klávesou "Escape" se aplikace ukončí love.event.quit() elseif k == 'r' then -- restart celé simulace po stisku klávesy [R] love.load() end end -- -- finito --
10. Programové vytvoření elastických vazeb
Na druhý demonstrační příklad, jehož zdrojový kód byl uveden v předchozí kapitole, navážeme příkladem třetím, v němž se bude taktéž používat 25 míčků v mřížce 5×5 míčků, ovšem míčky již budou vzájemně svázány vertikálními vazbami. Co to v praxi bude znamenat? Mezi každými dvěma míčky ležícími pod sebou bude natažena „pružina“, takže celkový počet elastických vazeb dosáhne dvaceti (pět sloupců míčků, v každém sloupci čtyři elastické vazby). Podívejme se nyní na programový kód, který tyto elastické vazby vytvoří při inicializaci aplikace:
-- -- Funkce volaná při inicializaci aplikace. -- function love.load() ... ... ... -- vytvoření mřížky 5×5 z míčků for i = 1, 5 do objects.mesh[i] = {} for j = 1, 5 do -- těleso představující míček local obj = {} -- počáteční umístění tělesa obj.body = love.physics.newBody(world, 300 + i * 30, 50 + j * 30, "dynamic") -- tvar a rozměry obj.shape = love.physics.newCircleShape(10) -- propojení tvaru s tělesem obj.fixture = love.physics.newFixture(obj.body, obj.shape, 5) obj.fixture:setRestitution(0.9) objects.mesh[i][j] = obj end end -- vytvoření pružné vazby mezi míčky for i = 1, 5 do for j = 1, 4 do local obj = {} local b1 = objects.mesh[i][j].body local b2 = objects.mesh[i][j+1].body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) joint:setLength(30) joint:setFrequency(10) end end end
Pro lepší ilustraci vlivu elastických vazeb na simulaci je vhodné si vazby zobrazit přímo v simulovaném světě. To je poměrně snadné, což ukazuje i následující příklad:
-- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() ... ... ... -- vykreslení pružné vazby love.graphics.setColor(150, 150, 250) for i = 1, 5 do for j = 1, 4 do local obj1 = objects.mesh[i][j] local obj2 = objects.mesh[i][j+1] love.graphics.line(obj1.body:getX(), obj1.body:getY(), obj2.body:getX(), obj2.body:getY()) end end ... ... ... end
Takto vytvořené elastické vazby ovlivní stav simulovaného světa, což si opět můžeme ověřit na sérií snímků:
Obrázek 21: Třetí demonstrační příklad – začátek simulace.
Obrázek 22: Třetí demonstrační příklad – pokračování simulace.
Obrázek 23: Třetí demonstrační příklad – pokračování simulace.
Obrázek 24: Třetí demonstrační příklad – pokračování simulace.
Obrázek 25: Třetí demonstrační příklad – pokračování simulace.
Obrázek 26: Třetí demonstrační příklad – pokračování simulace.
Obrázek 27: Třetí demonstrační příklad – pokračování simulace.
11. Třetí demonstrační příklad – vazby mezi míčky v mřížce 25×25
Elastické vazby mezi míčky, které se v mřížce 5×5 míčků nachází nad sebou, jsou implementovány v dnešním třetím demonstračním příkladu, jehož úplný zdrojový kód je umístěn pod tímto odstavcem:
-- -- Knihovna LÖVE -- -- Dvacátý šestý demonstrační příklad -- -- Vytvoření pružných vazeb mezi několika tělesy. -- -- rozměry okna, do něhož se bude provádět vykreslování width = 600 height = 600 ground_height = 50 -- tíhové zrychlení G = 9.81 -- počet pixelů odpovídající jednomu metru v simulovaném světě pixels_per_meter = 64 -- -- Funkce volaná při inicializaci aplikace. -- function love.load() -- načtení standardního fontu a nastavení grafického režimu local font = love.graphics.newFont(love.default_font, 40) -- inicializace grafického režimu love.graphics.setMode(width, height, false, false, 0) -- nastavení rozměrů simulovaného světa -- (mapování fyzikálních jednotek na pixely) love.physics.setMeter(pixels_per_meter) -- vytvoření simulovaného světa world = love.physics.newWorld(0, G * pixels_per_meter, true) -- tabulka s tělesy objects = {} -- těleso představující zemi objects.ground = {} -- počáteční umístění tělesa objects.ground.body = love.physics.newBody(world, width/2, height-ground_height/2-40) -- tvar a rozměry objects.ground.shape = love.physics.newRectangleShape(0, 0, width, ground_height, -0.15) -- propojení tvaru s tělesem objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape, 5) objects.ground.fixture:setRestitution(0.7) objects.mesh = {} for i = 1, 5 do objects.mesh[i] = {} for j = 1, 5 do -- těleso představující míček local obj = {} -- počáteční umístění tělesa obj.body = love.physics.newBody(world, 300 + i * 30, 50 + j * 30, "dynamic") -- tvar a rozměry obj.shape = love.physics.newCircleShape(10) -- propojení tvaru s tělesem obj.fixture = love.physics.newFixture(obj.body, obj.shape, 5) obj.fixture:setRestitution(0.9) objects.mesh[i][j] = obj end end -- vytvoření pružné vazby mezi míčky for i = 1, 5 do for j = 1, 4 do local obj = {} local b1 = objects.mesh[i][j].body local b2 = objects.mesh[i][j+1].body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) joint:setLength(30) joint:setFrequency(10) end end end -- -- Funkce volaná cca 30x za sekundu -- function love.update(dt) -- přepočítat parametry celého "světa" world:update(dt) local delay = 1/30 if dt < delay then love.timer.sleep(delay - dt) end end -- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() -- vykreslení země love.graphics.setColor(72, 160, 14) love.graphics.polygon("line", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- vykreslení všech míčků love.graphics.setColor(250, 150, 150) for i = 1, 5 do for j = 1, 5 do local obj = objects.mesh[i][j] love.graphics.circle("line", obj.body:getX(), obj.body:getY(), obj.shape:getRadius()) end end -- vykreslení pružné vazby love.graphics.setColor(150, 150, 250) for i = 1, 5 do for j = 1, 4 do local obj1 = objects.mesh[i][j] local obj2 = objects.mesh[i][j+1] love.graphics.line(obj1.body:getX(), obj1.body:getY(), obj2.body:getX(), obj2.body:getY()) end end -- výpis zprávy love.graphics.setColor(250, 250, 250) love.graphics.print("Escape: to exit.", 20, 615) end -- -- Callback funkce zavolaná při stisku klávesy. -- function love.keypressed(k) if k == 'escape' then -- klávesou "Escape" se aplikace ukončí love.event.quit() elseif k == 'r' then -- restart celé simulace po stisku klávesy [R] love.load() end end -- -- finito --
12. Čtvrtý demonstrační příklad – vytvoření horizontálních i vertikálních vazeb
Předchozí demonstrační příklad můžeme velmi snadno upravit takovým způsobem, aby se kromě vertikálních vazeb vytvořených mezi míčky, které se v mřížce nachází nad sebou, použily i vazby mezi sousedními horizontálními míčky. Úprava zdrojového kódu je snadná a především přímočará, takže se jen podívejme na jeho výslednou podobu:
-- -- Knihovna LÖVE -- -- Dvacátý sedmý demonstrační příklad -- -- Vytvoření pružných vazeb mezi několika tělesy. -- -- rozměry okna, do něhož se bude provádět vykreslování width = 600 height = 600 ground_height = 50 -- tíhové zrychlení G = 9.81 -- počet pixelů odpovídající jednomu metru v simulovaném světě pixels_per_meter = 64 -- -- Funkce volaná při inicializaci aplikace. -- function love.load() -- načtení standardního fontu a nastavení grafického režimu local font = love.graphics.newFont(love.default_font, 40) -- inicializace grafického režimu love.graphics.setMode(width, height, false, false, 0) -- nastavení rozměrů simulovaného světa -- (mapování fyzikálních jednotek na pixely) love.physics.setMeter(pixels_per_meter) -- vytvoření simulovaného světa world = love.physics.newWorld(0, G * pixels_per_meter, true) -- tabulka s tělesy objects = {} -- těleso představující zemi objects.ground = {} -- počáteční umístění tělesa objects.ground.body = love.physics.newBody(world, width/2, height-ground_height/2-40) -- tvar a rozměry objects.ground.shape = love.physics.newRectangleShape(0, 0, width, ground_height, -0.15) -- propojení tvaru s tělesem objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape, 5) objects.ground.fixture:setRestitution(0.7) objects.mesh = {} for i = 1, 5 do objects.mesh[i] = {} for j = 1, 5 do -- těleso představující míček local obj = {} -- počáteční umístění tělesa obj.body = love.physics.newBody(world, 300 + i * 30, 50 + j * 30, "dynamic") -- tvar a rozměry obj.shape = love.physics.newCircleShape(10) -- propojení tvaru s tělesem obj.fixture = love.physics.newFixture(obj.body, obj.shape, 5) obj.fixture:setRestitution(0.9) objects.mesh[i][j] = obj end end -- vytvoření pružné vazby mezi míčky for i = 1, 5 do for j = 1, 4 do local obj = {} local b1 = objects.mesh[i][j].body local b2 = objects.mesh[i][j+1].body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) joint:setLength(22) joint:setFrequency(20) end end -- vytvoření kolmé pružné vazby mezi míčky for i = 1, 4 do for j = 1, 5 do local obj = {} local b1 = objects.mesh[i][j].body local b2 = objects.mesh[i+1][j].body local joint = love.physics.newDistanceJoint(b1, b2, b1:getX(), b1:getY(), b2:getX(), b2:getY()) joint:setLength(22) joint:setFrequency(20) end end end -- -- Funkce volaná cca 30x za sekundu -- function love.update(dt) -- přepočítat parametry celého "světa" world:update(dt) local delay = 1/30 if dt < delay then love.timer.sleep(delay - dt) end end -- -- Tato funkce je volána automaticky při překreslení obsahu -- okna či obrazovky. -- function love.draw() -- vykreslení země love.graphics.setColor(72, 160, 14) love.graphics.polygon("line", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- vykreslení všech míčků love.graphics.setColor(250, 150, 150) for i = 1, 5 do for j = 1, 5 do local obj = objects.mesh[i][j] love.graphics.circle("line", obj.body:getX(), obj.body:getY(), obj.shape:getRadius()) end end -- vykreslení pružné vazby love.graphics.setColor(150, 150, 250) for i = 1, 5 do for j = 1, 4 do local obj1 = objects.mesh[i][j] local obj2 = objects.mesh[i][j+1] love.graphics.line(obj1.body:getX(), obj1.body:getY(), obj2.body:getX(), obj2.body:getY()) end end for i = 1, 4 do for j = 1, 5 do local obj1 = objects.mesh[i][j] local obj2 = objects.mesh[i+1][j] love.graphics.line(obj1.body:getX(), obj1.body:getY(), obj2.body:getX(), obj2.body:getY()) end end -- výpis zprávy love.graphics.setColor(250, 250, 250) love.graphics.print("Escape: to exit.", 20, 615) end -- -- Callback funkce zavolaná při stisku klávesy. -- function love.keypressed(k) if k == 'escape' then -- klávesou "Escape" se aplikace ukončí love.event.quit() elseif k == 'r' then -- restart celé simulace po stisku klávesy [R] love.load() end end -- -- finito --
Podívejme se nyní na sérii snímků získaných v průběhu simulace prováděné čtvrtým demonstračním příkladem:
Obrázek 28: Čtvrtý demonstrační příklad – začátek simulace.
Obrázek 29: Čtvrtý demonstrační příklad – pokračování simulace.
Obrázek 30: Čtvrtý demonstrační příklad – pokračování simulace.
Obrázek 31: Čtvrtý demonstrační příklad – pokračování simulace.
Obrázek 32: Čtvrtý demonstrační příklad – pokračování simulace.
Obrázek 33: Čtvrtý demonstrační příklad – pokračování simulace.
Obrázek 34: Čtvrtý demonstrační příklad – pokračování simulace.
Vidíme, že elastické vazby zcela nezabránily deformaci mřížky – bylo by nutné zavést ještě diagonální vazby, takový příklad si však ukážeme až v dalším pokračování tohoto seriálu.
13. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
Všechny čtyři demonstrační příklady, s nimiž jsme se v dnešním článku seznámili, byly uloženy do Git repositáře umístěného na GitHubu (https://github.com/tisnik/presentations). Každý demonstrační příklad je, podobně jako tomu bylo i příkladů z předchozích dvou článků, tvořen pouze zdrojovým kódem uloženým v souboru main.lua:
# | Příklad | Zdrojový kód |
---|---|---|
1 | example24 | https://github.com/tisnik/presentations/tree/master/love/example24 |
2 | example25 | https://github.com/tisnik/presentations/tree/master/love/example25 |
3 | example26 | https://github.com/tisnik/presentations/tree/master/love/example26 |
4 | example27 | https://github.com/tisnik/presentations/tree/master/love/example27 |
Poznámka: demonstrační příklady jsou číslovány průběžně, protože tento článek (alespoň nepřímo) navazuje na seriál o programovacích jazycích vhodných pro výuku počítačové grafiky.
14. Odkazy na Internetu
- Bounding Boxes
http://www.3dkingdoms.com/weekly/weekly.php?a=21 - Box2D (stránka projektu)
http://box2d.org/ - Box2D FAQ
https://github.com/erincatto/Box2D/wiki/FAQ - Box2D v2.2.0 User Manual
http://www.box2d.org/manual.html - Bounding Box (OpenStreetMap)
http://wiki.openstreetmap.org/wiki/Bounding_Box - Minimum Bounding Rectange (Wikipedia)
https://en.wikipedia.org/wiki/Minimum_bounding_rectangle - Minimum bounding box (Wikipedia)
https://en.wikipedia.org/wiki/Minimum_bounding_box - 2D Bounding Box Collision Detection
http://www.dreamincode.net/forums/topic/180069-xna-2d-bounding-box-collision-detection/ - Chipmunk2D (další fyzikální engine)
http://chipmunk-physics.net/ - Physics Engine (Wikipedia)
https://en.wikipedia.org/wiki/Physics_engine - Modul love.physics
https://love2d.org/wiki/love.physics - Modul love.physics: Objekt typu World
https://love2d.org/wiki/World - Modul love.physics: Objekt typu Body
https://love2d.org/wiki/Body - Modul love.physics: Objekt typu Shape
https://love2d.org/wiki/Shape - Modul love.physics: Objekt typu Joint
https://love2d.org/wiki/Joint - Modul love.physics: Objekt typu Fixture
https://love2d.org/wiki/Fixture - Particle Systems From the Ground Up
http://buildnewgames.com/particle-systems/ - Particle Systems
http://natureofcode.com/book/chapter-4-particle-systems/ - Domovská stránka systému LÖVE
http://love2d.org/ - Dokumentace k systému LÖVE
http://love2d.org/wiki/love - Domovská stránka programovacího jazyka Lua
http://www.lua.org/ - Seriál o programovacím jazyku Lua (root.cz):
http://www.root.cz/serialy/programovaci-jazyk-lua/ - Domovská stránka systému LÖVE
http://love2d.org/ - Domovská stránka programovacího jazyka Lua
http://www.lua.org/ - Web o Lieru, Gusanos, GeneRally, Atari atd.
http://karelik.wz.cz/ - Web o Lieru, Gusanos
http://karelik.wz.cz/gusanos.php - GUSANOS
http://gusanos.sourceforge.net/ - GUSANOS Download
http://sourceforge.net/projects/gusanos/ - Lua
http://www.linuxexpres.cz/praxe/lua - Lua
http://cs.wikipedia.org/wiki/Lua - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - The Lua Programming Language
http://www.tiobe.com/index.php/paperinfo/tpci/Lua.html - Lua Programming Gems
http://www.lua.org/gems/ - LuaForge
http://luaforge.net/ - Forge project tree
http://luaforge.net/softwaremap/trove_list.php - SdlBasic home page
http://www.sdlbasic.altervista.org/main/ - SdlBasic examples
http://nitrofurano.linuxkafe.com/sdlbasic/ - SdlBasic na Wikipedii
http://en.wikipedia.org/wiki/SdlBasic - Simple DirectMedia Layer
http://en.wikipedia.org/wiki/Simple_DirectMedia_Layer - SDLBASIC – The high-level interpreter for all?
http://openbytes.wordpress.com/2008/11/08/sdlbasic-the-high-level-interpreter-for-all/ - FreeBasic home page
http://www.freebasic.net/ - FreeBASIC (Wikipedia EN)
https://en.wikipedia.org/wiki/FreeBASIC - FreeBASIC Wiki
http://www.freebasic.net/wiki/wikka.php?wakka=FBWiki - FreeBASIC Manual
http://www.freebasic.net/wiki/wikka.php?wakka=DocToc - FreeBASIC (Wikipedia CZ)
http://cs.wikipedia.org/wiki/FreeBASIC - The Griffon Legend
http://syn9.thingie.net/?table=griffonlegend - Seriál Letní škola programovacího jazyka Logo
http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/ - Scratch: oficiální stránka projektu
http://scratch.mit.edu/ - Scratch: galerie projektů vytvořených ve Scratchi
http://scratch.mit.edu/galleries/browse/newest - Scratch: nápověda
file:///usr/share/scratch/Help/en/index.html - Scratch: obrazovky nápovědy
file:///usr/share/scratch/Help/en/allscreens.html - Scratch (Wikipedie CZ)
http://cs.wikipedia.org/wiki/Scratch - Scratch (programming language)
http://en.wikipedia.org/wiki/Scratch_(programming_language) - Scratch Modification
http://wiki.scratch.mit.edu/wiki/Scratch_Modification - Scratch Lowers Resistance to Programming
http://www.wired.com/gadgetlab/2009/03/scratch-lowers/ - Snap!
http://snap.berkeley.edu/ - Prostředí Snap!
http://snap.berkeley.edu/snapsource/snap.html - Alternatives to Scratch
http://wiki.scratch.mit.edu/wiki/Alternatives_to_Scratch - Basic-256 home page
http://www.basic256.org/index_en - Basic-256 Language Documentation
http://doc.basic256.org/doku.php - Basic-256 Art Gallery
http://www.basic256.org/artgallery - Basic-256 Tutorial
http://www.basic256.org/tutorials - Why BASIC?
http://www.basic256.org/whybasic - A book to teach ANYBODY how to program a computer (using BASIC)
http://www.basicbook.org/ - BASIC Computer Games (published 1978) - Hammurabi
http://atariarchives.org/basicgames/showpage.php?page=78 - Hamurabi - zdrojový kód v BASICu
http://www.dunnington.u-net.com/public/basicgames/HMRABI