Hlavní navigace

Hrátky se systémem LÖVE - pružné vazby mezi tělesy

7. 7. 2009
Doba čtení: 15 minut

Sdílet

V osmnácté části našeho seriálu o programovacím jazyce Lua a knihovnách určených pro tento jazyk si ukážeme, jakým způsobem je možné v systému LÖVE vytvořit pružné (elastické) vazby mezi několika tělesy a jak tyto vazby ovlivní vzájemnou interakci těles v simulovaném prostoru.

Obsah

1. Hrátky se systémem LÖVE – pružné vazby mezi tělesy
2. Systémy těles spojených pomocí elastických vazeb
3. Objekt DistanceJoint
4. První demonstrační příklad – pružné vazby vytvořené mezi trojicí míčků
5. Složitější topologie vazeb
6. Druhý demonstrační příklad – pružné vazby mezi větším množstvím těles
7. Animace vytvořené pomocí druhého demonstračního příkladu
8. Literatura a odkazy na Internetu
9. Obsah následující části seriálu

1. Hrátky se systémem LÖVE – pružné vazby mezi tělesy

V předchozích částech tohoto seriálu jsme si vysvětlili, jakým způsobem se za pomoci systému LÖVE provádí simulace s tělesy, které mají většinou tvar konvexního polygonu. Tato tělesa se mohou volně pohybovat v prostoru, narážet na sebe, klouzat či se valit po povrchu jiných těles atd. Na tělesa, která se vyskytují v modelovaném systému, může v průběhu simulace působit několik druhů sil, z nichž mezi ty nejvýznamnější patří gravitace (tu již známe z předchozích částí tohoto seriálu) a síla, kterou je simulován odpor okolního prostředí proti pohybu těles. Ve složitějších simulacích, například při vložení těles do silového pole, se používají i další typy sil, ovšem jejich použití je v systému LÖVE již obtížnější. Síly, jež působí na tělesa, mění jejich rychlost, pozici a rotaci. 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 výše zmíněné 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 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 pomocí 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í.

2. Systémy těles spojených pomocí elastických vazeb

Pružné (elastické) vazby lze použít pro vytvoření 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. 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í.

Při inicializaci aplikace, ve které se provádí simulace, se musí nastavit počáteční pozice a rychlosti těles. Následně jsou vytvořeny vazby mezi tělesy, přičemž lze 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 ve druhém 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 modelů, které v průběhu simulace (a s ní souvisejících deformací modelu) nemění svoji topologii. V některých případech však může být žádoucí, aby bylo možné modelovaný objekt rozdělit, tj. přerušit některé elastické vazby. 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 ve své současné verzi nepodporuje, ovšem lze je napodobit programově, neboť informace o stavu každé vazby lze poměrně jednoduše získat v kterémkoli okamžiku simulace.

3. Objekt DistanceJoint

Elastická vazba, jenž je v systému LÖVE představovaná objektem typu DistanceJoint, se vytvoří voláním konstruktoru love.physics.new­DistanceJoint(). Tento konstruktor musí být zavolaný se šesticí parametrů. 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 poslední dvojice parametrů 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 oba demonstrační příklady, není to však nutné). Konstruktor love.physics.new­DistanceJoint() vytvoří objekt, nad nímž je možné volat metody vypsané v následující tabulce:

Metoda Význam
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).

4. První demonstrační příklad – pružné vazby vytvořené mezi trojicí míčků

V dnešním prvním demonstračním příkladu je ukázáno, jak lze pomocí pružných (elastických) vazeb navzájem propojit trojici míčků. Určení, zda mají být elastické vazby vytvořeny či zda se budou míčky pohybovat nezávisle na sobě, se řídí globální konstantou use_joints. Pokud je tato konstanta nastavena na logickou hodnotu true, je mezi každé dva míčky 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.new­DistanceJoint(), 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 pomocí metody elasticka_vaz­ba:setLength() nastavena klidová délka pružiny a dále metodou elasticka_vaz­ba:setFrequen­cy() rychlost odezvy pružiny na její natažení či naopak stlačení. Pozice pružin (elastických vazeb) je naznačena pomocí úseček, které spojují středy míčků. Následuje výpis zdrojového kódu prvního demonstračního příkladu:

