Ve druhém článku o fyzikálním enginu, který je součástí knihovny LÖVE, si na čtveřici demonstračních příkladů ukážeme použití jiných tvarů těles v simulovaném světě, seznámíme se s konceptem takzvaných obalových těles používaných při detekci kolizí a taktéž se způsobem řešení některých problémů plynoucích z nepřesných výpočtů těchto kolizí.

Obsah

1. Fyzikální engine implementovaný v knihovně LÖVE (2.část)

2. Použití dalších tvarů těles v simulovaném světě

3. Zdrojový kód prvního demonstračního příkladu

4. Detekce kolizí a obalová tělesa používaná v knihovně LÖVE

5. Zdrojový kód druhého demonstračního příkladu

6. Problematika „průstřelu“ tenkých stěn a způsob jejího řešení

7. Zdrojový kód třetího demonstračního příkladu

8. Použití přesnějšího výpočtu kolizí

9. Zdrojový kód čtvrtého demonstračního příkladu

10. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů

11. Odkazy na Internetu

1. Fyzikální engine implementovaný v knihovně LÖVE (2.část)

V prvním článku o fyzikálním enginu implementovaném v knihovně LÖVE jsme si řekli základní informace o tom, 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. Taktéž 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. Připomeňme si tedy jen ve stručnosti, že fyzikální engine je v knihovně LÖVE dostupný přes modul pojmenovaný love.physics a – podobně jako celá knihovna LÖVE – je orientován téměř výhradně na dvoudimenzionální (2D) grafiku. Díky omezení počtu rozměrů bylo možné všechny výpočty dynamiky těles urychlit a navíc je celé programové rozhraní modulu love.physics poměrně snadno pochopitelné, a to i pro začátečníky (na tomto místě je vhodné si uvědomit, že samotný engine je interně dosti složitý, ovšem úlohou správně navrženého API je tuto složitost od uživatelů odstínit, což se v tomto případě podařilo, i tak je však love.physics nejrozsáhlejším modulem v celé knihovně LÖVE).

01

Obrázek 1: Jednoduchá simulace vytvořená s využitím modulu love.physics.

V předchozím článku jsme se taktéž dozvěděli, že celý simulovaný svět i všechna tělesa, která se v tomto světě pohybují, se popisuje pomocí následujících pěti typů objektů:

# Typ Význam
1 World objekt představující celý simulovaný dvourozměrný svět
2 Body objekt představující nestlačitelnou hmotu v simulovaném světě
3 Shape geometrický tvar přiřazený k tělesu
4 Fixture vazba mezi již vytvořeným tvarem (shape) a tělesem (body)
5 Joint vazba mezi tělesy, které se nachází v simulovaném světě

03

Obrázek 2: Vztah mezi čtyřmi základními typy objektů: World, Body(ies), Shape(s) a Fixture(s), bližší informace viz první článek na toto téma.

2. Použití dalších tvarů těles v simulovaném světě

V předchozích dvou demonstračních příkladech byly použity statické i pohybující se objekty, jejichž tvar (shape) odpovídal obdélníkům. Jedná se o jeden z nejjednodušších tvarů, s nímž může engine love.physics pracovat, pro většinu aplikací by to ale bylo zcela nedostatečné. Proto jsou k dispozici i další tvary. Všechny dostupné tvary i jejich konstruktory (tj. funkce volané pro jejich vytvoření) jsou pro přehlednost vypsány v následující tabulce:

# Tvar Konstruktor Popis
1 rectangle love.physics.newRectangleShape() obdélník či čtverec (libovolně natočený)
2 polygon love.physics.newPolygonShape() konvexní polygon s maximálně osmi vrcholy
3 circle love.physics.newCircleShape() kruh/kružnice
4 edge love.physics.newEdgeShape() jen úsečka, tento tvar nemá vnitřek, omezené použití
5 chain love.physics.newChainShape() polyčára, tento tvar nemá vnitřek, omezené použití

04

Obrázek 3: V tomto simulovaném světě se používají pouze tělesa s tvarem obdélníku.

Poznámka 1: pro pohybující se objekty je možné reálně využít jen první tři tvary: rectangle (speciální typ polygonu), konvexní polygon a circle. Další dva tvary jsou určeny pro nehybná tělesa, takže je možné je použít například pro vymodelování terénu. Zajímavý a potenciálně užitečný je v tomto ohledu tvar edge shape, který nemusí být konvexní, na rozdíl od tvaru polygon (a samozřejmě automaticky též rectangle).

