Obsah
1. Typy vazeb mezi tělesy a vytvoření těchto vazeb
2. Vazba typu „DistanceJoint“
3. Vazba typu „MouseJoint“
4. První demonstrační příklad – použití vazby typu „MouseJoint“
5. Vazba typu „RevoluteJoint“
6. Druhý demonstrační příklad – vazba typu „RevoluteJoint“
7. Vazba typu „PrismaticJoint“
8. Literatura a odkazy na Internetu
9. Obsah další části seriálu
1. Typy vazeb mezi tělesy a vytvoření těchto vazeb
V dnešní části seriálu o programovacím jazyku Lua a knihovnách vytvořených pro tento jazyk dokončíme popis herního systému LÖVE. Ukážeme si způsob vytvoření a využití dalších tří typů vazeb mezi dvourozměrnými tělesy, pomocí nichž je například možné simulovat chování kladky, páky či podobného jednoduchého stroje. V předchozí části tohoto seriálu jsme si popsali a na několika demonstračních příkladech ukázali využití pružné (elastické) vazby představované objekty typu love.physics.DistanceJoint, kromě nich je však možné použít i vazby reprezentované objekty typu love.physics.RevoluteJoint (rotace těles okolo společného bodu, rotaci je dokonce možné pohánět pomocí motoru přiřazeného k tomuto typu vazby), love.physics.PrismaticJoint (posun těles po společné ose, opět s využitím pohonu přiřazeného k vazbě) a také love.physics.MouseJoint (posun tělesa pomocí myši). Všechny čtyři typy objektů představujících vazby mezi tělesy jsou pro přehlednost vypsány v následující tabulce:
Název objektu | Popis |
---|---|
love.physics.DistanceJoint | pružná vazba, která se snaží zachovat konstantní vzdálenost mezi tělesy, jenž jsou touto vazbou spojeny |
love.physics.RevoluteJoint | definice společné osy otáčení pro dvojici těles |
love.physics.PrismaticJoint | definice společné osy posuvu pro dvojici těles |
love.physics.MouseJoint | vazba, pomocí níž je možné působit na zvolené těleso silou, jejíž vektor je odvozen od aktuální souřadnice kurzoru myši a polohou koncového bodu vazby |
Každý objekt reprezentující vazbu se vytváří pomocí jednoho ze čtyř konstruktorů, které jsou vypsány v tabulce níže. První tři typy vazeb se vytváří vždy mezi dvojicí existujících těles, tj. první dva parametry představují objekt typu těleso (body). Pouze u posledního typu vazby je určeno jen jedno těleso – druhým objektem je v tomto případě vždy kurzor myši, resp. jeho aktivní bod.
Konstruktor | Počet parametrů | Parametry |
---|---|---|
love.physics.newDistanceJoint() | 6 | dvě tělesa a koncové body pružné vazby (kotvicí body) |
love.physics.newRevoluteJoint() | 4 | dvě tělesa a souřadnice kotvicího bodu (osy) v rovině |
love.physics.newPrismaticJoint() | 6 | dvě tělesa, souřadnice kotvicího bodu v rovině a bod určující (spolu s kotvicím bodem) společnou osu pro posuv |
love.physics.newMouseJoint() | 3 | jedno těleso a koncový bod vazby (druhé těleso i příslušný kotvicí bod odpovídá kurzoru myši) |
2. Vazba typu „DistanceJoint“
Vazbu typu DistanceJoint jsme si již poměrně dopodrobna popsali v předchozí části tohoto seriálu, spolu s několika demonstračními příklady. Pomocí objektů typu love.physics.DistanceJoint je možné vytvořit elastické vazby, které simulují (tlumené) pružiny natažené mezi dvojicí těles, které se v průběhu simulace snaží dosáhnout své klidové délky. Při natažení či stlačení pružiny, tj. ve chvíli, kdy se změní nastavená vzdálenost mezi tělesy, začne na obě tělesa spojená touto vazbou působit síla úměrná rozdílu mezi klidovou délkou a délkou aktuální. Systém LÖVE se snaží napodobit reálné pružiny, takže se při výpočtu sil působících na obě tělesa bere do úvahy i tlumení, které mj. zabraňuje numerickým nestabilitám ve výpočtech (pokud by tlumení bylo nulové, mohlo by při numerických výpočtech dojít ke stálému růstu vzdáleností i sil působících mezi tělesy, takže by zákmity pružiny neustále rostly). V tabulce níže jsou vypsány metody nabízené objektem typu love.physics.DistanceJoint:
Název metody | Popis |
---|---|
getLength() | získání klidové vzdálenosti mezi oběma tělesy. |
getFrequency() | získání rychlosti reakce pružiny na její natažení či stlačení. |
getDamping() | získání míry tlumení pružiny (eliminace zákmitů). |
setLength() | nastavení klidové vzdálenosti mezi oběma tělesy. |
setFrequency() | nastavení rychlosti reakce pružiny na její natažení či stlačení. |
setDamping() | nastavení míry tlumení. |
destroy() | explicitní zrušení elastické vazby (většinou není nutné provádět). |
Animace 1: Využití objektů typu DistanceJoint pro vytvoření systémů těles propojených elastickými vazbami.
3. Vazba typu „MouseJoint“
Vazba typu MouseJoint se používá tehdy, pokud je zapotřebí působit na těleso silou, jenž je odvozena od aktuální pozice kurzoru myši, tj. tělesem je možné posunovat popř. otáčet pomocí myši nebo i programově, protože místo souřadnic kurzoru myši lze dosadit libovolnou dvojici numerických hodnot. V určitém ohledu se tato vazba, vytvořená konstruktorem love.physics.newMouseJoint(), podobá pružné vazbě vytvořené pomocí objektu DistanceJoint, ovšem s tím rozdílem, že se u vazby MouseJoint nezadává vzdálenost mezi kurzorem myši a bodem ležícím uvnitř tělesa, ale pouze jediný koncový bod, z jehož pozice a souřadnice kurzoru myši se odvozuje síla, která na těleso působí. Čím více se kurzor vzdálí od bodu zadaného metodou setTarget() (popř. přímo při volání konstruktoru), tím větší na těleso působí síla, která však nemůže přesáhnout hodnotu nastavenou pomocí metody setMaxForce(). Vektor síly, tj. jeho orientace i velikost, je samozřejmě vypočten na základě zadaného koncového bodu a aktuální polohy myši na obrazovce.
Název metody | Popis |
---|---|
getTarget() | vrací souřadnice koncového bodu této vazby (dvojici hodnot x, y) |
getMaxForce() | vrací maximální nastavenou sílu, kterou může tato vazba na těleso působit |
setTarget() | nastavení (změna) koncového bodu vazby |
setMaxForce() | nastavení maximální síly, kterou může vazba působit na vybrané těleso při změně pozice kurzoru myši či při pohybu tělesa |
destroy() | explicitní zrušení této vazby (většinou není nutné provádět). |
4. První demonstrační příklad – použití vazby typu „MouseJoint“
V dnešním prvním demonstračním příkladu je ukázáno, jakým způsobem je možné použít vazbu typu MouseJoint pro změnu polohy vybraného tělesa v simulovaném 2D světě. Ve vytvořené scéně se nachází větší množství těles, přičemž každému tělesu je přiřazen tvar (shape) odpovídající obdélníku. Jedno z těles, které je vykresleno žlutou barvou, je pomocí již zmíněné vazby MouseJoint spojeno s kurzorem myši. Po spuštění příkladu se kurzor myši nachází uprostřed tohoto tělesa, kam byl umístěn pomocí metody love.mouse.setPosition(x,y), a při každém jeho pohybu a stlačení některého z tlačítek myši na těleso působí síla, která ho z původní polohy vychyluje – viz animace uvedená pod výpisem zdrojového kódu tohoto demonstračního příkladu. Červenou úsečkou je naznačen vektor síly. Směr i velikost tohoto vektoru samozřejmě odpovídá odchylce pozice kurzoru myši (ve chvíli, kdy bylo stlačeno některé její tlačítko) od jeho výchozí polohy v tělese. Zdrojový kód tohoto demonstračního příkladu má tvar:
------------------------------------------------- -- Seriál "Programovací jazyk Lua", část číslo 19 -- -- První demonstrační příklad: využití vazby -- typu MouseJoint. ------------------------------------------------- -- rozměry okna window = { width = 800, height = 600 } -- objekt představující svět, ve kterém se provádí simulace world = nil -- odrazivost tělesa body_restitution = 0.3 -- počitadlo snímků frame = 0 -- pole těles bodies = {} -- pole tvarů shapes = {} -- těleso a tvar ovládaný myší mouse_body = nil mouse_shape = nil -- inicializace všech potřebných objektů a datových struktur function load() -- inicializace grafického režimu love.graphics.setMode(window.width, window.height, false, false, 0) -- načtení fontu local font = love.graphics.newFont(love.default_font, 16) love.graphics.setFont(font) -- vytvoření "světa" o rozměrech 2000x2000 délkových jednotek world = love.physics.newWorld(2000, 2000) world:setGravity(0, 50) -- podlaha na souřadnicích [0, 0] s nulovou hmotností ground_body = love.physics.newBody(world, 0, 0, 0) -- obdélník představující podlahu ground_shape = love.physics.newRectangleShape(ground_body, 400, 550, 700, 10) createBodies() createShapes() createMouseBody() end -- vytvoření těles na zadaných souřadnicích function createBodies() bodies[0] = love.physics.newBody(world, 200, 550-100) bodies[1] = love.physics.newBody(world, 350, 550-100) bodies[2] = love.physics.newBody(world, 500, 550-100) bodies[3] = love.physics.newBody(world, 650, 550-100) bodies[4] = love.physics.newBody(world, 275, 550-250) bodies[5] = love.physics.newBody(world, 425, 550-250) bodies[6] = love.physics.newBody(world, 575, 550-250) end -- vytvoření tvarů k tělesům uloženým v asociativním poli bodies function createShapes() for i = 0, #bodies do -- přiřazení tvaru k tělesu shapes[i] = love.physics.newRectangleShape(bodies[i], 0, 0, 100, 100) -- nastavení odrazivosti shapes[i]:setRestitution(body_restitution) -- výpočet hmotnosti bodies[i]:setMassFromShapes() end end -- vytvoření tělesa ovládaného myší function createMouseBody() -- těleso ovládané myší mouse_body = love.physics.newBody(world, 420, 100) mouse_shape = love.physics.newRectangleShape(mouse_body, 0, 0, 50, 50) -- nastavení odrazivosti tělesa mouse_shape:setRestitution(body_restitution) -- výpočet hmotnosti tělesa mouse_body:setMassFromShapes() -- povolení zobrazení kurzoru myši a vytvoření vazby love.mouse.setVisible(true) love.mouse.setPosition(420, 100) mouseJoint = love.physics.newMouseJoint(mouse_body, 420, 100) --mouseJoint:setMaxForce(10000000) end -- pravidelně volaná callback funkce function update(dt) world:update(dt) end -- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí -- překreslit obsah okna function draw() love.graphics.setColor(160, 250, 160) love.graphics.setLineWidth(2) -- vykreslení podlahy a dalších nepohyblivých předmětů love.graphics.polygon(love.draw_line, ground_shape:getPoints()) -- vykreslení všech těles love.graphics.setColor(250, 160, 250) for i=0, #bodies do local body = bodies[i] love.graphics.polygon(love.draw_line, shapes[i]:getPoints()) end -- vykreslení ovládaného tělesa love.graphics.setColor(250, 250, 0) love.graphics.polygon(love.draw_line, mouse_shape:getPoints()) -- vykreslení vektoru představujícího vazbu love.graphics.setColor(250, 100, 100) -- první koncový bod úsečky je získán přímo z objektu představujícího vazbu local x, y = mouseJoint:getTarget() -- druhý koncový bod je představován středem tělesa love.graphics.line(x, y, mouse_body:getX(), mouse_body:getY()) -- výpis počitadla snímků love.graphics.setColor(255, 0, 255) love.graphics.draw(string.format("frame: %d", frame), 10, 20) frame = frame + 1 -- výpis nápovědy love.graphics.setColor(0, 255, 128) love.graphics.draw("[q] quit", 560, 20) love.graphics.draw("[r] restart", 560, 40) end -- callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu function keypressed(key) -- klávesou [ESC] nebo [Q] se celá simulace ukončí if key == love.key_escape or key == love.key_q then love.system.exit() end -- klávesou [R] se celý systém restartuje if key == love.key_r then love.system.restart() end end -- callback funkce volaná ve chvíli, kdy uživatel stlačí či pustí nějaké tlačítko myši function mousepressed(x, y, button) -- nastavení nového výchozího bodu pro vazbu mouseJoint:setTarget(x,y) end -- finito
Animace 2: Posun žlutého tělesa pomocí myši.
5. Vazba typu „RevoluteJoint“
Dalším typem vazby podporované systémem LÖVE, je vazba typu RevoluteJoint. Pomocí této vazby je možné simulovat například kladku, páku nebo jakoukoli dvojici těles, které jsou v jednom bodu spojeny osou a mohou se okolo této osy otáčet. Vzhledem k tomu, že jsou veškeré simulace prováděny v rovině, musí systém LÖVE pracovat pouze s jedním stupněm volnosti. Při vytváření této vazby se zadávají čtyři parametry – obě tělesa, která mají být pomocí této vazby propojena a souřadnice bodu, jenž reprezentuje osu otáčení (střed kladky popř. bod, okolo kterého se otáčí páka). Tuto vazbu je možné nakonfigurovat tak, aby úhel mezi oběma tělesy (měřený v ose otáčení) zůstával v nastavených limitech – pro tento účel slouží metody setLimitsEnabled(), setUpperLimit(), setLowerLimit() a setLimits(). Limitní úhly se zadávají v radiánech. Taktéž je možné k ose připojit fiktivní motor, který oběma tělesy otáčí. Pro povolení použití motoru a nastavení jeho vlastnostní se využívají metody setMotorEnabled(), setMotorSpeed() (rychlost otáčení) a setMaxMotorTorque() (točivá síla motoru, kterou může působit na tělesa).
Obrázek 1: Vazba typu RevoluteJoint.
Název metody | Popis |
---|---|
getAngle() | získání aktuálně vypočteného úhlu |
getSpeed() | získání aktuální úhlové rychlosti |
isMotorEnabled() | vrátí hodnotu true pokud je povolené použití motoru |
isLimitsEnabled() | vrátí hodnotu true pokud jsou povoleny limity při výpočtu vazby mezi dvojicí těles |
getMaxMotorTorque() | vrátí maximální nastavenou točivou sílu motoru |
getMotorSpeed() | aktuální rychlost motoru |
getMotorTorque() | aktuální točivá síla motoru |
getLowerLimit() | vrátí dolní limit vazby |
getUpperLimit() | vrátí horní limit vazby |
getLimits() | vrátí dvojici: dolní i horní limit vazby |
setMotorEnabled( motor ) | povolí či zakáže použití motoru |
setMaxMotorTorque( force ) | nastaví maximální točivou sílu motoru |
setMotorSpeed( speed ) | nastaví rychlost motoru |
setLimitsEnabled( limit ) | povolí či zakáže použití limitů |
setUpperLimit( upper ) | nastaví horní limit vazby |
setLowerLimit( lower ) | nastaví spodní limit vazby |
setLimits( lower, upper ) | nastaví dolní i horní limit vazby |
destroy() | explicitní zrušení této vazby (většinou není nutné provádět). |
6. Druhý demonstrační příklad – vazba typu „RevoluteJoint“
V dnešním druhém demonstračním příkladu je ukázáno použití vazby typu RevoluteJoint. V simulované scéně se nachází trojice těles představujících páky. Vždy dvojice pák (první a druhá, popř. druhá a třetí) má nastavenu společnou osu otáčení. Díky působení gravitace na druhou a třetí páku (první páka je vytvořena takovým způsobem, aby na ni gravitace nepůsobila – viz příslušný konstruktor) se celá soustava pák při simulaci pohybuje, ovšem nastavené vazby nedovolí, aby se páky od sebe oddělily – jediný povolený pohyb je rotace okolo nastavených os. Odstraněním poznámek u příkazů joints[1]:setMotorEnabled(true), joints[1]:setMotorSpeed(200) a joints[1]:setMaxMotorTorque(100000000) je možné ke druhé ose rotace připojit „motor“ o zadané rychlosti otáčení a síle. Tento typ vazby je základem pro výpočet inverzní kinematiky. Následuje výpis zdrojového kódu dnešního druhého demonstračního příkladu:
------------------------------------------------- -- Seriál "Programovací jazyk Lua", část číslo 19 -- -- Druhý demonstrační příklad: využití vazby -- typu RevoluteJoint. ------------------------------------------------- -- rozměry okna window = { width = 800, height = 600 } -- objekt představující svět, ve kterém se provádí simulace world = nil -- odrazivost tělesa body_restitution = 0.3 -- počitadlo snímků frame = 0 -- pole těles bodies = {} -- pole tvarů shapes = {} -- inicializace všech potřebných objektů a datových struktur function load() -- inicializace grafického režimu love.graphics.setMode(window.width, window.height, false, false, 0) -- načtení fontu local font = love.graphics.newFont(love.default_font, 16) love.graphics.setFont(font) -- vytvoření "světa" o rozměrech 2000x2000 délkových jednotek world = love.physics.newWorld(2000, 2000) -- zákaz gravitace world:setGravity(0, 50) -- podlaha na souřadnicích [0, 0] s nulovou hmotností ground_body = love.physics.newBody(world, 0, 0, 0) -- obdélník představující podlahu ground_shape = love.physics.newRectangleShape(ground_body, 400, 550, 700, 10) createBodies() createShapes() createJoint() end -- vytvoření těles na zadaných souřadnicích function createBodies() bodies[0] = love.physics.newBody(world, 300, 200, 0) bodies[1] = love.physics.newBody(world, 300+200-20, 200) bodies[2] = love.physics.newBody(world, 300+200-40+200, 200) end -- vytvoření tvarů k tělesům uloženým v asociativním poli bodies function createShapes() -- přiřazení tvaru k tělesu shapes[0] = love.physics.newRectangleShape(bodies[0], 0, 0, 200, 20) shapes[1] = love.physics.newRectangleShape(bodies[1], 0, 0, 200, 20) shapes[2] = love.physics.newRectangleShape(bodies[2], -20, 0, 150, 20) -- nastavení odrazivosti shapes[1]:setRestitution(body_restitution) shapes[2]:setRestitution(body_restitution) -- výpočet hmotnosti bodies[1]:setMassFromShapes() bodies[2]:setMassFromShapes() end -- vytvoření vazby function createJoint() revolvePoint = {} revolvePoint.x= bodies[0]:getX() + 100 - 10 revolvePoint.y= bodies[0]:getY() joints = {} joints[0] = love.physics.newRevoluteJoint(bodies[0], bodies[1], revolvePoint.x, revolvePoint.y) revolvePoint.x= bodies[1]:getX() + 100 - 10 revolvePoint.y= bodies[1]:getY() joints[1] = love.physics.newRevoluteJoint(bodies[1], bodies[2], revolvePoint.x, revolvePoint.y) --joints[1]:setMotorEnabled(true) --joints[1]:setMotorSpeed(200) --joints[1]:setMaxMotorTorque(100000000) end -- pravidelně volaná callback funkce function update(dt) world:update(dt/8) end -- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí -- překreslit obsah okna function draw() love.graphics.setColor(160, 250, 160) love.graphics.setLineWidth(2) -- vykreslení podlahy a dalších nepohyblivých předmětů love.graphics.polygon(love.draw_line, ground_shape:getPoints()) -- vykreslení všech těles love.graphics.setColor(160, 250, 250) for i=0, #bodies do local body = bodies[i] love.graphics.polygon(love.draw_line, shapes[i]:getPoints()) end -- výpis počitadla snímků a dalších informací love.graphics.setColor(255, 0, 255) love.graphics.draw(string.format("frame: %d", frame), 10, 20) frame = frame + 1 -- výpis nápovědy love.graphics.setColor(0, 255, 128) love.graphics.draw("[q] quit", 560, 20) love.graphics.draw("[r] restart", 560, 40) end -- callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu function keypressed(key) -- klávesou [ESC] nebo [Q] se celá simulace ukončí if key == love.key_escape or key == love.key_q then love.system.exit() end -- klávesou [R] se celý systém restartuje if key == love.key_r then love.system.restart() end end -- callback funkce volaná ve chvíli, kdy uživatel stlačí či pustí nějaké tlačítko myši function mousepressed(x, y, button) -- nastavení nového výchozího bodu pro vazbu mouseJoint:setTarget(x,y) end -- finito
Animace 3: Použití vazby typu RevoluteJoint.
7. Vazba typu „PrismaticJoint“
Posledním typem vazby mezi tělesy, který je v systému LÖVE podporován, je vazba typu PrismaticJoint. S využitím této vazby je možné spojit dvojici těles tak, aby se od sebe mohly vzdalovat (či se naopak k sobě přibližovat) pouze po jedné ose, tj. stupeň volnosti pohybu je o jednu dimenzi snížen. Podobně jako u předchozí vazby, i zde je možné nastavit limity, popř. lze k vazbě připojit motor, který může oběma tělesy po zadané ose pohybovat.
Obrázek 2: Vazba typu PrismaticJoint.
Název metody | Popis |
---|---|
getTranslation() | získání aktuálně vypočteného posunu |
getSpeed() | získání aktuální rychlosti |
isMotorEnabled() | vrátí hodnotu true pokud je povolené použití motoru |
isLimitsEnabled() | vrátí hodnotu true pokud jsou povoleny limity při výpočtu vazby mezi dvojicí těles |
getMaxMotorForce() | vrátí maximální nastavenou sílu motoru |
getMotorSpeed() | aktuální rychlost motoru |
getMotorForce() | aktuální síla motoru |
getLowerLimit() | vrátí dolní limit vazby |
getUpperLimit() | vrátí horní limit vazby |
getLimits() | vrátí dvojici: dolní i horní limit vazby |
setMotorEnabled( motor ) | povolí či zakáže použití motoru |
setMaxMotorForce( force ) | nastaví maximální sílu motoru |
setMotorSpeed( speed ) | nastaví rychlost motoru |
setLimitsEnabled( limit ) | povolí či zakáže použití limitů |
setUpperLimit( upper ) | nastaví horní limit vazby |
setLowerLimit( lower ) | nastaví spodní limit vazby |
setLimits( lower, upper ) | nastaví dolní i horní limit vazby |
destroy() | explicitní zrušení této vazby (většinou není nutné provádět). |
8. Literatura a odkazy na Internetu
- Baraff David and Witkin Andrew:
Physically Based Modeling,
SIGGRAPH Course Notes, July 1998, pages B1–C12 - Magnetat-Thalmann B., Thalmann D. and Arnaldi B.:
Computer Animation and Simulation 2000,
Springer Verlag, Wien, ISBN 3–2118–3549–0 - Box2D Physics Engine (jedná se o engine, na němž je knihovna love.physics založena)
http://www.box2d.org/ - Domovská stránka systému LÖVE
http://love2d.org/ - Tutoriály k systému LÖVE
http://love2d.org/?page=documentation - Screenshoty aplikací vytvořených v LÖVE
http://love2d.org/screenshots - Domovská stránka programovacího jazyka Lua
http://www.lua.org/ - 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)
9. Obsah další části seriálu
V následující části seriálu o programovacím jazyku Lua si ukážeme, jakým způsobem je možné použít jednu z poměrně zdařilých implementací tohoto jazyka. Jedná se o implementaci nazvanou příhodně LuaJ, protože je vytvořena v programovacím jazyku Java, přičemž výsledný interpret lze použít jak pro platformu J2SE (desktopy), tak i J2ME (rozličná mobilní zařízení). Ve své podstatě se jedná o virtuální stroj (Lua Virtual Machine) běžící ve druhém virtuálním stroji (JVM – Java Virtual Machine). Použitím platformy Java je mj. umožněno, aby vytvářené skripty využívaly bez větších omezení všech knihoven poskytovaných běhovým prostředím Javy (JRE), podobně jako tomu je například v případě skriptovacího jazyka Scala či Jythonu (implementace Pythonu pro JRE). Dokonce je možné použít interpret jazyka Lua jako standardní skriptovací engine odpovídající JSR-233 (dynamické skriptování). Javovská verze jazyka Lua si přitom zachovává svoji relativně malou velikost – celý archiv obsahující jak interpret, tak i překladač, má necelých 160 kB.