-------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- První demonstrační příklad: pružné vazby
-- vytvořené mezi trojicí míčků
-------------------------------------------------

-- 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

-- povolení vytvoření vazeb mezi míčky
use_joints = true

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, 500, 700, 10, -10)

    -- šikmý plát pro "rozjetí" míčku
    plate_body = love.physics.newBody(world, 0, -100, 0)
    plate_shape = love.physics.newRectangleShape(plate_body, 100, 440, 300, 10, 50)

    -- rastrový obrázek představující těleso - míček
    ball = love.graphics.newImage("green_ball.png")

    -- vytvoření tělesa na zadaných souřadnicích
    ball_bodies = {}
    ball_bodies[0] = love.physics.newBody(world, 50, 100)
    ball_bodies[1] = love.physics.newBody(world, 250, 100)
    ball_bodies[2] = love.physics.newBody(world, 150, 40)
    ball_shapes = {}
    for i = 0, 2 do
        -- přiřazení tvaru k tělesu
        ball_shapes[i] = love.physics.newCircleShape(ball_bodies[i], 31)

        -- nastavení odrazivosti
        ball_shapes[i]:setRestitution(body_restitution)

        -- výpočet hmotnosti
        ball_bodies[i]:setMassFromShapes()
    end

    dist_joint = {}

    -- vytvoření vazeb
    if use_joints then
        for i=0, 2 do
            local ball_body1 = ball_bodies[i]
            local ball_body2 = ball_bodies[(i+1)%3]
            dist_joint[i] = love.physics.newDistanceJoint(
                ball_body1, ball_body2,
                ball_body1:getX(), ball_body1:getY(),
                ball_body2:getX(), ball_body2:getY())
            dist_joint[i]:setLength(100)
            dist_joint[i]:setFrequency(3)
        end
    end
end

-- pravidelně volaná callback funkce
function update(dt)
    world:update(dt/4)
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 nakloněné roviny
    love.graphics.polygon(love.draw_line, ground_shape:getPoints())
    love.graphics.polygon(love.draw_line, plate_shape:getPoints())

    -- vykreslení míčků se správným natočením
    for i=0, 2 do
        local ball_body = ball_bodies[i]
        love.graphics.draw(ball, ball_body:getX(), ball_body:getY(), ball_body:getAngle())
    end

    -- naznačení vazeb mezi míčky
    if use_joints then
        for i=0, 2 do
            local ball_body1 = ball_bodies[i]
            local ball_body2 = ball_bodies[(i+1)%3]
            love.graphics.line(ball_body1:getX(), ball_body1:getY(), ball_body2:getX(), ball_body2:getY())
        end
    end

    -- 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)
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
end

-- finito
lua1801

Obrázek 1: Animace vytvořená pomocí prvního demonstračního příkladu. Povšimněte si, že v prvních snímcích dochází k ustálení tvaru výsledného objektu, protože počáteční vzdálenosti míčků jsou odlišné od klidové délky pružin (elastických vazeb).

5. Složitější topologie vazeb

Ve druhé kapitole jsme si řekli, že pro zajištění stability celého systému těles, která jsou spojena 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 postačuje vytvořit vazby tak, 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 druhém demonstračním příkladu je celý systém složen ze čtrnácti těles tvořících jednu řadu šesti čtverců. 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ě vytvoří řada čtverců), 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. 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 větší odpor.

6. Druhý demonstrační příklad – pružné vazby mezi větším množstvím těles