Poznámka 2: v modulu love.physics se žádným způsobem neřeší způsob vykreslení tvarů na obrazovku. Tuto činnost musí explicitně zajistit programátor a to typicky takovým způsobem, že s využitím metod objekt.shape:getPoints(), objekt.body:getX(), objekt.body:getY() a objekt.body:getRadius() přečte aktuální pozici a rozměry tvaru, které následně předá do kreslicích funkcí modulu love.graphics. Alternativně je samozřejmě možné pouze získat pozici tvaru a vykreslit zcela odlišný objekt, například sprite. Volání zmíněných funkcí se většinou provádí v callback funkci love.draw, což použijeme i v demonstračních příkladech.

14

Obrázek 4: V další simulaci se již používají i kulatá tělesa.

3. Zdrojový kód prvního demonstračního příkladu

Použití dalších tvarů těles, zde konkrétně tvaru kruhu/kružnice, je ukázáno v dnešním prvním demonstračním příkladu. Ten je založen na obou příkladech, s nimiž jsme se seznámili minule, ovšem do simulovaného světa byly přidány dva míčky:

--
-- 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 = love.physics.newBody(world, 400, 100, "dynamic")
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(40)
    -- 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, 225, 50, "dynamic")
    -- tvar a rozměry
    objects.ball2.shape = love.physics.newCircleShape(50)
    -- propojení tvaru s tělesem
    objects.ball2.fixture = love.physics.newFixture(objects.ball2.body, objects.ball2.shape, 5000)
    objects.ball2.fixture:setRestitution(0.1)
    ...
    ...
    ...
end

Tyto míčky se v callback funkci love.draw() vykreslí velmi snadno s využitím metod pro přečtení aktuální souřadnice středu a poloměru zmíněných v předchozí kapitole:

--
-- Tato funkce je volána automaticky při překreslení obsahu
-- okna či obrazovky.
--
function love.draw()
    ...
    ...
    ...
 
    -- 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())
    ...
    ...
    ...
end

05

Obrázek 5: Svět simulovaný v dnešním prvním demonstračním příkladu.

Úplný zdrojový kód demonstračního příkladu:

--
-- Knihovna LÖVE
--
-- Dvacátý demonstrační příklad
--
-- Jednoduchý "svět", v němž se nachází dva míčky a jeden kvádr.
--
 
 
 
