fix(study): 강한 압력 즉시 반응 — 3단계 threshold + dynamic range 확장 + thinning 키움

사용자 보고: 빡세게 눌러도 굵기 차이 거의 안 남.

원인 분석:
1. raw pressure 0.1~0.99 만 활용했는데 dynamic range 그대로 → 변동 작음.
2. 속도 기반 변동 폭 0.3~1.0 작음 + dist/25 비율 작음.
3. INTENT alpha 0.25 너무 느림 → 강한 변화도 stroke 내내 못 따라감.
4. thinning 0.4 변동 폭 부족.

Fix:
- raw pressure 0.1~0.99 → 0.3~1.0 으로 매핑. dynamic range 확장.
- 속도 기반 0.25~1.0 + 비율 dist/18. 변동 폭 키움.
- 3단계 threshold:
  · dev < 0.15 (잡음) → alpha 0.03 (fixed 유지)
  · 0.15 ≤ dev < 0.3 (의도적) → alpha 0.5 (이전 0.25 → 빠르게 따라감)
  · dev ≥ 0.3 (매우 큼, 빡세게 누름) → 즉시 update (alpha 1.0)
- thinning 0.4 → 0.5. 폭 변동 더 명확.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-27 15:44:05 +09:00
parent 294bd775a9
commit fb73f96d2e
@@ -218,9 +218,8 @@
if (!path) {
const outline = getStroke(pts, {
size,
// thinning 0.4 — fixedPressure 가 평소 잡음 흡수하므로 변동 폭 키워도 안정.
// 의도적 큰 압력 변화 시만 굵기 변화 명확히 보임.
thinning: 0.4,
// thinning 0.5 큰 압력 변화 시 굵기 차이 명확. fixedPressure 가 잡음 흡수.
thinning: 0.5,
smoothing: 0.99,
streamline: 0.75,
// simulatePressure: false — getStrokePressure 가 raw pressure / 속도 추정 /
@@ -350,35 +349,41 @@
// 부드러운 곡선 생성 → 보간 점 적게 둘 수록 일정 간격 vertex 패턴 (= 사용자
// 보고 "선 사이사이 마디") 안 발생. 단 30px+ gap 은 보간으로 점선 방지.
const MAX_GAP_PX = 16;
// Smart pressure — fixed 유지 (잡음/평소) + 의도적 큰 변화 시 따라감.
// 사용자 시나리오:
// - Pencil 정상 + 평소 압력 → fixed 유지 (Notability felt: stroke 내부 균일)
// - Pencil 정상 + 의도적 압력 변화 → 따라감 (굵기 변화)
// - Pencil 비정상 / mouse → raw pressure 가 0 또는 일정 → 속도 기반 추정.
const FIXED_THRESHOLD = 0.15; // 변동 < 15% 잡음, ≥ 15% 의도적
// Smart pressure — 3단계 threshold.
// 잡음 (dev < 15%) → alpha 0.03 (거의 무시, fixed 유지)
// 의도적 (15% ≤ dev < 30%) → alpha 0.5 (빠르게 따라감)
// 매우 큼 (dev ≥ 30%) → 즉시 update (사용자 빡세게 누름 같은 큰 변화)
const FIXED_THRESHOLD = 0.15;
const FIXED_LARGE = 0.3;
const FIXED_ALPHA_NOISE = 0.03;
const FIXED_ALPHA_INTENT = 0.25;
const FIXED_ALPHA_INTENT = 0.5;
function getStrokePressure(target: Stroke, x: number, y: number, rawPressure: number): number {
// 1. inputP 결정 — raw pressure 정상이면 그것, 비정상이면 속도 기반.
// 1. inputP 결정.
let inputP: number;
if (Number.isFinite(rawPressure) && rawPressure > 0.05 && rawPressure < 0.99) {
inputP = rawPressure;
if (Number.isFinite(rawPressure) && rawPressure > 0.1 && rawPressure < 0.99) {
// raw pressure 활용 — 0.1~0.99 → 0.3~1.0 으로 dynamic range 확장.
inputP = 0.3 + ((rawPressure - 0.1) / 0.89) * 0.7;
} else {
// 속도 기반. 변동 폭 0.25~1.0 (이전 0.3~1.0 보다 넓음).
const last = target.points[target.points.length - 1];
if (last) {
const dist = Math.hypot(x - last[0], y - last[1]);
// 빠른 (큰 dist) = 약함, 느린 (작은 dist) = 강함.
inputP = Math.max(0.3, Math.min(1.0, 1.5 - dist / 25));
inputP = Math.max(0.25, Math.min(1.0, 1.6 - dist / 18));
} else {
inputP = 0.55;
}
}
// 2. fixed 초기화 (첫 점) 또는 hybrid update.
// 2. fixed hybrid.
if (target.fixedPressure === undefined) {
target.fixedPressure = inputP;
return inputP;
}
const dev = Math.abs(inputP - target.fixedPressure);
if (dev >= FIXED_LARGE) {
// 매우 큰 변화 — 즉시 update.
target.fixedPressure = inputP;
return inputP;
}
const alpha = dev < FIXED_THRESHOLD ? FIXED_ALPHA_NOISE : FIXED_ALPHA_INTENT;
target.fixedPressure = target.fixedPressure + (inputP - target.fixedPressure) * alpha;
return target.fixedPressure;