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,
};
}
|