-- 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, 400, 100, "dynamic")
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(40)
    -- 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, 225, 50, "dynamic")
    -- tvar a rozměry
    objects.ball2.shape = love.physics.newCircleShape(50)
    -- propojení tvaru s tělesem
    objects.ball2.fixture = love.physics.newFixture(objects.ball2.body, objects.ball2.shape, 5000)
    objects.ball2.fixture:setRestitution(0.1)
 
    -- 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, 50, "dynamic")
    -- tvar a rozměry
    objects.block3.shape = love.physics.newRectangleShape(100, 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í 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()
    end
end
 
 
 
--
-- finito
--

06

Obrázek 6: Svět simulovaný v dnešním prvním demonstračním příkladu.

4. Detekce kolizí a obalová tělesa používaná v knihovně LÖVE

V mnoha aplikacích, především ve hrách, je nutné zaregistrovat okamžik, ve kterém dojde ke kolizi dvou či většího množství těles. Může se například jednat o střet hráče se stěnou, náraz střely do protihráče apod. Engine pak vypočte, jakým způsobem se od sebe tělesa odrazí (připomeňme si, že se stále jedná o nestlačitelná a dokonale neelastická tělesa, takže náraz ani odraz nezmění tvar tělesa). Samotná detekce kolizí je – i když jsou veškeré výpočty prováděny pouze v dvourozměrné ploše – poměrně výpočetně náročná, proto je také množina tvarů, které je možné navázat na tělesa, omezená na výše zmíněné kruhy/kružnice a (uzavřené) konvexní polygony s maximálně osmi hranami. Každý tvar (shape) je navíc vždy uzavřen do takzvaného „obalového tělesa“ (bounding rectangle, obecně bounding box či jen bbox), jehož strany jsou rovnoběžné se souřadnými osami a tudíž je detekce kolizí výrazně jednodušší než v případě obecného polygonu či kruhu. Pokud se totiž neprotínají či ani nedotýkají obalové boxy, nemá smysl provádět složitější výpočet skutečného dotyku těles, což se v praxi ukazuje být velmi významnou optimalizační technikou (dokonce ještě větší význam má v 3D enginech).

07

Obrázek 7: Obalová tělesa (bounding boxes) zobrazená pro všechny objekty v simulovaném světě.

Obalový obdélník (bounding box) je možné pro libovolný tvar získat metodou Fixture:getBoundingBox(), což si taktéž ukážeme v následujícím demonstračním příkladu. Při každé změně pozice i natočení tvaru je velikost obalového tělesa přepočítána tak, aby tvar vždy ležel uvnitř tohoto tělesa a současně byla jeho plocha minimální (jak bude z animací patrné, tato podmínka není vždy splněna, protože se ještě bere v úvahu vektor rychlosti pohybu tělesa). Jak jsme si již řekli v předchozím odstavci, probíhá detekce kolizí vždy nejprve nad obalovými tělesy a teprve v okamžiku, kdy dojde k jejich prolnutí (či alespoň dotyku) nastává obecně složitější část výpočtu – detekce kolize samotných tvarů. Jen pro úplnost: výše zmíněná metoda Fixture:getBoundingBox() vrací dva protilehlé vrcholy obalového obdélníku. V předchozích verzích knihovny LÖVE existovala i podobně pojmenovaná metoda Shape:getBoundingBox() vracející čtveřici vrcholů (tedy osm souřadnic). Předností této metody byl fakt, že její návratové hodnoty (osm hodnot) bylo možné ihned předat funkci love.draw.polygon() a obalové těleso tak vykreslit na obrazovku.

08

Obrázek 8: Obalová tělesa (bounding boxes) zobrazená pro všechny objekty v simulovaném světě (i pro polygon).

5. Zdrojový kód druhého demonstračního příkladu

V dnešním druhém demonstračním příkladu je ukázáno, jakým způsobem je možné pro všechny objekty, které se nachází v simulovaném světě, získat a následně zobrazit obalové těleso, a to nezávisle na tom, jaký má objekt tvar. Jediná změna byla provedena v callback funkci love.draw():

--
-- Tato funkce je volána automaticky při překreslení obsahu
-- okna či obrazovky.
--
function love.draw()
    ...
    ...
    ...
    -- vykreslení obalových těles
    love.graphics.setColor(128, 128, 128)
    local x1,y1,x2,y2 = objects.ball1.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
    local x1,y1,x2,y2 = objects.ball2.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
    local x1,y1,x2,y2 = objects.block3.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
    ...
    ...
    ...
end

09

Obrázek 9: Povšimněte si, jak se při pohybu obalová tělesa „natahují“ a neodpovídají přímo tvarům objektů.

Úplný zdrojový kód demonstračního příkladu:

--
-- Knihovna LÖVE
--
-- Dvacátý první demonstrační příklad
--
-- Jednoduchý "svět", v němž se nachází dva míčky a jeden kvádr.
-- Při vykreslování scény se zvýrazní i obalová tělesa.
--
 
 
 
-- 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.07)
    -- 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, 400, 100, "dynamic")
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(40)
    -- propojení tvaru s tělesem
    objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5)
    objects.ball1.fixture:setRestitution(0.2)
 
    -- 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, 260, 10, "dynamic")
    -- tvar a rozměry
    objects.ball2.shape = love.physics.newCircleShape(50)
    -- propojení tvaru s tělesem
    objects.ball2.fixture = love.physics.newFixture(objects.ball2.body, objects.ball2.shape, 5000)
    objects.ball2.fixture:setRestitution(0.1)
 
    -- těleso představující kvádr
    objects.block3 = {}
    -- počáteční umístění tělesa
    objects.block3.body = love.physics.newBody(world, 400, 20, "dynamic")
    -- tvar a rozměry
    objects.block3.shape = love.physics.newRectangleShape(150, 10)
    -- propojení tvaru s tělesem
    objects.block3.fixture = love.physics.newFixture(objects.block3.body, objects.block3.shape, 5)
    objects.block3.fixture:setRestitution(0.1)
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í 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()))
 
    -- vykreslení obalových těles
    love.graphics.setColor(128, 128, 128)
    local x1,y1,x2,y2 = objects.ball1.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
    local x1,y1,x2,y2 = objects.ball2.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
    local x1,y1,x2,y2 = objects.block3.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
 
    -- 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()
    end
