fix: Initial smoothing improved
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
@@ -266,7 +266,7 @@ const generateElementCanvas = (
|
|||||||
|
|
||||||
const rc = rough.canvas(canvas);
|
const rc = rough.canvas(canvas);
|
||||||
|
|
||||||
drawElementOnCanvas(element, rc, context, renderConfig);
|
drawElementOnCanvas(element, rc, context, renderConfig, scale);
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
||||||
@@ -402,6 +402,7 @@ const drawElementOnCanvas = (
|
|||||||
rc: RoughCanvas,
|
rc: RoughCanvas,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
renderConfig: StaticCanvasRenderConfig,
|
renderConfig: StaticCanvasRenderConfig,
|
||||||
|
scale = 1,
|
||||||
) => {
|
) => {
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
@@ -429,7 +430,7 @@ const drawElementOnCanvas = (
|
|||||||
}
|
}
|
||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
context.save();
|
context.save();
|
||||||
drawFreeDrawSegments(element, context, renderConfig, 0);
|
drawFreeDrawSegments(element, context, renderConfig, 0, undefined, scale);
|
||||||
context.restore();
|
context.restore();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,11 +161,13 @@ const drawSubdividedSegment = (
|
|||||||
t0y: number,
|
t0y: number,
|
||||||
t1x: number,
|
t1x: number,
|
||||||
t1y: number,
|
t1y: number,
|
||||||
|
scale: number,
|
||||||
) => {
|
) => {
|
||||||
const segLen = Math.sqrt((p1x - p0x) ** 2 + (p1y - p0y) ** 2);
|
const segLen = Math.sqrt((p1x - p0x) ** 2 + (p1y - p0y) ** 2);
|
||||||
|
// Target spacing is in screen pixels; divide by scale to get scene units.
|
||||||
const nSubdiv = Math.max(
|
const nSubdiv = Math.max(
|
||||||
1,
|
1,
|
||||||
Math.ceil(segLen / BEZIER_SUBDIVIDE_TARGET_SPACING),
|
Math.ceil((segLen * scale) / BEZIER_SUBDIVIDE_TARGET_SPACING),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cubic Bezier control points derived from Catmull-Rom tangents.
|
// Cubic Bezier control points derived from Catmull-Rom tangents.
|
||||||
@@ -220,6 +222,7 @@ export const drawFreeDrawSegments = (
|
|||||||
renderConfig: StaticCanvasRenderConfig,
|
renderConfig: StaticCanvasRenderConfig,
|
||||||
fromIndex: number,
|
fromIndex: number,
|
||||||
upToIndex?: number,
|
upToIndex?: number,
|
||||||
|
scale = 1,
|
||||||
) => {
|
) => {
|
||||||
const { points, pressures } = element;
|
const { points, pressures } = element;
|
||||||
const N = points.length;
|
const N = points.length;
|
||||||
@@ -296,6 +299,7 @@ export const drawFreeDrawSegments = (
|
|||||||
t0[1],
|
t0[1],
|
||||||
t1[0],
|
t1[0],
|
||||||
t1[1],
|
t1[1],
|
||||||
|
scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -546,6 +550,7 @@ export const generateOrUpdateFreeDrawIncrementalCanvas = (
|
|||||||
renderConfig,
|
renderConfig,
|
||||||
committedFromIndex,
|
committedFromIndex,
|
||||||
newCommittedCount, // upToIndex - stop before the last provisional segment
|
newCommittedCount, // upToIndex - stop before the last provisional segment
|
||||||
|
canvasScale,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
inc.committedPointCount = newCommittedCount;
|
inc.committedPointCount = newCommittedCount;
|
||||||
@@ -567,6 +572,7 @@ export const generateOrUpdateFreeDrawIncrementalCanvas = (
|
|||||||
renderConfig,
|
renderConfig,
|
||||||
inc.committedPointCount - 1,
|
inc.committedPointCount - 1,
|
||||||
undefined, // draw to natural end (the tip segment)
|
undefined, // draw to natural end (the tip segment)
|
||||||
|
canvasScale,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9495,6 +9495,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
let prevRawY: number | null = null;
|
let prevRawY: number | null = null;
|
||||||
let prevTs: number | null = null; // previous event timestamp (ms)
|
let prevTs: number | null = null; // previous event timestamp (ms)
|
||||||
|
|
||||||
|
// Pen pressure warmup: the first few samples from a pen digitizer are
|
||||||
|
// unreliable (often spike to ~0.5). We apply an EMA whose alpha ramps
|
||||||
|
// from PEN_PRESSURE_INITIAL_ALPHA up to 1 over PEN_PRESSURE_WARMUP_SAMPLES
|
||||||
|
// so the stroke gradually eases from heavy smoothing into raw pressure.
|
||||||
|
let penPressureEma: number | null = null;
|
||||||
|
let penPressureSampleCount = 0;
|
||||||
|
const PEN_PRESSURE_WARMUP_SAMPLES = 20;
|
||||||
|
const PEN_PRESSURE_INITIAL_ALPHA = 0.1;
|
||||||
|
|
||||||
// Tuning constants:
|
// Tuning constants:
|
||||||
// MIN_CUTOFF – cutoff frequency (Hz) when speed ≈ 0; smaller = more smoothing
|
// MIN_CUTOFF – cutoff frequency (Hz) when speed ≈ 0; smaller = more smoothing
|
||||||
// BETA – rate at which cutoff grows with speed; larger = quicker adaptation
|
// BETA – rate at which cutoff grows with speed; larger = quicker adaptation
|
||||||
@@ -10236,7 +10245,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
emaDy = alphaD * rawDyDt + (1 - alphaD) * emaDy;
|
emaDy = alphaD * rawDyDt + (1 - alphaD) * emaDy;
|
||||||
|
|
||||||
// 2. Adaptive cutoff: higher speed → higher cutoff → less lag.
|
// 2. Adaptive cutoff: higher speed → higher cutoff → less lag.
|
||||||
const speed = Math.sqrt(emaDx * emaDx + emaDy * emaDy);
|
// Normalize to screen-space pixels/sec so BETA behaves the same
|
||||||
|
// regardless of zoom level (scene units are 1/zoom px at high zoom).
|
||||||
|
const speed =
|
||||||
|
Math.sqrt(emaDx * emaDx + emaDy * emaDy) *
|
||||||
|
this.state.zoom.value;
|
||||||
const cutoff = MIN_CUTOFF + BETA * speed;
|
const cutoff = MIN_CUTOFF + BETA * speed;
|
||||||
const alphaP = emaAlpha(cutoff, dt);
|
const alphaP = emaAlpha(cutoff, dt);
|
||||||
|
|
||||||
@@ -10257,7 +10270,27 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (!lastPoint || lastPoint[0] !== dx || lastPoint[1] !== dy) {
|
if (!lastPoint || lastPoint[0] !== dx || lastPoint[1] !== dy) {
|
||||||
const pt = pointFrom<LocalPoint>(dx, dy);
|
const pt = pointFrom<LocalPoint>(dx, dy);
|
||||||
newPoints.push(pt);
|
newPoints.push(pt);
|
||||||
newPressures.push(ev.pressure);
|
|
||||||
|
let pressure = ev.pressure;
|
||||||
|
if (event.pointerType === "pen") {
|
||||||
|
// Gradually ease from aggressive smoothing into raw pressure to
|
||||||
|
// suppress the unreliable spike at the start of a pen stroke.
|
||||||
|
const progress = Math.min(
|
||||||
|
1,
|
||||||
|
penPressureSampleCount / PEN_PRESSURE_WARMUP_SAMPLES,
|
||||||
|
);
|
||||||
|
const alpha =
|
||||||
|
PEN_PRESSURE_INITIAL_ALPHA +
|
||||||
|
progress * (1 - PEN_PRESSURE_INITIAL_ALPHA);
|
||||||
|
penPressureEma =
|
||||||
|
penPressureEma === null
|
||||||
|
? pressure
|
||||||
|
: alpha * pressure + (1 - alpha) * penPressureEma;
|
||||||
|
pressure = penPressureEma;
|
||||||
|
penPressureSampleCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPressures.push(pressure);
|
||||||
lastPoint = pt;
|
lastPoint = pt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user