Physics · Electromagnetism · common
Electric Field Lines
add every charge's push, then follow the arrows
Coulomb fields add as vectors; tracing streamlines along the net field draws lines that leave positive charges, end on negative ones and never cross.
Cyan spheres are positive charges, rose spheres negative and grey neutral; yellow lines are traced field lines with orange markers flowing along E. Slide the right charge through zero to flip a dipole into two like charges, and change spacing and line density.
What it is
Every point charge creates a field that points straight away from it (or toward it, if negative) and weakens with the square of distance. With several charges you simply add the vectors: that is superposition, and it is the whole trick.
A field line is a curve that is tangent to the net field everywhere. You draw one by starting just outside a positive charge and repeatedly taking a small step along the normalised field direction until the line reaches a negative charge or leaves the screen. Start a number of lines proportional to each charge and the line density shows field strength.
Game uses: magnet and force-field abilities, homing projectiles that follow a potential, flow fields for crowds and particles (the same tracing as a particle system advecting through a vector field), and debug views of any vector field. The same field drives the charged particle motion sheet.
Key formulas
- Field of point charges (superposition)
k = 1/(4πε₀) ≈ 8.99 × 10⁹ N·m²/C². Scenes usually set k = 1.
- Force on a test charge
- Streamline step
+ from positive seeds, − when tracing backwards from negative seeds.
- Softened distance
Caps the field near a charge so nothing divides by zero.
Rules of thumb
- Seed
≈ 12 × |q|lines per charge, evenly spaced on a circle of radius ~0.3–0.4 units around it. - Step size around 0.1 units and 80–120 steps per line; stop within ~0.4 units of another charge or 6 units from the origin.
- Like charges produce a null point between them where E = 0; stop tracing when |E| falls below ~1e-4.
- Normalise the step: stepping by raw E makes lines jump past charges where the field is huge.
- Trace once when charges change and cache the polylines; only animate markers along them per frame.
Implementation
type Charge = { x: number; y: number; q: number }
export function fieldAt(cs: Charge[], x: number, y: number) {
let ex = 0, ey = 0
for (const c of cs) {
const dx = x - c.x, dy = y - c.y
const r2 = Math.max(dx * dx + dy * dy, 0.12) // softening
const s = c.q / (r2 * Math.sqrt(r2))
ex += dx * s; ey += dy * s
}
return { ex, ey }
}
export function trace(cs: Charge[], seed: Charge, angle: number, h = 0.1) {
const dir = seed.q > 0 ? 1 : -1
let x = seed.x + 0.35 * Math.cos(angle), y = seed.y + 0.35 * Math.sin(angle)
const pts = [x, y, 0]
for (let i = 0; i < 100; i++) {
const { ex, ey } = fieldAt(cs, x, y)
const len = Math.hypot(ex, ey)
if (len < 1e-4) break // null point
x += (dir * h * ex) / len; y += (dir * h * ey) / len
pts.push(x, y, 0)
if (cs.some((c) => c !== seed && Math.hypot(x - c.x, y - c.y) < 0.4)) break
}
return pts // feed to THREE.BufferGeometry / drei <Line>
}Common mistakes
- Not normalising the step, so lines overshoot charges and wander off where the field is strong.
- Seeding a fixed number of lines per charge, which hides the fact that a 2q charge has twice the flux.
- Seeding from both charges of a dipole, which draws every line twice (once each way).
- Dividing by r² without softening: a seed that lands on a charge produces NaN and an invisible line.
- Re-tracing every line every frame when nothing moved; trace on change and cache.
Prompt
Write a TypeScript module for Three.js that draws 2D electric field lines for a list of point charges {x, y, q} (k = 1). Implement fieldAt(charges, x, y, out) with a softened r² (min 0.12) that writes into a reusable object, and traceLines(charges, linesPerUnitCharge = 12) that seeds round(12·|q|) lines on a 0.36-unit circle around each positive charge (or every charge if none is positive), steps 0.11 units along the normalised field (backwards for negative seeds), and stops near another charge (< 0.42), outside radius 6, after 110 steps or when |E| < 1e-4. Return flat Float32Arrays for THREE.Line. Add tests: the field at a dipole's midpoint points from + to −, falls as 1/r² from a single charge, and a line from + ends on −.Related
Further reading
Updated 2026-11-01