end
 
 
 
--
-- finito
--
 

10

Obrázek 10: Screenshot z demonstračního příkladu.

11

Obrázek 11: Povšimněte si, že při kolizi těles dochází k protnutí jejich bounding boxů.

6. Problematika „průstřelu“ tenkých stěn a způsob jejího řešení

Při použití modulu love.physics v simulovaných světech, ve kterých se pohybují objekty vysokou rychlostí a kde se současně nachází úzké stěny, může dojít k případům, kdy kvůli numerickým chybám vzniklým při výpočtu dynamiky těles dojde k nechtěnému „průstřelu“ stěny, tj. těleso je jakoby teleportováno na druhou stranu aniž by došlo k detekci kolize. Aby k tomu problému došlo, musí být splněny dvě tři podmínky:

  • Těleso se musí pohybovat poměrně vysokou rychlostí.
  • Stěna musí být úzká.
  • Navíc musí být stěna taktéž pohyblivá, protože u statických objektů je použit odlišný výpočet kolizí.

Podívejme se nyní na příklad, který problematiku průstřelu ilustruje. Nejprve vytvořme v simulovaném světě úzkou stěnu (její šířka je řízena proměnnou wall_width:

    -- těleso představující stěnu
    objects.wall = {}
    -- počáteční umístění tělesa
    objects.wall.body = love.physics.newBody(world, 200, height/2, "dynamic")
    -- tvar a rozměry
    -- hodnota wall_width bude typicky hodně malá, například 1, 2, 3 jednotky
    objects.wall.shape = love.physics.newRectangleShape(wall_width, 200)
    -- propojení tvaru s tělesem
    objects.wall.fixture = love.physics.newFixture(objects.wall.body, objects.wall.shape, 5)
    objects.wall.fixture:setRestitution(0.7)

Následně je do scény přidáno těleso, které se ke stěně rychle přibližuje (v kolmém směru, ale to ve skutečnosti není zcela nezbytné):

    -- 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, 550, height/2, "dynamic")
    -- vysoká počáteční rychlost s vektorem směřujícím doleva
    objects.ball1.body:setLinearVelocity(-10000000,0)
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(5)
    -- propojení tvaru s tělesem
    objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5)
    objects.ball1.fixture:setRestitution(0.2)

Co se nyní stane dobře ilustrují následující tři obrázky, na nichž je taktéž vidět obalové těleso míčku:

12

Obrázek 12: Začátek simulace; ve světě se nachází jen podlaha a stěna.

13

Obrázek 13: Ke stěně se začíná přibližovat rychle se pohybující těleso. Povšimněte si „natažení“ jeho bounding boxu.

14

Obrázek 14: Těleso se pohybuje tak rychle, že algoritmus detekce kolize nezareagoval na průstřel (kulička by se měla odrazit).

7. Zdrojový kód třetího demonstračního příkladu

Problematiku průstřelu úzkých stěn si můžeme snadno předvést na dalším (dnes již třetím) demonstračním příkladu. V něm jsou vytvořena pouhá tři tělesa: nehybná podlaha, stěna (ta musí být „dynamická“, aby se použil správný algoritmus detekce kolize) a samotná rychle se pohybující kulička. Po spuštění by mělo dojít k průstřelu, na který ani jedno těleso (stěna, kulička) správně nezareaguje: kulička stěnou prolétne a stěna spadne na podlahu (dokonce se ani nenakloní). Následuje výpis zdrojového kódu dnešního třetího demonstračního příkladu:

--
-- Knihovna LÖVE
--
-- Dvacátý druhý demonstrační příklad
--
-- Jednoduchý "svět", v němž se nachází stěna a rychle se pohybující kulička
-- ("střela").
--
 
 
 
-- rozměry okna, do něhož se bude provádět vykreslování
width = 600
height = 600
 
-- parametry podlahy a stěny
ground_height = 50
wall_width = 2
 
