dos-3D-Engine/README.md
2026-05-15 14:30:47 +02:00

324 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Moteur 3D DOS (TESTING)
**Version : 0.15**
## Vue d'ensemble
Moteur 3D software en C ciblant le DOS 32-bit via l'extender **CauseWay** (ou DOS/4GW).
Compilateur : **OpenWatcom wcc386** exécuté depuis WSL.
Affichage : **VGA Mode 13h** — 320×200, 256 couleurs, rendu Gouraud en virgule fixe 16.16.
* **Rendu 3D complet :** Filaire (Wireframe), Lignes cachées (Hidden), et Plein (Solid).
* **Rasterisation :** Remplissage de triangles avec interpolation de Gouraud (16.16) et Z-Buffer 16 bits.
* **Mathématiques :** Arithmétique en virgule fixe (16.16) et tables trigonométriques pré-calculées.
* **Génération procédurale :** Création dynamique de géométries (Cubes, Sphères, Cylindres, Cônes, Plans, Tores, Surfaces mathématiques).
* **Mode "Minimal Player" :** Capacité d'exporter la scène entière en un header statique C pour compiler un exécutable ultra-léger sans les fonctions de génération mathématique.
---
## Exemples
![Screenshot FlatShading](/assets/Capture-Flatshading.png)
Vue en flatshading
![Screenshot Gouraud](/assets/Capture-Gouraud.png)
Vue en Gouraud
---
## Structure du projet
```
src/
main/main.c Point d'entrée, scène, boucle principale
part3D/
defines.h Types, macros fixed-point, constantes écran, prototypes, externs
engine.c Maths (sin/cos), matrices, pipeline de rendu
geometry.c Génération de meshes (sphère, cylindre) + I/O mesh
graph.c VGA init, palette, backbuffer/zbuffer
image.c gestion des fichiers image
raster.c Rastériseur triangle Gouraud, tracé de ligne Z-buffered
Makefile Build via WSL + wcc386 Windows natif
```
---
## Arithmétique virgule fixe 16.16
| Macro/fonction | Opération |
|---|---|
| `int_to_f(a)` | `a << 16` |
| `f_to_int(a)` | `a >> 16` |
| `f_mul(a,b)` | `(int64_t)a * b >> 16` |
| `f_div(a,b)` | `(int64_t)a << 16 / b` |
---
## Constantes écran
Définies dans `defines.h` :
```c
#define SCREEN_W 320
#define SCREEN_H 200
#define SCREEN_PIXELS (SCREEN_W * SCREEN_H) /* 64 000 */
```
Utilisées partout dans `graph.c` et `raster.c` à la place du littéral `64000`.
---
## Palette VGA
256 entrées organisées en **16 couleurs × 16 niveaux d'intensité**.
Index pixel = `(base_color << 4) | shade` avec shade ∈ [0..15].
Les 16 couleurs de base sont dans `my_palette[]` (`graph.c`).
La palette est chargée dans le DAC VGA au démarrage via `setup_vga_palette()`.
---
## Projection perspective
```c
v_cache[i].x = 160 + f_to_int(f_div(rx << 8, pz));
v_cache[i].y = 100 - f_to_int(f_div(ry << 8, pz));
```
`<< 8` = distance focale implicite de 256 pixels.
`pz` est clampé à `int_to_f(20)` minimum pour éviter la division par zéro.
---
## Back-face culling et convention de winding
Le pipeline applique la **transposée** de la matrice de rotation aux sommets (convention colonne Watcom). Cette transposition inverse le handedness des triangles projetés.
**Convention retenue :** winding CCW vu de l'extérieur → produit vectoriel 2D **> 0** en espace écran → face visible.
```c
front = ((p2->x - p1->x) * (p3->y - p1->y)
- (p2->y - p1->y) * (p3->x - p1->x)) > 0;
```
Toutes les géométries (sphère, corps cylindre, fonds cylindre) respectent cette convention.
---
## Shading Gouraud
La lumière est assimilée à la direction de vue (−Z monde). Le dot product de la normale rotée avec (0,0,−1) est `−nz`.
```c
nz = f_mul(n->x, rot.m[0][2]) + f_mul(n->y, rot.m[1][2]) + f_mul(n->z, rot.m[2][2]);
intensity = (nz >= 0) ? int_to_f(1) : f_mul(-nz, int_to_f(14)) + int_to_f(1);
```
`f_mul(-nz, int_to_f(14))` est une multiplication en virgule fixe 16.16 (≠ multiplication entière).
| nz | Signification | Intensité (shade) |
|---|---|---|
| −1.0 | Face directement vers caméra | 15 (max) |
| 0 | Face tangentielle | 1 (ambiant) |
| > 0 | Face opposée à la caméra | 1 (ambiant, face culléé) |
L'intensité est interpolée en virgule fixe 16.16 entre les sommets du triangle.
Le shade final est clampé dans [0, 15] — y compris vers le bas (intensité négative → 0).
---
## Génération des meshes
### Sphère (`add_sphere`)
```
Object3D *add_sphere(Universe *uni, fixed x, fixed y, fixed z, fixed r, int det, uint8_t col, uint8_t wire);
```
Paramètres : centre local, rayon `r`, détail `det` (nombre de subdivisions).
Génère `(det+1) × det` sommets et `det × det × 2` faces.
Les normales sont les vecteurs de position unitaires (normales sphériques exactes).
Winding : `add_face(a, b, c)` et `add_face(b, d, c)` — CCW vu de l'extérieur.
### Cylindre (`add_cylinder`)
```
Object3D *add_cylinder(Universe *uni, fixed x, fixed y, fixed z, fixed r, fixed h, int det, uint8_t col, uint8_t wire);
```
Paramètres : centre local, rayon `r`, hauteur `h`, détail `det`.
**Corps (barrel) :**
`det` paires de sommets bas/haut. Normales radiales `(cos θ, 0, sin θ)`.
`2×det` faces. Winding : `(bas_i, haut_i, bas_{i+1})` et `(haut_i, haut_{i+1}, bas_{i+1})`.
**Fond bas :**
1 sommet centre + `det` sommets de rebord. Normale `(0, −1, 0)`.
`det` faces en éventail depuis le centre. Winding : `(centre, rebord[i], rebord[i+1])`.
**Fond haut :**
1 sommet centre + `det` sommets de rebord. Normale `(0, +1, 0)`.
`det` faces en éventail depuis le centre. Winding inversé (normale opposée) : `(centre, rebord[i+1], rebord[i])`.
**Total par cylindre (det=16) :** 2+4×det = 66 sommets, 4×det = 64 faces.
### Boîte / Cube (`add_box`)
```
Object3D *add_box(Universe *uni, fixed x, fixed y, fixed z, fixed dx, fixed dy, fixed dz, uint8_t col, uint8_t wire);
```
Paramètres : centre local, dimensions indépendantes `dx, dy, dz`.
Génère 24 sommets (4 par face pour ne pas lisser les arêtes avec Gouraud) et 12 faces.
Normales : alignées sur les axes orthogonaux (±X, ±Y, ±Z).
### Cône (`add_cone`)
```
Object3D *add_cone(Universe *uni, fixed x, fixed y, fixed z, fixed r, fixed h, int det, uint8_t col, uint8_t wire);
```
Paramètres : centre local, rayon `r`, hauteur `h`, détail `det`.
Dérivé du cylindre, mais la boucle supérieure converge en un point unique avec des sommets dupliqués pour préserver l'interpolation radiale des normales du corps. Ne possède qu'un "fond bas".
### Plan / Grille (`add_plane`)
```
Object3D *add_plane(Universe *uni, fixed x, fixed y, fixed z, fixed w, fixed d, int seg_x, int seg_z, uint8_t col, uint8_t wire);
```
Paramètres : centre local, largeur `w`, profondeur `d`, segments `seg_x`, `seg_z`.
Génère une surface plane subdivisée (idéal pour les sols et l'ombrage Gouraud par point).
Normales : dirigées vers le haut (+Y).
### Tore (`add_torus`)
```
Object3D *add_torus(Universe *uni, fixed x, fixed y, fixed z, fixed r_main, fixed r_tube, int seg_main, int seg_tube, uint8_t col, uint8_t wire);
```
Paramètres : centre local, rayon principal `r_main`, rayon du tube `r_tube`, subdivisions `seg_main`, `seg_tube`.
Génère un anneau ("donut") via deux boucles trigonométriques imbriquées. Excellente primitive de test pour l'éclairage.
### Surface Mathématique (`add_math_surface`)
```
Object3D *add_math_surface(Universe *uni, fixed x, fixed y, fixed z,
fixed w, fixed d, int seg_x, int seg_z,
uint8_t col, uint8_t wire, SurfaceFunc func);
```
Paramètres : similaires au plan, plus un pointeur de fonction `SurfaceFunc`.
Cette primitive génère une grille dont la hauteur Y est calculée dynamiquement par la fonction utilisateur passée en paramètre.
La fonction utilisateur doit respecter la signature : `fixed ma_fonction(fixed x, fixed z)`.
```
/* Exemple de formule : le "Sombrero" */
fixed mon_onde(fixed x, fixed z) {
double dx = (double)x / 65536.0;
double dz = (double)z / 65536.0;
double dist = sqrt(dx * dx + dz * dz);
return (fixed)(20.0 * sin(dist / 10.0) * 65536.0);
}
/* Appel dans le main */
add_math_surface(uni, 0, 0, 0, int_to_f(120), int_to_f(120), 30, 30, COLOR6, 0, mon_onde);
```
---
## Manipulation des Mesh
Une fois générés, les objets peuvent être positionnés, transformés ou recolorés à l'aide des fonctions suivantes (définies dans `engine.c` / `geometry.c`) :
### Placement de l'objet (`set_object_pos`)
```
void set_object_pos (Object3D *obj, fixed x, fixed y, fixed z);
```
Définit la position locale absolue de l'objet (`base_pos`) par rapport au centre de son espace d'orbite.
### Deplacement de l'objet (`move_object`)
```
void move_object (Object3D *obj, fixed dx, fixed dy, fixed dz);
```
Applique une translation relative à l'objet en ajoutant le décalage spécifié à sa position courante.
### Changement de la couleur de l'objet (`set_object_color`)
```
void set_object_color (Object3D *obj, uint8_t col);
```
Modifie l'index de couleur de base (0-15) utilisé pour le rendu de l'ensemble des faces de l'objet.
### Rotation de l'objet dans l'univers (`bake_object_rotation`)
```
void bake_object_rotation(Universe *uni, Object3D *obj, int ax, int ay, int az);
```
Applique une rotation *permanente* (Baking) aux sommets et aux normales de l'objet directement dans le pool de l'univers.
Particulièrement utile pour orienter une primitive lors de l'initialisation (ex: coucher un cylindre sur le flanc) sans induire le moindre coût de calcul matriciel supplémentaire pendant la boucle de rendu en temps réel.
---
## Orbite et distance (`main.c`)
Les objets ont des positions de base fixes dans l'espace scène, définies par rapport au centre `(0, 0, 300)` :
| Objet | Offset base |
|---|---|
| Sphère (universe[0]) | (+60, 0, 0) |
| Cylindre (universe[1]) | (−60, 0, 0) |
Chaque frame, la matrice d'orbite `orbit = rotX(anglex) × rotY(angley)` est calculée et appliquée uniformément à chaque objet (transform 3D complet sur `base_pos`). La distance (initialement 300) peut être modifiée :
```c
obj->rot = orbit;
obj->pos.x = f_mul(bx, orbit.m[0][0]) + f_mul(by, orbit.m[1][0]) + f_mul(bz, orbit.m[2][0]);
obj->pos.y = f_mul(bx, orbit.m[0][1]) + f_mul(by, orbit.m[1][1]) + f_mul(bz, orbit.m[2][1]);
obj->pos.z = distance + f_mul(bx, orbit.m) + f_mul(by, orbit.m) + f_mul(bz, orbit.m);
```
Les trois composantes de `base_pos` (bx, by, bz) sont toutes prises en compte — les objets à `base_pos.y != 0` ou `base_pos.z != 0` sont correctement transformés.
La caméra reste à l'origine (+ translations clavier). L'effet visuel est celui d'un observateur qui orbite autour de la scène.
---
## Contrôles clavier
| Touche | Action |
|---|---|
| Z / S | Caméra avance / recule (axe Z) |
| Q / D | Caméra gauche / droite (axe X) |
| A / E | Caméra haut / bas (axe Y) |
| Flèche Haut / Bas | Orbite vertical (anglex ±3°) |
| Flèche Gauche / Droite | Orbite horizontal (angley ±3°) |
| F1 / F2 / F3 | Mode fil-de-fer / caché / solide |
| R | Reset caméra + angles |
| Esc | Quitter |
Les touches étendues (F1–F12, flèches…) retournent `0` depuis `getch()` ; le scan code suivant est alors consommé pour identifier la touche spéciale.
---
## Points de vigilance
| Sujet | Note |
|---|---|
| Winding | Toutes les géométries doivent respecter la convention CCW (cross > 0). Vérifier à chaque ajout de primitive. |
| Caméra sans rotation | La caméra n'a pas de matrice de vue — elle regarde toujours en +Z. L'orbite est simulée en faisant tourner la scène. Ajouter une matrice de vue si un vrai look-around est nécessaire. |
| Clamp pz | pz clampé à int_to_f(20). Si un objet passe derrière la caméra (pz < 0 avant clamp), il sera projeté incorrectement. |
| Modes fil-de-fer | MODE_WIRE, MODE_HIDDEN opérationnels. Le champ `face.force_wire` permet de forcer le fil-de-fer face par face en MODE_SOLID. |
| Z-test | `scanline_gouraud_c` et `draw_line_z` utilisent tous les deux `zv < zbuf` (strict), garantissant un comportement cohérent. |
| mat3_mul | Sûre contre l'aliasing (res peut être identique à a ou b). |
| I/O mesh | `save_mesh` / `load_mesh` / `free_mesh` disponibles dans `geometry.c` mais non utilisées depuis `main.c`. |
| Face.color | Le champ `face.color` a été supprimé : la couleur est portée par `obj->color`. `face.force_wire` reste le seul flag par-face. |