diff --git a/.gitignore b/.gitignore index 5ca7531..43b41bc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ voyager.code-workspace .vscode build *.err +assets/fond-320200.iff diff --git a/README.md b/README.md index ab28572..8894103 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ Affichage : **VGA Mode 13h** — 320×200, 256 couleurs, rendu Gouraud en virgul * **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 diff --git a/assets/Capture-Flatshading.png b/assets/Capture-Flatshading.png new file mode 100644 index 0000000..9e80065 Binary files /dev/null and b/assets/Capture-Flatshading.png differ diff --git a/assets/Capture-Gouraud.png b/assets/Capture-Gouraud.png new file mode 100644 index 0000000..dc3f4c9 Binary files /dev/null and b/assets/Capture-Gouraud.png differ diff --git a/src/main/main.c b/src/main/main.c index 1a65ca7..fa0d5ce 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -7,6 +7,7 @@ int main(void) { Object3D *tank, *pws1, *pws2, *inst1, *inst2; Camera cam; Matrix3 rotX, rotY, orbit; + Image *theme_img = NULL; Image *bg_img = NULL; int special=0; int anglex, angley, key, render, o; @@ -15,7 +16,6 @@ int main(void) { printf("Moteur 3D DOS (TESTING) - Version 0.15\n"); printf("Compile le %s a %s\n\n", __DATE__, __TIME__); printf("Appuyez sur une touche pour demarrer le rendu...\n"); - getch(); anglex = angley = key = 0; render = MODE_SOLID; @@ -31,88 +31,104 @@ int main(void) { /* --- Construction de la sonde Voyager --- */ /* 1. Bus principal (décagone blanc) */ - bus = add_cylinder(uni, 0, 0, 0, int_to_f(12), int_to_f(10), 10, COLOR7, 0); + bus = add_cylinder(uni, 0, 0, 0, int_to_f(12), int_to_f(10), 10, COLOR9, 0); set_object_pos(bus, 0, 0, 0); /* 1b. Réservoir d'hydrazine central (sphère dépassant sous le bus) */ - tank = add_sphere(uni, 0, 0, 0, int_to_f(6), 8, COLOR7, 0); + tank = add_sphere(uni, 0, 0, 0, int_to_f(6), 8, COLOR9, 0); set_object_pos(tank, 0, int_to_f(-3), 0); /* 2a. Antenne parabolique (Paraboloïde avec courbure réaliste) */ - dish = add_paraboloid(uni, 0, 0, 0, int_to_f(25), int_to_f(8), 24, 4, COLOR7, 0); + dish = add_paraboloid(uni, 0, 0, 0, int_to_f(25), int_to_f(8), 24, 4, COLOR9, 0); /* Pointant déjà vers le haut (+Y), on abaisse simplement sa base pour se reposer sur le bus */ set_object_pos(dish, 0, int_to_f(9), 0); /* 2b. Mât central de l'antenne (treillis triangulaire) */ - feed_boom = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(10), 3, COLOR7, 1); + feed_boom = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(10), 3, COLOR9, 1); set_object_pos(feed_boom, 0, int_to_f(10), 0); /* 2c. Sous-réflecteur de l'antenne (petit cône inversé) */ - sub_dish = add_cone(uni, 0, 0, 0, int_to_f(3), int_to_f(2), 12, COLOR7, 0); + sub_dish = add_cone(uni, 0, 0, 0, int_to_f(3), int_to_f(2), 12, COLOR9, 0); bake_object_rotation(uni, sub_dish, 180, 0, 0); set_object_pos(sub_dish, 0, int_to_f(16), 0); /* 3. Mât du RTG (Générateur thermoélectrique, structure en treillis carrée) */ - rtg_boom = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(40), 4, COLOR11, 1); + rtg_boom = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(40), 4, COLOR8, 1); bake_object_rotation(uni, rtg_boom, 0, 0, 90); /* Couché sur l'axe X */ set_object_pos(rtg_boom, int_to_f(25), int_to_f(-2), 0); /* 4. RTG (Générateurs thermoélectriques, 3 segments en série) */ - rtg1 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR11, 0); + rtg1 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR8, 0); bake_object_rotation(uni, rtg1, 0, 0, 90); set_object_pos(rtg1, int_to_f(48), int_to_f(-2), 0); - rtg2 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR11, 0); + rtg2 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR8, 0); bake_object_rotation(uni, rtg2, 0, 0, 90); set_object_pos(rtg2, int_to_f(54), int_to_f(-2), 0); - rtg3 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR11, 0); + rtg3 = add_cylinder(uni, 0, 0, 0, int_to_f(4), int_to_f(5), 8, COLOR8, 0); bake_object_rotation(uni, rtg3, 0, 0, 90); set_object_pos(rtg3, int_to_f(60), int_to_f(-2), 0); /* 5. Mât du magnétomètre (Très long, structure en treillis triangulaire) */ - mag_boom = add_cylinder(uni, 0, 0, 0, int_to_f(1), int_to_f(70), 3, COLOR11, 1); + mag_boom = add_cylinder(uni, 0, 0, 0, int_to_f(1), int_to_f(70), 3, COLOR8, 1); bake_object_rotation(uni, mag_boom, 0, 0, 90); set_object_pos(mag_boom, int_to_f(-40), int_to_f(-2), 0); /* 6. Mât de la plateforme scientifique (vers l'avant, axe Z, treillis carrée) */ - sci_boom = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(30), 4, COLOR11, 1); + sci_boom = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(30), 4, COLOR8, 1); bake_object_rotation(uni, sci_boom, 90, 0, 0); /* Couché sur l'axe Z */ set_object_pos(sci_boom, 0, int_to_f(-2), int_to_f(-20)); /* 7. Plateforme et caméras (cube gris) */ - cam_box = add_box(uni, 0, 0, 0, int_to_f(6), int_to_f(6), int_to_f(6), COLOR11, 0); + cam_box = add_box(uni, 0, 0, 0, int_to_f(6), int_to_f(6), int_to_f(6), COLOR8, 0); set_object_pos(cam_box, 0, int_to_f(-2), int_to_f(-35)); /* 8. Le célèbre Golden Record ! (Petit cylindre jaune/or collé sur le flanc) */ - record = add_cylinder(uni, 0, 0, 0, int_to_f(3), int_to_f(1), 10, COLOR4, 0); + record = add_cylinder(uni, 0, 0, 0, int_to_f(3), int_to_f(1), 10, COLOR5, 0); bake_object_rotation(uni, record, 0, 0, 90); set_object_pos(record, int_to_f(12), int_to_f(0), int_to_f(5)); /* 9. Antennes PWS (Plasma Wave Subsystem) - Longs tubes filaires en V */ - pws1 = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(60), 3, COLOR11, 1); + pws1 = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(60), 3, COLOR8, 1); bake_object_rotation(uni, pws1, 0, 0, 90); /* 1. On le couche sur l'axe X */ bake_object_rotation(uni, pws1, 0, 45, 0); /* 2. On l'écarte en V sur le plan XZ */ set_object_pos(pws1, int_to_f(-20), int_to_f(-5), int_to_f(20)); - pws2 = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(60), 3, COLOR11, 1); + pws2 = add_cylinder(uni, 0, 0, 0, int_to_f(1)/2, int_to_f(60), 3, COLOR8, 1); bake_object_rotation(uni, pws2, 0, 0, 90); /* 1. On le couche sur l'axe X */ bake_object_rotation(uni, pws2, 0, -45, 0);/* 2. On l'écarte en V sur le plan XZ */ set_object_pos(pws2, int_to_f(-20), int_to_f(-5), int_to_f(-20)); /* 10. Instruments optiques sur la plateforme scientifique (Télescopes/Caméras) */ - inst1 = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(8), 6, COLOR7, 0); + inst1 = add_cylinder(uni, 0, 0, 0, int_to_f(2), int_to_f(8), 6, COLOR9, 0); bake_object_rotation(uni, inst1, 90, 0, 0); set_object_pos(inst1, int_to_f(-2), int_to_f(1), int_to_f(-38)); - inst2 = add_cone(uni, 0, 0, 0, int_to_f(2), int_to_f(6), 8, COLOR7, 0); + inst2 = add_cone(uni, 0, 0, 0, int_to_f(2), int_to_f(6), 8, COLOR9, 0); bake_object_rotation(uni, inst2, 90, 0, 0); set_object_pos(inst2, int_to_f(2), int_to_f(1), int_to_f(-38)); cam.pos.x = cam.pos.y = cam.pos.z = 0; + getch(); + if (init_vga() != 0) { free(uni); return 1; } + /* --- Thème de la palette --- */ + /* Charge une image (ex: IFF 16 couleurs) pour en extraire la palette de base */ + { + RGB base_palette[256]; /* Prévoir assez grand au cas où */ + + theme_img = load_iff8("THEME.LBM", base_palette); + if (theme_img) { + printf("Theme de couleurs 'THEME.LBM' charge. Generation des rampes de Gouraud...\n"); + setup_vga_palette(base_palette); + remap_image_to_gouraud(theme_img); + bg_img = theme_img; /* On la conserve pour l'afficher en fond d'écran ! */ + } + } + /* Optionnel : charger un fond (BMP 8-bit de 320x200 couleurs indexees) */ /* S'assurer que le fichier est bien généré avec les couleurs de votre palette moteur ! */ /* bg_img = load_bmp8("stars.bmp", NULL); */ @@ -185,6 +201,7 @@ int main(void) { flip(); } + /* theme_img a déjà été libéré s'il a été chargé */ if (bg_img) free_image(bg_img); close_vga(); free(uni); diff --git a/src/part3D/defines.h b/src/part3D/defines.h index 3aaead5..c4e7cf9 100644 --- a/src/part3D/defines.h +++ b/src/part3D/defines.h @@ -153,5 +153,7 @@ void free_mesh(Mesh *m); Image *create_image(int width, int height); void free_image(Image *img); Image *load_bmp8(const char *filename, RGB *pal_out); +Image *load_iff8(const char *filename, RGB *pal_out); +void remap_image_to_gouraud(Image *img); #endif diff --git a/src/part3D/image.c b/src/part3D/image.c index acdce11..a41b22c 100644 --- a/src/part3D/image.c +++ b/src/part3D/image.c @@ -20,6 +20,28 @@ void free_image(Image *img) { } } +void remap_image_to_gouraud(Image *img) { + int i, total; + if (!img || !img->data) return; + total = img->width * img->height; + for (i = 0; i < total; i++) { + /* Transforme l'index 0-15 vers l'index de luminosité max de sa rampe */ + img->data[i] = (img->data[i] & 0x0F) << 4 | 15; + } +} + +static uint32_t read_be32(FILE *f) { + uint8_t b[4]; + if (fread(b, 1, 4, f) != 4) return 0; + return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3]; +} + +static uint16_t read_be16(FILE *f) { + uint8_t b[2]; + if (fread(b, 1, 2, f) != 2) return 0; + return (b[0] << 8) | b[1]; +} + Image *load_bmp8(const char *filename, RGB *pal_out) { FILE *f; uint8_t header[54]; @@ -79,6 +101,128 @@ Image *load_bmp8(const char *filename, RGB *pal_out) { } } + fclose(f); + return img; +} + +Image *load_iff8(const char *filename, RGB *pal_out) { + FILE *f; + char buf[4]; + char type[4]; + int is_ilbm, is_pbm; + int width = 0, height = 0, nPlanes = 0, masking = 0, compression = 0; + long body_offset = 0; + Image *img; + int planes_to_read, row_bytes; + uint8_t *row_buf; + int y, p; + + f = fopen(filename, "rb"); + if (!f) return NULL; + + if (fread(buf, 1, 4, f) != 4 || strncmp(buf, "FORM", 4) != 0) { fclose(f); return NULL; } + read_be32(f); /* Ignore form_size */ + if (fread(type, 1, 4, f) != 4) { fclose(f); return NULL; } + + is_ilbm = (strncmp(type, "ILBM", 4) == 0); + is_pbm = (strncmp(type, "PBM ", 4) == 0); + if (!is_ilbm && !is_pbm) { fclose(f); return NULL; } + + while (fread(buf, 1, 4, f) == 4) { + uint32_t chunk_size = read_be32(f); + long next_chunk = ftell(f) + chunk_size; + if (chunk_size & 1) next_chunk++; /* Les chunks IFF sont alignés sur 16-bit */ + + if (strncmp(buf, "BMHD", 4) == 0) { + width = read_be16(f); + height = read_be16(f); + fseek(f, 4, SEEK_CUR); /* Skip x, y */ + nPlanes = fgetc(f); + masking = fgetc(f); + compression = fgetc(f); + } + else if (strncmp(buf, "CMAP", 4) == 0 && pal_out) { + int num_colors = chunk_size / 3; + int i; + if (num_colors > 256) num_colors = 256; + for (i = 0; i < num_colors; i++) { + pal_out[i].r = (uint8_t)(fgetc(f) >> 2); /* Conversion 8-bit vers VGA DAC 6-bit */ + pal_out[i].g = (uint8_t)(fgetc(f) >> 2); + pal_out[i].b = (uint8_t)(fgetc(f) >> 2); + } + } + else if (strncmp(buf, "BODY", 4) == 0) { + body_offset = ftell(f); + break; /* Fin de l'en-tête, les données d'image commencent ici */ + } + + fseek(f, next_chunk, SEEK_SET); + } + + if (!width || !height || !nPlanes || body_offset == 0) { fclose(f); return NULL; } + + img = create_image(width, height); + if (!img) { fclose(f); return NULL; } + + fseek(f, body_offset, SEEK_SET); + + planes_to_read = is_ilbm ? (nPlanes + (masking == 1 ? 1 : 0)) : 1; + row_bytes = is_ilbm ? ((width + 15) / 16) * 2 : (width + 1) & ~1; + + row_buf = (uint8_t *)malloc(row_bytes * planes_to_read); + if (!row_buf) { free_image(img); fclose(f); return NULL; } + + for (y = 0; y < height; y++) { + for (p = 0; p < planes_to_read; p++) { + uint8_t *plane_dst = row_buf + p * row_bytes; + int bytes_to_read = row_bytes; + + if (compression == 0) { + fread(plane_dst, 1, bytes_to_read, f); + } else if (compression == 1) { + while (bytes_to_read > 0 && !feof(f)) { + int n = (signed char)fgetc(f); + if (n >= 0 && n <= 127) { /* Copie littérale */ + int count = n + 1; + int read_len = (count > bytes_to_read) ? bytes_to_read : count; + fread(plane_dst, 1, read_len, f); + if (count > read_len) fseek(f, count - read_len, SEEK_CUR); + plane_dst += read_len; + bytes_to_read -= read_len; + } else if (n >= -127 && n <= -1) { /* RLE (répétition) */ + int count = -n + 1; + int val = fgetc(f); + int fill_len = (count > bytes_to_read) ? bytes_to_read : count; + memset(plane_dst, val, fill_len); + plane_dst += fill_len; + bytes_to_read -= fill_len; + } /* n == -128 est un no-op en PackBits */ + } + } + } + + if (is_pbm) { + /* PBM est chunky : copie directe */ + memcpy(img->data + y * width, row_buf, width); + } else if (is_ilbm) { + /* ILBM est planar : on recombine les bits en chunky */ + uint8_t *dest_row = img->data + y * width; + int x; + memset(dest_row, 0, width); + for (p = 0; p < nPlanes; p++) { + uint8_t *plane_src = row_buf + p * row_bytes; + for (x = 0; x < width; x++) { + int byte_idx = x / 8; + int bit_idx = 7 - (x % 8); + if (plane_src[byte_idx] & (1 << bit_idx)) { + dest_row[x] |= (1 << p); + } + } + } + } + } + + free(row_buf); fclose(f); return img; } \ No newline at end of file