-- 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(width, ground_height)
    -- 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í stěnu
    objects.wall = {}
    -- počáteční umístění tělesa
    objects.wall.body = love.physics.newBody(world, 200, height/2, "dynamic")
    -- tvar a rozměry
    objects.wall.shape = love.physics.newRectangleShape(wall_width, 200)
    -- propojení tvaru s tělesem
    objects.wall.fixture = love.physics.newFixture(objects.wall.body, objects.wall.shape, 5)
    objects.wall.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, 550, height/2, "dynamic")
    objects.ball1.body:setLinearVelocity(-10000000,0)
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(5)
    -- propojení tvaru s tělesem
    objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5)
    objects.ball1.fixture:setRestitution(0.2)
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í stěny
    love.graphics.setColor(250, 100, 100)
    love.graphics.polygon("line", objects.wall.body:getWorldPoints(objects.wall.shape:getPoints()))
 
    -- vykreslení míčku
    love.graphics.setColor(250, 250, 150)
    love.graphics.circle("line", objects.ball1.body:getX(), objects.ball1.body:getY(),
                                 objects.ball1.shape:getRadius())
 
    -- vykreslení obalových těles
    love.graphics.setColor(128, 128, 128)
    local x1,y1,x2,y2 = objects.ball1.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
 
    -- 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()
    end
end
 
 
 
--
-- finito
--

15

Obrázek 15: Namísto přímého vykreslení tvarů s využitím úseček, polyčar, kružnic a polygonů lze použít i sprity.

8. Použití přesnějšího výpočtu kolizí

V enginu používaném modulem love.physics je možné provést výběr algoritmu pro výpočet kolizí. Pro statická tělesa (která se nepohybují) je implementován přesnější a současně i pomalejší výpočet, pro dynamická tělesa (která se naopak pohybují) pak výpočet rychlejší, ovšem s výše zmíněnou nepřesností. Pokud však programátor ví, že se v jeho simulovaném světě budou nacházet rychle se pohybující tělesa a úzké stěny, může algoritmus detekce kolizí pro vybraná tělesa jednoduše přepnout, a to konkrétně metodou Body:setBullet(). Této metodě se předává pravdivostní hodnota true či false podle toho, zda má být dané těleso považováno za „střelu“, tj. rychle se pohybující předmět či za běžné pomaleji se pohybující těleso.

V praxi se celá simulace při přepnutí míčku do režimu „střely“ bude diametrálně odlišovat. Opět se podívejme na čtyři ilustrační obrázky, kde se míček/střela pohybuje velkou rychlostí proti úzké stěně:

16

Obrázek 16: Začátek simulace; ve světě se prozatím nachází jen podlaha a stěna.

17

Obrázek 17: Ke stěně se začíná přibližovat rychle se pohybující těleso (střela). Opět si povšimněte „natažení“ jeho bounding boxu.

18

Obrázek 18: Došlo ke kolizi a odrazu těles – stěna se posunula směrem doleva, kulička se odrazila doprava a ztratila část své rychlosti (vektor rychlosti se současně otočil).

19

Obrázek 19: Jak na stěnu, tak i na kuličku začíná výrazněji působit gravitace.

9. Zdrojový kód čtvrtého demonstračního příkladu

Přepnutí algoritmu pro výpočet kolizí na jeho přesnější a současně i pomalejší variantu je jednoduché, což je ostatně patrné i z následujícího úryvku zdrojového kódu, v němž je příslušný programový řádek zvýrazněn. Povšimněte si, že volbu algoritmu je možné provést pro každé těleso zvlášť, takže závisí jen a pouze na vývojáři, ve kterých případech bude preferovat rychlejší výpočet a kdy naopak výpočet přesnější:

--
-- 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 = love.physics.newBody(world, 550, height/2, "dynamic")
    objects.ball1.body:setLinearVelocity(-10000000,0)
    -- nutno nastavit na "true" - použije se pomalejší ale přesnější algoritmus výpočtu
    objects.ball1.body:setBullet(true)
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(5)
    -- propojení tvaru s tělesem
    objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5)
    objects.ball1.fixture:setRestitution(0.2)
    ...
    ...
    ...
end

Podívejme se nyní na výpis zdrojového kódu dnešního čtvrtého demonstračního příkladu:

--
-- Knihovna LÖVE
--
-- Dvacátý třetí demonstrační příklad
--
-- Vyřešení problémů s rychle se pohybujícími tělesy
--
 
 
 
-- rozměry okna, do něhož se bude provádět vykreslování
width = 600
height = 600
 
-- parametry podlahy a stěny
ground_height = 50
wall_width = 2
 