Ve druhém demonstračním příkladu, jehož výpis je uveden pod tímto odstavcem, je ukázáno použití elastických vazeb k vytvoření objektu, který se skládá ze čtrnácti těles čtvercového tvaru spojených elastickými vazbami. Které typy vazeb se vytvoří, je řízeno pomocí globálních konstant enable_horizon­tal_joints, enable_vertical_jo­ints a enable_diagonal_jo­ints. Vazby jsou vytvořeny programově ve funkcích createJoints() (vytvoření samotných vazeb) a fillBindings() (naplnění pole, které obsahuje dvojice vrcholů, které mají být spojeny vazbou). Povšimněte si, že při inicializaci vazeb se současně vypočítává i jejich klidová délka (na základě vzájemné vzdálenosti středů obou těles), tj. vytvořené těleso po spuštění simulace nezakmitává, jak tomu bylo v předchozím demonstračním příkladu. Taktéž stojí za povšimnutí nahrazení ternárního operátoru (který v jazyku Lua neexistuje) pomocí dvojice operátorů and a or při výpis nastavení aplikace – zde se využívá faktu, že oba operátory vyhodnocují své druhé operandy pouze tehdy, pokud není již z hodnoty prvního operandu zřejmý výsledek celého výrazu. Podobným způsobem se ostatně ternární operátor nahrazuje například i v shellu a dalších (nejenom) skriptovacích jazycích:

-------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Druhý demonstrační příklad: pružné vazby
-- mezi větším množstvím těles ve tvaru čtverce
-------------------------------------------------

-- povolení či zákaz vytváření některých typů vazeb
enable_horizontal_joints = true
enable_vertical_joints = true
enable_dialogal_joints = true

-- 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

body_count = 14

-- pole těles
bodies = {}

-- pole tvarů
shapes = {}

-- pole vazeb
joints = {}

-- indexy těles spojených vazbami
bindings = {}

-- inicializace všech potřebných objektů a datových struktur
function load()
    fillBindings()

    -- 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)

    -- nepohyblivé obdélníky
    plate_body1 = love.physics.newBody(world, 0, -100, 0)
    plate_shape1 = love.physics.newRectangleShape(plate_body1, 100, 440, 100, 100)

    plate_body2 = love.physics.newBody(world, 0, -100, 0)
    plate_shape2 = love.physics.newRectangleShape(plate_body2, 700, 440, 100, 100)

    createBodies()
    createShapes()
    createJoints()
end

-- vytvoření těles na zadaných souřadnicích
function createBodies()
    for i = 0, 6 do
        bodies[i*2] = love.physics.newBody(world, 100+i*100, 50)
        bodies[i*2+1] = love.physics.newBody(world, 100+i*100, 150)
    end
end

function createShapes()
    for i = 0, #bodies do
        -- přiřazení tvaru k tělesu
        shapes[i] = love.physics.newRectangleShape(bodies[i], 0, 0, 30, 30)

        -- nastavení odrazivosti
        shapes[i]:setRestitution(body_restitution)

        -- výpočet hmotnosti
        bodies[i]:setMassFromShapes()
    end
end

-- vytvoření vazeb
function createJoints()
    for i=1, #bindings do
        local body1 = bodies[bindings[i][1]]
        local body2 = bodies[bindings[i][2]]
        local x1 = body1:getX()
        local y1 = body1:getY()
        local x2 = body2:getX()
        local y2 = body2:getY()
        joints[i] = love.physics.newDistanceJoint(body1, body2, x1, y1, x2, y2)
        joints[i]:setLength(math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)))
        joints[i]:setFrequency(3)
    end
end

-- vytvoření vazeb: indexů těles, která se spojují vazbami
function fillBindings()
    if enable_vertical_joints then
        for i=0, body_count/2-1 do
            table.insert(bindings, {i*2,i*2+1})
        end
    end
    if enable_horizontal_joints then
        for i=0, (body_count-1)/2-1 do
            table.insert(bindings, {i*2,(i+1)*2})
            table.insert(bindings, {i*2+1,(i+1)*2+1})
        end
    end
    if enable_dialogal_joints then
        for i=0, (body_count-1)/2-1 do
            table.insert(bindings, {i*2,(i+1)*2+1})
            table.insert(bindings, {i*2+1,(i+1)*2})
        end
    end
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())
    love.graphics.polygon(love.draw_line, plate_shape1:getPoints())
    love.graphics.polygon(love.draw_line, plate_shape2:getPoints())

    -- vykreslení všech těles
    love.graphics.setColor(250, 250, 160)
    for i=0, #bodies do
        local body = bodies[i]
        love.graphics.polygon(love.draw_line, shapes[i]:getPoints())
    end

    -- naznačení vazeb mezi tělesy
    love.graphics.setColor(160, 160, 250)
    for i=1, #bindings do
        local body1 = bodies[bindings[i][1]]
        local body2 = bodies[bindings[i][2]]
        love.graphics.line(body1:getX(), body1:getY(), body2:getX(), body2:getY())
    end

    -- 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 aktuálního uživatelského nastavení
    -- povšimněte si použití operátorů "and" a "or" namísto
    -- chybějícího ternárního operátoru typu "? :"
    love.graphics.setColor(80, 80, 160)
    love.graphics.draw("Horizontal joints: " .. (enable_horizontal_joints and "enabled" or "disabled"), 10, 40)
    love.graphics.draw("Vertical joints: " .. (enable_vertical_joints and "enabled" or "disabled"), 10, 60)
    love.graphics.draw("Diagonal joints: " .. (enable_diagonal_joints and "enabled" or "disabled"), 10, 80)

    -- 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

