diff --git a/src/common/model/light-sources/ArcLightSource.ts b/src/common/model/light-sources/ArcLightSource.ts index 4478884..c06f4de 100644 --- a/src/common/model/light-sources/ArcLightSource.ts +++ b/src/common/model/light-sources/ArcLightSource.ts @@ -8,21 +8,19 @@ * π/2 = down (positive y-axis), consistent with Math.atan2(dy, dx). */ -import { BaseElement } from "../optics/BaseElement.js"; import type { Point } from "../optics/Geometry.js"; import { normalize, point } from "../optics/Geometry.js"; import { BRIGHTNESS_CONTINUOUS_THRESHOLD, - BRIGHTNESS_NORMALIZE, POLARIZATION_SPLIT, RAY_DENSITY_SCALE, } from "../optics/OpticsConstants.js"; -import type { ElementCategory, SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import type { SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import { BaseLightSource } from "./BaseLightSource.js"; import { GREEN_WAVELENGTH } from "./LightSourceConstants.js"; -export class ArcLightSource extends BaseElement { +export class ArcLightSource extends BaseLightSource { public readonly type = "ArcSource"; - public readonly category: ElementCategory = "lightSource"; public position: Point; @@ -38,9 +36,6 @@ export class ArcLightSource extends BaseElement { */ public emissionAngle: number; - public brightness: number; - public wavelength: number; - public constructor( position: Point, direction = 0, @@ -48,12 +43,10 @@ export class ArcLightSource extends BaseElement { brightness = 0.5, wavelength = GREEN_WAVELENGTH, ) { - super(); + super(brightness, wavelength); this.position = position; this.direction = direction; this.emissionAngle = emissionAngle; - this.brightness = brightness; - this.wavelength = wavelength; } public override emitRays(rayDensity: number, _mode: ViewMode): SimulationRay[] { @@ -69,8 +62,7 @@ export class ArcLightSource extends BaseElement { ? maxRays : Math.max(1, Math.round((this.brightness / BRIGHTNESS_CONTINUOUS_THRESHOLD) * maxRays)); const angularStep = beta / numRays; - const bBase = isContinuous ? this.brightness : BRIGHTNESS_CONTINUOUS_THRESHOLD; - const b = bBase / BRIGHTNESS_NORMALIZE; + const b = this.normalizeBrightness(); const halfAngle = beta / 2; const startAngle = this.direction - halfAngle; diff --git a/src/common/model/light-sources/BaseLightSource.ts b/src/common/model/light-sources/BaseLightSource.ts new file mode 100644 index 0000000..d45d5c2 --- /dev/null +++ b/src/common/model/light-sources/BaseLightSource.ts @@ -0,0 +1,45 @@ +/** + * BaseLightSource.ts + * + * Abstract base class for all single-wavelength light source elements. + * Centralises the three properties shared by every concrete light source + * (brightness, wavelength, category) and provides a protected + * brightness-normalization helper used during ray emission. + */ + +import { BaseElement } from "../optics/BaseElement.js"; +import { + BRIGHTNESS_CONTINUOUS_THRESHOLD, + BRIGHTNESS_NORMALIZE, +} from "../optics/OpticsConstants.js"; +import type { ElementCategory } from "../optics/OpticsTypes.js"; +import { GREEN_WAVELENGTH } from "./LightSourceConstants.js"; + +export abstract class BaseLightSource extends BaseElement { + public readonly category: ElementCategory = "lightSource"; + + public brightness: number; + public wavelength: number; + + protected constructor(brightness: number, wavelength: number = GREEN_WAVELENGTH) { + super(); + this.brightness = brightness; + this.wavelength = wavelength; + } + + /** + * Returns the per-ray brightness factor (before polarization split). + * + * Two emission modes: + * - Discrete (brightness < BRIGHTNESS_CONTINUOUS_THRESHOLD): every ray + * carries a fixed intensity of BRIGHTNESS_CONTINUOUS_THRESHOLD so that + * visual appearance is consistent regardless of how many rays are drawn. + * - Continuous (brightness >= BRIGHTNESS_CONTINUOUS_THRESHOLD): intensity + * scales linearly with brightness up to BRIGHTNESS_NORMALIZE. + */ + protected normalizeBrightness(): number { + const isContinuous = this.brightness >= BRIGHTNESS_CONTINUOUS_THRESHOLD; + const bBase = isContinuous ? this.brightness : BRIGHTNESS_CONTINUOUS_THRESHOLD; + return bBase / BRIGHTNESS_NORMALIZE; + } +} diff --git a/src/common/model/light-sources/BeamSource.ts b/src/common/model/light-sources/BeamSource.ts index 69c1cd5..f1b18d9 100644 --- a/src/common/model/light-sources/BeamSource.ts +++ b/src/common/model/light-sources/BeamSource.ts @@ -6,38 +6,32 @@ * evenly distributed along the segment. */ -import { BaseElement } from "../optics/BaseElement.js"; import type { Point } from "../optics/Geometry.js"; import { distance, normalize, point, subtract } from "../optics/Geometry.js"; import { BEAM_RAY_DENSITY_SCALE, BRIGHTNESS_CONTINUOUS_THRESHOLD, - BRIGHTNESS_NORMALIZE, POLARIZATION_SPLIT, RAY_DENSITY_SCALE, } from "../optics/OpticsConstants.js"; -import type { ElementCategory, SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import type { SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import { BaseLightSource } from "./BaseLightSource.js"; import { GREEN_WAVELENGTH } from "./LightSourceConstants.js"; -export class BeamSource extends BaseElement { +export class BeamSource extends BaseLightSource { public readonly type = "Beam"; - public readonly category: ElementCategory = "lightSource"; /** First endpoint of the segment perpendicular to beam direction. */ public p1: Point; /** Second endpoint of the segment perpendicular to beam direction. */ public p2: Point; - public brightness: number; - public wavelength: number; /** Divergence half-angle in degrees (0 = perfectly parallel). */ public emisAngle: number; public constructor(p1: Point, p2: Point, brightness = 0.5, wavelength = GREEN_WAVELENGTH, emisAngle = 0) { - super(); + super(brightness, wavelength); this.p1 = p1; this.p2 = p2; - this.brightness = brightness; - this.wavelength = wavelength; this.emisAngle = emisAngle; } @@ -57,8 +51,7 @@ export class BeamSource extends BaseElement { const angularStep = (Math.PI * 2) / Math.max(1, Math.floor(rayDensity * RAY_DENSITY_SCALE)); const numAngledRays = 1 + Math.max(0, Math.ceil(halfAngle / angularStep) - 1) * 2; const brightnessFactor = 1.0 / numAngledRays; - const bBase = isContinuous ? this.brightness : BRIGHTNESS_CONTINUOUS_THRESHOLD; - const b = Math.min((bBase / BRIGHTNESS_NORMALIZE) * brightnessFactor, 1); + const b = Math.min(this.normalizeBrightness() * brightnessFactor, 1); const rays: SimulationRay[] = []; diff --git a/src/common/model/light-sources/PointSourceElement.ts b/src/common/model/light-sources/PointSourceElement.ts index fe10aea..2cf0732 100644 --- a/src/common/model/light-sources/PointSourceElement.ts +++ b/src/common/model/light-sources/PointSourceElement.ts @@ -5,31 +5,25 @@ * directions. The angular spacing of rays depends on rayDensity. */ -import { BaseElement } from "../optics/BaseElement.js"; import type { Point } from "../optics/Geometry.js"; import { point } from "../optics/Geometry.js"; import { BRIGHTNESS_CONTINUOUS_THRESHOLD, - BRIGHTNESS_NORMALIZE, POLARIZATION_SPLIT, RAY_DENSITY_SCALE, } from "../optics/OpticsConstants.js"; -import type { ElementCategory, SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import type { SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import { BaseLightSource } from "./BaseLightSource.js"; import { GREEN_WAVELENGTH } from "./LightSourceConstants.js"; -export class PointSourceElement extends BaseElement { +export class PointSourceElement extends BaseLightSource { public readonly type = "PointSource"; - public readonly category: ElementCategory = "lightSource"; public position: Point; - public brightness: number; - public wavelength: number; public constructor(position: Point, brightness = 0.5, wavelength = GREEN_WAVELENGTH) { - super(); + super(brightness, wavelength); this.position = position; - this.brightness = brightness; - this.wavelength = wavelength; } public override emitRays(rayDensity: number, mode: ViewMode): SimulationRay[] { @@ -42,8 +36,7 @@ export class PointSourceElement extends BaseElement { const startAngle = mode === "observer" ? -angularStep * 2 + 1e-6 : 0; // In discrete mode every ray has fixed intensity (BRIGHTNESS_CONTINUOUS_THRESHOLD). // In continuous mode intensity scales with brightness up to BRIGHTNESS_NORMALIZE. - const bBase = isContinuous ? this.brightness : BRIGHTNESS_CONTINUOUS_THRESHOLD; - const b = bBase / BRIGHTNESS_NORMALIZE; + const b = this.normalizeBrightness(); const rays: SimulationRay[] = []; let first = true; diff --git a/src/common/model/light-sources/SingleRaySource.ts b/src/common/model/light-sources/SingleRaySource.ts index 69b40f6..351a833 100644 --- a/src/common/model/light-sources/SingleRaySource.ts +++ b/src/common/model/light-sources/SingleRaySource.ts @@ -5,28 +5,23 @@ * a second point that indicates direction. Emits exactly one ray. */ -import { BaseElement } from "../optics/BaseElement.js"; import type { Point } from "../optics/Geometry.js"; import { normalize, point, subtract } from "../optics/Geometry.js"; import { POLARIZATION_SPLIT } from "../optics/OpticsConstants.js"; -import type { ElementCategory, SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import type { SimulationRay, ViewMode } from "../optics/OpticsTypes.js"; +import { BaseLightSource } from "./BaseLightSource.js"; import { GREEN_WAVELENGTH } from "./LightSourceConstants.js"; -export class SingleRaySource extends BaseElement { +export class SingleRaySource extends BaseLightSource { public readonly type = "SingleRay"; - public readonly category: ElementCategory = "lightSource"; public p1: Point; public p2: Point; - public brightness: number; - public wavelength: number; public constructor(p1: Point, p2: Point, brightness = 1, wavelength = GREEN_WAVELENGTH) { - super(); + super(brightness, wavelength); this.p1 = p1; this.p2 = p2; - this.brightness = brightness; - this.wavelength = wavelength; } public override emitRays(_rayDensity: number, _mode: ViewMode): SimulationRay[] {