-- 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(width, ground_height)
    -- 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í stěnu
    objects.wall = {}
    -- počáteční umístění tělesa
    objects.wall.body = love.physics.newBody(world, 200, height/2, "dynamic")
    -- tvar a rozměry
    objects.wall.shape = love.physics.newRectangleShape(wall_width, 200)
    -- propojení tvaru s tělesem
    objects.wall.fixture = love.physics.newFixture(objects.wall.body, objects.wall.shape, 5)
    objects.wall.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, 550, height/2, "dynamic")
    objects.ball1.body:setLinearVelocity(-10000000,0)
    -- nutno nastavit na "true" - použije se pomalejší ale přesnější algoritmus výpočtu
    objects.ball1.body:setBullet(true)
    -- tvar a rozměry
    objects.ball1.shape = love.physics.newCircleShape(5)
    -- propojení tvaru s tělesem
    objects.ball1.fixture = love.physics.newFixture(objects.ball1.body, objects.ball1.shape, 5)
    objects.ball1.fixture:setRestitution(0.2)
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í stěny
    love.graphics.setColor(250, 100, 100)
    love.graphics.polygon("line", objects.wall.body:getWorldPoints(objects.wall.shape:getPoints()))
 
    -- vykreslení míčku
    love.graphics.setColor(250, 250, 150)
    love.graphics.circle("line", objects.ball1.body:getX(), objects.ball1.body:getY(),
                                 objects.ball1.shape:getRadius())
 
    -- vykreslení obalových těles
    love.graphics.setColor(128, 128, 128)
    local x1,y1,x2,y2 = objects.ball1.fixture:getBoundingBox()
    love.graphics.rectangle("line", x1, y1, x2-x1, y2-y1)
 
    -- 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()
    end
end
 
 
 
--
-- finito
--

20

Obrázek 20: V příští části miniseriálu o modulu love.physics si ukážeme způsob vzájemného propojení těles různými vazbami.

10. 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 u obou příkladů z předchozího článku, tvořen pouze zdrojovým kódem uloženým v souboru main.lua:

# Příklad Zdrojový kód
1 example20 https://github.com/tisnik/presentations/tree/master/love/example20
2 example21 https://github.com/tisnik/presentations/tree/master/love/example21
3 example22 https://github.com/tisnik/presentations/tree/master/love/example22
4 example23 https://github.com/tisnik/presentations/tree/master/love/example23

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.

