new hachure only

This commit is contained in:
Preet Shihn
2020-01-10 14:22:13 -08:00
parent bd0bd44ab1
commit 71e218e675
6 changed files with 14 additions and 247 deletions

View File

@@ -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) };
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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) };
}

View File

@@ -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;
}
}

View File

@@ -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]
];
}