support filling of curves

This commit is contained in:
Preet Shihn
2020-04-06 15:53:49 -07:00
parent 2e0ed65f0a
commit 1f63516bf4
3 changed files with 115 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
import { Config, DrawingSurface, Options, Drawable, OpSet, ResolvedOptions, PathInfo, PatternInfo, SVGNS } from './core';
import { Point } from './geometry.js';
import { line, solidFillPolygon, patternFillPolygon, rectangle, ellipseWithParams, generateEllipseParams, linearPath, arc, patternFillArc, curve, svgPath } from './renderer.js';
import { Point, getPointsOnBezierCurves } from './geometry.js';
import { line, solidFillPolygon, patternFillPolygon, rectangle, ellipseWithParams, generateEllipseParams, linearPath, arc, patternFillArc, curve, svgPath, curveAsBezierPoints } from './renderer.js';
import { randomSeed } from './math';
const hasSelf = typeof self !== 'undefined';
@@ -125,12 +125,28 @@ export class RoughGenerator {
curve(points: Point[], options?: Options): Drawable {
const o = this._options(options);
return this._drawable('curve', [curve(points, o)], o);
const paths: OpSet[] = [];
const outline = curve(points, o);
if (o.fill && o.fill !== NOS) {
const bezPoints = curveAsBezierPoints(points, o);
if (bezPoints.length >= 4) {
const polyPoints = getPointsOnBezierCurves(bezPoints, Math.min(50, 50 * o.roughness));
if (o.fillStyle === 'solid') {
paths.push(solidFillPolygon(polyPoints, o));
} else {
paths.push(patternFillPolygon(polyPoints, o));
}
}
}
if (o.stroke !== NOS) {
paths.push(outline);
}
return this._drawable('curve', paths, o);
}
polygon(points: Point[], options?: Options): Drawable {
const o = this._options(options);
const paths = [];
const paths: OpSet[] = [];
const outline = linearPath(points, true, o);
if (o.fill) {
if (o.fillStyle === 'solid') {

View File

@@ -1,6 +1,5 @@
export declare type Point = [number, number];
export declare type Line = [Point, Point];
export type Point = [number, number];
export type Line = [Point, Point];
export interface Rectangle {
x: number;
@@ -33,4 +32,79 @@ export function lineLength(line: Line): number {
const p1 = line[0];
const p2 = line[1];
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
}
export function getPointsOnBezierCurves(points: Point[], tolerance: number): Point[] {
const newPoints: Point[] = [];
const numSegments = (points.length - 1) / 3;
for (let i = 0; i < numSegments; i++) {
const offset = i * 3;
getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints);
}
return newPoints;
}
function lerp(a: Point, b: Point, t: number): Point {
return [
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t,
];
}
function flatness(points: Point[], offset: number): number {
const p1 = points[offset + 0];
const p2 = points[offset + 1];
const p3 = points[offset + 2];
const p4 = points[offset + 3];
let ux = 3 * p2[0] - 2 * p1[0] - p4[0]; ux *= ux;
let uy = 3 * p2[1] - 2 * p1[1] - p4[1]; uy *= uy;
let vx = 3 * p3[0] - 2 * p4[0] - p1[0]; vx *= vx;
let vy = 3 * p3[1] - 2 * p4[1] - p1[1]; vy *= vy;
if (ux < vx) {
ux = vx;
}
if (uy < vy) {
uy = vy;
}
return ux + uy;
}
function getPointsOnBezierCurveWithSplitting(points: Point[], offset: number, tolerance: number, newPoints: Point[]) {
const outPoints = newPoints || [];
if (flatness(points, offset) < tolerance) {
const p0 = points[offset + 0];
if (outPoints.length) {
const d = lineLength([outPoints[outPoints.length - 1], p0]);
if (d > 1) {
outPoints.push(p0);
}
} else {
outPoints.push(p0);
}
outPoints.push(points[offset + 3]);
} else {
// subdivide
const t = .5;
const p1 = points[offset + 0];
const p2 = points[offset + 1];
const p3 = points[offset + 2];
const p4 = points[offset + 3];
const q1 = lerp(p1, p2, t);
const q2 = lerp(p2, p3, t);
const q3 = lerp(p3, p4, t);
const r1 = lerp(q1, q2, t);
const r2 = lerp(q2, q3, t);
const red = lerp(r1, r2, t);
getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints);
getPointsOnBezierCurveWithSplitting([red, r2, q3, p4], 0, tolerance, outPoints);
}
return outPoints;
}

View File

@@ -56,6 +56,24 @@ export function curve(points: Point[], o: ResolvedOptions): OpSet {
return { type: 'path', ops: o1.concat(o2) };
}
export function curveAsBezierPoints(points: Point[], o: ResolvedOptions): Point[] {
const ops = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
const bez: Point[] = [];
ops.forEach((op) => {
switch (op.op) {
case 'move':
bez.push([op.data[0], op.data[1]]);
break;
case 'bcurveTo':
bez.push([op.data[0], op.data[1]]);
bez.push([op.data[2], op.data[3]]);
bez.push([op.data[4], op.data[5]]);
break;
}
});
return bez;
}
export interface EllipseResult {
opset: OpSet;
estimatedPoints: Point[];