|
| 1 | +# Blocks in 3D |
| 2 | + |
| 3 | +În acest proiect am implementat o bibliotecă numită `libchunk`, care permite |
| 4 | +modelarea lumilor tri-dimensionale folosind structuri cubice |
| 5 | +(idee inspirată din Minecraft). |
| 6 | + |
| 7 | +Proiectul reprezintă un bun exercițiu practic pentru a înțelege: |
| 8 | +- matricile și operațiile pe pointeri în C |
| 9 | +- structurile vectoriale și referințele mutabile în Rust |
| 10 | + |
| 11 | + |
| 12 | +În implementarea în Rust, mi-am aprofundat abilitățile de a scrie **teste unitare parametrizate** |
| 13 | +cu [`rstest`](https://crates.io/crates/rstest) și am creat de la zero un script |
| 14 | +personalizat de `checker` pentru validarea rezultatelor. |
| 15 | + |
| 16 | +## Structura proiectului |
| 17 | + |
| 18 | +- `chunk_gen` |
| 19 | + - Validare punct în spațiu |
| 20 | + - Amplasare bloc |
| 21 | + - Generare cuboid/sferă |
| 22 | +- `chunk_process`: |
| 23 | + - Algoritmi de umplere |
| 24 | + - Creare înveliș |
| 25 | +- `chunk_transform`: |
| 26 | + - Rotație 90° pe axa Oy (plan xOz) |
| 27 | + - TODO: gravitație |
| 28 | +- `chunk_compress`: compresia/decompresia matricii 3D într-un șir de octeți |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +## 📌 Amplasare bloc |
| 33 | + |
| 34 | +Funcția modifică tipul blocului la coordonatele **(x, y, z)** primite. |
| 35 | + |
| 36 | +Pentru modularitate, am creat o funcție auxiliară care returnează **true** dacă |
| 37 | +coordonatele sunt în interiorul chunk-ului și **false** în caz contrar. |
| 38 | + |
| 39 | + |
| 40 | +`chunk_place_block` si `is_inside` sunt apelate pe tot parcursul proiectului |
| 41 | +pentru modificarea blocurilor, respectiv verificarea coordonatelor. |
| 42 | + |
| 43 | + |
| 44 | +## 📦 Creare cuboid |
| 45 | + |
| 46 | +Funcția primește 2 colțuri opuse ale unui paralelipiped dreptunghic. |
| 47 | +Coordonatele de iterație pornesc de la minimul fiecărei axe și merg până la maxim. |
| 48 | +Astfel, chiar dacă nu conteaza ordinea colțurilor, chunk-ul este parcurs corect. |
| 49 | + |
| 50 | +## 🪩 Creare sferă |
| 51 | + |
| 52 | +Notez cu `r` = întregul cel mai mare (`ceil`) la care se rotunjeste raza. |
| 53 | +o raza de **-1.2** se va rotunji la **-2**, iar pentru **1.2** `r` va fi egal cu **2**. |
| 54 | + |
| 55 | +Pentru fiecare offset din intervalul `[-r, r]` pe cele 3 axe, se calculează distanța euclidiană |
| 56 | +față de centru. Dacă distanța ≤ raza reală, plasez blocul; altfel, îl ignor. |
| 57 | + |
| 58 | +> **Distanta euclidiana** se calculeaza (folosind functiile `pow` si `sqrt` din biblioteca `math.h`) |
| 59 | +> ca suma patratelor diferentelor coordontalor pe cele 3 axe. |
| 60 | +> |
| 61 | +> `dist(P1, P2) = sqrt((P1.x - P2.x)^ 2 + (P1.y - P2.y)^ 2 + (P1.z - P2.z)^2 )` |
| 62 | +
|
| 63 | +## 📦 Înveliș |
| 64 | + |
| 65 | +Dacă `target_block` = `shell_block`, algoritmul clasic ar umple matricea complet. |
| 66 | +Pentru a evita această problemă, |
| 67 | +folosesc o structura pentru a retine coordonatele **(x, y, z)** de interes, |
| 68 | +descoperite in timpul iterarii matricii 3D. |
| 69 | + |
| 70 | +Mai apoi, pentru fiecare astfel de punct, |
| 71 | +functia `wrapper` plasează `shell_block` în locul vecinilor diferiți de `target_block`. |
| 72 | + |
| 73 | +Pentru a înveli un bloc, este nevoie de a verifica 8 puncte alăturate: |
| 74 | +**(x±1, y±1, z±1)**. |
| 75 | + |
| 76 | +## Fill |
| 77 | + |
| 78 | +Algoritm recursiv de umplere: pornește dintr-un punct și vizitează vecinii de același tip, |
| 79 | +fără să mai fie nevoie de memorie suplimentară pentru marcarea blocurilor vizitate. |
| 80 | + |
| 81 | +Cazul în care `target_block` este egal cu `new_block` este tratat separat |
| 82 | +înaintea rulării algoritmului de umplere: se va returna matricea ințială. |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +## ⟳ Rotirea în jurul axei Oy |
| 87 | + |
| 88 | +Se alocă o nouă matrice: |
| 89 | +- `new_width = old_depth` |
| 90 | +- `new_depth = old_width` |
| 91 | + |
| 92 | +> Practic interschimbă dimensiunile pentru **lățime** și **adâncime**. |
| 93 | +
|
| 94 | + |
| 95 | +Valorile sunt copiate conform regulii: |
| 96 | +```c |
| 97 | +new_mat[x][y][z] = chunk[z][y][depth - 1 - x] |
| 98 | +``` |
| 99 | + |
| 100 | +## 📥 Compresie |
| 101 | + |
| 102 | +1. **Serializare**: matricea 3D este aplatizată într-un vector liniar (ordinea **y->z->x**). |
| 103 | +2. Vectorul este parcurs pentru a determina secvențe de blocuri identice consecutive: |
| 104 | + - Se generează un vector de perechi `(num_occurrences, tip_block)` |
| 105 | + - Dacă numărul depășește 4095, se începe un nou **run** |
| 106 | +3. Transformarea **run**-urilor în octeți: |
| 107 | + - <32 apariții => codificare pe 1 octet |
| 108 | + - ≥32 apariții => codificare pe 2 octeți |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | +## 📤 Decompresie |
| 113 | + |
| 114 | +1. Se alocă matricea 3D pe baza dimensiunilor cunoscute. |
| 115 | +2. Se itereaza vectorul codificarii (byte cu byte), reconstruind blocurile. |
| 116 | +3. Decodificarea se bazează pe marcatorii din biții 5 și 6: |
| 117 | + - `0` => **run** pe 1 octet (5 biți pentru numărul de aparitii) |
| 118 | + - `10` => **run** pe 2 octeți (11 biți pentru număr de aparitii) |
| 119 | + |
| 120 | +Coordonatele **(x, y, z)** se actualizează în timpul reconstrucției vectorului astfel: |
| 121 | +1. Incrementează `x` |
| 122 | +2. Dacă `x` depășește limita, resetează `x = 0` și incrementează `z` |
| 123 | +3. Dacă `z` depășește limita, resetează `z = 0` și incrementează `y` |
| 124 | +4. Dacă `y` depășește limita (caz anormal), funcția se oprește forțat |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | +## Semnificația biților |
| 129 | + |
| 130 | +| Interval n | Primul octet (MSB -> LSB)| Al doilea octet (MSB -> LSB) | |
| 131 | +|:--------------|:-------------------------|:-----------------------------| |
| 132 | +| 1 ≤ n < 32 | b1 b0 0 n4 n3 n2 n1 n0 | - | |
| 133 | +| 32 ≤ n < 4096 | b1 b0 1 0 n11 n10 n9 n8 | n7 n6 n5 n4 n3 n2 n1 n0 | |
| 134 | + |
| 135 | +- `b1 b0` = tipul blocului (cei mai semnificativi 2 biți) |
| 136 | +- `0` / `10` = markeri pentru lungimea run-ului (1 sau 2 octeți) |
| 137 | +- `nX` = biții care codifică numărul de apariții |
| 138 | +- **MSB** = primul bit din stânga al unui octet |
| 139 | +- **LSB** = ultimul bit din dreapta al unui octet |
| 140 | + |
| 141 | +Un octet codifică atât tipul blocului, cât și numărul de apariții. |
| 142 | +- 1 ≤ n < 32 => codificare pe 1 octet |
| 143 | +- 32 ≤ n < 4096 => codificare pe 2 octeți |
| 144 | + |
| 145 | +Bitul 6 este markerul: |
| 146 | +- `0` => 1 octet |
| 147 | +- `10` => 2 octeți |
| 148 | +Tipul blocului ocupă cei mai semnificativi 2 biți. |
| 149 | + |
| 150 | + |
| 151 | +## Operații pe biți |
| 152 | + |
| 153 | +- Putere a lui 2: `1 << n` |
| 154 | +- Setare bit: `byte |= (1 << i)` |
| 155 | +- Verificare valoare (0/1) bit: |
| 156 | + - In C: `if (byte & (1 << i))` |
| 157 | + - In Rust: `if byte & (1 << i) > 0` |
0 commit comments