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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vizyman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # scax-engine
2
+
3
+ 눈 모델(Gullstrand / Navarro)에 대한 광선 추적, Sturm 간격 분석, 아핀 왜곡 추정, 유발 난시·프리즘 편위 계산을 제공하는 TypeScript 안경광학 시뮬레이션(단안 기준, OD) 라이브러리입니다. ESM, CJS, UMD 빌드를 지원합니다.
4
+
5
+ English documentation: [README-en.md](README-en.md)
6
+
7
+ ## 요구 사항
8
+
9
+ - **Node.js** 20 이상(로컬 개발·테스트)
10
+ - 런타임 의존성: **three** (패키지에 포함됨)
11
+
12
+
13
+ ## 설치
14
+
15
+ ```bash
16
+ npm install scax-engine
17
+ ```
18
+
19
+ ## 빠른 시작
20
+
21
+ ```ts
22
+ import { SCAXEngine } from "scax-engine";
23
+
24
+ const engine = new SCAXEngine({
25
+ eyeModel: "gullstrand",
26
+ eye: { s: -2.0, c: -0.75, ax: 90 },
27
+ lens: [
28
+ {
29
+ s: 0,
30
+ c: -0.5,
31
+ ax: 180,
32
+ position: { x: 0, y: 0, z: 12 },
33
+ tilt: { x: 0, y: 0 },
34
+ },
35
+ ],
36
+ light_source: { type: "grid", width: 10, height: 10, division: 6, z: -10, vergence: 0 },
37
+ pupil_type: "neutral",
38
+ });
39
+
40
+ const result = engine.simulate();
41
+ console.log(result.traced_rays.length);
42
+ console.log(result.info.astigmatism.combined);
43
+ console.log(result.info.prism.combined);
44
+ ```
45
+
46
+ UI(슬라이더, 애니메이션 등)에서 변경마다 `new SCAXEngine`을 다시 만들지 않고 하나의 엔진 인스턴스를 재사용하려면, 전체 `props` 객체로 `update`를 호출한 뒤 `simulate`를 다시 실행하세요. `update`에서 생략된 최상위 필드는 이전 상태와 병합되지 않으며 생성자 기본값으로 채워집니다.
47
+
48
+ ```ts
49
+ const engine = new SCAXEngine({ /* initial props */ });
50
+
51
+ engine.update({
52
+ eyeModel: "gullstrand",
53
+ eye: { s: -2.5, c: -0.75, ax: 90 },
54
+ lens: [],
55
+ light_source: { type: "grid", width: 10, height: 10, division: 6, z: -10, vergence: 0 },
56
+ pupil_type: "neutral",
57
+ });
58
+
59
+ const next = engine.simulate();
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### `new SCAXEngine(props?)`
65
+
66
+ 시뮬레이션 엔진 인스턴스를 생성합니다. 내부 `Sturm`, `Affine` 헬퍼는 인스턴스당 한 번만 생성됩니다.
67
+
68
+ #### `props`
69
+
70
+ - `eyeModel?: "gullstrand" | "navarro"` (기본값: `"gullstrand"`)
71
+ - `eye?: { s, c, ax, p?, p_ax?, tilt? }` (기본값: `{ s: 0, c: 0, ax: 0, p: 0, p_ax: 0 }`)
72
+ - `tilt?: { x?: number; y?: number }` — 각도(도); 렌더 회전·눈 자세에 반영됩니다.
73
+ - `lens?: LensConfig[]` (기본값: `[]`)
74
+ - `LensConfig = { s, c, ax, p?, p_ax?, position: { x, y, z }, tilt: { x, y } }`
75
+ - `position.z`를 생략하면 안경 정간 거리(VD) 기본값 **12 mm**가 적용됩니다.
76
+ - `light_source?: LightSourceConfig`
77
+ - Grid: `{ type: "grid", width, height, division, z, vergence, position?, tilt? }`
78
+ - Grid (색수차): `{ type: "grid_rg", ... }` — 격자 각 점에서 Fraunhofer **e**선·**C**선 광선을 함께 생성합니다(`division`은 4보다 커야 함).
79
+ - Radial: `{ type: "radial", radius, division, angle_division, z, vergence, position?, tilt? }`
80
+ - 공통으로 `position`·`tilt`(도)를 두면 광원 기준 위치·기울기를 바꿀 수 있습니다.
81
+ - 기본값: `{ type: "grid", width: 10, height: 10, division: 4, z: -10, vergence: 0 }`
82
+ - `pupil_type?: "constricted"(2.5 mm) | "neutral"(4.0 mm) | "dilated"(6.0 mm) | "none"(0 mm)` (기본값: `"neutral"`)
83
+ - `"none"`이면 동공(입사 구경) 제한을 끕니다.
84
+
85
+ 동일한 `props` 규칙이 `update()`에도 적용됩니다. 부분 객체만 넘기면 생략된 최상위 키는 이전 `update` 값이 아니라 **항상 기본값**으로 채워지므로, 한 필드만 바꾸려면 앱에서 전체 `props`를 조립해 넘기는 것이 안전합니다.
86
+
87
+ #### 프리즘 입력 컨벤션
88
+
89
+ - `eye.p` / `eye.p_ax`는 **교정량(처방값)** 입니다.
90
+ - `lens.p` / `lens.p_ax`도 **교정량(처방값)** 입니다.
91
+ - `p_ax`는 **임상 Base 방향**, **렌즈 쪽에서 각막을 바라보는 시점** 기준입니다.
92
+ - `simulate().info.prism.eye` 계산에서 눈 프리즘 효과는 실제 안구 편위를 표현하기 위해 내부에서 역방향으로 변환됩니다.
93
+ - 렌즈는 광선을 굴절시키는 물리 요소이므로, 광선 추적에는 입력 방향이 그대로 적용됩니다.
94
+ - 교정이 맞으면 `lens prism`과 `eye prism`이 크기·축에서 대응하고, 이때 `net_prism`은 0에 가깝습니다.
95
+
96
+ ### `engine.update(props?)`
97
+
98
+ 생성자와 동일한 설정 경로를 다시 실행합니다. `props`를 기준으로 eye / lens / 표면 / 광원을 재구성하며, 생략된 키에는 위 기본값이 적용됩니다.
99
+
100
+ - `Sturm`, `Affine` 인스턴스는 재생성되지 않지만, `update` 직후에는 Sturm·affine 분석 캐시가 비워지며 `simulate` 등으로 다시 채워집니다.
101
+
102
+ ### `engine.dispose()`
103
+
104
+ 엔진 인스턴스가 잡고 있던 추적/분석 캐시와 표면 trace history를 정리합니다.
105
+
106
+ - 해제 대상: traced rays, Sturm/affine 캐시, surface incident/refracted history
107
+ - UI에서 엔진을 자주 새로 만들 때(예: 프레임별/슬라이더 연속 갱신) 이전 인스턴스에 `dispose()`를 호출하면 메모리 사용량을 안정적으로 유지할 수 있습니다.
108
+
109
+ ### 인스턴스 메서드
110
+
111
+ - `update(props?)` — 위와 동일.
112
+ - `dispose()` — 내부 캐시와 표면 trace history를 정리합니다.
113
+
114
+ - `simulate()`
115
+ ```ts
116
+ (): {
117
+ traced_rays: Ray[];
118
+ info: {
119
+ astigmatism: {
120
+ eye: { tabo_deg: number; d: number | null };
121
+ lens: Array<{ name: string; type: string; tabo_deg: number; d: number | null }>;
122
+ combined: { tabo_deg: number; d: number | null };
123
+ };
124
+ prism: {
125
+ eye: { p_x: number; p_y: number; prism_angle: number; magnitude: number | null };
126
+ lens: { p_x: number; p_y: number; prism_angle: number; magnitude: number | null };
127
+ combined: { p_x: number; p_y: number; prism_angle: number; magnitude: number | null };
128
+ };
129
+ };
130
+ }
131
+ ```
132
+ - 광선 추적과 함께 난시/프리즘 요약 정보를 반환합니다.
133
+ - `d`, `magnitude`는 0에 가까우면 `null`로 반환됩니다.
134
+
135
+ - `getEyeRotation()`
136
+ ```ts
137
+ (): {
138
+ x_deg: number;
139
+ y_deg: number;
140
+ magnitude_deg: number;
141
+ }
142
+ ```
143
+ - 처방 프리즘·`eye.tilt`를 반영한 렌더용 눈 회전량을 반환합니다.
144
+
145
+ - `rayTracing()` — 광선 추적만 수행해 추적된 `Ray[]`를 반환합니다. `simulate()`는 이를 포함해 Sturm·난시/프리즘 요약까지 한 번에 계산합니다.
146
+
147
+ - `sturmCalculation(rays?)` — 추적 광선(인자 생략 시 마지막 `rayTracing`/`simulate` 결과)으로 Sturm 슬라이스·스펙트럼선별 분석 객체를 계산해 반환합니다. `simulate()` 호출 시 내부에서 Sturm도 갱신됩니다.
148
+
149
+ - `getAffineAnalysis()` — 현재 설정으로 망막 대응점 쌍을 만들어 아핀 적합을 수행(또는 캐시 재사용)합니다. 유효한 쌍이 부족하면 `null`이 될 수 있습니다.
150
+
151
+ ## UMD
152
+
153
+ UMD 빌드는 `dist/scax-engine.umd.js`에 생성되며 전역 이름 **`ScaxEngine`**으로 노출됩니다.
154
+
155
+ ## 개발
156
+
157
+ ```bash
158
+ npm install
159
+ npm run build
160
+ npm test
161
+ ```
162
+
163
+ ### 스크립트
164
+
165
+ - `npm run clean` — `dist` 삭제
166
+ - `npm run build` — 타입 선언 및 ESM / CJS / UMD 번들 빌드
167
+ - `npm test` — Vitest 1회 실행
168
+ - `npm run test:watch` — Vitest 워치 모드
169
+
170
+ 로컬에서 엔진 동작을 손으로 보려면 `test/ui-test/ui-test.html`을 브라우저에서 열 수 있습니다(빌드 산출물 경로에 맞게 스크립트를 조정해야 할 수 있음).
171
+
172
+ ## 배포
173
+
174
+ ```bash
175
+ npm run build
176
+ npm test
177
+ npm publish --access public
178
+ ```
179
+
180
+ ## 라이선스
181
+
182
+ MIT