Compare commits

...

13 Commits

Author SHA1 Message Date
1b45c515d4 différence M1 - M2 2025-12-29 16:57:16 +01:00
ebd2ba2e51 interferences réalistes 2025-12-29 16:40:55 +01:00
e78af26734 readme 2025-12-29 15:30:19 +01:00
42d69b6ce4 readme 2025-12-29 15:28:34 +01:00
8d42cda17f readme 2025-12-29 15:22:05 +01:00
5010c30886 odrre 2025-12-29 15:21:09 +01:00
bcdc8b1baa todo 2025-12-29 15:01:34 +01:00
40213d7e69 target fps 2025-12-29 15:00:48 +01:00
99fbef5c4b auto resize desktop 2025-12-29 14:46:33 +01:00
a4c62bc3a4 todo 2025-12-29 14:05:45 +01:00
a71641b989 web finish 2025-12-29 14:04:12 +01:00
ea3530c01e web background color 2025-12-29 13:59:09 +01:00
7b5cf06bd0 todo 2025-12-29 13:54:06 +01:00
6 changed files with 186 additions and 44 deletions

View File

@ -1,4 +1,65 @@
# TODO
# Simulateur d'Interféromètre de Michelson
- Caper les fps au choix
- Compiler web
Une simulation interactive temps réel et physiquement réaliste de l'interféromètre de Michelson. Ce projet permet de visualiser les figures d'interférences (franges et anneaux) en manipulant les composants optiques (miroirs, source lumineuse, milieu).
![Aperçu du simulateur](img/screenshot.png)
## Fonctionnalités
### Contrôle et Simulation
* **Miroir Mobile (M1) :** Translation et inclinaison sur deux axes.
* **Sources Lumineuses :**
* Spectre (380nm - 780nm).
* Gestion de **sources polychromatiques** (ajout/suppression de longueurs d'onde multiples).
* Rendu additif des couleurs.
* **Milieux dispersifs :** Ajout d'une cuve de gaz avec indice de réfraction variable n pour visualiser le déphasage.
### Performance & Rendu
* **Rendu GPU :** Calcul des interférences par **shader (GLSL)** pour des performances élevées.
* **Interface UI :** `raygui`.
* **Optimisation :** Sélecteur de **FPS Cible** (30, 60, 120, 144, Illimité).
* **Vues :** Schéma optique 2D + Vue "Écran".
## Installation et Compilation
### Prérequis
* **[Raylib](https://www.raylib.com/)**
* **[Emscripten](https://github.com/emscripten-core/emscripten/)**
### Compilation (Linux / Mac)
```bash
make
make run
```
ou
```bash
gcc main.c -o michelson -lraylib -lGL -lm -lpthread -ldl -lrt -lX11
./michelson
```
### Version Web (Emscripten)
```bash
make web
make webrun
```
ou
```bash
mkdir -p web && emcc -o web/index.html main.c -Os -Wall -std=c99 -DPLATFORM_WEB -s USE_GLFW=3 -s ALLOW_MEMORY_GROWTH=1 -I. --shell-file minshell.html --preload-file glsl/michelson_web.frag raylib/libraylib.web.a
```
## Contrôles Utilisateur
| Action | Commande |
| --- | --- |
| **Plein Écran** | Touche `F` |
| **Sélectionner une longueur d'onde** | `Clic Gauche` sur le spectre |
| **Ajouter une longueur d'onde** | `Clic Droit` sur une zone vide du spectre |
| **Supprimer une longueur d'onde** | `Clic Droit` sur une ligne existante |
| **Régler les miroirs** | Sliders / boutons `+` / `-` |
| **Précision / Vitesse** | Maintenir `Shift` |
## Physique du projet
Calcule de l'intensité lumineuse en tout point de l'écran en se basant sur la différence de marche.

View File

@ -47,27 +47,23 @@ void main() {
float relX = (pixelX - center.x) * zoom;
float relY = (pixelY - center.y) * zoom;
// r^2
float radiusSq = relX * relX + relY * relY;
// Diff de marche lamme d'air 2 * e cos(i) mais petits angles => cos(i) ~ 1 - i^2/2 mais i ~ r/f et r^2 = x^2 + y^2
float ringFactorBase = radiusSq * 0.065;
// Diff de marche du au coin d'air
float wDelta = (relX * angleM1 + relY * angleM1_Y) * 200.0;
float currDeltaBase = deltaLnm + wDelta;
float cosFactor = 1.0 - (radiusSq * 0.000004);
float currDelta = deltaLnm - ringFactorBase + wDelta;
float currDelta = (deltaLnm * cosFactor) + wDelta;
vec3 accumColor = vec3(0.0);
for(int i = 0; i < 10; i++) {
if (i >= lambdasCount) break;
float l = lambdas[i];
if (l < 1.0) l = 550.0;
vec3 baseColorVec = WavelengthToRGB(l);
float currDelta = currDeltaBase - ringFactorBase;
float K = PI / l;
float phase = currDelta * K;
float intensity = cos(phase);

View File

@ -45,9 +45,12 @@ void main() {
float relY = (pixelY - center.y) * zoom;
float radiusSq = relX * relX + relY * relY;
float ringFactorBase = radiusSq * 0.065;
float wDelta = (relX * angleM1 + relY * angleM1_Y) * 200.0;
float currDeltaBase = deltaLnm + wDelta;
float cosFactor = 1.0 - (radiusSq * 0.000004);
float currDelta = (deltaLnm * cosFactor) + wDelta;
vec3 accumColor = vec3(0.0);
@ -55,15 +58,16 @@ void main() {
if (i >= lambdasCount) break;
float l = lambdas[i];
if (l < 1.0) l = 550.0;
vec3 baseColorVec = WavelengthToRGB(l);
float currDelta = currDeltaBase - ringFactorBase;
float K = PI / l;
float phase = currDelta * K;
float intensity = cos(phase);
intensity = intensity * intensity;
accumColor += baseColorVec * intensity;
}

BIN
img/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

115
main.c
View File

@ -26,6 +26,7 @@ typedef struct {
Vector2 center;
bool gaz;
float nGaz;
int targetFps;
} Michelson;
Color WavelengthToColor(float lambda) {
@ -74,7 +75,8 @@ void DrawInterferenceViewGPU(Michelson *mic, Rectangle rec, Shader shader, bool
float centerX = rec.x + rec.width / 2.0f;
float centerY = rec.y + rec.height / 2.0f;
float center[2] = { centerX, centerY };
float scrHeight = (float)GetScreenHeight();
//float scrHeight = (float)GetScreenHeight();
float scrHeight = 1080.0f;
float zoom = *isFullscreen ? 0.7 : 1;
float addedOpticalPathOneWay = 0.0f;
@ -375,20 +377,24 @@ void DrawControlPanel(Michelson *mic) {
int rows = (mic->lambdasCount + cols - 1) / cols;
startY += (rows * (btnH + 5)) + 20;
// Pos M1
// Pos M1 M2 (d1 - d2)
startY += 0;
DrawText(TextFormat("Position M1 : %.2f um", mic->d1), startX, startY, 20, ORANGE);
float currentDiff = mic->d1 - mic->d2;
DrawText(TextFormat("Distance M1 - M2: %.2f um", currentDiff), startX, startY, 20, ORANGE);
// Pas des + et -
float stepD1 = speedMode ? 0.05f : 0.006f;
if (GuiButtonRepeat((Rectangle){startX, startY + 30, btnSize, 25}, "-", shouldRepeat)) mic->d1 -= stepD1;
GuiSlider((Rectangle){startX + btnSize + 5, startY + 30, sliderWidth - 75, 25}, NULL, NULL, &mic->d1, 100, 600);
float sliderVal = currentDiff;
GuiSlider((Rectangle){startX + btnSize + 5, startY + 30, sliderWidth - 75, 25}, NULL, NULL, &sliderVal, -100.0f, 100.0f);
mic->d1 = mic->d2 + sliderVal;
if (GuiButtonRepeat((Rectangle){startX, startY + 30, btnSize, 25}, "-", shouldRepeat)) mic->d1 -= stepD1;
if (GuiButtonRepeat((Rectangle){startX + btnSize + sliderWidth - 65, startY + 30, btnSize, 25}, "+", shouldRepeat)) mic->d1 += stepD1;
if (GuiButton((Rectangle){startX + contentWidth - 60, startY + 30, 60, 25}, "Egal")) mic->d1 = mic->d2;
// Angle M1 X
startY += 90;
DrawText(TextFormat("Inclinaison M1: %.3f deg", mic->angleM1), startX, startY, 20, ORANGE);
@ -442,15 +448,41 @@ void DrawControlPanel(Michelson *mic) {
float refLambda = mic->lambdas[mic->selectedLambdaIndex];
float p = (delta * 1000.0f) / refLambda;
DrawText(TextFormat("Ordre p (sel) = %.2f", p), startX, startY + 65, 20, WHITE);
DrawText(TextFormat("Ordre p = %.2f", p), startX, startY + 65, 20, WHITE);
// FPS
int bottomY = GetScreenHeight() - 40;
int perfStartY = bottomY - 80;
DrawLine(startX, perfStartY - 10, startX + contentWidth, perfStartY - 10, GRAY);
DrawText("PERFORMANCES (Cible FPS)", startX, perfStartY, 20, GRAY);
int fpsValues[] = {0, 30, 60, 120, 144};
const char* fpsTexts[] = {"MAX", "30", "60", "120", "144"};
int fpsCount = 5;
int fpsSpacing = 5;
int fpsBtnW = (contentWidth - (fpsSpacing * (fpsCount - 1))) / fpsCount;
for (int i = 0; i < fpsCount; i++) {
Rectangle fpsRect = {startX + i * (fpsBtnW + fpsSpacing), perfStartY + 30, fpsBtnW, 25};
bool isCurrent = (mic->targetFps == i);
if (GuiButton(fpsRect, fpsTexts[i])) {
mic->targetFps = i;
SetTargetFPS(fpsValues[i]);
}
if (isCurrent) {
DrawRectangleLinesEx(fpsRect, 2, COLOR_ACCENT);
DrawRectangle(fpsRect.x + 2, fpsRect.y + 2, fpsRect.width-4, fpsRect.height-4, Fade(COLOR_ACCENT, 0.2f));
}
}
// Bas
int bottomY = GetScreenHeight() - 40;
DrawLine(0, bottomY, UI_WIDTH, bottomY, Fade(WHITE, 0.1f));
int fps = GetFPS();
Color fpsColor = (fps >= 100) ? COLOR_ACCENT : (fps >= 60 ? GREEN : (fps >= 30 ? ORANGE : RED));
int currentFpsVal = fpsValues[mic->targetFps];
DrawText("STATUT:", startX, bottomY + 12, 20, COLOR_TEXT_DIM);
DrawText(TextFormat("%i FPS", fps), startX + 100, bottomY + 12, 20, fpsColor);
DrawText(TextFormat("%i FPS (%s)", fps, (currentFpsVal == 0 ? "Max" : TextFormat("%i", currentFpsVal))), startX + 100, bottomY + 12, 20, fpsColor);
}
Michelson mic = {0};
@ -458,33 +490,78 @@ Shader shader;
bool isFullscreen = false;
Rectangle normalBounds = {0};
Rectangle fullScreenBounds = {0};
#if !defined(PLATFORM_WEB)
RenderTexture2D target;
#endif
void UpdateDrawFrame(void) {
if (IsKeyPressed(KEY_F)) {
isFullscreen = !isFullscreen;
}
Rectangle currentViewBounds = isFullscreen ? fullScreenBounds : normalBounds;
#if defined(PLATFORM_WEB)
BeginDrawing();
ClearBackground(COLOR_BG);
float gridThick = 1.1f;
float gridAlpha = 0.03f;
// Grille fond
for(int i = UI_WIDTH; i < 1920; i += 100) DrawLine(i, 0, i, 1080, Fade(WHITE, 0.05f));
for(int i = 0; i < 1080; i += 100) DrawLine(UI_WIDTH, i, 1920, i, Fade(WHITE, 0.05f));
for(int i = UI_WIDTH; i < 1920; i += 100) {
DrawLineEx((Vector2){i, 0}, (Vector2){i, 1080}, gridThick, Fade(WHITE, gridAlpha));
}
for(int i = 0; i < 1080; i += 100) {
DrawLineEx((Vector2){UI_WIDTH, i}, (Vector2){1920, i}, gridThick, Fade(WHITE, gridAlpha));
}
DrawMichelsonSchema(&mic);
DrawControlPanel(&mic);
DrawInterferenceViewGPU(&mic, currentViewBounds, shader, &isFullscreen);
EndDrawing();
#else
float scale = fminf((float)GetScreenWidth() / 1920, (float)GetScreenHeight() / 1080);
float newWidth = 1920 * scale;
float newHeight = 1080 * scale;
float offsetX = (GetScreenWidth() - newWidth) * 0.5f;
float offsetY = (GetScreenHeight() - newHeight) * 0.5f;
SetMouseOffset(-offsetX, -offsetY);
SetMouseScale(1.0f / scale, 1.0f / scale);
BeginTextureMode(target);
ClearBackground(COLOR_BG);
float gridThick = 1.1f;
float gridAlpha = 0.03f;
// Grille fond
//for(int i = UI_WIDTH; i < 1920; i += 100) DrawLine(i, 0, i, 1080, Fade(WHITE, 0.05f));
//for(int i = 0; i < 1080; i += 100) DrawLine(UI_WIDTH, i, 1920, i, Fade(WHITE, 0.05f));
for(int i = UI_WIDTH; i < 1920; i += 100) {
DrawLineEx((Vector2){i, 0}, (Vector2){i, 1080}, gridThick, Fade(WHITE, gridAlpha));
}
for(int i = 0; i < 1080; i += 100) {
DrawLineEx((Vector2){UI_WIDTH, i}, (Vector2){1920, i}, gridThick, Fade(WHITE, gridAlpha));
}
DrawMichelsonSchema(&mic);
DrawControlPanel(&mic);
DrawInterferenceViewGPU(&mic, currentViewBounds, shader, &isFullscreen);
EndTextureMode();
BeginDrawing();
ClearBackground(COLOR_BG);
Rectangle sourceRec = {0.0f, 0.0f, (float)target.texture.width, -(float)target.texture.height};
Rectangle destRec = { offsetX, offsetY, newWidth, newHeight };
DrawTexturePro(target.texture, sourceRec, destRec, (Vector2){0, 0}, 0.0f, WHITE);
EndDrawing();
#endif
}
int main () {
//SetConfigFlags(FLAG_MSAA_4X_HINT);
#if !defined(PLATFORM_WEB)
#if defined(PLATFORM_WEB)
InitWindow(1920, 1080, "Interferometre de Michelson");
SetConfigFlags(FLAG_MSAA_4X_HINT);
#else
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT);
InitWindow(1280, 720, "Interferometre de Michelson");
#endif
InitWindow(1920, 1080, "Interferometre de Michelson");
SetTargetFPS(144);
SetTargetFPS(0);
#if defined(PLATFORM_WEB)
shader = LoadShader(0, "glsl/michelson_web.frag");
@ -492,8 +569,13 @@ int main () {
shader = LoadShader(0, "glsl/michelson.frag");
#endif
#if !defined(PLATFORM_WEB)
target = LoadRenderTexture(1920, 1080);
SetTextureFilter(target.texture, TEXTURE_FILTER_BILINEAR);
#endif
mic.center = (Vector2){ UI_WIDTH + (1920 - UI_WIDTH) / 2.0f - 100, 1080 / 2.0f };
mic.d1 = 250.0f;
mic.d1 = 265.0f;
mic.d2 = 250.0f;
mic.lambdas[0] = 550.0f;
mic.lambdasCount = 1;
@ -517,4 +599,3 @@ int main () {
CloseWindow();
return 0;
}

View File

@ -8,7 +8,7 @@
body {
margin: 0;
padding: 0;
background-color: #111;
background-color: #19191e;
overflow: hidden;
display: flex;
justify-content: center;
@ -19,7 +19,7 @@
canvas.emscripten {
border: 0px none;
background-color: black;
background-color: #19191e;
display: block;
aspect-ratio: 16 / 9;