dos-3D-Engine/WORKDOC.md
2026-05-10 02:29:00 +02:00

6.5 KiB
Raw Blame History

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, 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
  mat3_rotate_x/y         Construction matrices de rotation
  mat3_mul (orbite)       Composition de la matrice d'orbite
  → obj.rot = orbit       Application uniforme à tous les objets
  → obj.pos = orbit × base_pos + center   Rotation 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

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) : ((-nz) * 14) + int_to_f(1);
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.


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.


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 :

universe[o].rot   = orbit;
universe[o].pos.x = f_mul(base_x, orbit.m[0][0]);
universe[o].pos.y = f_mul(base_x, orbit.m[0][1]);
universe[o].pos.z = int_to_f(300) + f_mul(base_x, orbit.m[0][2]);

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°)
R Reset caméra + angles
Esc Quitter

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.
I/O mesh save_mesh / load_mesh / free_mesh disponibles dans geometry.c mais non utilisées depuis main.c.