scax-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+ import Affine from "./affine/affine";
2
+ import { GridLightSourceProps, GridRGLightSourceProps, LightSource, RadialLightSourceProps } from "./light-sources/light-source";
3
+ import Ray from "./ray/ray";
4
+ import Surface from "./surfaces/surface";
5
+ export type EyeModel = "gullstrand" | "navarro";
6
+ export type PupilType = "constricted" | "neutral" | "dilated" | "none";
7
+ export type LightSourceTransform = {
8
+ position?: {
9
+ x?: number;
10
+ y?: number;
11
+ z?: number;
12
+ };
13
+ tilt?: {
14
+ x?: number;
15
+ y?: number;
16
+ };
17
+ };
18
+ export type LightSourceConfig = ({
19
+ type: "radial";
20
+ } & RadialLightSourceProps & LightSourceTransform) | ({
21
+ type: "grid";
22
+ } & GridLightSourceProps & LightSourceTransform) | ({
23
+ type: "grid_rg";
24
+ } & GridRGLightSourceProps & LightSourceTransform);
25
+ export type LensConfig = {
26
+ s: number;
27
+ c: number;
28
+ ax: number;
29
+ p?: number;
30
+ p_ax?: number;
31
+ position: {
32
+ x: number;
33
+ y: number;
34
+ z: number;
35
+ };
36
+ tilt: {
37
+ x: number;
38
+ y: number;
39
+ };
40
+ };
41
+ export type EyeConfig = {
42
+ s: number;
43
+ c: number;
44
+ ax: number;
45
+ p?: number;
46
+ p_ax?: number;
47
+ tilt?: {
48
+ x?: number;
49
+ y?: number;
50
+ };
51
+ };
52
+ export type EyePowerInput = EyeConfig;
53
+ export type SCAXEngineProps = {
54
+ eyeModel?: EyeModel;
55
+ eye?: EyeConfig;
56
+ lens?: LensConfig[];
57
+ light_source?: LightSourceConfig;
58
+ pupil_type?: PupilType;
59
+ };
60
+ export type SCAxPower = {
61
+ s: number;
62
+ c: number;
63
+ ax: number;
64
+ };
65
+ export type SimulateResult = {
66
+ traced_rays: Ray[];
67
+ info: SimulationResultInfo;
68
+ };
69
+ export type PrismVector = {
70
+ x: number;
71
+ y: number;
72
+ magnitude: number;
73
+ angle_deg: number;
74
+ };
75
+ export type EyeRotationForRender = {
76
+ x_deg: number;
77
+ y_deg: number;
78
+ magnitude_deg: number;
79
+ };
80
+ export type LightDeviation = {
81
+ eye_prism_effect: PrismVector;
82
+ lens_prism_total: PrismVector;
83
+ net_prism: PrismVector;
84
+ x_angle_deg: number;
85
+ y_angle_deg: number;
86
+ net_angle_deg: number;
87
+ };
88
+ export type AstigmatismSummaryItem = {
89
+ tabo: number;
90
+ d: number;
91
+ }[];
92
+ export type PrismSummaryItem = {
93
+ p_x: number;
94
+ p_y: number;
95
+ prism_angle: number;
96
+ magnitude: number | null;
97
+ };
98
+ export type SimulationResultInfo = {
99
+ astigmatism: {
100
+ eye: AstigmatismSummaryItem[];
101
+ lens: AstigmatismSummaryItem[];
102
+ combined: AstigmatismSummaryItem[];
103
+ };
104
+ prism: {
105
+ eye: PrismSummaryItem;
106
+ lens: PrismSummaryItem;
107
+ combined: PrismSummaryItem;
108
+ };
109
+ };
110
+ export type AffineAnalysisResult = ReturnType<Affine["estimate"]>;
111
+ /**
112
+ * legacy simulator.js를 TypeScript로 옮긴 핵심 시뮬레이터입니다.
113
+ * - 광원 광선을 생성하고
114
+ * - 표면들을 순서대로 통과시키며 굴절을 계산한 뒤
115
+ * - 망막 대응쌍, Sturm 분석, 왜곡(affine) 분석까지 제공합니다.
116
+ */
117
+ export declare class SCAXEngineCore {
118
+ private static readonly EYE_ROTATION_PIVOT_FROM_CORNEA_MM;
119
+ private eyeModel;
120
+ private surfaces;
121
+ private lens;
122
+ private light_source;
123
+ private eyeModelParameter;
124
+ private tracedRays;
125
+ private lastSourceRaysForSturm;
126
+ private sturm;
127
+ private affine;
128
+ private lastSturmGapAnalysis;
129
+ private lastAffineAnalysis;
130
+ private pupilDiameterMm;
131
+ private hasPupilStop;
132
+ private eyePower;
133
+ private lensConfigs;
134
+ private lensPowers;
135
+ private eyePrismEffectVector;
136
+ private lensPrismVector;
137
+ private eyePrismPrescription;
138
+ private eyeRotationQuaternion;
139
+ private eyeRotationQuaternionInverse;
140
+ private eyeRotationPivot;
141
+ private eyeTiltDeg;
142
+ private lightSourcePosition;
143
+ private lightSourceTiltDeg;
144
+ private lightSourceRotationQuaternion;
145
+ private lightSourceRotationPivot;
146
+ private sortedLensSurfaces;
147
+ private sortedEyeSurfaces;
148
+ private currentProps;
149
+ constructor(props?: SCAXEngineProps);
150
+ /**
151
+ * 생성자와 동일한 기본값 규칙으로 광학 설정을 다시 적용합니다.
152
+ * 생략한 최상위 필드는 매번 기본값으로 돌아갑니다(이전 값과 병합하지 않음).
153
+ */
154
+ update(props?: SCAXEngineProps): void;
155
+ dispose(): void;
156
+ private configure;
157
+ /**
158
+ * 기본 시뮬레이션 진입점입니다.
159
+ * includeSturmData 값과 무관하게 확장 분석(Sturm/retinaPairs)을 항상 포함합니다.
160
+ */
161
+ simulate(): SimulateResult;
162
+ /**
163
+ * eye.p / eye.p_ax(처방값)를 기준으로, 렌더링에서 바로 쓸 눈 회전량을 반환합니다.
164
+ * - x_deg/y_deg는 프리즘 회전량에 eye.tilt를 합산한 최종 렌더 회전량입니다.
165
+ */
166
+ getEyeRotation(): EyeRotationForRender;
167
+ /**
168
+ * 1) Ray tracing 전용 함수
169
+ * 광원에서 시작한 광선을 표면 순서대로 굴절시켜 최종 광선 집합을 반환합니다.
170
+ */
171
+ rayTracing(): Ray[];
172
+ /**
173
+ * 2) Sturm calculation 전용 함수
174
+ * traced ray 집합에서 z-scan 기반 Sturm 슬라이스/근사 중심을 계산합니다.
175
+ */
176
+ sturmCalculation(rays?: Ray[]): {
177
+ slices_info: {
178
+ count: number;
179
+ slices: {
180
+ z: number;
181
+ depth: number;
182
+ ratio: number;
183
+ size: number;
184
+ profile: {
185
+ at: {
186
+ x: number;
187
+ y: number;
188
+ z: number;
189
+ };
190
+ wMajor: number;
191
+ wMinor: number;
192
+ angleMajorDeg: number;
193
+ angleMinorDeg: number;
194
+ majorDirection: {
195
+ x: number;
196
+ y: number;
197
+ z: number;
198
+ };
199
+ minorDirection: {
200
+ x: number;
201
+ y: number;
202
+ z: number;
203
+ };
204
+ };
205
+ }[];
206
+ };
207
+ sturm_info: {
208
+ has_astigmatism: boolean;
209
+ method: string;
210
+ anterior: {
211
+ z: number;
212
+ depth: number;
213
+ ratio: number;
214
+ size: number;
215
+ profile: {
216
+ at: {
217
+ x: number;
218
+ y: number;
219
+ z: number;
220
+ };
221
+ wMajor: number;
222
+ wMinor: number;
223
+ angleMajorDeg: number;
224
+ angleMinorDeg: number;
225
+ majorDirection: {
226
+ x: number;
227
+ y: number;
228
+ z: number;
229
+ };
230
+ minorDirection: {
231
+ x: number;
232
+ y: number;
233
+ z: number;
234
+ };
235
+ };
236
+ };
237
+ posterior: {
238
+ z: number;
239
+ depth: number;
240
+ ratio: number;
241
+ size: number;
242
+ profile: {
243
+ at: {
244
+ x: number;
245
+ y: number;
246
+ z: number;
247
+ };
248
+ wMajor: number;
249
+ wMinor: number;
250
+ angleMajorDeg: number;
251
+ angleMinorDeg: number;
252
+ majorDirection: {
253
+ x: number;
254
+ y: number;
255
+ z: number;
256
+ };
257
+ minorDirection: {
258
+ x: number;
259
+ y: number;
260
+ z: number;
261
+ };
262
+ };
263
+ } | null;
264
+ approx_center: {
265
+ x: number;
266
+ y: number;
267
+ z: number;
268
+ mode: string;
269
+ } | null;
270
+ line: "g" | "F" | "e" | "d" | "C" | "r";
271
+ wavelength_nm: number;
272
+ color: number | null;
273
+ ray_count: number;
274
+ analysis_axis: {
275
+ x: number;
276
+ y: number;
277
+ z: number;
278
+ };
279
+ }[];
280
+ };
281
+ /**
282
+ * 3) Affine 왜곡 추정 전용 함수
283
+ * 광선 대응쌍(sx,sy)->(tx,ty)에 대해 최소자승 2D affine을 적합합니다.
284
+ */
285
+ private estimateAffineDistortion;
286
+ /**
287
+ * 현재 eye+lens 설정 기준 affine 왜곡 결과를 반환합니다.
288
+ * traced ray/affine 결과는 기존 계산값을 우선 재사용합니다.
289
+ */
290
+ getAffineAnalysis(): AffineAnalysisResult;
291
+ private surfaceOrderZ;
292
+ private readSurfacePosition;
293
+ private getRayPoints;
294
+ private isRayInsidePupil;
295
+ private powerVectorFromCylinder;
296
+ private aggregatePowerVector;
297
+ private principalMeridiansFromVector;
298
+ private principalMeridiansFromPowers;
299
+ private normalizePrismAmount;
300
+ private normalizeAngle360;
301
+ private normalizeEyeTilt;
302
+ private normalizeLightSourcePose;
303
+ private toFiniteNumber;
304
+ private refreshSortedSurfaces;
305
+ private prismVectorFromBase;
306
+ private vectorToPrismInfo;
307
+ private toPrismSummaryItem;
308
+ private prismComponentToAngleDeg;
309
+ private prismMagnitudeToAngleDeg;
310
+ private applyPrismVectorToRay;
311
+ private rotatePointAroundPivot;
312
+ private translateRay;
313
+ private transformRayAroundPivot;
314
+ private applyLightSourceTransformToRay;
315
+ private calculateLightDeviation;
316
+ private effectiveCylinderFromOpticSurfaces;
317
+ private createAffinePairs;
318
+ }
319
+ /**
320
+ * 외부 공개 API 전용 Facade입니다.
321
+ * 내부 광학 상태/연산은 SCAXEngineCore에 위임합니다.
322
+ */
323
+ export default class SCAXEngine {
324
+ private readonly core;
325
+ constructor(props?: SCAXEngineProps);
326
+ get lens(): Surface[];
327
+ get light_source(): LightSource;
328
+ get surfaces(): Surface[];
329
+ update(props?: SCAXEngineProps): void;
330
+ dispose(): void;
331
+ simulate(): SimulateResult;
332
+ getEyeRotation(): EyeRotationForRender;
333
+ rayTracing(): Ray[];
334
+ sturmCalculation(rays?: Ray[]): {
335
+ slices_info: {
336
+ count: number;
337
+ slices: {
338
+ z: number;
339
+ depth: number;
340
+ ratio: number;
341
+ size: number;
342
+ profile: {
343
+ at: {
344
+ x: number;
345
+ y: number;
346
+ z: number;
347
+ };
348
+ wMajor: number;
349
+ wMinor: number;
350
+ angleMajorDeg: number;
351
+ angleMinorDeg: number;
352
+ majorDirection: {
353
+ x: number;
354
+ y: number;
355
+ z: number;
356
+ };
357
+ minorDirection: {
358
+ x: number;
359
+ y: number;
360
+ z: number;
361
+ };
362
+ };
363
+ }[];
364
+ };
365
+ sturm_info: {
366
+ has_astigmatism: boolean;
367
+ method: string;
368
+ anterior: {
369
+ z: number;
370
+ depth: number;
371
+ ratio: number;
372
+ size: number;
373
+ profile: {
374
+ at: {
375
+ x: number;
376
+ y: number;
377
+ z: number;
378
+ };
379
+ wMajor: number;
380
+ wMinor: number;
381
+ angleMajorDeg: number;
382
+ angleMinorDeg: number;
383
+ majorDirection: {
384
+ x: number;
385
+ y: number;
386
+ z: number;
387
+ };
388
+ minorDirection: {
389
+ x: number;
390
+ y: number;
391
+ z: number;
392
+ };
393
+ };
394
+ };
395
+ posterior: {
396
+ z: number;
397
+ depth: number;
398
+ ratio: number;
399
+ size: number;
400
+ profile: {
401
+ at: {
402
+ x: number;
403
+ y: number;
404
+ z: number;
405
+ };
406
+ wMajor: number;
407
+ wMinor: number;
408
+ angleMajorDeg: number;
409
+ angleMinorDeg: number;
410
+ majorDirection: {
411
+ x: number;
412
+ y: number;
413
+ z: number;
414
+ };
415
+ minorDirection: {
416
+ x: number;
417
+ y: number;
418
+ z: number;
419
+ };
420
+ };
421
+ } | null;
422
+ approx_center: {
423
+ x: number;
424
+ y: number;
425
+ z: number;
426
+ mode: string;
427
+ } | null;
428
+ line: "g" | "F" | "e" | "d" | "C" | "r";
429
+ wavelength_nm: number;
430
+ color: number | null;
431
+ ray_count: number;
432
+ analysis_axis: {
433
+ x: number;
434
+ y: number;
435
+ z: number;
436
+ };
437
+ }[];
438
+ };
439
+ getAffineAnalysis(): AffineAnalysisResult;
440
+ }
441
+ //# sourceMappingURL=scax-engine.d.ts.map
@@ -0,0 +1,81 @@
1
+ import Ray from "../ray/ray";
2
+ type FraunhoferLine = "g" | "F" | "e" | "d" | "C" | "r";
3
+ type SturmSlice = {
4
+ z: number;
5
+ depth: number;
6
+ ratio: number;
7
+ size: number;
8
+ profile: {
9
+ at: {
10
+ x: number;
11
+ y: number;
12
+ z: number;
13
+ };
14
+ wMajor: number;
15
+ wMinor: number;
16
+ angleMajorDeg: number;
17
+ angleMinorDeg: number;
18
+ majorDirection: {
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ };
23
+ minorDirection: {
24
+ x: number;
25
+ y: number;
26
+ z: number;
27
+ };
28
+ };
29
+ };
30
+ /**
31
+ * Sturm 계산 전용 클래스입니다.
32
+ * - traced ray 집합에서 z-scan slice를 생성하고
33
+ * - 평탄도/최소타원/근사중심을 계산해 분석 결과를 반환합니다.
34
+ */
35
+ export default class Sturm {
36
+ private lastResult;
37
+ calculate(rays: Ray[], effectiveCylinderD: number, axisReferenceRays?: Ray[]): {
38
+ slices_info: {
39
+ count: number;
40
+ slices: SturmSlice[];
41
+ };
42
+ sturm_info: {
43
+ has_astigmatism: boolean;
44
+ method: string;
45
+ anterior: SturmSlice;
46
+ posterior: SturmSlice | null;
47
+ approx_center: {
48
+ x: number;
49
+ y: number;
50
+ z: number;
51
+ mode: string;
52
+ } | null;
53
+ line: FraunhoferLine;
54
+ wavelength_nm: number;
55
+ color: number | null;
56
+ ray_count: number;
57
+ analysis_axis: {
58
+ x: number;
59
+ y: number;
60
+ z: number;
61
+ };
62
+ }[];
63
+ };
64
+ /**
65
+ * 마지막 Sturm 계산 결과를 반환합니다.
66
+ */
67
+ getLastResult(): unknown;
68
+ private getRayPoints;
69
+ private readonly lineOrder;
70
+ private analysisFrameFromRays;
71
+ private sampleRayPointAtDepth;
72
+ private depthRangeFromRays;
73
+ private secondMomentProfileAtDepth;
74
+ private collectSturmSlices;
75
+ private axisDiffDeg;
76
+ private buildApproxCenter;
77
+ private groupByFraunhoferLine;
78
+ private analyzeSturmSlices;
79
+ }
80
+ export {};
81
+ //# sourceMappingURL=sturm.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { Vector3 } from "three";
2
+ import Ray from "../ray/ray";
3
+ import Surface from "./surface";
4
+ export type ApertureStopSurfaceProps = {
5
+ type: "aperture_stop";
6
+ shape: "circle" | "rectangle";
7
+ radius?: number;
8
+ width?: number;
9
+ height?: number;
10
+ name: string;
11
+ position: {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ };
16
+ tilt: {
17
+ x: number;
18
+ y: number;
19
+ };
20
+ };
21
+ export default class ApertureStopSurface extends Surface {
22
+ private shape;
23
+ private radius;
24
+ private width;
25
+ private height;
26
+ constructor(props: ApertureStopSurfaceProps);
27
+ private worldQuaternion;
28
+ private localPointFromWorld;
29
+ private surfaceNormalWorld;
30
+ private intersectForward;
31
+ private isInsideAperture;
32
+ incident(ray: Ray): Vector3 | null;
33
+ refract(ray: Ray): Ray | null;
34
+ }
35
+ //# sourceMappingURL=aperture-stop-surface.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { Vector3 } from "three";
2
+ import { RefractiveIndexSpec } from "../optics/refractive-index";
3
+ import Ray from "../ray/ray";
4
+ import Surface from "./surface";
5
+ export type AsphericalSurfaceProps = {
6
+ type: "aspherical";
7
+ name: string;
8
+ position: {
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ };
13
+ tilt: {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ r: number;
18
+ conic: number;
19
+ n_before: RefractiveIndexSpec;
20
+ n_after: RefractiveIndexSpec;
21
+ };
22
+ export default class AsphericalSurface extends Surface {
23
+ /**
24
+ * 수치 계산 상수들
25
+ * - MIN_T: 현재 시작점과의 자기 재교차를 피하기 위한 최소 진행 거리
26
+ * - MAX_ITERS: 뉴턴법 반복 횟수
27
+ * - BISECTION_ITERS: 이분법 반복 횟수
28
+ */
29
+ private static readonly MIN_T;
30
+ private static readonly MAX_ITERS;
31
+ private static readonly BISECTION_ITERS;
32
+ private static readonly BRACKET_SCAN_STEPS;
33
+ private static readonly MAX_BRACKET_T_MM;
34
+ private r;
35
+ private conic;
36
+ private n_before;
37
+ private n_after;
38
+ constructor(props: AsphericalSurfaceProps);
39
+ private refractiveIndicesForRay;
40
+ /**
41
+ * 비구면 사그(sag)와 그 기울기(미분)를 계산합니다.
42
+ *
43
+ * 회전대칭 비구면 식:
44
+ * z = c*rho^2 / (1 + sqrt(1 - (1+k)*c^2*rho^2))
45
+ * - c = 1 / R (곡률)
46
+ * - k = conic constant
47
+ * - rho^2 = x^2 + y^2
48
+ *
49
+ * 반환값:
50
+ * - sag: 꼭지점 기준 z 변위(mm)
51
+ * - dzdx, dzdy: 표면 기울기 (법선 계산과 뉴턴법 미분에 사용)
52
+ */
53
+ private geometryAtXY;
54
+ /**
55
+ * 광선 파라미터 t에서의 표면 방정식 값 f(t)를 계산합니다.
56
+ * f(t)=0 이면 교점입니다.
57
+ */
58
+ private surfaceEquationAtT;
59
+ /**
60
+ * 뉴턴법이 실패했을 때를 대비해, 일정 구간에서 부호가 바뀌는 브래킷을 찾습니다.
61
+ */
62
+ private scanBracketRange;
63
+ /**
64
+ * 브래킷 [a,b] 내부에서 이분법으로 f(t)=0의 근을 찾습니다.
65
+ */
66
+ private bisectionRoot;
67
+ incident(ray: Ray): Vector3 | null;
68
+ refract(ray: Ray): Ray | null;
69
+ }
70
+ //# sourceMappingURL=aspherical-surface.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { Vector3 } from "three";
2
+ import Ray from "../ray/ray";
3
+ import Surface from "./surface";
4
+ export type ParaxialSurfaceProps = {
5
+ type: "paraxial";
6
+ name: string;
7
+ position: {
8
+ x: number;
9
+ y: number;
10
+ z: number;
11
+ };
12
+ tilt: {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ s: number;
17
+ c: number;
18
+ ax: number;
19
+ n: number;
20
+ };
21
+ export default class ParaxialSurface extends Surface {
22
+ private s;
23
+ private c;
24
+ private ax;
25
+ private n;
26
+ constructor(props: ParaxialSurfaceProps);
27
+ /**
28
+ * 이 surface는 입력된 d선 기준 굴절률(n)만 사용한다.
29
+ * 파장(프라운호퍼 선) 변화에 따른 굴절률 업데이트는 클래스 외부에서 처리한다.
30
+ */
31
+ private lensN;
32
+ /**
33
+ * 난시 축(ax)과 구면/난시 도수(s, c)를 2x2 파워 행렬 형태로 만든다.
34
+ */
35
+ private basePowerMatrix;
36
+ /**
37
+ * 사입사(광선이 비스듬히 들어오는 경우)에서 생기는 유도 구면/난시를
38
+ * slope 기반 근사식으로 기본 파워 행렬에 반영한다.
39
+ */
40
+ private powerMatrixWithOblique;
41
+ /**
42
+ * surface 평면(법선은 로컬 +Z)을 따라 광선이 "앞으로" 만나는 교점을 찾는다.
43
+ */
44
+ private intersectForward;
45
+ private centerWorld;
46
+ /**
47
+ * 표면 회전은 tilt(x,y)만 반영한다.
48
+ * 주의: 축(ax)은 basePowerMatrix에서 이미 반영하므로 z회전을 여기서 더하지 않는다.
49
+ */
50
+ private worldQuaternion;
51
+ private worldToLocalPoint;
52
+ private worldDirToLocal;
53
+ private localDirToWorld;
54
+ incident(ray: Ray): Vector3 | null;
55
+ /**
56
+ * 박막(paraxial thin lens) 근사를 사용해 굴절 후 광선을 생성한다.
57
+ * 1) 입사점 계산 -> 2) 로컬 좌표에서 slope 갱신 -> 3) 월드 좌표로 되돌림
58
+ */
59
+ refract(ray: Ray): Ray | null;
60
+ }
61
+ //# sourceMappingURL=paraxial-surface.d.ts.map