numerics
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file apps/em_demo/main.cpp
2///
3/// DC current through an aluminum rod + movable permanent magnet.
4///
5/// Physics:
6/// Rod B field -- precomputed once via div(sigmagradphi)=0 -> J -> laplacianA=-mu_0J -> B=curlA
7/// Magnet -- magnetic dipole field, evaluated analytically every frame:
8/// B_dip = mu_0/4pi * [3(m*r_hat)r_hat - m] / r^3 (m = M0*y_hat)
9/// Total B -- B_rod (sampled from grid) + B_dip (analytical)
10///
11/// Controls:
12/// LMB drag -- orbit camera
13/// RMB drag -- pan
14/// Scroll -- zoom
15/// Shift + LMB drag -- move magnet (horizontal -> XZ, vertical -> Y)
16#include "field.hpp"
17
18#include <raylib.h>
19#include <raymath.h>
20
21#include <chrono>
22#include <cmath>
23#include <cstdio>
24#include <vector>
25
26// Scene parameters
27static constexpr int NX = 32, NY = 32, NZ = 32;
28static constexpr float DX = 0.05f;
29static constexpr float SIM_DOMAIN = NX * DX; // 1.6 m
30
31static constexpr int ROD_CX = NX / 2;
32static constexpr int ROD_CZ = NZ / 2;
33static constexpr int ROD_R = 4; // cells (0.2 m)
34static constexpr float ROD_SIG = 1.0f;
35static constexpr float BG_SIG = 1e-6f;
36static constexpr float V_TOP = 1.0f;
37static constexpr float V_BOT = 0.0f;
38
39// Magnet dipole moment magnitude [A*m^2] -- tuned so dipole is
40// visible relative to the rod field at ~0.3 m distance
41static constexpr float MAG_M0 = 0.01f;
42static constexpr float MAG_VIS_R = 0.07f; // drawn sphere radius
43
44static constexpr int STRIDE = 4; // sample every N cells in x,y,z
45static constexpr float MAX_LEN = DX * 2.2f; // longest arrow in world units
46
47// Helpers
48static bool inside_rod(int i, int k) {
49 int di = i - ROD_CX, dk = k - ROD_CZ;
50 return di*di + dk*dk <= ROD_R*ROD_R;
51}
52static int flat(int i, int j, int k) { return k*NY*NX + j*NX + i; }
53
54static Color field_color(float t) {
55 t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t);
56 return ColorFromHSV((1.0f - t) * 240.0f, 1.0f, 0.95f);
57}
58
59static void draw_arrow(Vector3 origin, Vector3 dir, float len, Color col) {
60 Vector3 tip = { origin.x + dir.x*len,
61 origin.y + dir.y*len,
62 origin.z + dir.z*len };
63 DrawLine3D(origin, tip, col);
64 DrawSphere(tip, len * 0.12f, col);
65}
66
67// Dipole field: B_dip = mu_0/4pi * [3(m*r)r - m*r^2] / r^5
68// with m = M0*y_hat -> m*r = M0*ry
69static void dipole_B(float wx, float wy, float wz,
70 float mx, float my, float mz,
71 float& bx, float& by, float& bz) {
72 float rx = wx - mx, ry = wy - my, rz = wz - mz;
73 float r2 = rx*rx + ry*ry + rz*rz;
74 if (r2 < 1e-5f) { bx = by = bz = 0.0f; return; }
75 float r5 = r2 * r2 * sqrtf(r2);
76 float mdr = MAG_M0 * ry; // m*r (m = M0*y_hat)
77 constexpr float C = 1e-7f; // mu_0/4pi
78 bx = C * 3.0f * mdr * rx / r5;
79 by = C * MAG_M0 * (3.0f*ry*ry - r2) / r5;
80 bz = C * 3.0f * mdr * rz / r5;
81}
82
83// Main
84int main() {
85 physics::ScalarField3D sigma(NX, NY, NZ, DX);
86 for (int k = 0; k < NZ; ++k)
87 for (int j = 0; j < NY; ++j)
88 for (int i = 0; i < NX; ++i)
89 sigma.grid().set(i, j, k, inside_rod(i,k) ? ROD_SIG : BG_SIG);
90
91 std::vector<physics::ElectrodeBC> bcs;
92 for (int k = 0; k < NZ; ++k)
93 for (int i = 0; i < NX; ++i) {
94 if (!inside_rod(i, k)) continue;
95 bcs.push_back({ flat(i, 0, k), V_BOT });
96 bcs.push_back({ flat(i, NY-1, k), V_TOP });
97 }
98
99 printf("Solving... ");
100 fflush(stdout);
101 auto t0 = std::chrono::steady_clock::now();
102
103 physics::ScalarField3D phi(NX, NY, NZ, DX);
104 auto r_phi = physics::ElectricSolver::solve_potential(phi, sigma, bcs);
105
108
109 double solve_ms = std::chrono::duration<double,std::milli>(
110 std::chrono::steady_clock::now() - t0).count();
111 printf("done in %.0f ms\n", solve_ms);
112
113 const int W = 1280, H = 800;
114 InitWindow(W, H, "EM Demo -- rod + magnet");
115 SetTargetFPS(60);
116
117 Camera3D camera = {};
118 camera.up = { 0.0f, 1.0f, 0.0f };
119 camera.fovy = 45.0f;
120 camera.projection = CAMERA_PERSPECTIVE;
121
122 Vector3 orbit_target = { SIM_DOMAIN*0.5f, SIM_DOMAIN*0.5f, SIM_DOMAIN*0.5f };
123 float azimuth = 0.785f;
124 float elevation = 0.524f;
125 float distance = SIM_DOMAIN * 2.6f;
126
127 auto update_cam = [&]() {
128 camera.position = {
129 orbit_target.x + distance * cosf(elevation) * sinf(azimuth),
130 orbit_target.y + distance * sinf(elevation),
131 orbit_target.z + distance * cosf(elevation) * cosf(azimuth)
132 };
133 camera.target = orbit_target;
134 };
135
136 float mag_x = ROD_CX * DX + 0.45f;
137 float mag_y = SIM_DOMAIN * 0.5f;
138 float mag_z = ROD_CZ * DX;
139
140 // Render loop
141 while (!WindowShouldClose()) {
142 bool shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
143
144 // --- Mouse drag ---
145 if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
146 Vector2 d = GetMouseDelta();
147 if (shift) {
148 // Move magnet: horizontal -> along camera's right (XZ), vertical -> Y
149 Vector3 view = Vector3Normalize(
150 Vector3Subtract(camera.target, camera.position));
151 Vector3 right = Vector3Normalize(
152 Vector3CrossProduct(view, camera.up));
153 // Project right into XZ so magnet stays on a horizontal plane
154 // unless the user drags vertically
155 float spd = distance * 0.0012f;
156 mag_x += right.x * d.x * spd;
157 mag_z += right.z * d.x * spd;
158 mag_y -= d.y * spd;
159 } else {
160 azimuth -= d.x * 0.005f;
161 elevation += d.y * 0.005f;
162 if (elevation > 1.55f) elevation = 1.55f;
163 if (elevation < -1.55f) elevation = -1.55f;
164 }
165 }
166
167 // --- Scroll zoom ---
168 float scroll = GetMouseWheelMove();
169 if (scroll != 0.0f) {
170 distance -= scroll * distance * 0.08f;
171 if (distance < 0.05f) distance = 0.05f;
172 }
173
174 // --- Right drag pan ---
175 if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) {
176 Vector2 d = GetMouseDelta();
177 float spd = distance * 0.001f;
178 float raz = azimuth + 1.5708f;
179 orbit_target.x += (cosf(raz) * d.x
180 - sinf(elevation) * sinf(azimuth) * d.y) * spd;
181 orbit_target.y -= cosf(elevation) * d.y * spd;
182 orbit_target.z += (sinf(raz) * d.x
183 - sinf(elevation) * cosf(azimuth) * d.y) * spd;
184 }
185
186 update_cam();
187
188 // Compute B_max over 3-D sample grid (rod + dipole combined)
189 float B_max = 1e-30f;
190 for (int j = 0; j < NY; j += STRIDE)
191 for (int k = 0; k < NZ; k += STRIDE)
192 for (int i = 0; i < NX; i += STRIDE) {
193 float wx = i*DX, wy = j*DX, wz = k*DX;
194 float bx = (float)B.x.grid()(i, j, k);
195 float by = (float)B.y.grid()(i, j, k);
196 float bz = (float)B.z.grid()(i, j, k);
197 float dbx, dby, dbz;
198 dipole_B(wx, wy, wz, mag_x, mag_y, mag_z, dbx, dby, dbz);
199 bx += dbx; by += dby; bz += dbz;
200 float mag = sqrtf(bx*bx + by*by + bz*bz);
201 if (mag > B_max) B_max = mag;
202 }
203
204 // Draw
205 BeginDrawing();
206 ClearBackground({ 15, 15, 20, 255 });
207
208 BeginMode3D(camera);
209
210 // Domain wireframe
211 DrawCubeWires({ SIM_DOMAIN*0.5f, SIM_DOMAIN*0.5f, SIM_DOMAIN*0.5f },
212 SIM_DOMAIN, SIM_DOMAIN, SIM_DOMAIN, { 60, 60, 80, 255 });
213
214 // Aluminum rod
215 float rod_wr = ROD_R * DX;
216 DrawCylinder({ ROD_CX*DX, 0.0f, ROD_CZ*DX },
217 rod_wr, rod_wr, SIM_DOMAIN, 24, { 180, 185, 195, 220 });
218 DrawCylinderWires({ ROD_CX*DX, 0.0f, ROD_CZ*DX },
219 rod_wr, rod_wr, SIM_DOMAIN, 24, { 100, 110, 130, 255 });
220 // Electrode caps
221 DrawCylinder({ ROD_CX*DX, SIM_DOMAIN - DX*0.5f, ROD_CZ*DX },
222 rod_wr, rod_wr, DX*0.5f, 24, { 220, 60, 60, 255 });
223 DrawCylinder({ ROD_CX*DX, 0.0f, ROD_CZ*DX },
224 rod_wr, rod_wr, DX*0.5f, 24, { 60, 80, 220, 255 });
225
226 // Magnet sphere -- N pole (top, red) and S pole (bottom, blue)
227 DrawSphere({ mag_x, mag_y + MAG_VIS_R*0.5f, mag_z }, MAG_VIS_R, { 210, 60, 60, 255 });
228 DrawSphere({ mag_x, mag_y - MAG_VIS_R*0.5f, mag_z }, MAG_VIS_R, { 60, 80, 210, 255 });
229 // Dipole moment axis (white line)
230 DrawLine3D({ mag_x, mag_y - MAG_VIS_R*1.6f, mag_z },
231 { mag_x, mag_y + MAG_VIS_R*1.6f, mag_z }, WHITE);
232
233 // B field arrows -- full 3D volume sample grid
234 // Length uses log scale so weak far-field arrows are still visible
235 // but strong near-field arrows don't dominate. Skip near-zero.
236 static constexpr float B_THRESH = 0.002f; // fraction of B_max to skip
237 for (int j = 0; j < NY; j += STRIDE)
238 for (int k = 0; k < NZ; k += STRIDE)
239 for (int i = 0; i < NX; i += STRIDE) {
240 float wx = i*DX, wy = j*DX, wz = k*DX;
241 float bx = (float)B.x.grid()(i, j, k);
242 float by = (float)B.y.grid()(i, j, k);
243 float bz = (float)B.z.grid()(i, j, k);
244 float dbx, dby, dbz;
245 dipole_B(wx, wy, wz, mag_x, mag_y, mag_z, dbx, dby, dbz);
246 bx += dbx; by += dby; bz += dbz;
247
248 float mag = sqrtf(bx*bx + by*by + bz*bz);
249 float t = mag / B_max;
250 if (t < B_THRESH) continue;
251
252 // log scale: strong fields -> long arrows, weak -> short but visible
253 float t_log = logf(1.0f + (t / B_THRESH - 1.0f)) /
254 logf(1.0f + (1.0f / B_THRESH - 1.0f));
255 float len = MAX_LEN * (0.15f + 0.85f * t_log);
256 draw_arrow({ wx, wy, wz }, { bx/mag, by/mag, bz/mag }, len,
257 field_color(t));
258 }
259
260 EndMode3D();
261
262 // HUD
263 DrawRectangle(8, 8, 440, 148, { 0, 0, 0, 160 });
264 DrawText("Aluminum Rod + Permanent Magnet", 16, 14, 18, WHITE);
265 char buf[128];
266 snprintf(buf, sizeof(buf), "Solve: %.0f ms | phi iters=%llu res=%.1e",
267 solve_ms, (unsigned long long)r_phi.iterations, r_phi.residual);
268 DrawText(buf, 16, 38, 14, LIGHTGRAY);
269 snprintf(buf, sizeof(buf), "Magnet (%.2f, %.2f, %.2f) M0=%.3f A*m^2",
270 (double)mag_x, (double)mag_y, (double)mag_z, (double)MAG_M0);
271 DrawText(buf, 16, 58, 14, LIGHTGRAY);
272 snprintf(buf, sizeof(buf), "B_max = %.3e T (rod+dipole)", (double)B_max);
273 DrawText(buf, 16, 78, 14, LIGHTGRAY);
274 DrawText("LMB: orbit RMB: pan Scroll: zoom", 16, 100, 14, GRAY);
275 DrawText("Shift+LMB: move magnet (XZ + Y)", 16, 118, 14,
276 shift ? YELLOW : GRAY);
277
278 // Colour legend
279 for (int px = 0; px < 200; ++px) {
280 Color c = field_color(px / 199.0f);
281 DrawRectangle(W - 220 + px, H - 36, 1, 18, c);
282 }
283 DrawText("0", W - 224, H - 36, 14, LIGHTGRAY);
284 DrawText("B_max", W - 16, H - 36, 14, LIGHTGRAY);
285
286 EndDrawing();
287 }
288
289 CloseWindow();
290 return 0;
291}
void set(int i, int j, int k, real v)
Definition grid3d.hpp:29
static VectorField3D solve_magnetic_field(const VectorField3D &J, double tol=1e-6, int max_iter=500)
Definition fields.cpp:136
static VectorField3D current_density(const ScalarField3D &sigma, const ScalarField3D &phi)
Compute current density J = −σ∇φ [A/m²].
Definition fields.cpp:120
Grid3D & grid()
Definition fields.hpp:29
static num::SolverResult solve_potential(ScalarField3D &phi, const ScalarField3D &sigma, const std::vector< ElectrodeBC > &bcs, double tol=1e-6, int max_iter=500)
Definition field.cpp:13
int main()
Definition main.cpp:84
EM-specific field types and solvers.
ScalarField3D z
x, y, z components on the same grid layout
Definition fields.hpp:57
ScalarField3D x
Definition fields.hpp:57
ScalarField3D y
Definition fields.hpp:57