mirror of
https://github.com/rough-stuff/rough.git
synced 2026-04-22 03:00:28 -04:00
.
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point, Line } from '../geometry';
|
||||
import { Point, Line, lineLength, lineIntersection, doIntersect, isPointInPolygon } from '../geometry';
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
|
||||
interface IntersectionInfo {
|
||||
point: Point;
|
||||
distance: number;
|
||||
}
|
||||
|
||||
export class HachureFiller implements PatternFiller {
|
||||
private helper: RenderHelper;
|
||||
|
||||
@@ -15,21 +20,83 @@ export class HachureFiller implements PatternFiller {
|
||||
}
|
||||
|
||||
protected _fillPolygon(points: Point[], o: ResolvedOptions, connectEnds: boolean = false): OpSet {
|
||||
const lines = polygonHachureLines(points, o);
|
||||
const ops = this.renderLines(lines, o, connectEnds);
|
||||
let lines = polygonHachureLines(points, o);
|
||||
if (connectEnds) {
|
||||
const connectingLines = this.connectingLines(points, lines);
|
||||
// console.log(lines);
|
||||
// console.log(connectingLines);
|
||||
lines = lines.concat(connectingLines);
|
||||
}
|
||||
const ops = this.renderLines(lines, o);
|
||||
return { type: 'fillSketch', ops };
|
||||
}
|
||||
|
||||
private renderLines(lines: Line[], o: ResolvedOptions, connectEnds: boolean): Op[] {
|
||||
private renderLines(lines: Line[], o: ResolvedOptions): Op[] {
|
||||
const ops: Op[] = [];
|
||||
let prevPoint: Point | null = null;
|
||||
for (const line of lines) {
|
||||
ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
|
||||
if (connectEnds && prevPoint) {
|
||||
ops.push(...this.helper.doubleLineOps(prevPoint[0], prevPoint[1], line[0][0], line[0][1], o));
|
||||
}
|
||||
prevPoint = line[1];
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
private connectingLines(polygon: Point[], lines: Line[]): Line[] {
|
||||
const result: Line[] = [];
|
||||
if (lines.length > 1) {
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const prev = lines[i - 1];
|
||||
if (lineLength(prev) < 3) {
|
||||
continue;
|
||||
}
|
||||
const current = lines[i];
|
||||
const segment: Line = [current[0], prev[1]];
|
||||
if (lineLength(segment) > 3) {
|
||||
const segSplits = this.splitOnIntersections(polygon, segment);
|
||||
result.push(...segSplits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private splitOnIntersections(polygon: Point[], segment: Line): Line[] {
|
||||
const error = Math.max(5, lineLength(segment) * 0.1);
|
||||
const intersections: IntersectionInfo[] = [];
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
const p1 = polygon[i];
|
||||
const p2 = polygon[(i + 1) % polygon.length];
|
||||
if (doIntersect(p1, p2, ...segment)) {
|
||||
const ip = lineIntersection(p1, p2, segment[0], segment[1]);
|
||||
if (ip) {
|
||||
const d0 = lineLength([ip, segment[0]]);
|
||||
const d1 = lineLength([ip, segment[1]]);
|
||||
if (d0 > error && d1 > error) {
|
||||
intersections.push({
|
||||
point: ip,
|
||||
distance: d0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intersections.length > 1) {
|
||||
const ips = intersections.sort((a, b) => a.distance - b.distance).map<Point>((d) => d.point);
|
||||
if (!isPointInPolygon(polygon, ...segment[0])) {
|
||||
ips.shift();
|
||||
}
|
||||
if (!isPointInPolygon(polygon, ...segment[1])) {
|
||||
ips.pop();
|
||||
}
|
||||
if (ips.length <= 1) {
|
||||
return [segment];
|
||||
}
|
||||
const spoints = [segment[0], ...ips, segment[1]];
|
||||
const slines: Line[] = [];
|
||||
for (let i = 0; i < (spoints.length - 1); i += 2) {
|
||||
slines.push([spoints[i], spoints[i + 1]]);
|
||||
}
|
||||
return slines;
|
||||
} else {
|
||||
return [segment];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,95 @@ 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 lineIntersection(a: Point, b: Point, c: Point, d: Point): Point | null {
|
||||
const a1 = b[1] - a[1];
|
||||
const b1 = a[0] - b[0];
|
||||
const c1 = a1 * (a[0]) + b1 * (a[1]);
|
||||
const a2 = d[1] - c[1];
|
||||
const b2 = c[0] - d[0];
|
||||
const c2 = a2 * (c[0]) + b2 * (c[1]);
|
||||
const determinant = a1 * b2 - a2 * b1;
|
||||
return determinant ? [(b2 * c1 - b1 * c2) / determinant, (a1 * c2 - a2 * c1) / determinant] : null;
|
||||
}
|
||||
|
||||
export function isPointInPolygon(points: Point[], x: number, y: number, ): boolean {
|
||||
const vertices = points.length;
|
||||
|
||||
// There must be at least 3 vertices in polygon
|
||||
if (vertices < 3) {
|
||||
return false;
|
||||
}
|
||||
const extreme: Point = [Number.MAX_SAFE_INTEGER, y];
|
||||
const p: Point = [x, y];
|
||||
let count = 0;
|
||||
for (let i = 0; i < vertices; i++) {
|
||||
const current = points[i];
|
||||
const next = points[(i + 1) % vertices];
|
||||
if (doIntersect(current, next, p, extreme)) {
|
||||
if (orientation(current, p, next) === 0) {
|
||||
return onSegment(current, p, next);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// true if count is off
|
||||
return count % 2 === 1;
|
||||
}
|
||||
|
||||
// Check if q lies on the line segment pr
|
||||
function onSegment(p: Point, q: Point, r: Point) {
|
||||
return (
|
||||
q[0] <= Math.max(p[0], r[0]) &&
|
||||
q[0] >= Math.min(p[0], r[0]) &&
|
||||
q[1] <= Math.max(p[1], r[1]) &&
|
||||
q[1] >= Math.min(p[1], r[1])
|
||||
);
|
||||
}
|
||||
|
||||
// For the ordered points p, q, r, return
|
||||
// 0 if p, q, r are collinear
|
||||
// 1 if Clockwise
|
||||
// 2 if counterclickwise
|
||||
function orientation(p: Point, q: Point, r: Point) {
|
||||
const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
|
||||
if (val === 0) {
|
||||
return 0;
|
||||
}
|
||||
return val > 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
// Check is p1q1 intersects with p2q2
|
||||
export function doIntersect(p1: Point, q1: Point, p2: Point, q2: Point) {
|
||||
const o1 = orientation(p1, q1, p2);
|
||||
const o2 = orientation(p1, q1, q2);
|
||||
const o3 = orientation(p2, q2, p1);
|
||||
const o4 = orientation(p2, q2, q1);
|
||||
|
||||
if (o1 !== o2 && o3 !== o4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
|
||||
if (o1 === 0 && onSegment(p1, p2, q1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// p1, q1 and p2 are colinear and q2 lies on segment p1q1
|
||||
if (o2 === 0 && onSegment(p1, q2, q1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
|
||||
if (o3 === 0 && onSegment(p2, p1, q2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
|
||||
if (o4 === 0 && onSegment(p2, q1, q2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
29
visual-tests/canvas/polygon2.html
Normal file
29
visual-tests/canvas/polygon2.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
|
||||
<title>RoughJS Polygon</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas width="800" height="800"></canvas>
|
||||
|
||||
<script type="module">
|
||||
import rough from '../../bin/rough.js';
|
||||
const canvas = document.querySelector('canvas');
|
||||
const rc = rough.canvas(canvas);
|
||||
|
||||
rc.polygon([
|
||||
[10, 300],
|
||||
[150, 200],
|
||||
[310, 300],
|
||||
[200, 50],
|
||||
[100, 50]
|
||||
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 20 });
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user