mirror of
https://github.com/rough-stuff/rough.git
synced 2026-04-22 03:00:28 -04:00
new hachure only
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point, Line } from '../geometry';
|
||||
import { hachureLinesForPolygon, hachureLinesForEllipse, lineLength } from './filler-utils';
|
||||
import { Point, Line, lineLength } from '../geometry';
|
||||
import { polygonHachureLines, ellipseHachureLines } from './scan-line-hachure';
|
||||
|
||||
export class DashedFiller implements PatternFiller {
|
||||
private helper: RenderHelper;
|
||||
@@ -11,12 +11,12 @@ export class DashedFiller implements PatternFiller {
|
||||
}
|
||||
|
||||
fillPolygon(points: Point[], o: ResolvedOptions): OpSet {
|
||||
const lines = hachureLinesForPolygon(points, o);
|
||||
const lines = polygonHachureLines(points, o);
|
||||
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
|
||||
}
|
||||
|
||||
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet {
|
||||
const lines = hachureLinesForEllipse(this.helper, cx, cy, width, height, o);
|
||||
const lines = ellipseHachureLines(this.helper, cx, cy, width, height, o);
|
||||
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point, Line } from '../geometry';
|
||||
import { hachureLinesForPolygon, hachureLinesForEllipse, lineLength } from './filler-utils';
|
||||
import { Point, Line, lineLength } from '../geometry';
|
||||
import { polygonHachureLines, ellipseHachureLines } from './scan-line-hachure';
|
||||
|
||||
export class DotFiller implements PatternFiller {
|
||||
private helper: RenderHelper;
|
||||
@@ -11,14 +11,14 @@ export class DotFiller implements PatternFiller {
|
||||
}
|
||||
|
||||
fillPolygon(points: Point[], o: ResolvedOptions): OpSet {
|
||||
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
|
||||
const lines = hachureLinesForPolygon(points, o);
|
||||
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0, roughness: 1 });
|
||||
const lines = polygonHachureLines(points, o);
|
||||
return this.dotsOnLines(lines, o);
|
||||
}
|
||||
|
||||
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet {
|
||||
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
|
||||
const lines = hachureLinesForEllipse(this.helper, cx, cy, width, height, o);
|
||||
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0, roughness: 1 });
|
||||
const lines = ellipseHachureLines(this.helper, cx, cy, width, height, o);
|
||||
return this.dotsOnLines(lines, o);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Point, Segment, Line } from '../geometry';
|
||||
import { ResolvedOptions } from '../core';
|
||||
import { HachureIterator } from '../utils/hachure';
|
||||
import { RenderHelper } from './filler-interface';
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
function getIntersectingLines(line: number[], points: Point[]): Point[] {
|
||||
const intersections: Point[] = [];
|
||||
const s1 = new Segment([line[0], line[1]], [line[2], line[3]]);
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const s2 = new Segment(points[i], points[(i + 1) % points.length]);
|
||||
if (s1.intersects(s2)) {
|
||||
intersections.push([s1.xi, s1.yi]);
|
||||
}
|
||||
}
|
||||
return intersections;
|
||||
}
|
||||
|
||||
function affine(x: number, y: number, cx: number, cy: number, sinAnglePrime: number, cosAnglePrime: number, R: number): Point {
|
||||
const A = -cx * cosAnglePrime - cy * sinAnglePrime + cx;
|
||||
const B = R * (cx * sinAnglePrime - cy * cosAnglePrime) + cy;
|
||||
const C = cosAnglePrime;
|
||||
const D = sinAnglePrime;
|
||||
const E = -R * sinAnglePrime;
|
||||
const F = R * cosAnglePrime;
|
||||
return [
|
||||
A + C * x + D * y,
|
||||
B + E * x + F * y
|
||||
];
|
||||
}
|
||||
|
||||
export function hachureLinesForPolygon(points: Point[], o: ResolvedOptions): Line[] {
|
||||
const ret: Line[] = [];
|
||||
if (points && points.length) {
|
||||
let left = points[0][0];
|
||||
let right = points[0][0];
|
||||
let top = points[0][1];
|
||||
let bottom = points[0][1];
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
left = Math.min(left, points[i][0]);
|
||||
right = Math.max(right, points[i][0]);
|
||||
top = Math.min(top, points[i][1]);
|
||||
bottom = Math.max(bottom, points[i][1]);
|
||||
}
|
||||
const angle = o.hachureAngle;
|
||||
let gap = o.hachureGap;
|
||||
if (gap < 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
gap = Math.max(gap, 0.1);
|
||||
const radPerDeg = Math.PI / 180;
|
||||
const hachureAngle = (angle % 180) * radPerDeg;
|
||||
const cosAngle = Math.cos(hachureAngle);
|
||||
const sinAngle = Math.sin(hachureAngle);
|
||||
const tanAngle = Math.tan(hachureAngle);
|
||||
const it = new HachureIterator(top - 1, bottom + 1, left - 1, right + 1, gap, sinAngle, cosAngle, tanAngle);
|
||||
let rect: number[] | null;
|
||||
while ((rect = it.nextLine()) != null) {
|
||||
const lines = getIntersectingLines(rect, points);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (i < (lines.length - 1)) {
|
||||
const p1 = lines[i];
|
||||
const p2 = lines[i + 1];
|
||||
ret.push([p1, p2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function hachureLinesForEllipse(helper: RenderHelper, cx: number, cy: number, width: number, height: number, o: ResolvedOptions): Line[] {
|
||||
const ret: Line[] = [];
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
rx += helper.randOffset(rx * 0.05, o);
|
||||
ry += helper.randOffset(ry * 0.05, o);
|
||||
const angle = o.hachureAngle;
|
||||
let gap = o.hachureGap;
|
||||
if (gap <= 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
const radPerDeg = Math.PI / 180;
|
||||
const hachureAngle = (angle % 180) * radPerDeg;
|
||||
const tanAngle = Math.tan(hachureAngle);
|
||||
const aspectRatio = ry / rx;
|
||||
const hyp = Math.sqrt(aspectRatio * tanAngle * aspectRatio * tanAngle + 1);
|
||||
const sinAnglePrime = aspectRatio * tanAngle / hyp;
|
||||
const cosAnglePrime = 1 / hyp;
|
||||
const gapPrime = gap / ((rx * ry / Math.sqrt((ry * cosAnglePrime) * (ry * cosAnglePrime) + (rx * sinAnglePrime) * (rx * sinAnglePrime))) / rx);
|
||||
let halfLen = Math.sqrt((rx * rx) - (cx - rx + gapPrime) * (cx - rx + gapPrime));
|
||||
for (let xPos = cx - rx + gapPrime; xPos < cx + rx; xPos += gapPrime) {
|
||||
halfLen = Math.sqrt((rx * rx) - (cx - xPos) * (cx - xPos));
|
||||
const p1 = affine(xPos, cy - halfLen, cx, cy, sinAnglePrime, cosAnglePrime, aspectRatio);
|
||||
const p2 = affine(xPos, cy + halfLen, cx, cy, sinAnglePrime, cosAnglePrime, aspectRatio);
|
||||
ret.push([p1, p2]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point, Line } from '../geometry';
|
||||
import { hachureLinesForPolygon, hachureLinesForEllipse, lineLength } from './filler-utils';
|
||||
import { Point, Line, lineLength } from '../geometry';
|
||||
import { polygonHachureLines, ellipseHachureLines } from './scan-line-hachure';
|
||||
|
||||
export class ZigZagLineFiller implements PatternFiller {
|
||||
private helper: RenderHelper;
|
||||
@@ -14,7 +14,7 @@ export class ZigZagLineFiller implements PatternFiller {
|
||||
const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
|
||||
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
|
||||
o = Object.assign({}, o, { hachureGap: gap + zo });
|
||||
const lines = hachureLinesForPolygon(points, o);
|
||||
const lines = polygonHachureLines(points, o);
|
||||
return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export class ZigZagLineFiller implements PatternFiller {
|
||||
const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
|
||||
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
|
||||
o = Object.assign({}, o, { hachureGap: gap + zo });
|
||||
const lines = hachureLinesForEllipse(this.helper, cx, cy, width, height, o);
|
||||
const lines = ellipseHachureLines(this.helper, cx, cy, width, height, o);
|
||||
return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { Segment } from '../geometry';
|
||||
|
||||
export class HachureIterator {
|
||||
top: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
gap: number;
|
||||
sinAngle: number;
|
||||
tanAngle: number;
|
||||
pos: number;
|
||||
deltaX: number = 0;
|
||||
hGap: number = 0;
|
||||
sLeft?: Segment;
|
||||
sRight?: Segment;
|
||||
|
||||
constructor(top: number, bottom: number, left: number, right: number, gap: number, sinAngle: number, cosAngle: number, tanAngle: number) {
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.gap = gap;
|
||||
this.sinAngle = sinAngle;
|
||||
this.tanAngle = tanAngle;
|
||||
|
||||
if (Math.abs(sinAngle) < 0.0001) {
|
||||
this.pos = left + gap;
|
||||
} else if (Math.abs(sinAngle) > 0.9999) {
|
||||
this.pos = top + gap;
|
||||
} else {
|
||||
this.deltaX = (bottom - top) * Math.abs(tanAngle);
|
||||
this.pos = left - Math.abs(this.deltaX);
|
||||
this.hGap = Math.abs(gap / cosAngle);
|
||||
this.sLeft = new Segment([left, bottom], [left, top]);
|
||||
this.sRight = new Segment([right, bottom], [right, top]);
|
||||
}
|
||||
}
|
||||
|
||||
nextLine(): number[] | null {
|
||||
if (Math.abs(this.sinAngle) < 0.0001) {
|
||||
if (this.pos < this.right) {
|
||||
const line = [this.pos, this.top, this.pos, this.bottom];
|
||||
this.pos += this.gap;
|
||||
return line;
|
||||
}
|
||||
} else if (Math.abs(this.sinAngle) > 0.9999) {
|
||||
if (this.pos < this.bottom) {
|
||||
const line = [this.left, this.pos, this.right, this.pos];
|
||||
this.pos += this.gap;
|
||||
return line;
|
||||
}
|
||||
} else {
|
||||
let xLower = this.pos - this.deltaX / 2;
|
||||
let xUpper = this.pos + this.deltaX / 2;
|
||||
let yLower = this.bottom;
|
||||
let yUpper = this.top;
|
||||
if (this.pos < (this.right + this.deltaX)) {
|
||||
while (((xLower < this.left) && (xUpper < this.left)) || ((xLower > this.right) && (xUpper > this.right))) {
|
||||
this.pos += this.hGap;
|
||||
xLower = this.pos - this.deltaX / 2;
|
||||
xUpper = this.pos + this.deltaX / 2;
|
||||
if (this.pos > (this.right + this.deltaX)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const s = new Segment([xLower, yLower], [xUpper, yUpper]);
|
||||
if (this.sLeft && s.intersects(this.sLeft)) {
|
||||
xLower = s.xi;
|
||||
yLower = s.yi;
|
||||
}
|
||||
if (this.sRight && s.intersects(this.sRight)) {
|
||||
xUpper = s.xi;
|
||||
yUpper = s.yi;
|
||||
}
|
||||
if (this.tanAngle > 0) {
|
||||
xLower = this.right - (xLower - this.left);
|
||||
xUpper = this.right - (xUpper - this.left);
|
||||
}
|
||||
const line = [xLower, yLower, xUpper, yUpper];
|
||||
this.pos += this.hGap;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Point, Rectangle } from '../geometry';
|
||||
|
||||
export function clip(p1: Point, p2: Point, box: Rectangle): [Point, Point] | null {
|
||||
const xmin = box.x;
|
||||
const xmax = box.x + box.width;
|
||||
const ymin = box.y;
|
||||
const ymax = box.y + box.height;
|
||||
|
||||
let t0 = 0;
|
||||
let t1 = 1;
|
||||
const dx = p2[0] - p1[0];
|
||||
const dy = p2[1] - p1[0];
|
||||
let p = 0;
|
||||
let q = 0;
|
||||
let r = 0;
|
||||
|
||||
for (let edge = 0; edge < 4; edge++) {
|
||||
if (edge === 0) { p = -dx; q = -(xmin - p1[0]); }
|
||||
if (edge === 1) { p = dx; q = (xmax - p1[0]); }
|
||||
if (edge === 2) { p = -dy; q = -(ymin - p1[1]); }
|
||||
if (edge === 3) { p = dy; q = (ymax - p1[1]); }
|
||||
r = q / p;
|
||||
if (p === 0 && q < 0) {
|
||||
return null;
|
||||
}
|
||||
if (p < 0) {
|
||||
if (r > t1) return null;
|
||||
else if (r > t0) t0 = r;
|
||||
} else if (p > 0) {
|
||||
if (r < t0) return null;
|
||||
else if (r < t1) t1 = r;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
[p1[0] + t0 * dx, p1[1] + t0 * dy],
|
||||
[p1[0] + t1 * dx, p1[1] + t1 * dy]
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user