diff --git a/README.md b/README.md index 40e1747..72f966f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# WORKDOC — Moteur 3D DOS (TESTING) +# Moteur 3D DOS (TESTING) ## Vue d'ensemble @@ -6,6 +6,12 @@ Moteur 3D software en C ciblant le DOS 32-bit via l'extender **CauseWay** (ou DO 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. + --- ## Structure du projet diff --git a/WORKDOC.md b/WORKDOC.md new file mode 100644 index 0000000..244ebb7 --- /dev/null +++ b/WORKDOC.md @@ -0,0 +1,248 @@ +# Moteur 3D DOS (TESTING) + +## 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. + +--- + +## 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 + raster.c Rastériseur triangle Gouraud, tracé de ligne Z-buffered +build/ + app.exe Binaire DOS final (CauseWay) + link.lnk Script de link généré automatiquement +Makefile Build via WSL + wcc386 Windows natif +``` + +--- + +## Pipeline de rendu + +``` +init_engine_math() Précalcul sin/cos (tables entières 16.16) +add_sphere / add_cylinder Génération mesh en espace local (0,0,0) +─── Boucle principale ─── + kbhit / getch Gestion clavier (touches étendues consommées en deux appels) + mat3_rotate_x/y Construction matrices de rotation + mat3_mul (orbite) Composition de la matrice d'orbite (sûre contre l'aliasing) + → obj.rot = orbit Application uniforme à tous les objets + → obj.pos = orbit × base_pos + center Rotation 3D complète des positions + clear_buffers Effacement backbuffer + zbuffer (0xFFFF) + render_universe Transformation + projection + Gouraud fill + flip memcpy backbuffer → 0xA0000 (VGA) +``` + +--- + +## 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`) + +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`) + +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`) +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`) +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`) +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`) +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`) +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); +``` + +--- + +## Orbite de l'observateur (`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`) : + +```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 = int_to_f(300) + f_mul(bx, orbit.m[0][2]) + f_mul(by, orbit.m[1][2]) + f_mul(bz, orbit.m[2][2]); +``` + +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) | +| + / − | Orbite vertical (anglex ±3°) | +| / / * | Orbite horizontal (angley ±3°) | +| 1 / 2 / 3 | 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 consommé sans effet. + +--- + +## 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. | diff --git a/src/part3D/defines.h b/src/part3D/defines.h index 949dcba..ae73d9c 100644 --- a/src/part3D/defines.h +++ b/src/part3D/defines.h @@ -1,9 +1,13 @@ #ifndef DEFINE_H #define DEFINE_H +// #define MINIMAL_PLAYER + #include #include -#include +#ifndef MINIMAL_PLAYER +#include // Uniquement nécessaire pour la génération +#endif #include #include #include diff --git a/src/part3D/engine.c b/src/part3D/engine.c index 7c0ccc4..8780bc1 100644 --- a/src/part3D/engine.c +++ b/src/part3D/engine.c @@ -9,12 +9,14 @@ void mat3_identity(Matrix3 *mat) { } void init_engine_math(void) { +#ifndef MINIMAL_PLAYER int i; for (i = 0; i < 360; i++) { double r = (double)i * 3.14159265 / 180.0; sintab[i] = (fixed)(sin(r) * 65536.0); costab[i] = (fixed)(cos(r) * 65536.0); } +#endif } void mat3_mul(Matrix3 *res, Matrix3 *a, Matrix3 *b) { diff --git a/src/part3D/geometry.c b/src/part3D/geometry.c index c7308b2..da26afa 100644 --- a/src/part3D/geometry.c +++ b/src/part3D/geometry.c @@ -1,5 +1,7 @@ #include "defines.h" +#include +#ifndef MINIMAL_PLAYER static void push_face(Universe *uni, Object3D *obj, int a, int b, int c, uint8_t wire) { Face *f = &uni->faces[uni->num_faces++]; f->a = a; f->b = b; f->c = c; @@ -486,3 +488,64 @@ void free_mesh(Mesh *m) { free(m->faces); free(m); } + +void export_universe_to_c(Universe *uni, const char *filename) { + int i; + FILE *f = fopen(filename, "w"); + if (!f) return; + + + fprintf(f, "#ifndef EXPORTED_DATA_H\n#define EXPORTED_DATA_H\n\n#include \"defines.h\"\n\n"); + + // 1. Tables trigonométriques + fprintf(f, "fixed sintab[360] = {"); + for(i=0; i<360; i++) fprintf(f, "%d%s", sintab[i], i<359?", ":""); + fprintf(f, "};\n\nfixed costab[360] = {"); + for(i=0; i<360; i++) fprintf(f, "%d%s", costab[i], i<359?", ":""); + fprintf(f, "};\n\n"); + + // 2. Vertices et Normales + fprintf(f, "Vector3 uni_verts[%d] = {\n", uni->num_verts); + for(i=0; inum_verts; i++) fprintf(f, " {%d, %d, %d}%s\n", uni->verts[i].x, uni->verts[i].y, uni->verts[i].z, inum_verts-1?",":""); + fprintf(f, "};\n\n"); + + fprintf(f, "Vector3 uni_normals[%d] = {\n", uni->num_verts); + for(i=0; inum_verts; i++) fprintf(f, " {%d, %d, %d}%s\n", uni->v_normals[i].x, uni->v_normals[i].y, uni->v_normals[i].z, inum_verts-1?",":""); + fprintf(f, "};\n\n"); + + // 3. Faces + fprintf(f, "Face uni_faces[%d] = {\n", uni->num_faces); + for(i=0; inum_faces; i++) fprintf(f, " {%d, %d, %d, %d}%s\n", uni->faces[i].a, uni->faces[i].b, uni->faces[i].c, uni->faces[i].force_wire, inum_faces-1?",":""); + fprintf(f, "};\n\n"); + + // 4. Objets + fprintf(f, "Object3D uni_objects[%d] = {\n", uni->num_objects); + for(i=0; inum_objects; i++) { + Object3D *o = &uni->objects[i]; + fprintf(f, " { %d, %d, %d, %d, ", o->vert_start, o->num_verts, o->face_start, o->num_faces); + fprintf(f, "{%d,%d,%d}, {%d,%d,%d}, ", o->pos.x, o->pos.y, o->pos.z, o->base_pos.x, o->base_pos.y, o->base_pos.z); + // Matrice identité par défaut ou état actuel + fprintf(f, "{{{%d,%d,%d},{%d,%d,%d},{%d,%d,%d}}}, %d }%s\n", + o->rot.m[0][0], o->rot.m[0][1], o->rot.m[0][2], + o->rot.m[1][0], o->rot.m[1][1], o->rot.m[1][2], + o->rot.m[2][0], o->rot.m[2][1], o->rot.m[2][2], o->color, inum_objects-1?",":""); + } + fprintf(f, "};\n\n"); + + // 5. Structure Universe globale + fprintf(f, "Universe static_universe = {\n"); + fprintf(f, " {0}, {0}, {0}, %d, %d, {0}, %d\n};\n\n", uni->num_verts, uni->num_faces, uni->num_objects); + + fprintf(f, "void load_static_universe(Universe *u) {\n"); + fprintf(f, " memcpy(u->verts, uni_verts, sizeof(uni_verts));\n"); + fprintf(f, " memcpy(u->v_normals, uni_normals, sizeof(uni_normals));\n"); + fprintf(f, " memcpy(u->faces, uni_faces, sizeof(uni_faces));\n"); + fprintf(f, " memcpy(u->objects, uni_objects, sizeof(uni_objects));\n"); + fprintf(f, " u->num_verts = %d; u->num_faces = %d; u->num_objects = %d;\n", uni->num_verts, uni->num_faces, uni->num_objects); + fprintf(f, "}\n\n"); + + fprintf(f, "#endif\n"); + fclose(f); +} + +#endif /* MINIMAL_PLAYER */