1539 lines
62 KiB
Typst
1539 lines
62 KiB
Typst
#set page(
|
|
paper: "a4",
|
|
margin: (y: 30mm, inside: 35mm, outside: 25mm),
|
|
)
|
|
|
|
#set par(justify: false)
|
|
#set text(size: 12pt, font: "Calibri", hyphenate: false)
|
|
|
|
#show heading: set text(size: 12pt, weight: "regular")
|
|
#show heading.where(level: 1): set text(size: 18pt, weight: "bold")
|
|
#show heading.where(level: 2): set text(size: 16pt)
|
|
#show heading.where(level: 3): set text(size: 14pt)
|
|
|
|
#set math.equation(numbering: "(1)")
|
|
|
|
|
|
#let leading = 1.5em // Your line spacing (1, 1.5, 2, etc.)
|
|
#let leading = leading - 0.75em // "Normalization"
|
|
#set par(leading: leading)
|
|
|
|
#show figure.where(kind: raw): set figure(kind: image, supplement: [Slika])
|
|
#show figure.where(kind: image): set figure(supplement: [Slika])
|
|
#show figure.where(kind: table): set figure(supplement: [Tabela])
|
|
#set heading(numbering: "1.1", supplement: [Poglavje])
|
|
|
|
#set math.equation(numbering: (n, ..) => {
|
|
numbering("(1.1)", counter(heading).get().first(), n)
|
|
})
|
|
|
|
#set figure(numbering: (n, ..) => {
|
|
numbering("1.1", counter(heading).get().first(), n)
|
|
})
|
|
|
|
#show figure.caption: it => {
|
|
let pattern = "^[^:]+" + sym.space.nobreak + "[\d.]+"
|
|
it
|
|
}
|
|
|
|
#show heading.where(level: 1): it => {
|
|
counter(math.equation).update(0)
|
|
counter(figure.where(kind: image)).update(0)
|
|
counter(figure.where(kind: table)).update(0)
|
|
counter(figure.where(kind: raw)).update(0)
|
|
it
|
|
}
|
|
|
|
#set math.equation(supplement: "Enačba")
|
|
|
|
#let avthor = "Nikola Petrov"
|
|
#let mentor = "red. prof. dr. Aleš Holobar"
|
|
#let naslov = upper("Uporabniško vodena evolucija fraktalnih dreves")
|
|
#let naslov_en = upper("User-guided evolution of fractal trees")
|
|
|
|
#set document(
|
|
title: [#naslov],
|
|
author: avthor,
|
|
)
|
|
|
|
#align(center + top)[
|
|
#image("assets/UM_FERI.png", width: 30%)
|
|
]
|
|
|
|
#align(center + horizon)[
|
|
#set text(size: 14pt)
|
|
#avthor
|
|
#show title: set text(size: 26pt)
|
|
#title()
|
|
Diplomsko delo
|
|
]
|
|
|
|
#align(center + bottom)[
|
|
Maribor, oktober 2025
|
|
]
|
|
#pagebreak()
|
|
|
|
#align(center + top)[
|
|
#image("assets/UM_FERI.png", width: 30%)
|
|
]
|
|
|
|
#align(center + horizon)[
|
|
#set text(size: 14pt)
|
|
#avthor
|
|
#show title: set text(size: 26pt)
|
|
#title()
|
|
Diplomsko delo
|
|
]
|
|
|
|
#align(center + bottom)[
|
|
Maribor, oktober 2025
|
|
]
|
|
#pagebreak()
|
|
|
|
#counter(page).update(1)
|
|
#set page(numbering: "i")
|
|
|
|
#align(center + horizon)[
|
|
#title()
|
|
Diplomsko delo
|
|
]
|
|
|
|
#align(bottom)[
|
|
#grid(
|
|
columns: 2,
|
|
gutter: 10pt,
|
|
|
|
[Študent], [#avthor],
|
|
[Študijski program], [visokošolski študijski program \ računalništvo in informacijske tehnologije],
|
|
[Mentor], [#mentor],
|
|
)
|
|
]
|
|
|
|
#pagebreak()
|
|
|
|
|
|
|
|
#align(horizon)[
|
|
#set par(justify: false)
|
|
#set text(hyphenate: false, size: 14pt)
|
|
#grid(
|
|
columns: (1fr, 1fr),
|
|
[],
|
|
[
|
|
*Zahvala*
|
|
|
|
Na tem mestu bi se rad iskreno
|
|
zahvalil vsem, ki so prispevali
|
|
k uspešnemu zaključku te diplomske
|
|
naloge.
|
|
|
|
Zahvaljujem se svojemu
|
|
mentorju #mentor,
|
|
za njegovo pomoč in nasvete skozi
|
|
celoten proces pisanja.
|
|
|
|
Posebna zahvala gre moji družini ter
|
|
starim in novim prijateljem, ki
|
|
so me ves čas podpirali
|
|
in spodbujali skozi celoten študij.
|
|
Brez vas ne bi dosegel tega, kar sem.
|
|
],
|
|
)
|
|
]
|
|
|
|
#pagebreak()
|
|
|
|
|
|
#naslov
|
|
|
|
*Ključne besede:*
|
|
|
|
*UDK:*
|
|
|
|
*Povzetek*
|
|
|
|
#pagebreak()
|
|
|
|
|
|
#naslov_en
|
|
|
|
*Keywords:*
|
|
|
|
*UDC:*
|
|
|
|
*Abstract*
|
|
|
|
#pagebreak()
|
|
|
|
IZJAVA O AVTORSTVU ZAKLJUČNEGA DELA
|
|
|
|
#pagebreak()
|
|
|
|
#outline(
|
|
title: "KAZALO VSEBINE",
|
|
depth: 3,
|
|
)
|
|
|
|
#pagebreak()
|
|
|
|
#outline(
|
|
title: "KAZALO SLIK",
|
|
target: figure.where(kind: image),
|
|
)
|
|
|
|
#pagebreak()
|
|
|
|
#outline(
|
|
title: "KAZALO TABEL",
|
|
target: figure.where(kind: table),
|
|
)
|
|
|
|
#pagebreak()
|
|
|
|
#counter(page).update(1)
|
|
#set page(numbering: "1")
|
|
|
|
|
|
= UVOD
|
|
V današnjem digitalnem svetu je interaktivnost in prilagodljivost ključnega pomena za ustvarjanje uporabniških izkušenj, ki so tako zabavne kot tudi funkcionalne. Fraktalna drevesa, kot matematična in umetniška oblika, ponujajo neskončno raznolikost struktur, ki jih je mogoče uporabiti v raznovrstnih aplikacijah, od vizualnih umetnosti do simulacij naravnih procesov. V tem kontekstu se pojavi vprašanje: *Kako lahko uporabniki aktivno vplivajo na razvoj in oblikovanje teh struktur?*
|
|
|
|
Ta diploma se osredotoča na razvoj aplikacije *Treender*, ki uporabnikom omogoča interaktivno vodenje evolucije fraktalnih dreves. S pomočjo algoritmov za genetsko evolucijo in uporabniških ocen aplikacija ustvarja in prilagaja fraktalna drevesa, ki odražajo individualne okuse in preference uporabnikov.
|
|
|
|
Cilj projekta je raziskati, kako lahko algoritmi za evolucijo in z uporabniško povratno informacijo ustvarimo dinamične in osebne izkušnje.
|
|
|
|
V nadaljevanju bomo predstavili osnovne koncepte za generiranje in evolucijo fraktalnih dreves, opisali strukturo aplikacije in podrobno razložili uporabljene algoritme. Prav tako bomo raziskali uporabniški vmesnik in proces objave aplikacije na Google Play Store. Na koncu bomo oceni uporabniško izkušnjo in učinkovitost algoritmov, ki jih uporabljamo.
|
|
#pagebreak()
|
|
|
|
= OPIS APLIKACIJE
|
|
Treender je aplikacije, ki nam omogoča, da izbiramo in ocenjujemo fraktalna drevesa, ki se na podlagi naših ocen evoluirajo in prilagajajo našim okusom. V desnem zgornjem kotu nam prikaže všečnost, na dnu nam pa prikaže generacijo evolucije in kateri vzorec se prikazuje (@aplikacija-slika-def). Če povlečemo po zaslonu levo se nam v zgornjem levem kotu prikaže napis "DISLIKE" (@aplikacija-slika-dislike) v desno pa se nam prikaže napis "LIKE" (@aplikacija-slika-like).
|
|
|
|
#grid(
|
|
columns: 3,
|
|
gutter: 10pt,
|
|
[
|
|
#figure(
|
|
image("assets/rand/capture_show_noraml.jpg"),
|
|
caption: [
|
|
Primer aplikacije
|
|
],
|
|
)<aplikacija-slika-def>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/rand/capture_show_liked.jpg"),
|
|
caption: [
|
|
Primer aplikacije like
|
|
],
|
|
)<aplikacija-slika-like>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/rand/capture_show_dislike.jpg"),
|
|
caption: [
|
|
Primer aplikacije dislike
|
|
],
|
|
)<aplikacija-slika-dislike>
|
|
],
|
|
)
|
|
|
|
Ko vzorec ocenimo si aplikacija zapomni in nato, ko ocenimo vse vzorce se nam prikaže črna slika z napisom "new generation". V tem trenutku se glede na ocenitve ustvari nova generacija in izračuna všečnost.
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= UPORABLJENE TEHNOLOGIJE
|
|
|
|
== C++
|
|
C++ je visokonivojni programski jezik, ki je nastal kot razširitev jezika C. Razvil ga je Bjarne Stroustrup v zgodnjih 1980-ih letih z namenom, da združi moč in učinkovitost jezika C z objektno-usmerjenimi koncepti, ki podpirajo boljšo organizacijo in ponovno uporabo kode @wiki_cpp.
|
|
|
|
=== Glavne značilnosti C++:
|
|
|
|
1. *Objektno-usmerjeno programiranje (OOP)*: C++ podpira objektno-usmerjeno programiranje, kar omogoča uporabo razredov in objektov za modeliranje realnih svetovnih problemov. To olajša organizacijo kode in omogoča ponovno uporabo kode.
|
|
|
|
2. *Učinkovitost*: C++ je znan po svoji visoki učinkovitosti in hitrosti izvajanja, kar ga naredi primerno za razvoj aplikacij, ki zahtevajo visoko zmogljivost, kot so igre, sistemsko programiranje in razvoj vgrajenih sistemov.
|
|
|
|
3. *Nizkonivojni dostop*: C++ omogoča dostop do spomina in drugih sistemskih virov na nizki ravni, kar omogoča večjo kontrolo nad strojno opremo. To je posebno koristno v sistemskem programiranju in razvoju vgrajenih sistemov.
|
|
|
|
4. *Standardna knjižnica (STL)*: C++ vsebuje bogato standardno knjižnico, ki vključuje različne strukture podatkov (npr. vektorji, seznami, množice) in algoritme, ki olajšajo razvoj.
|
|
|
|
5. *Preprostost in moč*: C++ združuje preprostost jezika C z dodatnimi zmožnostmi, kot so razredi, dedovanje, polimorfizem in preobremenjevanje operatorjev, kar omogoča večjo fleksibilnost in moč pri programiranju.
|
|
|
|
=== Uporaba C++:
|
|
|
|
- *Razvoj iger*: Zaradi svoje visoke učinkovitosti in možnosti za nizkonivojni dostop je C++ priljubljen jezik za razvoj iger.
|
|
- *Sistemsko programiranje*: Uporablja se za razvoj operacijskih sistemov, gonilnikov in drugih sistemskih programov.
|
|
- *Vgrajeni sistemi*: Zaradi svoje učinkovitosti in možnosti za nizkonivojni dostop je C++ priljubljen za razvoj vgrajenih sistemov.
|
|
- *Računalniška grafika*: Uporablja se za razvoj grafičnih aplikacij in orodij za obdelavo slik.
|
|
- *Znanstvene in inženirske aplikacije*: Zaradi svoje zmogljivosti in učinkovitosti se uporablja za razvoj znanstvenih in inženirskih aplikacij, ki zahtevajo visoko zmogljivost.
|
|
|
|
#pagebreak()
|
|
|
|
== RAYLIB
|
|
Raylib je odprtokodna knjižnica, namenjena predvsem razvoju iger in multimedijskih aplikacij. Zasnovana je tako, da je enostavna za uporabo in omogoča hitro ustvarjanje 2D in 3D grafike, zvoka, vhodnih naprav in drugih multimedijskih vsebin @web_raylib @web_raylib_cheatsheet.
|
|
|
|
=== Glavne značilnosti Raylib:
|
|
|
|
1. *Enostavnost uporabe*: Raylib je zasnovan tako, da je enostaven za uporabo, z minimalnim številom funkcij, ki so potrebne za osnovno delovanje igre. To omogoča hitrejši razvoj in lažje učenje.
|
|
|
|
2. *Podpora za 2D in 3D grafiko*: Raylib podpira tako 2D kot 3D grafiko, kar omogoča razvoj različnih vrst iger in aplikacij.
|
|
|
|
3. *Vhodne naprave*: Podpira različne vhodne naprave, kot so tipkovnica, miška, igralni nadzorniki in dotikalni zasloni.
|
|
|
|
4. *Zvok*: Vključuje osnovno podporo za predvajanje zvoka in glasbe, kar je pomembno za razvoj iger.
|
|
|
|
5. *Preprostost vključevanja*: Raylib je zasnovan kot enostavna knjižnica, ki se lahko vključi v projekt z minimalnimi nastavitvami.
|
|
|
|
6. *Odprtokodnost*: Ker je Raylib odprtokoden, ga lahko razvijalci prilagajajo svojim potrebam in prispevajo k njegovemu razvoju.
|
|
|
|
=== Uporaba Raylib:
|
|
|
|
- *Razvoj iger*: Raylib je priljubljen med razvijalci iger, saj omogoča hitro ustvarjanje prototipov in enostavno upravljanje z grafiko, zvokom in vhodnimi napravami.
|
|
- *Izobraževanje*: Zaradi svoje enostavnosti in odprtokodnosti je Raylib pogosto uporabljen v izobraževalnih nameneh za učenje osnov razvoja iger.
|
|
- *Multimedijske aplikacije*: Uporablja se za razvoj različnih multimedijskih aplikacij, ki zahtevajo upravljanje z grafiko in zvokom.
|
|
|
|
Raylib je zaradi svoje preprostosti in zmogljivosti priljubljen izbir za razvijalce, ki želijo hitro ustvariti igre ali multimedijske aplikacije brez potrebe po kompleksnih orodjih in knjižnicah.
|
|
#pagebreak()
|
|
|
|
== ImGui
|
|
Dear ImGui (ali preprosto ImGui) je odprtokodna knjižnica uporabniškega vmesnika, ki omogoča hitro in enostavno ustvarjanje uporabniških vmesnikov za aplikacije v realnem času. Zasnovana je za razvijalce, ki potrebujejo preprost in učinkovit način za dodajanje uporabniških vmesnikov v svoje aplikacije, predvsem v igrah, orodjih in drugih interaktivnih programih @git_imgui @git_rlImgui.
|
|
|
|
=== Glavne značilnosti ImGui:
|
|
|
|
1. *Enostavnost uporabe*: ImGui je zasnovan tako, da je enostaven za uporabo in integracijo v obstoječe projekte. Njegova preprosta arhitektura omogoča hitro ustvarjanje in spreminjanje uporabniških vmesnikov.
|
|
|
|
2. *Učinkovitost*: Zasnovana je za visoko učinkovitost, kar je posebno pomembno za aplikacije v realnem času, kot so igre in simulacije.
|
|
|
|
3. *Immediate Mode GUI*: ImGui uporablja koncept "immediate mode" GUI, kar pomeni, da se uporabniški vmesnik ustvari in posodobi vsak frame. To omogoča veliko fleksibilnost in enostavnost pri spreminjanju vmesnika v realnem času.
|
|
|
|
4. *Podpora za različne platforme*: ImGui je prenosljiv in podpira več platform in grafičnih API-jev, kot so OpenGL, DirectX, Vulkan in Metal.
|
|
|
|
5. *Odprtokodnost*: Ker je ImGui odprtokoden, ga lahko razvijalci prilagajajo svojim potrebam in prispevajo k njegovemu razvoju.
|
|
|
|
=== Uporaba ImGui:
|
|
|
|
- *Razvoj iger*: Uporablja se za ustvarjanje orodij za razvoj iger, kot so urejevalniki nivojev, debuggerji in druga orodja, ki zahtevajo interaktivni uporabniški vmesnik.
|
|
- *Interaktivna orodja*: Uporablja se za razvoj različnih interaktivnih orodij, kot so simulacije, vizualizacije podatkov in druge aplikacije, ki zahtevajo uporabniški vmesnik.
|
|
- *Prototipiranje*: Zaradi svoje enostavnosti in hitrosti je ImGui priljubljen za prototipiranje uporabniških vmesnikov.
|
|
|
|
ImGui je zaradi svoje preprostosti, učinkovitosti in fleksibilnosti priljubljen izbir za razvijalce, ki potrebujejo hitro in enostavno rešitev za ustvarjanje uporabniških vmesnikov v svojih aplikacij
|
|
#pagebreak()
|
|
|
|
== CMAKE
|
|
*CMake* je orodje za avtomatizacijo gradnje (build automation tool), ki je zasnovano za upravljanje procesa prevajanja, sestavljanja in paketiranja programske kode, zlasti v večplatformskih okoljih. Razvil ga je Kitware leta 2000, da bi olajšal proces gradnje programov, ki so napisani v različnih programskih jezikih, kot so C, C++, Fortran in drugi @web_cmake @wiki_cmake.
|
|
|
|
=== Glavne značilnosti CMake:
|
|
|
|
1. *Večplatformska podpora* CMake omogoča generiranje gradbenih datotek (npr. Makefile za Unix/Linux, projektne datoteke za Visual Studio na Windowsu, Xcode na macOS) za različne operacijske sisteme in razvojna okolja. To pomeni, da lahko isti izvorni kod prevajate in sestavljate na različnih platformah brez spreminjanja gradbenih skript.
|
|
|
|
2. *Konfiguracijske datoteke (CMakeLists.txt)* CMake uporablja tekstovne datoteke z imenom `CMakeLists.txt`, ki vsebujejo navodila za konfiguracijo projekta. Te datoteke opisujejo, kako naj se projekt sestavi: kateri viri (source files) so vključeni, katere knjižnice so potrebne, in kakšne so odvisnosti med posameznimi komponentami.
|
|
|
|
3. *Upravljanje odvisnosti* CMake omogoča enostavno upravljanje odvisnosti med knjižnicami in programi. Podpira iskanje in povezovanje z zunanjimi knjižnicami (npr. OpenCV, Boost) in omogoča definicijo lastnih ciljev (targets), kot so izvršljivi programi ali knjižnice.
|
|
|
|
4. *Podpora za različne prevajalnike* CMake deluje z različnimi prevajalniki (npr. GCC, Clang, MSVC) in omogoča uporabo specifičnih nastavitev za vsakega od njih.
|
|
|
|
5. *Modularnost in ponovno uporabo kode* CMake podpira modularno strukturo projektov, kar olajša ponovno uporabo kode in deljenje knjižnic med različnimi projekti.
|
|
|
|
=== Kako CMake deluje?
|
|
|
|
1. *Konfiguracija:* CMake prebere datoteke `CMakeLists.txt` in generira gradbene datoteke za izbrani sistem (npr. Makefile ali Visual Studio projekt).
|
|
2. *Gradnja:* Uporabnik nato uporabi ustrezno gradbeno orodje (npr. `make`, Visual Studio) za prevajanje in sestavljanje projekta.
|
|
3. *Namestitev (opcijsko):* CMake lahko generira tudi namestitvene skripte za namestitev programa ali knjižnice v sistem.
|
|
|
|
=== Tipična uporaba CMake:
|
|
|
|
- *Razvoj programske opreme v C/C++:* CMake je standardno orodje za gradnjo projektov v C in C++, zlasti v večjih ali kompleksnih projektih.
|
|
- *Večplatformski projekti:* Če razvijate programsko opremo, ki mora delovati na več platformah (Windows, Linux, macOS), je CMake idealno orodje za upravljanje gradnje.
|
|
- *Integracija z drugimi orodji:* CMake se lahko integrira z orodji za testiranje (npr. CTest), paketiranje (npr. CPack) in dokumentacijo.
|
|
|
|
|
|
|
|
=== Primer preproste datoteke `CMakeLists.txt`:
|
|
|
|
```Cmake
|
|
cmake_minimum_required(VERSION 3.10)
|
|
project(MojProjekt)
|
|
|
|
add_executable(moj_program main.cpp)
|
|
```
|
|
Ta datoteka določa, da je minimalna potrebna različica CMake 3.10, da je ime projekta "MojProjekt" in da naj se iz datoteke `main.cpp` generira izvršljivi program z imenom `moj_program`.
|
|
|
|
CMake je močno orodje, ki olajša in avtomatizira proces gradnje programske opreme, zlasti v večplatformskih okoljih. Njegova glavna prednost je fleksibilnost in možnost generiranja gradbenih datotek za različne sisteme, kar ga naredi nezaobhodljivega pri razvoju v C in C++. Ali te zanima kakšen specifičen vidik CMake ali primer uporabe?
|
|
#pagebreak()
|
|
|
|
== SQLITE3
|
|
*SQLite* je lahka, samostoječa in vdelana relacijska baza podatkov, ki deluje neposredno v programu, brez potrebe po ločenem strežniku. Razvil jo je D. Richard Hipp leta 2000 z namenom, da ponudi preprosto, hitro in zanesljivo rešitev za upravljanje podatkov, ki ne zahteva nastavljanja ali upravljanja strežnika. SQLite je napisana v jeziku C in je na voljo kot knjižnica, kar pomeni, da se lahko integrira neposredno v aplikacije @web_sqlite @wiki_sqlite.
|
|
|
|
|
|
|
|
=== Glavne značilnosti SQLite:
|
|
|
|
1. *Samostoječa in vdelana* SQLite deluje kot del programa in ne zahteva ločenega procesa ali strežnika. To pomeni, da aplikacija komunicira neposredno s podatkovno bazo, kar zmanjša kompleksnost in poveča hitrost delovanja.
|
|
|
|
2. *Enostavnost in prenosljivost* Celotna baza podatkov je shranjena v eni datoteki, kar olajša prenos in upravljanje. Datoteka je preprosto kopirljiva med različnimi sistemi, ne da bi potrebovali dodatne konfiguracije.
|
|
|
|
3. *Nizke zahteve* SQLite ne potrebuje nastavljanja ali upravljanja, kar jo naredi idealno za uporabo v mobilnih napravah, vgrajenih sistemih in manjših aplikacijah, kjer so viri omejeni.
|
|
|
|
4. *Podpora za SQL* SQLite podpira večino standardnih SQL ukazov (npr. `SELECT`, `INSERT`, `UPDATE`, `DELETE`), kar omogoča enostavno upravljanje podatkov z uporabo poznanega jezika.
|
|
|
|
5. *Zanesljivost in varnost* SQLite zagotavlja transakcijsko varnost (ACID), kar pomeni, da so podatki vedno dosledni, tudi v primeru napak ali prekinitve dela.
|
|
|
|
6. *Odprtokodna in brezplačna* SQLite je odprtokodna in brezplačna, kar pomeni, da jo lahko prostovoljno uporabljate v komercialnih in nekomercialnih projektih.
|
|
|
|
|
|
|
|
|
|
=== Uporaba SQLite:
|
|
|
|
- *Mobilne aplikacije* SQLite je priljubljena izbira za shranjevanje podatkov v mobilnih aplikacijah (npr. Android, iOS), saj ne potrebuje strežnika in deluje lokalno na napravi.
|
|
|
|
- *Vgrajeni sistemi* Zaradi svoje preprostosti in nizkih zahtev je SQLite pogosto uporabljena v vgrajenih sistemih, kot so pametni ure, senzorji in druga naprava z omejenimi viri.
|
|
|
|
- *Spletne aplikacije* Uporablja se za lokalno shranjevanje podatkov v brskalnikih (npr. Web SQL API) ali kot preprosta rešitev za manjšo spletno stran.
|
|
|
|
- *Testiranje in razvoj* SQLite je pogosto uporabljena med razvojem in testiranjem, saj omogoča hitro in enostavno ustvarjanje in upravljanje podatkovnih baz.
|
|
|
|
- *Aplikacije za namizje* Številne namizne aplikacije uporabljajo SQLite za lokalno shranjevanje podatkov, saj ne zahteva dodatne infrastrukture.
|
|
|
|
SQLite je torej idealna rešitev, če potrebujete preprosto, hitro in zanesljivo podatkovno bazo, ki deluje neposredno v vaši aplikaciji. Je ena najpogosteje uporabljanih podatkovnih baz na svetu, predvsem zaradi svoje preprostosti in učinkovitosti.
|
|
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= STRUKTURA APLIKACIJE
|
|
Aplikacija ima tri glavne enote DNA menedžer, DNA vizualizator in Jedro. DNA manedžer skrbi za shranjevanje trenutnega dna in mutacijo, DNA vizualizator pa skrbi za izris dna, njegovo delo je, da dobi dna sekvenco in jo pretvori v sliko. V jedru se pa nahajajo ostale komponente, ki so pomembne za delovanje aplikacije, omrežni vmesnik in vmesnik za shranjevanje.
|
|
|
|
Jedro je hrbtenica, ki povezuje vse komponente, ob zagonu preračuna razmerja za prikaz besedila in slik dreves, skrbi za premik in rotacijo slike ko s prstom povlečemo po zaslonu, skrbi da, ko pridemo s prstom do roba in hočemo označiti sliko da se izbira pošlje v dna manager ki nam nato vrne nasledni dna za prikaz jedro tukaj poskrbi da se prenese v dna vizualizator in ko dobi sliko, da jo prikaže uporabniku, po vsaki izbiri se izbira tudi shrani na disk, ko pridemo pa do konca generacije se še cela generacija shrani na disk in nato se tudi pošlje na strežnik.
|
|
|
|
#figure(
|
|
image("assets/potek_diagram/diagram.svg", height: 400pt),
|
|
caption: [
|
|
Diagram poteka podatkov
|
|
],
|
|
)
|
|
|
|
#pagebreak()
|
|
|
|
== UPORABNIŠKI VMESNIK ROTACIJA SLIKE
|
|
Proces rotiranja slike glede na premik miške, je sestavljen iz treh korakov
|
|
in uporabo trigometrije
|
|
|
|
=== Prvi korak: Ob pritisku miške
|
|
V prvem koraku moramo izračunati velikost(magnituda) in kot vektorja med miško in levim spodnim kotom slike (Cyan vektor @Prikaz_vektorjev) in shraniti si moramo trenutno pozicijo miške (Oranžni vektor/Zelena pika @Prikaz_vektorjev)
|
|
|
|
$
|
|
"mouseStartV" = "mouseV"\
|
|
"sizeOfVectorMI" = sqrt(("mouseV"_x - "imageV"_x)^2 + ("mouseV"_y - "imageV"_y)^2)\
|
|
"oldAngleOfVectorMI" = "atan2"("imageV"_x - "mouseV"_x, "imageV"_y - "mouseV"_y)
|
|
$
|
|
- $"mouseV"$ - trenutni vektor miške
|
|
- $"imageV"$ - vektor pozicije slike (Modri vektor @Prikaz_vektorjev)
|
|
- $"vectorMI"$ - vectorMouseImage - vector med miško in sliko
|
|
- $"sizeOfVectorMI"$ - velikost vektorjaMI
|
|
- $"oldAngleOfVectorMI"$ - kot vektorjaMI v radianih
|
|
|
|
#grid(
|
|
columns: 2,
|
|
gutter: 10pt,
|
|
[
|
|
#figure(
|
|
image("assets/rand/cordinats_show_mouse_picture.jpg"),
|
|
caption: [
|
|
Prikaz vektorjev za premik slike
|
|
],
|
|
)<Prikaz_vektorjev>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/rand/show_mouse_movement.jpg"),
|
|
caption: [
|
|
Prikaz delta razdalje
|
|
],
|
|
)<Prikaz_razdalje>
|
|
],
|
|
)
|
|
|
|
#pagebreak()
|
|
=== Drugi korak: Medtem ko miško držimo stisnjeno
|
|
Sedaj lahko izračunamo rotacijo slike glede na horizontalni premik miške,in posledično novo pozicijo slike.
|
|
|
|
$
|
|
Delta"mouseV"_x & = "mouseV"_x - "mouseStart"_x \
|
|
a & = frac(Delta"mouseV"_x, "screenWidth") \
|
|
b & = frac(a + 1, 2) \
|
|
"rotation" & = "lerp"(45,-45, b) \
|
|
"rotationRad" & = (("rotation"* pi) / 180.0)
|
|
$<mat_rotacija>
|
|
|
|
- $"screenWidth"$ - širina zaslona
|
|
- $Delta"mouseV"_x$ - sprememba pozicije $[-"screenWidth", "screenWidth"]$
|
|
- $a$ - normalizirano na razpon $[-1, 1]$
|
|
- $b$ - normalizirano na razpon $[0, 1]$
|
|
- $"rotation"$ - kot za koliko se more slika obrniti v stopinjah
|
|
- $"rotationRad"$ - v radianih
|
|
|
|
#v(10pt)
|
|
V @mat_rotacija Za $Delta"mouseV"$ lahko vidimo v (@Prikaz_razdalje) da, ko je začetek v zeleni piki in se premikamo proti modri je delta pozitivna in ko se premikamo proti rdeči je negativna zato potrebujemo normalizacijo $a$ in $b$ ker lerp sprejme vrednosti med $[0, 1]$
|
|
#v(10pt)
|
|
|
|
$
|
|
"newAngleOfVectorMI" & = "rotationRad" + "oldAngleOfVectorM" \
|
|
"vectorMI"_x & = "sizeOfVecorMI" * cos("newAngleOfVectorMI") \
|
|
"vectorMI"_y & = "sizeOfVecorMI" * sin("newAngleOfVectorMI") \
|
|
"imageV"_x & = "vectorMI"_x + "mouseV"_x \
|
|
"imageV"_y & = "vectorMI"_y + "mouseV"_y \
|
|
"imageRotation" & = 90 + "rotation"
|
|
$
|
|
|
|
- $"imageRotation"$ - novi kot slike v stopinjah
|
|
- $"imageV"$ - pozicija slike
|
|
|
|
#v(10pt)
|
|
Sedaj lahko končno narišemo sliko na poziciji $"imageV"$ in pot kotom $"imageRotation"$
|
|
|
|
#pagebreak()
|
|
=== Tretji korak: Ob spustitvi miške
|
|
Ko miško spustimo moramo samo ponastaviti vrednosti slike da je spet pokončno in jo postaviti nazaj na originalno pozicijo.
|
|
|
|
$
|
|
"imageRotation" & = 90 \
|
|
"imageV" & = "orgImageV"
|
|
$
|
|
|
|
- $"orgImageV"$ - originalna pozicija slike
|
|
|
|
=== Uporabljene funkcije
|
|
|
|
==== atan2
|
|
Funkcija atan2 je matematična funkcija, ki se uporablja za izračun tangente loka količnika, vendar namesto enega samega razmerja ($y/x$) vzame dva ločena argumenta ($y$ in $x$). Zaradi tega je še posebej uporaben za določanje kota v pravilnem kvadrantu, ki temelji na znakih obeh koordinat. @wiki_atan2:
|
|
|
|
$
|
|
"atan2"(x, y) := cases(
|
|
arctan(frac(y, x)) & "if" x > 0,
|
|
arctan(frac(y, x))+pi & "if" x<0 "and" y >= 0,
|
|
arctan(frac(y, x))-pi & "if" x<0 "and" y < 0,
|
|
+ frac(pi, 2) & "if" x = 0 "and" y > 0,
|
|
- frac(pi, 2) & "if" x = 0 "and" y < 0,
|
|
"undefined" & "if" x = 0 "and" y = 0
|
|
)
|
|
$
|
|
|
|
==== LERP
|
|
Lerp (okrajšava za linearno interpolacijo) je matematična tehnika, ki se uporablja za iskanje vrednosti med dvema danima vrednostma na podlagi oločenega interpolacijskega parametra, ki je pogosto označen kot $t$. @wiki_lerp
|
|
|
|
$
|
|
"lerp"(v 0, v 1, t) = v 0 + t * (v 1 - v 0)
|
|
$
|
|
|
|
#pagebreak()
|
|
|
|
== DNA MANAGER
|
|
#let next = `next`
|
|
#let like = `like`
|
|
#let newGen = `newGen`
|
|
|
|
Namenjena uporaba je, najprej pokličemo #next, ki nam vrne objekt z DNA podatki in indeks DNA-a, ko se uporabnik odloči ali mu je všec, to sporočimo managerju preko funkcije #like, ko nam #like vrne `false` in #next vrne `nullptr` vemo, da smo prikazali in označili vse DNA-je v generaciji in moramo generirati novo generacijo s klicom #newGen. (@dna_manager)
|
|
|
|
Dna manager nam dovoli, da večkrat pridobimo naslednji DNA brez, da takoj označimo ali nam je všeč. V aplikaciji je to uporabljeno tako da pridobimo dva DNA za prikaz. Prvi je na vrhu in ga lahko premikamo in označimo, drugi je pa v ozadju. Po označitvi se zgornji in spodnji zamenjata in spodnji se zamenja z naslednjim.
|
|
|
|
Funkcija #next vrne DNA iz `voctor[queued]` in ga nato inkrementira `queued` če je `queued` večji od `vector` vrne `nullptr`.
|
|
|
|
Funkcija #like sprejme indeks in liked in nato shrani index v vector liked ali disliked, nato inkrementira `seen` in če je večji od `vector` vrne `false`.
|
|
|
|
Medtem funkcija #newGen vsebuje glavno logiko. Deluje na principu naravne evolucije naključno izbere dva DNA in ustvari otroka tako da naključno kopira DNA od obeh staršev, da ima v povprečju 50% DNA od enega starša in 50% od drugega starša. Ko ustvari novo generacijo celotno generacijo mutira tako da naključno spremeni specificirano število genov(byte). Tukaj lahko upazimo robni primer kaj če imamo samo en ali nič DNA-jev ki so uporabniku všeč, v primeru da kloniramo dokler ne ustvarimo novo generacijo in v primeru ko uporabniku ni noben všeč generiramo novo naključno generacijo.
|
|
|
|
#figure(caption: "DnaManager")[
|
|
#grid(
|
|
columns: 2,
|
|
[
|
|
```cpp
|
|
struct DnaManagerData
|
|
{
|
|
int generation;
|
|
uint128 randSeed;
|
|
int64_t id;
|
|
int queued;
|
|
int seen;
|
|
std::vector<Dna> vector;
|
|
std::vector<int> liked;
|
|
std::vector<int> disliked;
|
|
};
|
|
|
|
namespace DnaManager
|
|
{
|
|
UiUnit next(DnaManagerData *data);
|
|
bool like(UiUnit unit, DnaManagerData *data);
|
|
void newGen(DnaManagerData *data);
|
|
}
|
|
```
|
|
],
|
|
[
|
|
```cpp
|
|
struct UiUnit
|
|
{
|
|
Dna *dna;
|
|
Liked liked;
|
|
int index;
|
|
};
|
|
|
|
enum Liked
|
|
{
|
|
tbd,
|
|
yes,
|
|
no
|
|
};
|
|
```
|
|
],
|
|
)
|
|
|
|
]<dna_manager>
|
|
|
|
#pagebreak()
|
|
|
|
== DNA
|
|
Dna je sestavljen iz dveh struktur, (@dna_objekt). Zanimljiv del ki ga opazimo je da je vsak gen točno en bite dolg in celoten objekt je v pomnilniku samo en velik list bitov (array of bites). To značinost uporabljamo v complementarnih funkcijah `newDna`, `makeChiled`, `clone` in `mutate`.
|
|
|
|
#figure(caption: "DNA objekt")[
|
|
|
|
#grid(
|
|
columns: 2,
|
|
[
|
|
```cpp
|
|
struct Dna
|
|
{
|
|
uint128 mountenSeed;
|
|
uint128 starSeed;
|
|
uint128 branchSeed;
|
|
|
|
uint8_t moonX;
|
|
uint8_t moonY;
|
|
uint8_t moonSize;
|
|
uint8_t colorSet;
|
|
Branch branches[MAX_DEPTH];
|
|
};
|
|
```
|
|
],
|
|
[
|
|
```cpp
|
|
struct Branch
|
|
{
|
|
uint8_t colorR;
|
|
uint8_t colorG;
|
|
uint8_t colorB;
|
|
int8_t colorR_change;
|
|
int8_t colorG_change;
|
|
int8_t colorB_change;
|
|
uint8_t colorVar;
|
|
|
|
uint8_t size;
|
|
uint8_t sizeParent;
|
|
uint8_t sizeLevel;
|
|
uint8_t sizeChange;
|
|
uint8_t sizeVar;
|
|
|
|
uint8_t length;
|
|
uint8_t lengthVar;
|
|
|
|
uint8_t branchCount;
|
|
uint8_t branchAngleVar;
|
|
};
|
|
```
|
|
],
|
|
)
|
|
]<dna_objekt>
|
|
|
|
#pagebreak()
|
|
|
|
|
|
Če pogledamo definicijo od `newDna` (@dna_funkcije) vidimo pretvorbo(cast) iz `Dna` v `uint8_t*` in sedaj se lahko enostavno sprehodimo čez objekt in spremenimo vrednosti.
|
|
|
|
#figure(caption: [Primer DNA funkcije])[
|
|
```cpp
|
|
namespace DNA
|
|
{
|
|
void newDna(Dna *dna, uint128 *randState);
|
|
void makeChild(Dna *p1, Dna *p2, Dna *c, uint128 *randState);
|
|
void clone(Dna *p1, Dna *c, uint128 *randState);
|
|
void mutate(Dna *dna, uint32_t num, uint128 *randState);
|
|
}
|
|
|
|
void newDna(Dna *dna, uint128 *state)
|
|
{
|
|
uint8_t *array = (uint8_t *)dna;
|
|
for (size_t i = 0; i < sizeof(Dna); i++)
|
|
{
|
|
array[i] = mrand::getValue(256, state);
|
|
}
|
|
}
|
|
```
|
|
]<dna_funkcije>
|
|
|
|
Ostale funkcije so podobne najprej pretvorimo v `uint_8*` in nato:
|
|
- `makeChild` naključno kopiramo iz staršev
|
|
- `clone` naključno kopiramo iz starša in vstavljamo naključne vrednosti
|
|
- `mutate` pa sprecificirano število vrednosti spremenimo v naključne vrednosti
|
|
|
|
|
|
|
|
#pagebreak()
|
|
|
|
== DNA VIZUALIZACIJA
|
|
|
|
|
|
#figure(
|
|
image("assets/rand/slika_drevesa.jpg", height: 200pt),
|
|
caption: [
|
|
Končna skika po vizualizaciji
|
|
],
|
|
)
|
|
|
|
Je sestavljena iz treh razredov `Canvas`, `BackGround`, `Tree`(@razredi_vizualizacije). Tukaj Canvas je samo umesni razred ki postavi risanje na teksturo in nato pokliče BackGround in Tree
|
|
|
|
#figure(caption: [Razredi za vizualizacijo])[
|
|
#grid(
|
|
columns: 2,
|
|
[
|
|
```cpp
|
|
class Canvas
|
|
{
|
|
public:
|
|
void draw(
|
|
RenderTexture2D &target,
|
|
Dna *dna);
|
|
private:
|
|
BackGround backGround;
|
|
Tree tree;
|
|
};
|
|
|
|
class BackGround
|
|
{
|
|
public:
|
|
void draw(Dna *dna);
|
|
private:
|
|
void drawStars();
|
|
void drawSun();
|
|
void drawMounten(
|
|
size_t mountenSegments, int min,
|
|
int max, Color color, float scale);
|
|
};
|
|
```
|
|
],
|
|
[
|
|
```cpp
|
|
class Tree
|
|
{
|
|
public:
|
|
void draw(Dna *dna);
|
|
private:
|
|
void drawBranch();
|
|
std::vector<DrawArgs> drawCalls;
|
|
};
|
|
|
|
struct DrawArgs
|
|
{
|
|
Vector2 startPoint;
|
|
float angle;
|
|
int depth;
|
|
Color parent;
|
|
int perentSize;
|
|
};
|
|
```
|
|
],
|
|
)
|
|
]<razredi_vizualizacije>
|
|
|
|
#pagebreak()
|
|
|
|
=== Ozadje
|
|
Postopek izrisa ozadja je sestavljen iz 5 korakov
|
|
|
|
+ Nebo je narisano z pomočjo raylib funkcije `DrawRectangleGradientV`(@steps_nebo)
|
|
|
|
+ Če je noč, zvezde narišemo z naključnim semenom iz DNA ki se uporabi za pozicijo, in funkcijo `DrawTriangle` (@steps_zvezde)
|
|
|
|
+ Sonce in Luna so narisane z raylib funkcijo `DrawCircle` (@steps_sonce)
|
|
|
|
+ Gore so bile težje za narisat odločil sem se da bom uporabil triangle strip, (@Triangle_Strip) @wiki_triangle_strip najprej se odlocimo iz koliko segmentov jo bomo narisali v aplikaciji uporabljamo 150 segmentov s tem imamo širino segmenta. Sedaj sledimo tem korakom:
|
|
+ Prvo točko postavimo v spodnjem levem kotu (0,0)
|
|
+ Za drugo točko si izberemo naključno številko za višino gore (0,80)
|
|
+ Naslednja točka je spet na telh samo zamaknjena za širino segmenta na desno(20,0)
|
|
+ Za naslednjo točko si zberemo naključno število za višino gore in zamaknjeno na desno od prejšne višine (20, 90)
|
|
+ Sedaj ponavljamo točko 3 in 4 dokler ne pridemo do desnega roba slike
|
|
|
|
+ Ponovimo korak 4 še dvakrat da dobimo gorovje (@steps_gora_5)
|
|
|
|
#block(breakable: false)[
|
|
#grid(
|
|
columns: 3,
|
|
gutter: 10pt,
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_1.png", height: 100pt),
|
|
caption: [
|
|
Korak nebo
|
|
],
|
|
)<steps_nebo>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_2_2.png", height: 100pt),
|
|
caption: [
|
|
Korak nebo
|
|
],
|
|
)<steps_zvezde>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_2.png", height: 100pt),
|
|
caption: [
|
|
Korak soce
|
|
],
|
|
)<steps_sonce>
|
|
],
|
|
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_3_1.png", height: 100pt),
|
|
caption: [
|
|
Korak gora 1
|
|
],
|
|
)<steps_gora_1>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_3_2.png", height: 100pt),
|
|
caption: [
|
|
Korak gora 2
|
|
],
|
|
)<steps_gora_2>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_3.png", height: 100pt),
|
|
caption: [
|
|
Korak gora 3
|
|
],
|
|
)<steps_gora_3>
|
|
],
|
|
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_4.png", height: 100pt),
|
|
caption: [
|
|
Korak gora 4
|
|
],
|
|
)<steps_gora_4>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/draw_steps/steps_1_5.png", height: 100pt),
|
|
caption: [
|
|
Korak gora 5
|
|
],
|
|
)<steps_gora_5>
|
|
],
|
|
)
|
|
#figure(
|
|
image("assets/rand/Triangle_Strip.svg.png", height: 150pt),
|
|
caption: [
|
|
Triangle strip
|
|
],
|
|
)<Triangle_Strip>
|
|
]
|
|
|
|
#pagebreak()
|
|
|
|
|
|
=== Drevo
|
|
#let da = `DrawArgs`
|
|
#let dn = `Branch`
|
|
|
|
Algoritem za izris drevesa je iterativni proceduralni generator. Za izdris uporabljamo strukturo #dn (@dna_objekt) in `std::list` (FIFO) #da (@razredi_vizualizacije)
|
|
|
|
==== DNA Branch
|
|
Veja je struktura ki vsebuje podatke o:
|
|
|
|
- Začetni barvi (`colorX`) [0, 255]
|
|
- Spremembi barve (`colorX_change`) za koliko se barva spremeni čez dolžino veje [0, 255]
|
|
- Variaciji zečetne barve (`colorVar`) za koliko se lahko naključno spremeni začetna barva [-15, -15]
|
|
- Začetna debelina (`size`) [2, 20]
|
|
- Debelina glede na starša (`sizeParent`) [0.0, 1.0]
|
|
- Debelina glede na globino (`sizeLevel`) iz @table_glob_data procent koliko je podoben predefinirani velikosti [0.0, 1.0]
|
|
- Sprememba debeline (`sizeChange`) za končna debelina veje [-5, 5]
|
|
- Variacija debeline (`sizeVar`) za koliko se lahko naključno spremeni začetna debelina [-5, 5]
|
|
- Dolžina (`length`) veje [0.5, 1.3]
|
|
- Variacija dolžine (`lengthVar`) za koliko se lahko nakključno spremeni dolžina veje [-0.15, 0.15]
|
|
- Stevilo otrokov (`branchCount`) koliko vej bo nadaljevalo [2, 3]
|
|
- Variacija kota med vejami (`branchAngleVar`) naključna variacija kota med vejami za to da ni enakomerno razporejeno [0, 20]
|
|
|
|
#figure(caption: [Globalni podatki])[
|
|
#table(
|
|
columns: 9,
|
|
table.header([Depth], [0], [1], [2], [3], [4], [5], [6], [7]),
|
|
table.hline(),
|
|
[Sizes], [20], [18], [16], [14], [12], [10], [8], [6],
|
|
[Lengths \*], [1/4], [7/40], [], [], [], [], [], [],
|
|
)
|
|
\* Lengths for 0 is 1/4 of length of a canvas next are 70% shorter of previous
|
|
]<table_glob_data>
|
|
|
|
#pagebreak()
|
|
|
|
// TODO: Dodaj slike za lazjo predstavo
|
|
==== Algoritem
|
|
Začnemo tako, da damo v list @prvi_drawArgs in ponavljamo naslednje postopke dokler imamo nekaj v listi
|
|
#figure(caption: "Prvi DrawArgs")[
|
|
```cpp
|
|
struct DrawArgs
|
|
{
|
|
startPoint : {x: screenWidth/2, y:0},
|
|
angle: 90.0f,
|
|
depth: 0
|
|
};
|
|
```
|
|
]<prvi_drawArgs>
|
|
|
|
+ Korak: Predpostavek
|
|
|
|
Da lahko narišemo drevo predvidevamo da imamo strukturo `Dna`
|
|
|
|
+ Korak: Priprava
|
|
Pridobimo `DrawArgs arg` in `Branch b`
|
|
|
|
```cpp
|
|
DrawArgs arg = list.pop_front();
|
|
Branch b = Dna.branches[argument.depth];
|
|
```
|
|
|
|
+ Korak: Začetna točka
|
|
```cpp
|
|
Vector2 StartPoint = arg.startPoint;
|
|
```
|
|
@viztree0
|
|
+ Korak: Dolžina
|
|
```cpp
|
|
float Len = Lengths[arg.dep] * b.length * (b.lengthVar * randFloat());
|
|
```
|
|
`Lengths` je iz @table_glob_data
|
|
|
|
@viztree1
|
|
+ Korak: Končna točka in naključni kot
|
|
```cpp
|
|
float AngleVar = lerp(b.branchAngleVar, -b.branchAngleVar, randFloat());
|
|
float tmpAng = AngleVar + arg.angle;
|
|
float nx = Len * sin(tmpAng);
|
|
float ny = Len * cos(tmpAng);
|
|
Vector2 EndPoint;
|
|
EndPoint.x = StartPoint.x + nx;
|
|
EndPoint.y = StartPoint.y + ny;
|
|
```
|
|
@viztree2
|
|
+ Korak: Začetna debelina
|
|
```cpp
|
|
float tmp1 = b.size + (b.sizeVar * randFloat());
|
|
float tmp2 = lerp(tmp1 ,arg.parentSize, b.sizeParent);
|
|
float StartSize = lerp(tmp2, Sizes[arg.dep], b.sizeLevel);
|
|
```
|
|
`Sizes` je iz @table_glob_data
|
|
|
|
@viztree3
|
|
|
|
+ Korak: Končna debelina
|
|
```cpp
|
|
float EndSize = StartSize + b.sizeChange;
|
|
```
|
|
@viztree3
|
|
|
|
+ Korak: Začetna barva
|
|
```cpp
|
|
Color StartColor = lerp(b.colorX, arg.parentX , 0.7) + b.colorVar * randFloat();
|
|
```
|
|
@viztree4
|
|
|
|
+ Korak: Končna barva
|
|
```cpp
|
|
Color EndColor = StartColor * b.colorChange * randFloat();
|
|
```
|
|
@viztree4
|
|
|
|
+ Korak: Izris veje
|
|
```cpp
|
|
float fstep = 1.0 / ((Len / sizeStart) * 2.0f);
|
|
for (float i = 0; i < 1; i += fstep)
|
|
{
|
|
Vector2 point = Vector2Lerp(StartPoint, EndPoint, i);
|
|
Color color = ColorLerp(StartColor, EndColor, i);
|
|
int size = Lerp(StartSize, EndSize, i);
|
|
DrawCircleV(point, size, color);
|
|
}
|
|
```
|
|
@viztree5
|
|
|
|
+ Korak: Dodaj otroke v list
|
|
```cpp
|
|
if(arg.dep + 1 > MAX_DEPTH) return;
|
|
float sectors = b.branchCount + 1;
|
|
float degres = 180.0f / sectors;
|
|
for (size_t i = 0; i < b.branchCount; i++)
|
|
{
|
|
float newAngle = arg.angle - 90 + (degres * (i + 1));
|
|
drawCalls.push_back({EndPoint, newAngle, arg.dep + 1, ColorEnd, SizeEnd});
|
|
}
|
|
```
|
|
@viztree6
|
|
|
|
+ Korak: Ponovimo
|
|
|
|
Sedaj gremo spet na Korak: Priprava (@viztree7)
|
|
|
|
#block(breakable: false)[
|
|
#grid(
|
|
columns: 3,
|
|
gutter: 10pt,
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika00.png", height: 100pt),
|
|
caption: [
|
|
Začetna točka
|
|
],
|
|
)<viztree0>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika01.png", height: 100pt),
|
|
caption: [
|
|
Dolžina veje
|
|
],
|
|
)<viztree1>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika02.png", height: 100pt),
|
|
caption: [
|
|
Kot in končna točka
|
|
],
|
|
)<viztree2>
|
|
],
|
|
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika03.png", height: 100pt),
|
|
caption: [
|
|
Debeline
|
|
],
|
|
)<viztree3>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika04.png", height: 100pt),
|
|
caption: [
|
|
Barve
|
|
],
|
|
)<viztree4>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika05.png", height: 100pt),
|
|
caption: [
|
|
Izris veje
|
|
],
|
|
)<viztree5>
|
|
],
|
|
)
|
|
#grid(
|
|
columns: 2,
|
|
gutter: 10pt,
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika06.png", height: 200pt),
|
|
caption: [
|
|
Novi otriki
|
|
],
|
|
)<viztree6>
|
|
],
|
|
[
|
|
#figure(
|
|
image("assets/vizualizacija_drevo/slika07.png", height: 200pt),
|
|
caption: [
|
|
Ponovimo
|
|
],
|
|
)<viztree7>
|
|
],
|
|
)
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
#pagebreak()
|
|
|
|
== OPTIMIZACIJE
|
|
|
|
=== Optimizacija izrisa
|
|
|
|
Sprava se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je izrisevalo vsak okvir(frame). Kasneje smo hoteli dodali veje z spremenljivo debelino, najlažja rešitev ki smo se jo spomnili je bila da narišemo krogce z spremenljivim polmerom. To nas je prisililo da moramo narisati čez 55,000 krogov. Z raylib-ovo funkcijo `DrawCircleV` za risanje krogov, je applikacija začela zaostajati(lagged). To smo rešili tako da najprej drevo narišemo na teksturo, ki je zelo enostavna za izris. To je rešilo večino problemov občasno se je ševedno pojavil zaostanek(lag spoke), ki smo ga rešili tako da smo omejili število risalnih klicov na okvir in drevo narisali v parih okvirih.
|
|
|
|
#align(center)[
|
|
#figure(caption: "Optimizacije izrisa")[
|
|
#table(
|
|
columns: 5,
|
|
align: horizon,
|
|
table.header(
|
|
[Optimizacija], [Povprečen ms/okvir], [Skupni čas izrisa slike], [Maks ms/okvir po zamenjavi slike], [FPS]
|
|
),
|
|
table.hline(),
|
|
[Izris z črtami], [2], [2], [3], [350],
|
|
[Izris z krogi vsak okvir], [100], [100], [100], [10],
|
|
[Izris z krogi v teksturo], [0.2], [100], [100], [5000],
|
|
[Izris z krogi v teksturo čez več okvirov], [0.2], [100], [14], [5000],
|
|
)
|
|
]
|
|
]
|
|
|
|
#pagebreak()
|
|
=== DNA manager optimizacija
|
|
|
|
Na zažetku (@dna_manager_v1) je DnaManager samo bil struktura kazalcev na `Dna`, s tem imamo problem da nemoremo shraniti kazalcev v datoteko. Smo spremenili na indekse (@dna_manager_v2), ki se dajo zapisati v datoteko, kasneje smo se spomnili da bi bilo dobro za predpomnilnik da so indeksi v queued sortirani. In med razhroscevanjem smo opazili da queued in showed delujeta kot premikajoči indeksi za queued, pred njim so stvari ki so že prikazane za njim pa so stvari, ki jih še moramo prikazati. Za to smo spremenili queued in showed v en indeks namesto list (@dna_manager_v3).
|
|
|
|
#align(center)[
|
|
#grid(
|
|
columns: 2,
|
|
gutter: 16pt,
|
|
align: center,
|
|
[
|
|
#figure(caption: "DnaManager v1")[
|
|
```cpp
|
|
struct DnaManagerData
|
|
{
|
|
std::vector<Dna> vector;
|
|
std::list<Dna *> queued;
|
|
std::list<Dna *> showed;
|
|
std::vector<Dna *> liked;
|
|
std::vector<Dna *> disliked;
|
|
}
|
|
```
|
|
]<dna_manager_v1>
|
|
],
|
|
[
|
|
#figure(caption: "DnaManager v2")[
|
|
```cpp
|
|
struct DnaManagerData
|
|
{
|
|
std::vector<Dna> vector;
|
|
std::list<int> queued;
|
|
std::list<int> showed;
|
|
std::vector<int> liked;
|
|
std::vector<int> disliked;
|
|
}
|
|
```
|
|
]<dna_manager_v2>
|
|
],
|
|
)
|
|
]
|
|
|
|
#figure(caption: "DnaManager v3")[
|
|
```cpp
|
|
struct DnaManagerData
|
|
{
|
|
int queued;
|
|
int showed;
|
|
std::vector<Dna> vector;
|
|
std::vector<int> liked;
|
|
std::vector<int> disliked;
|
|
}
|
|
```
|
|
]<dna_manager_v3>
|
|
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= PODOBNOST
|
|
Da smo lahko prikazali koliko so uporabniku slike všeč. Smo se odločili, da bomo izračunali podobnost med slikami, bolj ko so si slike podobne bolj so všeč uporabniku in obratno. Za to smo preučili sedem algoritmov.
|
|
|
|
== Opis algoritmov
|
|
=== Evklidska razdalja
|
|
Evklidska razdalja je najkrajša razdalja med dvema točkama v evklidskem prostoru, ki je najpogosteje uporabljen koncept razdalje v matematiki in fiziki. @wiki_euclidean_distance
|
|
|
|
==== Formula
|
|
Za dve točki p in q v n-dimenzionalnem prostoru se izračuna:
|
|
$ d(p, q) = sqrt(sum_(i=1)^n (q_i - p_i)^2) $
|
|
|
|
==== Normalizacija
|
|
Da normaliziramo Evklidsko razdaljo moramo deliti z najdaljšo možno razdaljo v prostoru:
|
|
$ "maxDistance" = 255 sqrt(n) $
|
|
Normalizacija je nato:
|
|
$ "normalizedDistance" = frac(d(p, q), "maxDistance") $
|
|
|
|
=== Skalarni product
|
|
Skalarni produkt (tudi dot produkt ali notranji produkt) je operacija med dvema vektorjema v vektorskem prostoru, ki kot rezultat vrne skalar (realno število). @wiki_dot_product
|
|
|
|
==== Formula:
|
|
Za vektorja A in B v n-dimenzionalnem prostoru:
|
|
|
|
$ upright(bold(A)) op(dot) upright(bold(B)) = sum_(i=1)^n A_i B_i $
|
|
|
|
==== Normalizacija
|
|
Da noramliziramo skalarni produkt moramo deliti z največjim produktom v prostoru:
|
|
$ "maxDotProduct" = n*255*255 $
|
|
Normalizacija je nato:
|
|
$
|
|
"normalizedDotProdut" = frac(
|
|
upright(bold(A)) op(dot) upright(bold(B)),
|
|
"maxDotProduct"
|
|
)
|
|
$
|
|
|
|
=== Kosinusna podobnost
|
|
Kosinusna podobnost je mera, ki se uporablja za določanje podobnosti dveh vektorjev, ne glede na njuno velikost. Izračuna kosinus kota med dvema vektorjema v n-dimenzionalnem prostoru. @wiki_cosine_similarity
|
|
|
|
==== Formula:
|
|
$
|
|
"similarity" = frac(upright(bold(A)) op(dot) upright(bold(B)), ||upright(bold(A))|| ||upright(bold(B))||)
|
|
$
|
|
|
|
==== Variation
|
|
Variaciaja, ki jo bomo testirali je da najprej mapiramo vrednosti genov iz (0, 255) v (-127 to 128) in s tem razširimo izhodno območje.
|
|
|
|
=== Hamming razdalja
|
|
Hammingova razdalja je koncept v matematiki in računalništvu, ki meri razliko med dvema zaporedjema enake dolžine. Najpogosteje se uporablja za nize znakov, binarne zaporedja (npr. bitne nize) ali DNA zaporedja. @wiki_hamming_distance
|
|
|
|
|
|
=== Jaccard indeks
|
|
Jaccardov indeks (ali Jaccardov koeficient podobnosti) je metrika, ki meri podobnost med dvema množicama. Izračuna se kot razmerje med velikostjo presečiška obeh množic in velikostjo njihove unije. @wiki_jaccard_index
|
|
|
|
==== Formula:
|
|
$
|
|
J(A, B) = frac(|A #sym.inter B|, |A #sym.union B|)
|
|
$
|
|
|
|
=== Levenshteinova razdalja
|
|
Levenshteinova razdalja (ali urejevalna razdalja) je metrika, ki meri razliko med dvema nizoma znakov. Določa najmanjše število urejevalnih operacij (vstavitev, izbris ali zamenjava znakov), potrebnih za pretvorbo enega niza v drugega. @wiki_levenshtein_distance
|
|
|
|
==== Formula
|
|
#let head = "head"
|
|
#let tail = "tail"
|
|
#let lev = "lev"
|
|
#let sif = "if"
|
|
$
|
|
lev(a, b) := cases(
|
|
|a| & sif |b| = 0,
|
|
|b| & sif |a| = 0,
|
|
lev(tail(a), tail(b)) & sif head(a) = head(b),
|
|
1 + min
|
|
cases(
|
|
lev(tail(a), b),
|
|
lev(a, tail(b)),
|
|
lev(tail(a), tail(b)),
|
|
) & "otherwise"
|
|
)
|
|
$
|
|
|
|
Where the
|
|
|
|
tail(x) is a string of all but the first character of x
|
|
|
|
head(x) is first character of x
|
|
|
|
|
|
=== Needleman-Wunsch algoritem
|
|
Needleman-Wunschov algoritem je algoritem, ki se uporablja v bioinformatiki za poravnavo beljakovinskih ali nukleotidnih zaporedij. Bil je ena prvih aplikacij dinamičnega programiranja za primerjavo bioloških zaporedij. Včasih se imenuje tudi algoritem optimalnega ujemanja in tehnika globalne poravnave. Needleman-Wunschov algoritem se še vedno široko uporablja za optimalno globalno poravnavo, zlasti kadar je kakovost globalne poravnave najpomembnejša. Algoritem dodeli oceno vsakemu možnemu poravnavanju, njegov namen pa je najti vsa možna poravnavanja z najvišjo oceno. @wiki_needlemanwunsch_algorithm
|
|
|
|
#pagebreak()
|
|
== Testiranje algoritmov
|
|
Testiranje je potekalo na večjih primerih evolucije. Spodaj je prikazani en primer rezultatov podobnosti in časa računanja podobnosti v mikrosecundah. Slike generacij so v prilogi.
|
|
|
|
=== Testno okolje
|
|
Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 16 GB rama. Vsak algoritem je bil zagnan petkrat in podatki so povprečje rezultatov.
|
|
|
|
// TODO: Dodaj opis testnega okolja
|
|
=== Rezultati podobnosti
|
|
|
|
#align(center)[
|
|
#figure(caption: "Rezultati podobnosti")[
|
|
#table(
|
|
columns: 7,
|
|
table.header([generacija], [evklidij], [dot], [cos\_sim], [cos\_sim\_var], [hamming], [levenshtein]),
|
|
table.hline(),
|
|
[0], [58.835], [25.4743], [75.0422], [0.19221], [0.449075], [50.3356],
|
|
[1], [63.2906], [27.6056], [80.0648], [22.6274], [22.8889], [61.4676],
|
|
[2], [67.9607], [29.0597], [84.9029], [41.0515], [44.9491], [72.4768],
|
|
[3], [68.5758], [29.181], [85.409], [42.257], [46.75], [73.375],
|
|
[4], [68.7648], [29.1488], [85.5507], [42.8455], [46.8519], [73.4259],
|
|
[5], [69.4333], [29.2939], [86.089], [44.4245], [48.463], [74.2315],
|
|
[6], [68.6623], [28.8901], [85.3302], [41.7158], [46.6667], [73.3333],
|
|
[7], [68.3941], [28.7663], [85.0723], [40.4149], [44.9213], [72.4606],
|
|
[8], [64.988], [28.0012], [82.0021], [28.3094], [27.3796], [63.6898],
|
|
[9], [73.4526], [27.977], [88.825], [57.7922], [59.213], [79.6065],
|
|
[10], [77.0616], [29.2913], [91.6422], [68.0308], [67.3981], [83.6991],
|
|
[11], [82.7646], [31.4961], [95.4701], [81.8052], [81.8981], [90.9491],
|
|
[12], [82.313], [31.8334], [95.2544], [80.7246], [80.7778], [90.3889],
|
|
)
|
|
]
|
|
]
|
|
|
|
#import "@preview/lilaq:0.5.0" as lq
|
|
|
|
#let xs = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
|
#let se = (58.835, 63.2906, 67.9607, 68.5758, 68.7648, 69.4333, 68.6623, 68.3941, 64.988, 73.4526, 77.0616, 82.7646, 82.313)
|
|
#let sd = (25.4743, 27.6056, 29.0597, 29.181, 29.1488, 29.2939, 28.8901, 28.7663, 28.0012, 27.977, 29.2913, 31.4961, 31.8334)
|
|
#let scs = (75.0422, 80.0648, 84.9029, 85.409, 85.5507, 86.089, 85.3302, 85.0723, 82.0021, 88.825, 91.6422, 95.4701, 95.2544)
|
|
#let scsv = (0.19221, 22.6274, 41.0515, 42.257, 42.8455, 44.4245, 41.7158, 40.4149, 28.3094, 57.7922, 68.0308, 81.8052, 80.7246)
|
|
#let sh = (0.449075, 22.8889, 44.9491, 46.75, 46.8519, 48.463, 46.6667, 44.9213, 27.3796, 59.213, 67.3981, 81.8981, 80.7778)
|
|
#let sl = (50.3356, 61.4676, 72.4768, 73.375, 73.4259, 74.2315, 73.3333, 72.4606, 63.6898, 79.6065, 83.6991, 90.9491, 90.3889)
|
|
|
|
#lq.diagram(
|
|
legend: (position: (100% + .5em, 0%)),
|
|
height: 200pt,
|
|
width: 300pt,
|
|
lq.plot(xs, se, mark: none, label: [euclidean]),
|
|
lq.plot(xs, sd, mark: none, label: [dot]),
|
|
lq.plot(xs, scs, mark: none, label: [cos_sim]),
|
|
lq.plot(xs, scsv, mark: none, label: [cos_sim_var]),
|
|
lq.plot(xs, sh, mark: none, label: [hamming]),
|
|
lq.plot(xs, sl, mark: none, label: [levenshtein]),
|
|
)
|
|
|
|
#pagebreak()
|
|
=== Časa računanja podobnosti v us
|
|
#align(center)[
|
|
#figure(caption: "Čas računanja podobnosti")[
|
|
#table(
|
|
columns: 7,
|
|
table.header([generation], [euclidean], [dot], [cos\_sim], [cos\_sim\_var], [hamming], [levenshtein]),
|
|
table.hline(),
|
|
[0], [91], [41], [117], [181], [87], [105799],
|
|
[1], [60], [40], [78], [305], [250], [100331],
|
|
[2], [61], [40], [78], [121], [105], [97438],
|
|
[3], [66], [40], [81], [124], [106], [97529],
|
|
[4], [60], [40], [78], [127], [108], [96296],
|
|
[5], [62], [39], [85], [131], [104], [96456],
|
|
[6], [61], [40], [81], [125], [106], [96510],
|
|
[7], [61], [40], [81], [125], [103], [97253],
|
|
[8], [61], [40], [81], [125], [78], [97409],
|
|
[9], [60], [40], [82], [125], [103], [99816],
|
|
[10], [62], [40], [81], [128], [81], [98978],
|
|
[11], [68], [40], [81], [126], [58], [98289],
|
|
[12], [61], [39], [88], [130], [60], [99663],
|
|
)
|
|
]
|
|
]
|
|
|
|
#let ts = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
|
#let te = (91, 60, 61, 66, 60, 62, 61, 61, 61, 60, 62, 68, 61)
|
|
#let td = (41, 40, 40, 40, 40, 39, 40, 40, 40, 40, 40, 40, 39)
|
|
#let tcs = (117, 78, 78, 81, 78, 85, 81, 81, 81, 82, 81, 81, 88)
|
|
#let tcsv = (181, 305, 121, 124, 127, 131, 125, 125, 125, 125, 128, 126, 130)
|
|
#let th = (87, 250, 105, 106, 108, 104, 106, 103, 78, 103, 81, 58, 60)
|
|
#let tl = (105799, 100331, 97438, 97529, 96296, 96456, 96510, 97253, 97409, 99816, 98978, 98289, 99663)
|
|
|
|
#lq.diagram(
|
|
legend: (position: (100% + .5em, 0%)),
|
|
height: 200pt,
|
|
width: 300pt,
|
|
ylim: (0, 350),
|
|
lq.plot(ts, te, mark: none, label: [euclidean]),
|
|
lq.plot(ts, td, mark: none, label: [dot]),
|
|
lq.plot(ts, tcs, mark: none, label: [cos_sim]),
|
|
lq.plot(ts, tcsv, mark: none, label: [cos_sim_var]),
|
|
lq.plot(ts, th, mark: none, label: [hamming]),
|
|
lq.plot(ts, tl, mark: none, label: [levenshtein]),
|
|
)
|
|
|
|
#pagebreak()
|
|
== Odločitev
|
|
Odločili smo se da bomo uporabili Hammingovo razdaljo, ker nam je všeč rezultat podobnosti, ker zače pri 0% nadaljuje proti 100% in kako enostavna je implementacija. Čas računanja podobnosti nismo upoštevali ker so vsi razen Levenshteinova razdalja bili manj kot 0.5 milisekunde in je to za nas dovolj hitro.
|
|
|
|
Opazili ste da Jaccard indeks in Needleman-Wunsch algoritem manjakta v testiranju in to je zato ker:
|
|
|
|
- Jaccardov indeks ni primeren za nas, ker deluje na skupini unikatnih vrednosti kar mi nimamo, mi lahko imamo dve isti vrednosti ki predstavljata dva različni lastnosti slike. Zato tudi če bi dobili 100% ujemanje ne bi pomenilo da se sliki 100% ujemata.
|
|
- Needleman-Wunsch alogitem nam pa ne vrne nevtralne stevilke ki bi jo lahko uporabili za rezultat podobnosti.
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= APP STORE
|
|
Da lahko objavimo aplikacijo na Google Play Store potrebujemo Play Console razvijalski račun @web_google_play_console. Koraki za izdelavo računa so zelo enostavni, vpiši potrebne podatke, sprejmi razvojni sporazum, in plačaj registracijo. Naši problemi so se začeli z vnosom podatkov ni so hoteli sprejeti naše telefonske številke, ker ni so mogli preverit, ko smo pa sledili korakom za odstranjevaje težav, ki so večinoma samo bili počakajte en dan in poskusite ponovno, še vedno ni delovalo. Nato smo kontaktirali njihovo pomoč, ki so nam vrnili podoben odgovor da naj čakamo, po tem smo obupali in samo uporabili drugo telefonsko številko.
|
|
|
|
V naslednjih korakih moramo naložiti zapakirano aplikacijo, dodati opis applikacije, slike delovanja in politiko varstva osebnih podatkov (@privacy_policy). Po tem pa moramo spraviti aplikacijo čez notranje testiranje, ki je zelo ne specificirano, generalna ideja naj bi bila da potrebujemo 12 testerjev, ki naj bi uporabljali aplikacijo vsaj 12 dni nikjer pa ni omenjeno koliko časa na dan morajo uporabljati aplikacijo da se šteje. Par tednov smo poskušali z prijatelji ugotoviti kdaj postaneš aktiven samo smo brez uspeha. Nato smo se odločili da bi pogledali po internetu kako ta problem rešujejo ostali. Našli smo dve rešitvi. Prva zastonj rešitev, je nekaj forumov na spletu kjer ljudje objavljajo svoje aplikacije in ideja je da si med samo pomagajo s tem da testirajo drug od drugih aplikacije. Droga plačljiva rešitev za katero smo se mi odločili je da najamemo testerja za pribljižno 20€. Z testerjem smo se zmenili kaj potrebujemo in po izmenjavi potrebnih podatko je po dveh tednih aplikacija testirana in lahko nadaljuje na zadnji pregled pred objavo.
|
|
|
|
Ob zadnjem pregledu je postalo zelo naporno. Opis naše aplikacije je navdihnil opis aplikacije Tinder, saj sta aplikaciji konceptno zelo podobni (@zacetni_opis). Takrat so pri Googlu začeli natančno pregledovati aplikacijo, ker do takrat je bilo vsakič, ko smo poslali aplikacijo v pregled, vse odobreno do naslednjega dne, tokrat pa je trajalo nekaj tednov. Njihov odgovor je bil, da opis naše aplikacije ni primeren in krši pravila, čeprav je bil zelo podoben Tindrovemu. Pri tem smo ugotovili, da se je ekipa, ki je to pregledovala, vedla precej pristransko. V skladu z navodili, ki so nam jih poslali, smo popravili vse kršitve pravil. Tako se je ponovilo še nekajkrat, kar je skupaj trajalo skoraj štiri mesece. Zadnja verzija je (@koncni_opis).
|
|
|
|
Po vsem tem je aplikacija končno objavljena na Play Store @web_google_treender
|
|
|
|
|
|
#figure(caption: [Začetni opis])[
|
|
#set align(left)
|
|
|
|
*ROOT FOR LOVE, BRANCH OUT & LEAF A MARK. IT ALL STARTS WITH A SWIPE.*
|
|
|
|
With countless matches and growing, Treender is the top free app for tree enthusiasts and nature lovers. Whether you're looking for a leafy companion, a blossoming friendship, or a deep-rooted relationship, Treender is the place to connect with fellow tree admirers. Our app offers leading safety features to ensure you can explore your love for trees safely.
|
|
|
|
*Come As You Are: Whether you're a mighty oak, a graceful willow, or a playful pine, be true to your roots and find your perfect match.*
|
|
|
|
*Branch Out: Share your interests and discover more about your matches to start meaningful conversations.*
|
|
|
|
*Seek What You're Looking For: Set your relationship goals and find your perfect tree mate on your terms.*
|
|
]<zacetni_opis>
|
|
|
|
#line()
|
|
|
|
#figure(caption: [Končni opis])[
|
|
#set align(left)
|
|
|
|
Welcome to Treender, an innovative undergraduate thesis project that combines the fun of swiping with the beauty of fractal trees. Dive into a world where fractal trees become your potential matches, and your swipes shape their evolution.
|
|
|
|
How It Works:
|
|
|
|
- Swipe Right to Like: Admire a fractal tree? Swipe right to like it and watch it evolve based on your preferences.
|
|
- Swipe Left to Pass: Not feeling a connection? Swipe left to pass and continue your journey through the forest of possibilities.
|
|
- Evolve Your Matches: As you interact with the trees, they adapt and grow, reflecting your unique tastes and preferences.
|
|
- Aim for Perfection: Your goal is to get your match percentage as close to 100% as possible, creating a personalized fractal tree that's uniquely yours.
|
|
|
|
]<koncni_opis>
|
|
|
|
#pagebreak()
|
|
|
|
#text(size: 18pt, weight: "bold")[PRIVACY POLICY]
|
|
|
|
At Treender, we are committed to protecting your privacy and ensuring the security of any data we collect. This Privacy Policy outlines our practices regarding the collection, use, and protection of user data.
|
|
|
|
#text(size: 16pt, weight: "bold")[1 Data Collection]
|
|
|
|
#text(size: 14pt)[1.1 Sensitive Data]
|
|
|
|
Our service/application does not collect any sensitive data about users. Sensitive data includes, but is not limited to:
|
|
|
|
- Personal information (e.g., names, addresses, phone numbers, email addresses)
|
|
- Financial data (e.g., credit card numbers, bank account details)
|
|
- Health information (e.g., medical records, health status)
|
|
|
|
#text(size: 14pt)[1.2 Non-Sensitive Data]
|
|
|
|
Any non-sensitive data collected by Treender is used solely for the purpose of improving our service. This may include:
|
|
|
|
- Usage statistics
|
|
- Performance metrics
|
|
- Anonymous feedback
|
|
|
|
#text(size: 16pt, weight: "bold")[2 Data Sharing]
|
|
|
|
We do not share user data with third parties. Your data remains confidential and is used only to enhance your experience with Treender.
|
|
|
|
#text(size: 16pt, weight: "bold")[3 Security Measures]
|
|
|
|
We take the following security measures to protect any data we collect:
|
|
|
|
- *Access Controls*: Strict access controls are in place to ensure that only authorized personnel can access user data.
|
|
- *Secure Storage*: Data is stored in secure servers with robust physical and digital security measures.
|
|
|
|
#text(size: 16pt, weight: "bold")[4 User Rights]
|
|
|
|
You have the following rights regarding your data:
|
|
|
|
- *Access*: You can request access to the data we hold about you.
|
|
- *Correction*: You can request that we correct any inaccuracies in your data.
|
|
- *Deletion*: You can request that we delete your data from our systems.
|
|
- *Objection*: You can object to the processing of your data for specific purposes.
|
|
|
|
#text(size: 16pt, weight: "bold")[5 Contact Information]
|
|
|
|
If you have any questions or concerns about our Privacy Policy or the data we collect, please contact us at:
|
|
|
|
*Email*: email
|
|
|
|
We value your privacy and are committed to protecting your data. Thank you for using Treender.
|
|
|
|
#line(length: 100%)
|
|
|
|
This Privacy Policy is effective as of February 26, 2025. We reserve the right to update this policy as needed to reflect changes in our practices or legal requirements.
|
|
|
|
#figure(caption: [Privacy Policy])[]<privacy_policy>
|
|
#pagebreak()
|
|
|
|
= UPORABNIŠKE IZKUŠNJE
|
|
|
|
Po tem ko smo objavili aplikacijo na Play Store smo prosili prijatelje in sodelavce, naj probajo aplikacijo. Med uporabo so naleteli na nekaj pričakovanih in nepričakovanih problemov.
|
|
|
|
== Ni inititivno kako uprabljati aplikacijo
|
|
Uporabnikom ni bilo jasno kako uporabiti aplikacijo, ni jim bilo jasno da lahko povlečejo sliko levo ali delsno da označijo ali jim je všeč ali ne. Da rešimo to lahko ob prvem zagonu aplikacije prikažemo animacijo kako lahko premikajo sliko po zaslonu.
|
|
|
|
== Ne pričakovani rezultati
|
|
Po razlagi kako uporabljati aplikacijo jim ni bilo jasno zakaj niso videli nobenih sprememb na drevesih in v rezultatu podobnosti. Tukaj je glavni problem majhno stevilo vzorcev na generacijo (trenutno 16) s tem težko izločimo kaj je uporabniku všeč. Med testiranjem pa nam večje število vzorcev ni bilo všeč ker predolgo traja da pridemo skozi generacijo, in vidimo spremembe. In med izdelavo aplikacije, ker smo vedeli kako deluje algoritem, nismo uporabljali aplikacijo kot uporabnik. Namesto da bi izbirali izgled celotne slike smo izbirali specifične lastnosti na slikah. Nekaterim uporabnikom je tudi ratalo to ugotoviti in so s tem ustvarili precej lepe slike.
|
|
|
|
== Motil rezultat podobnosti
|
|
Nekaj uporabnikov nam je sporočilo da jih je motili da jim ni uspelo doseči 100% podobnost. Da lahko to rešimo bi mogoče lahko uporabili kak drugačen algoritem, ali bi pa lahko preslikali rezultat, da vsaka vrednost nad 85% se prikaže kot 100%.
|
|
|
|
== Zgodovina
|
|
Uporabniki so želeli zgodovino. To bi bilo dokaj enostavno dodati saj hranimo celotno stanje aplikacije v sitemu v primeru da lahko stanje preživi izklop aplikacije. Samo med izdelavo se nismo spomnili da bi dodali zgodovino.
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= IZKUŠNJE
|
|
Izkušnje, ki smo jih pridobili med izdelavo aplikacije.
|
|
|
|
== Strežnik in podatkovna baza
|
|
Med izdelavo aplikacije smo izdelali strežnik in odjemalec sistem da smo lahko pridobili podatke o uporabnikih in njihovih preferencah. To nas je naučilo nekaj stvari.
|
|
|
|
- Omeji število prejetih podatkov na povezavo, da se nemore nekdo samo povezati in začeti pošiljati naključne podatke.
|
|
- Omeji kdo se lahko poveže, z uporabo nekega skrivnostnega ključa ali z registracijo.
|
|
- Uporabi prepovedni sistem, če se odjemalec lepo ne obnaša mu prepovej povezavo na strežnik.
|
|
- Med filtriranjem podatkov v podatkovni bazi ikoli ne briši samo označi, da je izbrisano.
|
|
- Naj odjemalec pošlje vse podatke na enkrat.
|
|
- Imej paritetne kode
|
|
|
|
Naš sistem je bil tako narejen da je vsak odjemalec imel skrivno stevilko ki jo je uporabil pri komunikaciji, samo smo še vedno naleteli da se je naključno lahko nekdo povezal na strežnik in je zadel število, in ker nismo imeli dobre filtracije podatkov se je vse kar nam je poslal direktno shranjevalo v bazo. Bi bilo dobro da imamo paritetne kode na koncu sporočila da vemo ali je sporočilo validno. Na to ko nam je zlonamerni udjemalec napolnil bazo smo se odločilo da bomo odstranili vse podatke ki ne sledijo pravilni evoluciji, in ker smo imeli napako v kodi smo si zbrisali celotno bazo podatkov. Med testiranjem smo tudi našli par primerov kjer se sporočila niso prenesla v celoti.
|
|
|
|
#pagebreak()
|
|
== Gradilni sistem
|
|
Začeli smo z enostavno Makefile datoteko kjer smo enostavno samo vse kompilirali skupaj.
|
|
|
|
```Makefile
|
|
gcc -c raylib.c -o raylib.o
|
|
g++ -c main.cpp -o main.o
|
|
g++ main.o raylib.o -o main
|
|
```
|
|
|
|
Ampak je to čez čas postalo, kar naporno za upravljanje, ko se je število datotek povečevalo in razvijali smo na dveh operacijskih sistemih (Linux in Windows) in po tem smo naredili našo največjo napako. Navdihnil nas je tsoding nob.h @git_nob in odločili smo se da bomo napisali svojo skripto za gradno, ki bo pravilno izbrala pravi prevajalnik g++ za Linux in zig c++ za Windows. To je delovalo prej dobbro, vendar smo sčasoma več časa porabili za skripto kot za naš projekt.
|
|
|
|
Prišlo je do točke kjer je skripta lahko
|
|
- Izvajala ukaze
|
|
- Preverjala če se je skripta spremenila in se je nato sama ponovno zgradila
|
|
- Dodali smo inkrementalno gradnjo projekta
|
|
- Prenesla in kompilirala raylib
|
|
- Na koncu smo celo dodali da lahko skompilira vse datoteke v direktoriju
|
|
|
|
Na to smo morali dodati še gradno enega programa, kar je bilo mogoče z našo skripto, vendar nas je motilo, koliko časa smo porabili za vzdrževanje skripte. Odločili smo se da bomo probali CMake, ki je prvo gradno malo počasnejšo saj smo morali samo prenesti raylib in druge odvisnosti, vendar je bilo vse ostalo popolno in je preprosto delovalo.
|
|
|
|
|
|
|
|
#pagebreak()
|
|
|
|
= ZAKLJUČEK
|
|
|
|
V tej diplomski nalogi smo razvili aplikacijo Treender, ki uporabnikom omogoča interaktivno vodenje evolucije fraktalnih dreves z uporabo genetskih algoritmov in preprostih uporabniških gest. Cilj projekta je bil raziskati, kako lahko kombinacija algoritmov in uporabniških ocen ustvarja osebne in dinamične izkušnje.
|
|
|
|
Za razvoj smo uporabljali tehnologije, kot so C++, Raylib in SQLite3, ki so omogočile učinkovito delovanje in vizualizacijo. Ključni izziv je bila optimizacija algoritmov za generiranje in risanje dreves, ki smo ga rešili z risanjem na teksturo in omejevanjem števila risalnih klicev. Ugotovili smo, da je pomembno, da je uporabniški vmesnik intuitiven, saj so uporabniki na začetku težave razumeli osnovne funkcije, kot je ocenjevanje s povlekanjem slike.
|
|
|
|
Za izračun podobnosti med drevesi smo izbrali Hammingovo razdaljo, saj je nudila enostavno implementacijo in jasne rezultate. Objava aplikacije na Google Play Store je zahtevala prilagoditve opisa in politike zasebnosti, da smo ustrezali zahtevam platforme.
|
|
|
|
Projekt je uspešno dosegel svoje cilje: uporabnikom je omogočil ustvarjanje in prilagajanje fraktalnih dreves, hkrati pa nam je prinesel dragocene izkušnje na področju razvoja, optimizacije in uporabniškega izkušenj. V prihodnosti bi lahko aplikacijo izboljšali z dodajanjem zgodovine in izboljšavo komunikacije podobnosti.
|
|
#pagebreak()
|
|
|
|
|
|
|
|
#bibliography("citations.bib", title: [VIRI IN LITERATURA], style: "ieee.csl")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|