improve balls
This commit is contained in:
@@ -1233,6 +1233,11 @@ const roundPoint = (point: readonly number[]) =>
|
|||||||
const averagePoint = (A: readonly number[], B: readonly number[]) =>
|
const averagePoint = (A: readonly number[], B: readonly number[]) =>
|
||||||
`${round((A[0] + B[0]) / 2, 2)},${round((A[1] + B[1]) / 2, 2)} `;
|
`${round((A[0] + B[0]) / 2, 2)},${round((A[1] + B[1]) / 2, 2)} `;
|
||||||
|
|
||||||
|
const getReadonlyPointDistance = (
|
||||||
|
pointA: readonly number[],
|
||||||
|
pointB: readonly number[],
|
||||||
|
) => Math.hypot(pointA[0] - pointB[0], pointA[1] - pointB[1]);
|
||||||
|
|
||||||
// Trim SVG path data so number are each two decimal points. This
|
// Trim SVG path data so number are each two decimal points. This
|
||||||
// improves SVG exports, and prevents rendering errors on points
|
// improves SVG exports, and prevents rendering errors on points
|
||||||
// with long decimals.
|
// with long decimals.
|
||||||
@@ -1263,6 +1268,9 @@ const FIXED_FREEDRAW_MIN_CORNER_ROUNDING = 0.75;
|
|||||||
const FIXED_FREEDRAW_MAX_CORNER_ROUNDING = 6;
|
const FIXED_FREEDRAW_MAX_CORNER_ROUNDING = 6;
|
||||||
const FIXED_FREEDRAW_CORNER_ROUNDING_FACTOR = 0.35;
|
const FIXED_FREEDRAW_CORNER_ROUNDING_FACTOR = 0.35;
|
||||||
const FIXED_FREEDRAW_CORNER_ROUNDING_WIDTH_FACTOR = 1.5;
|
const FIXED_FREEDRAW_CORNER_ROUNDING_WIDTH_FACTOR = 1.5;
|
||||||
|
const FIXED_FREEDRAW_MIN_TERMINAL_STUB = 0.75;
|
||||||
|
const FIXED_FREEDRAW_MAX_TERMINAL_STUB = 2.5;
|
||||||
|
const FIXED_FREEDRAW_TERMINAL_STUB_WIDTH_FACTOR = 1.5;
|
||||||
|
|
||||||
const shouldSmoothFixedFreeDrawPoint = (
|
const shouldSmoothFixedFreeDrawPoint = (
|
||||||
previousPoint: readonly number[],
|
previousPoint: readonly number[],
|
||||||
@@ -1344,6 +1352,15 @@ const getFixedFreeDrawRoundedCorner = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFixedFreeDrawTerminalStubThreshold = (strokeWidth: number) =>
|
||||||
|
Math.min(
|
||||||
|
Math.max(
|
||||||
|
strokeWidth * FIXED_FREEDRAW_TERMINAL_STUB_WIDTH_FACTOR,
|
||||||
|
FIXED_FREEDRAW_MIN_TERMINAL_STUB,
|
||||||
|
),
|
||||||
|
FIXED_FREEDRAW_MAX_TERMINAL_STUB,
|
||||||
|
);
|
||||||
|
|
||||||
const getSvgPathFromFixedFreeDrawPoints = (
|
const getSvgPathFromFixedFreeDrawPoints = (
|
||||||
points: ReadonlyArray<readonly number[]>,
|
points: ReadonlyArray<readonly number[]>,
|
||||||
strokeWidth: number,
|
strokeWidth: number,
|
||||||
@@ -1359,11 +1376,17 @@ const getSvgPathFromFixedFreeDrawPoints = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path = `M${roundPoint(points[0])}`;
|
let path = `M${roundPoint(points[0])}`;
|
||||||
|
const lastPoint = points[len - 1];
|
||||||
|
let endsAtLastPoint = false;
|
||||||
|
|
||||||
for (let index = 1; index < len - 1; index++) {
|
for (let index = 1; index < len - 1; index++) {
|
||||||
const previousPoint = points[index - 1];
|
const previousPoint = points[index - 1];
|
||||||
const currentPoint = points[index];
|
const currentPoint = points[index];
|
||||||
const nextPoint = points[index + 1];
|
const nextPoint = points[index + 1];
|
||||||
|
const isLastCurveSegment = index === len - 2;
|
||||||
|
const terminalStubThreshold = getFixedFreeDrawTerminalStubThreshold(
|
||||||
|
strokeWidth,
|
||||||
|
);
|
||||||
const shouldSmooth = shouldSmoothFixedFreeDrawPoint(
|
const shouldSmooth = shouldSmoothFixedFreeDrawPoint(
|
||||||
previousPoint,
|
previousPoint,
|
||||||
currentPoint,
|
currentPoint,
|
||||||
@@ -1377,17 +1400,37 @@ const getSvgPathFromFixedFreeDrawPoints = (
|
|||||||
nextPoint,
|
nextPoint,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
);
|
);
|
||||||
|
const shouldCollapseSmoothTerminalStub =
|
||||||
|
isLastCurveSegment &&
|
||||||
|
shouldSmooth &&
|
||||||
|
getReadonlyPointDistance(currentPoint, nextPoint) / 2 <=
|
||||||
|
terminalStubThreshold;
|
||||||
|
const shouldCollapseRoundedTerminalStub =
|
||||||
|
isLastCurveSegment &&
|
||||||
|
!!roundedCorner &&
|
||||||
|
getReadonlyPointDistance(roundedCorner.exitPoint, lastPoint) <=
|
||||||
|
terminalStubThreshold;
|
||||||
|
|
||||||
path += shouldSmooth
|
path += shouldSmooth
|
||||||
? `Q${roundPoint(currentPoint)}${averagePoint(currentPoint, nextPoint)}`
|
? `Q${roundPoint(currentPoint)}${
|
||||||
|
shouldCollapseSmoothTerminalStub
|
||||||
|
? roundPoint(lastPoint)
|
||||||
|
: averagePoint(currentPoint, nextPoint)
|
||||||
|
}`
|
||||||
: roundedCorner
|
: roundedCorner
|
||||||
? `L${roundPoint(roundedCorner.entryPoint)}Q${roundPoint(
|
? `L${roundPoint(roundedCorner.entryPoint)}Q${roundPoint(
|
||||||
currentPoint,
|
currentPoint,
|
||||||
)}${roundPoint(roundedCorner.exitPoint)}`
|
)}${
|
||||||
|
shouldCollapseRoundedTerminalStub
|
||||||
|
? roundPoint(lastPoint)
|
||||||
|
: roundPoint(roundedCorner.exitPoint)
|
||||||
|
}`
|
||||||
: `L${roundPoint(currentPoint)}`;
|
: `L${roundPoint(currentPoint)}`;
|
||||||
|
endsAtLastPoint =
|
||||||
|
shouldCollapseSmoothTerminalStub || shouldCollapseRoundedTerminalStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${path}L${roundPoint(points[len - 1])}`;
|
return endsAtLastPoint ? path : `${path}L${roundPoint(lastPoint)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSvgPathFromStroke = (
|
const getSvgPathFromStroke = (
|
||||||
|
|||||||
@@ -56,6 +56,32 @@ describe("freedraw stroke shape", () => {
|
|||||||
expect(path).toBe("M0,0 L10,0 L9,1 ");
|
expect(path).toBe("M0,0 L10,0 L9,1 ");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("curves directly to the endpoint for tiny final smooth segments", () => {
|
||||||
|
const element = API.createElement({
|
||||||
|
type: "freedraw",
|
||||||
|
strokeShape: "fixed",
|
||||||
|
strokeWidth: 1,
|
||||||
|
points: [pointFrom(0, 0), pointFrom(0.5, 0.1), pointFrom(0.8, 0.3)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [path] = ShapeCache.generateElementShape(element, null);
|
||||||
|
|
||||||
|
expect(path).toBe("M0,0 Q0.5,0.1 0.8,0.3 ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("curves directly to the endpoint for tiny final rounded corners", () => {
|
||||||
|
const element = API.createElement({
|
||||||
|
type: "freedraw",
|
||||||
|
strokeShape: "fixed",
|
||||||
|
strokeWidth: 1,
|
||||||
|
points: [pointFrom(0, 0), pointFrom(10, 0), pointFrom(10, 1)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [path] = ShapeCache.generateElementShape(element, null);
|
||||||
|
|
||||||
|
expect(path).toBe("M0,0 L9.65,0 Q10,0 10,1 ");
|
||||||
|
});
|
||||||
|
|
||||||
it("smooths dense fixed stroke points without dropping them", () => {
|
it("smooths dense fixed stroke points without dropping them", () => {
|
||||||
const element = API.createElement({
|
const element = API.createElement({
|
||||||
type: "freedraw",
|
type: "freedraw",
|
||||||
|
|||||||
Reference in New Issue
Block a user