- C 96.2%
- Makefile 3.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
WORKDOC — 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 :
#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
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.
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.
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) :
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. |