-- finito

7. Animace vytvořené pomocí druhého demonstračního příkladu

lua1802

Obrázek 2: Tělesa jsou spojena pouze horizontálními elastickými vazbami; ve skutečnosti se tedy jedná o dva objekty.

lua1803

Obrázek 3: Tělesa jsou spojena pouze vertikálními elastickými vazbami; opět se jedná o několik samostatných objektů.

lua1804

Obrázek 4: Použití horizontálních a současně i vertikálních elastických vazeb nepostačuje k tomu, aby byl tvar celého objektu zachován.

lua1805

Obrázek 5: Tělesa jsou spojena pouze úhlopříčnými vazbami, což ve skutečnosti opět vede k vytvoření dvou navzájem nepropojených objektů.

lua1806

Obrázek 6: Horizontální, vertikální i úhlopříčné vazby – výsledný objekt si zachovává původní tvar, protože tři sousední tělesa tvoří vrcholy jednoho trojúhelníku, jehož strany jsou tvořeny elastickými vazbami.

ict ve školství 24

8. Literatura a odkazy na Internetu

  1. Baraff David and Witkin Andrew:
    Physically Based Modeling,
    SIGGRAPH Course Notes, July 1998, pages B1–C12
  2. Magnetat-Thalmann B., Thalmann D. and Arnaldi B.:
    Computer Animation and Simulation 2000,
    Springer Verlag, Wien, ISBN 3–2118–3549–0
  3. Box2D Physics Engine (jedná se o engine, na němž je knihovna love.physics založena)
    http://www.box2d­.org/
  4. Domovská stránka systému LÖVE
    http://love2d­.org/
  5. Tutoriály k systému LÖVE
    http://love2d­.org/?page=do­cumentation
  6. Screenshoty aplikací vytvořených v LÖVE
    http://love2d­.org/screenshots
  7. Domovská stránka programovacího jazyka Lua
    http://www.lu­a.org/ 
  8. Lua
    http://www.li­nuxexpres.cz/pra­xe/lua
  9. Lua
    http://cs.wiki­pedia.org/wiki/Lua
  10. Lua (programming language)
    http://en.wiki­pedia.org/wiki/Lu­a_(programmin­g_language)

9. Obsah následující části seriálu

V navazující části seriálu o programovacím jazyku Lua již popis „herního“ systému LÖVE dokončíme. Ukážeme si způsob vytvoření dalších typů vazeb mezi tělesy, pomocí nichž je například možné simulovat chování kladky či podobného jednoduchého stroje. Následně se již budeme věnovat popisu dalších knihoven, které různým způsobem původní funkcionalitu jazyka Lua (přesněji řečeno funkcionalitu nabízenou základními knihovnami tohoto jazyka) rozšiřují a umožňují tak použití Lua jak na webových serverech, tak i v různých nástrojích zaměřených na simulace, numerické výpočty či zpracování textu. V neposlední řadě se také budeme poněkud podrobněji zabývat velmi zajímavým projektem metalua, pomocí něhož se rozšiřuje samotná sémantika programovacího jazyka, například o další jazykové konstrukce (nové typy smyček, ternární operátor atd.) či o možnost modifikace překládaného kódu na základě speciálních pravidel zapisovaných přímo do zdrojového textu programu.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.