aboutsummaryrefslogtreecommitdiff
path: root/src/models/electric-flux/physics.ts
blob: 78c21d030f77e4a8bcc26166d78a6d6191e76d83 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/** All physics values use SI units. No rendering or localization dependencies. */
export const VACUUM_PERMITTIVITY = 8.8541878128e-12;
export const COULOMB_CONSTANT = 1 / (4 * Math.PI * VACUUM_PERMITTIVITY);

export type Vector3 = Readonly<{ x: number; y: number; z: number }>;
export type SurfaceShape = 'sphere' | 'cube';
export type Membership = 'inside' | 'outside' | 'boundary';

export interface ClosedSurface {
  shape: SurfaceShape;
  /** Sphere radius or cube half side length, in metres. Center is the origin. */
  size: number;
}

export interface PointCharge {
  coulombs: number;
  position: Vector3;
}

export function surfaceMembership(position: Vector3, surface: ClosedSurface): Membership {
  const distance = surface.shape === 'sphere'
    ? Math.hypot(position.x, position.y, position.z)
    : Math.max(Math.abs(position.x), Math.abs(position.y), Math.abs(position.z));
  // Absorb floating-point slider arithmetic without hiding a physical boundary region.
  if (Math.abs(distance - surface.size) < 1e-9) return 'boundary';
  return distance < surface.size ? 'inside' : 'outside';
}

/** Returns null at the singular source of a nonzero ideal point charge. */
export function electricFieldAt(point: Vector3, charge: PointCharge): Vector3 | null {
  if (charge.coulombs === 0) return { x: 0, y: 0, z: 0 };
  const x = point.x - charge.position.x;
  const y = point.y - charge.position.y;
  const z = point.z - charge.position.z;
  const r = Math.hypot(x, y, z);
  if (r === 0) return null;
  const scale = COULOMB_CONSTANT * charge.coulombs / r ** 3;
  return { x: x * scale, y: y * scale, z: z * scale };
}

export function measureFlux(charge: PointCharge, surface: ClosedSurface) {
  const membership = surfaceMembership(charge.position, surface);
  const area = surface.shape === 'sphere' ? 4 * Math.PI * surface.size ** 2 : 24 * surface.size ** 2;
  const enclosedCharge = charge.coulombs === 0 ? 0
    : membership === 'boundary' ? null
      : membership === 'inside' ? charge.coulombs : 0;
  return {
    membership,
    area,
    enclosedCharge,
    netFlux: enclosedCharge === null ? null : enclosedCharge / VACUUM_PERMITTIVITY,
  };
}