-
-
Notifications
You must be signed in to change notification settings - Fork 29
feat: add interactive Magnetic Field simulation #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new interactive Magnetic Field simulation to PhysicsHub, allowing users to visualize magnetic dipole field lines and manipulate a magnetic rod's orientation and position. The implementation includes physics calculations, UI components, and navigation integration.
Key Changes
- New magnetic dipole field simulation with interactive rod manipulation
- Fixed pointer-events handling for simulation info panels to allow canvas interaction
- Downgraded npm dependencies from v11 to v10 and semantic-release packages
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
app/(pages)/simulations/MagneticField/page.jsx |
New simulation page with interactive magnetic rod and field line visualization |
app/(core)/physics/MagneticField.js |
Physics implementation for magnetic dipole field calculations and particle classes |
app/(core)/data/configs/MagneticField.js |
Configuration and input mappings for the simulation |
app/(core)/data/chapters.js |
Added chapter entry for Magnetic Field simulation with theory documentation |
app/(core)/components/P5Wrapper.jsx |
Restructured to allow pointer events through to canvas |
app/(core)/components/Theme.tsx |
Fixed hydration mismatch by deferring animations until client mount |
app/(core)/styles/index.css |
CSS updates for pointer-events handling (contains critical bug) |
package.json |
Added dev:local script |
package-lock.json |
Downgraded npm and semantic-release dependencies |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .contribution-card::before { | ||
| content: ""; | ||
| /* Let pointer events pass through the panel background so the canvas below | ||
| can receive mouse input; keep controls inside the panel interactive. */ | ||
| pointer-events: none; /* allow clicks to reach canvas */ | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
| height: 4px; | ||
| .sim-info-panel * { | ||
| pointer-events: auto; | ||
| } |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CSS structure is malformed here. The .contribution-card::before pseudo-element is missing its content property and has an unrelated .sim-info-panel * selector embedded within it. This breaks the CSS syntax and will cause styling issues.
The correct structure should have:
- The
content: "";property at the beginning of the.contribution-card::beforeblock - The
.sim-info-panel *rule should be a separate, standalone CSS rule, not nested inside.contribution-card::before
| this.length = length; // meters | ||
| this.angle = (angleDeg * Math.PI) / 180; // radians | ||
| this.moment = moment; // dipole moment magnitude (A·m^2) | ||
| this.color = "#ffcc00"; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color property is defined in the constructor but never used in the code. Either use it in the draw method to style the rod, or remove it to avoid confusion.
| applyLorentz(Bvec) { | ||
| // In 2D, v x B (with B in-plane vector) -> use 3D cross via scalar Bz if necessary. | ||
| // Here Bvec is a vector in-plane; assume B has only in-plane components from dipole. | ||
| // Force = q (v × B) -> compute as 2D cross: F = q*(v_x * B_y - v_y * B_x) out-of-plane | ||
| // To keep particle in plane, approximate using perpendicular acceleration a = (q/m) * (v ⨯ B)_z rotated back. | ||
| const v = this.state.vel; | ||
| // Compute pseudo-acc: a = (q/m) * (v × B) rotated 90° -> aVec = (q/m) * (v.y*Bz, -v.x*Bz) but Bz unknown. | ||
| // Simpler: approximate with a = (q/m) * (v ⨯ B_scalar) where B_scalar = (B.x * nx + B.y * ny) | ||
| // For visualization we use (q/m) * (v ⨯ B_perp) where B_perp = scalar perpendicular magnitude | ||
| const Bz = 0; // dipole field is in-plane; full Lorentz would need 3D. Use small placeholder to allow curved motion. | ||
| const ax = (this.charge / this.mass) * (v.y * Bz); | ||
| const ay = (this.charge / this.mass) * (-v.x * Bz); | ||
| this.state.acc.add(this.p.createVector(ax, ay)); | ||
| } |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The applyLorentz method always sets Bz = 0, making the computed acceleration always zero (ax and ay are always 0). This means the Lorentz force is never actually applied to the particle. If this class is intended to be unused, consider removing it or documenting why it's incomplete.
| } catch (e) {} | ||
| } | ||
| } | ||
| if (particleRef.current) particleRef.current.stopDragging(); |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The particleRef is defined but never initialized or used meaningfully. Line 234 attempts to call particleRef.current.stopDragging() but particleRef.current is always null. Either remove this unused ref and the related line, or implement the particle functionality if it's intended to be used.
| if (particleRef.current) particleRef.current.stopDragging(); |
| try { | ||
| setInputs((prev) => ({ ...prev, rodX: Number(rod.pos.x.toFixed(2)), rodY: Number(rod.pos.y.toFixed(2)) })); | ||
| } catch (e) {} | ||
| return; | ||
| } | ||
|
|
||
| if (!rod.isDragging) return; | ||
| const mxM = toMeters(p.mouseX); | ||
| const myM = toMeters(p.mouseY); | ||
| const dx = mxM - rod.pos.x; | ||
| const dy = rod.pos.y - myM; // upward positive | ||
| const ang = (Math.atan2(dy, dx) * 180) / Math.PI; | ||
| setInputs((prev) => ({ ...prev, angle: Number(ang.toFixed(2)) })); | ||
| }; | ||
|
|
||
| p.mouseReleased = () => { | ||
| if (rod) { | ||
| rod.isDragging = false; | ||
| if (rod.isTranslating) { | ||
| rod.isTranslating = false; | ||
| // ensure final sidebar position saved | ||
| try { | ||
| setInputs((prev) => ({ ...prev, rodX: Number(rod.pos.x.toFixed(2)), rodY: Number(rod.pos.y.toFixed(2)) })); | ||
| } catch (e) {} |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty catch blocks silently suppress errors without any logging or handling. This makes debugging difficult. Consider at least logging the error with console.error(e) or documenting why errors are intentionally ignored.
| @@ -0,0 +1,166 @@ | |||
| import { toMeters, toPixels } from "../constants/Utils.js"; | |||
| import { integrate } from "../constants/Utils.js"; | |||
| import { SCALE } from "../constants/Config.js"; | |||
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import SCALE.
| import { SCALE } from "../constants/Config.js"; |
| import { | ||
| computeDelta, | ||
| resetTime, | ||
| isPaused, | ||
| setPause, | ||
| } from "../../../(core)/constants/Time.js"; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import computeDelta.
| setPause, | ||
| } from "../../../(core)/constants/Time.js"; | ||
| import getBackgroundColor from "../../../(core)/utils/getBackgroundColor"; | ||
| import { drawGlow } from "../../../(core)/utils/drawUtils.js"; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import drawGlow.
| import { drawGlow } from "../../../(core)/utils/drawUtils.js"; |
| }; | ||
|
|
||
| p.draw = () => { | ||
| const { clientWidth: w, clientHeight: h } = p._userNode; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable w.
| const { clientWidth: w, clientHeight: h } = p._userNode; | |
| const { clientHeight: h } = p._userNode; |
| }; | ||
|
|
||
| p.draw = () => { | ||
| const { clientWidth: w, clientHeight: h } = p._userNode; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable h.
| const { clientWidth: w, clientHeight: h } = p._userNode; | |
| const { clientWidth: w } = p._userNode; |
|
Can you please send a full video that shows how to use this simulation, I can't fully understand how it works with screenshots. What is that green line on the bottom of the canvas? Can you fix the simInfos container css, it seems to be a little bit moved off its position. |
What this PR adds
Implementation details
app/(core)/physics/MagneticField.jsapp/(core)/data/configs/MagneticField.jsapp/(pages)/simulations/MagneticField/page.jsxTesting
##Suggestions

-Would appreciate it if the icon for magnetic field simulation was added,language change and the order of chapter 8 was corrected and implemented to this as well!
Notes