11. Odkazy na Internetu

  1. Bounding Boxes
    http://www.3dkingdoms.com/weekly/weekly.php?a=21
  2. Box2D (stránka projektu)
    http://box2d.org/
  3. Box2D FAQ
    https://github.com/erincatto/Box2D/wiki/FAQ
  4. Box2D v2.2.0 User Manual
    http://www.box2d.org/manual.html
  5. Bounding Box (OpenStreetMap)
    http://wiki.openstreetmap.org/wiki/Bounding_Box
  6. Minimum Bounding Rectange (Wikipedia)
    https://en.wikipedia.org/wiki/Minimum_bounding_rectangle
  7. Minimum bounding box (Wikipedia)
    https://en.wikipedia.org/wiki/Minimum_bounding_box
  8. 2D Bounding Box Collision Detection
    http://www.dreamincode.net/forums/topic/180069-xna-2d-bounding-box-collision-detection/
  9. Chipmunk2D (další fyzikální engine)
    http://chipmunk-physics.net/
  10. Physics Engine (Wikipedia)
    https://en.wikipedia.org/wiki/Physics_engine
  11. Modul love.physics
    https://love2d.org/wiki/love.physics
  12. Modul love.physics: Objekt typu World
    https://love2d.org/wiki/World
  13. Modul love.physics: Objekt typu Body
    https://love2d.org/wiki/Body
  14. Modul love.physics: Objekt typu Shape
    https://love2d.org/wiki/Shape
  15. Modul love.physics: Objekt typu Joint
    https://love2d.org/wiki/Joint
  16. Modul love.physics: Objekt typu Fixture
    https://love2d.org/wiki/Fixture
  17. Particle Systems From the Ground Up
    http://buildnewgames.com/particle-systems/
  18. Particle Systems
    http://natureofcode.com/book/chapter-4-particle-systems/
  19. Domovská stránka systému LÖVE
    http://love2d.org/
  20. Dokumentace k systému LÖVE
    http://love2d.org/wiki/love
  21. Domovská stránka programovacího jazyka Lua
    http://www.lua.org/
  22. Seriál o programovacím jazyku Lua (root.cz):
    http://www.root.cz/serialy/programovaci-jazyk-lua/
  23. Domovská stránka systému LÖVE
    http://love2d.org/
  24. Domovská stránka programovacího jazyka Lua
    http://www.lua.org/
  25. Web o Lieru, Gusanos, GeneRally, Atari atd.
    http://karelik.wz.cz/
  26. Web o Lieru, Gusanos
    http://karelik.wz.cz/gusanos.php
  27. GUSANOS
    http://gusanos.sourceforge.net/
  28. GUSANOS Download
    http://sourceforge.net/projects/gusanos/
  29. Lua
    http://www.linuxexpres.cz/praxe/lua
  30. Lua
    http://cs.wikipedia.org/wiki/Lua
  31. Lua (programming language)
    http://en.wikipedia.org/wiki/Lua_(programming_language)
  32. The Lua Programming Language
    http://www.tiobe.com/index.php/paperinfo/tpci/Lua.html
  33. Lua Programming Gems
    http://www.lua.org/gems/
  34. LuaForge
    http://luaforge.net/
  35. Forge project tree
    http://luaforge.net/softwaremap/trove_list.php
  36. SdlBasic home page
    http://www.sdlbasic.altervista.org/main/
  37. SdlBasic examples
    http://nitrofurano.linuxkafe.com/sdlbasic/
  38. SdlBasic na Wikipedii
    http://en.wikipedia.org/wiki/SdlBasic
  39. Simple DirectMedia Layer
    http://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  40. SDLBASIC – The high-level interpreter for all?
    http://openbytes.wordpress.com/2008/11/08/sdlbasic-the-high-level-interpreter-for-all/
  41. FreeBasic home page
    http://www.freebasic.net/
  42. FreeBASIC (Wikipedia EN)
    https://en.wikipedia.org/wiki/FreeBASIC
  43. FreeBASIC Wiki
    http://www.freebasic.net/wiki/wikka.php?wakka=FBWiki
  44. FreeBASIC Manual
    http://www.freebasic.net/wiki/wikka.php?wakka=DocToc
  45. FreeBASIC (Wikipedia CZ)
    http://cs.wikipedia.org/wiki/FreeBASIC
  46. The Griffon Legend
    http://syn9.thingie.net/?table=griffonlegend
  47. Seriál Letní škola programovacího jazyka Logo
    http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/
  48. Scratch: oficiální stránka projektu
    http://scratch.mit.edu/
  49. Scratch: galerie projektů vytvořených ve Scratchi
    http://scratch.mit.edu/galleries/browse/newest
  50. Scratch: nápověda
    file:///usr/share/scratch/Help/en/index.html
  51. Scratch: obrazovky nápovědy
    file:///usr/share/scratch/Help/en/allscreens.html
  52. Scratch (Wikipedie CZ)
    http://cs.wikipedia.org/wiki/Scratch
  53. Scratch (programming language)
    http://en.wikipedia.org/wiki/Scratch_(programming_language)
  54. Scratch Modification
    http://wiki.scratch.mit.edu/wiki/Scratch_Modification
  55. Scratch Lowers Resistance to Programming
    http://www.wired.com/gadgetlab/2009/03/scratch-lowers/
  56. Snap!
    http://snap.berkeley.edu/
  57. Prostředí Snap!
    http://snap.berkeley.edu/snapsource/snap.html
  58. Alternatives to Scratch
    http://wiki.scratch.mit.edu/wiki/Alternatives_to_Scratch
  59. Basic-256 home page
    http://www.basic256.org/index_en
  60. Basic-256 Language Documentation
    http://doc.basic256.org/doku.php
  61. Basic-256 Art Gallery
    http://www.basic256.org/artgallery
  62. Basic-256 Tutorial
    http://www.basic256.org/tutorials
  63. Why BASIC?
    http://www.basic256.org/whybasic
  64. A book to teach ANYBODY how to program a computer (using BASIC)
    http://www.basicbook.org/
  65. BASIC Computer Games (published 1978) - Hammurabi
    http://atariarchives.org/basicgames/showpage.php?page=78
  66. Hamurabi - zdrojový kód v BASICu
    http://www.dunnington.u-net.com/public/basicgames/HMRABI