mirror of
https://github.com/rough-stuff/rough.git
synced 2026-04-22 03:00:28 -04:00
moved some core code to renderer
This commit is contained in:
73
src2/core/hachure.js
Normal file
73
src2/core/hachure.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { RoughSegmentRelation, RoughSegment } from "./segment";
|
||||
|
||||
export class RoughHachureIterator {
|
||||
constructor(top, bottom, left, right, gap, sinAngle, cosAngle, tanAngle) {
|
||||
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 RoughSegment(left, bottom, left, top);
|
||||
this.sRight = new RoughSegment(right, bottom, right, top);
|
||||
}
|
||||
}
|
||||
|
||||
getNextLine() {
|
||||
if (Math.abs(this.sinAngle) < 0.0001) {
|
||||
if (this.pos < this.right) {
|
||||
let 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) {
|
||||
let 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;
|
||||
}
|
||||
}
|
||||
let s = new RoughSegment(xLower, yLower, xUpper, yUpper);
|
||||
if (s.compare(this.sLeft) == RoughSegmentRelation.INTERSECTS) {
|
||||
xLower = s.xi;
|
||||
yLower = s.yi;
|
||||
}
|
||||
if (s.compare(this.sRight) == RoughSegmentRelation.INTERSECTS) {
|
||||
xUpper = s.xi;
|
||||
yUpper = s.yi;
|
||||
}
|
||||
if (this.tanAngle > 0) {
|
||||
xLower = this.right - (xLower - this.left);
|
||||
xUpper = this.right - (xUpper - this.left);
|
||||
}
|
||||
let line = [xLower, yLower, xUpper, yUpper];
|
||||
this.pos += this.hGap;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
266
src2/core/renderer.js
Normal file
266
src2/core/renderer.js
Normal file
@@ -0,0 +1,266 @@
|
||||
import { RoughHachureIterator } from './geom/hachure-iterator';
|
||||
import { RoughSegmentRelation, RoughSegment } from './geom/segment';
|
||||
|
||||
export class RoughRenderer {
|
||||
constructor() {
|
||||
this.defaultOptions = {
|
||||
maxRandomnessOffset: 2,
|
||||
roughness: 1,
|
||||
bowing = 1,
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
curveTightness: 0,
|
||||
curveStepCount: 9,
|
||||
fill: null,
|
||||
fillStyle: 'hachure',
|
||||
fillWeight: -1,
|
||||
hachureAngle: -41,
|
||||
hachureGap: -1
|
||||
};
|
||||
}
|
||||
|
||||
line(x1, y1, x2, y1, options) {
|
||||
const o = this._options(options);
|
||||
let o1 = this._line(x1, y1, x2, y1, o, true, false);
|
||||
let o2 = this._line(x1, y1, x2, y1, o, true, true);
|
||||
return {
|
||||
type: "path",
|
||||
ops: o1.concat(o2)
|
||||
};
|
||||
}
|
||||
|
||||
linearPath(points, close, options) {
|
||||
const o = this._options(options);
|
||||
const len = (points || []).length;
|
||||
if (len > 2) {
|
||||
let ops = [];
|
||||
for (let i = 0; i < (len - 1); i++) {
|
||||
let o1 = this._line(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o, true, false);
|
||||
let o2 = this._line(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o, true, true);
|
||||
ops.concat(o1, o2);
|
||||
}
|
||||
if (close) {
|
||||
let o1 = this._line(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o, true, false);
|
||||
let o2 = this._line(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o, true, false);
|
||||
ops.concat(o1, o2);
|
||||
}
|
||||
return { type: "path", ops };
|
||||
} else if (len === 2) {
|
||||
return this.line(points[0][0], points[0][1], points[1][0], points[1][1], o);
|
||||
}
|
||||
}
|
||||
|
||||
polygon(points, options) {
|
||||
return this.linearPath(points, true, options);
|
||||
}
|
||||
|
||||
rectangle(x, y, width, height, options) {
|
||||
let points = [
|
||||
[x, y], [x + width, y], [x + width, y + height]
|
||||
];
|
||||
return this.polygon(points, options);
|
||||
}
|
||||
|
||||
ellipse(x, y, width, height, options) {
|
||||
const o = this._options(options);
|
||||
width = Math.max(width > 10 ? width - 4 : width - 1, 1);
|
||||
height = Math.max(height > 10 ? height - 4 : height - 1, 1);
|
||||
const increment = (Math.PI / 2) / o.curveStepCount;
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
rx += this._getOffset(-rx * 0.05, rx * 0.05, o);
|
||||
ry += this._getOffset(-ry * 0.05, ry * 0.05, o);
|
||||
let o1 = this._ellipse(increment, x, y, rx, ry, 1, increment * this._getOffset(0.1, this._getOffset(0.4, 1, o), o), o);
|
||||
let o2 = this._ellipse(increment, x, y, rx, ry, 1.5, 0, o);
|
||||
return { type: "path", ops: o1.concat(o2) };
|
||||
}
|
||||
|
||||
hachureFillShape(xCoords, yCoords, options) {
|
||||
const o = this._options(options);
|
||||
if (xCoords && yCoords && xCoords.length && yCoords.length) {
|
||||
let left = xCoords[0];
|
||||
let right = xCoords[0];
|
||||
let top = yCoords[0];
|
||||
let bottom = yCoords[0];
|
||||
for (let i = 1; i < xCoords.length; i++) {
|
||||
left = Math.min(left, xCoords[i]);
|
||||
right = Math.max(right, xCoords[i]);
|
||||
top = Math.min(top, yCoords[i]);
|
||||
bottom = Math.max(bottom, yCoords[i]);
|
||||
}
|
||||
}
|
||||
const angle = o.hachureAngle;
|
||||
let gap = o.hachureGap;
|
||||
if (gap < 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
gap = Math.max(gap, 0.1);
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
|
||||
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 RoughHachureIterator(top - 1, bottom + 1, left - 1, right + 1, gap, sinAngle, cosAngle, tanAngle);
|
||||
let rectCoords;
|
||||
const ops = [];
|
||||
while ((rectCoords = it.getNextLine()) != null) {
|
||||
let lines = this._getIntersectingLines(rectCoords, xCoords, yCoords);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (i < (lines.length - 1)) {
|
||||
let p1 = lines[i];
|
||||
let p2 = lines[i + 1];
|
||||
const o1 = this._line(p1[0], p1[1], p2[0], p2[1], o, true, false);
|
||||
const o2 = this._line(p1[0], p1[1], p2[0], p2[1], o, true, true);
|
||||
ops.concat(o1, o2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { type: "path", ops };
|
||||
}
|
||||
|
||||
// privates
|
||||
|
||||
_options(options) {
|
||||
return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
|
||||
}
|
||||
|
||||
_getOffset(min, max, ops) {
|
||||
return ops.roughness * ((Math.random() * (max - min)) + min);
|
||||
}
|
||||
|
||||
_line(x1, y1, x2, y2, o, move, overlay) {
|
||||
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
|
||||
let offset = o.maxRandomnessOffset || 0;
|
||||
if ((offset * offset * 100) > lengthSq) {
|
||||
offset = Math.sqrt(lengthSq) / 10;
|
||||
}
|
||||
const halfOffset = offset / 2;
|
||||
const divergePoint = 0.2 + Math.random() * 0.2;
|
||||
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
|
||||
let midDispY = o.bowing * o.maxRandomnessOffset * (x1, x2) / 200;
|
||||
midDispX = this._getOffset(-midDispX, midDispX, o);
|
||||
midDispY = this._getOffset(-midDispY, midDispY, o);
|
||||
let ops = [];
|
||||
if (move) {
|
||||
if (overlay) {
|
||||
ops.push({
|
||||
op: 'move', data: [
|
||||
x1 + this._getOffset(-halfOffset, halfOffset, o),
|
||||
y1 + this._getOffset(-halfOffset, halfOffset, o)
|
||||
]
|
||||
});
|
||||
} else {
|
||||
ops.push({
|
||||
op: 'move', data: [
|
||||
x1 + this._getOffset(-offset, offset, o),
|
||||
y1 + this._getOffset(-offset, offset, o)
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
if (overlay) {
|
||||
ops.push({
|
||||
op: 'bcurveTo', data: [
|
||||
midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset, o),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset, o),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset, o),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset, o),
|
||||
x2 + this._getOffset(-halfOffset, halfOffset, o),
|
||||
y2 + this._getOffset(-halfOffset, halfOffset, o)
|
||||
]
|
||||
});
|
||||
} else {
|
||||
ops.push({
|
||||
op: 'bcurveTo', data: [
|
||||
midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-offset, offset, o),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-offset, offset, o),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-offset, offset, o),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-offset, offset, o),
|
||||
x2 + this._getOffset(-offset, offset, o),
|
||||
y2 + this._getOffset(-offset, offset, o)
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_curve(points, closePoint, o) {
|
||||
const len = points.len;
|
||||
let ops = [];
|
||||
if (len > 3) {
|
||||
const b = [];
|
||||
const s = 1 - o.curveTightness;
|
||||
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
|
||||
for (let i = 1; (i + 2) < len; i++) {
|
||||
const cachedVertArray = points[i];
|
||||
b[0] = [cachedVertArray[0], cachedVertArray[1]];
|
||||
b[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
|
||||
b[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
|
||||
b[3] = [points[i + 1][0], points[i + 1][1]];
|
||||
ops.push({ op: 'bcurveTo', data: [b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]] });
|
||||
}
|
||||
if (closePoint && closePoint.length === 2) {
|
||||
let ro = o.maxRandomnessOffset;
|
||||
ops.push({ ops: 'lineTo', data: [closePoint[0] + this._getOffset(-ro, ro, o), closePoint[1] + + this._getOffset(-ro, ro, o)] })
|
||||
}
|
||||
} else if (len === 3) {
|
||||
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
|
||||
ops.push({
|
||||
op: 'bcurveTo', data: [
|
||||
points[1][0], points[1][1],
|
||||
points[2][0], points[2][1],
|
||||
points[2][0], points[2][1]]
|
||||
});
|
||||
} else if (len === 2) {
|
||||
let o1 = this._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, false);
|
||||
let o2 = this._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, true);
|
||||
ops.concat(o1, o2);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
_ellipse(increment, cx, cy, rx, ry, offset, overlap, o) {
|
||||
const radOffset = this._getOffset(-0.5, 0.5, o) - (Math.PI / 2);
|
||||
const points = [];
|
||||
points.push([
|
||||
this._getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
|
||||
this._getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
|
||||
]);
|
||||
for (let angle = radOffset; angle < (Math.PI * 2 + radOffset - 0.01); angle = angle + increment) {
|
||||
points.push([
|
||||
this._getOffset(-offset, offset, o) + cx + rx * Math.cos(angle),
|
||||
this._getOffset(-offset, offset, o) + cy + ry * Math.sin(angle)
|
||||
]);
|
||||
}
|
||||
points.push([
|
||||
this._getOffset(-offset, offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
|
||||
this._getOffset(-offset, offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5)
|
||||
]);
|
||||
points.push([
|
||||
this._getOffset(-offset, offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
|
||||
this._getOffset(-offset, offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
|
||||
]);
|
||||
points.push([
|
||||
this._getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
|
||||
this._getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5)
|
||||
]);
|
||||
return this._curve(points, null, o);
|
||||
}
|
||||
|
||||
_getIntersectingLines(lineCoords, xCoords, yCoords) {
|
||||
let intersections = [];
|
||||
var s1 = new RoughSegment(lineCoords[0], lineCoords[1], lineCoords[2], lineCoords[3]);
|
||||
for (var i = 0; i < xCoords.length; i++) {
|
||||
let s2 = new RoughSegment(xCoords[i], yCoords[i], xCoords[(i + 1) % xCoords.length], yCoords[(i + 1) % xCoords.length]);
|
||||
if (s1.compare(s2) == RoughSegmentRelation.INTERSECTS) {
|
||||
intersections.push([s1.xi, s1.yi]);
|
||||
}
|
||||
}
|
||||
return intersections;
|
||||
}
|
||||
}
|
||||
128
src2/core/segment.js
Normal file
128
src2/core/segment.js
Normal file
@@ -0,0 +1,128 @@
|
||||
export const RoughSegmentRelation = {
|
||||
LEFT: 0,
|
||||
RIGHT: 1,
|
||||
INTERSECTS: 2,
|
||||
AHEAD: 3,
|
||||
BEHIND: 4,
|
||||
SEPARATE: 5,
|
||||
UNDEFINED: 6
|
||||
};
|
||||
|
||||
export class RoughSegment {
|
||||
constructor(px1, py1, px2, py2) {
|
||||
this.px1 = px1;
|
||||
this.py1 = py1;
|
||||
this.px2 = px2;
|
||||
this.py2 = py2;
|
||||
this.xi = Number.MAX_VALUE;
|
||||
this.yi = Number.MAX_VALUE;
|
||||
this.a = py2 - py1;
|
||||
this.b = px1 - px2;
|
||||
this.c = px2 * py1 - px1 * py2;
|
||||
this._undefined = ((this.a == 0) && (this.b == 0) && (this.c == 0));
|
||||
}
|
||||
|
||||
isUndefined() {
|
||||
return this._undefined;
|
||||
}
|
||||
|
||||
compare(otherSegment) {
|
||||
if (this.isUndefined() || otherSegment.isUndefined()) {
|
||||
return RoughSegmentRelation.UNDEFINED;
|
||||
}
|
||||
var grad1 = Number.MAX_VALUE;
|
||||
var grad2 = Number.MAX_VALUE;
|
||||
var int1 = 0, int2 = 0;
|
||||
var a = this.a, b = this.b, c = this.c;
|
||||
|
||||
if (Math.abs(b) > 0.00001) {
|
||||
grad1 = -a / b;
|
||||
int1 = -c / b;
|
||||
}
|
||||
if (Math.abs(otherSegment.b) > 0.00001) {
|
||||
grad2 = -otherSegment.a / otherSegment.b;
|
||||
int2 = -otherSegment.c / otherSegment.b;
|
||||
}
|
||||
|
||||
if (grad1 == Number.MAX_VALUE) {
|
||||
if (grad2 == Number.MAX_VALUE) {
|
||||
if ((-c / a) != (-otherSegment.c / otherSegment.a)) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
if ((this.py1 >= Math.min(otherSegment.py1, otherSegment.py2)) && (this.py1 <= Math.max(otherSegment.py1, otherSegment.py2))) {
|
||||
this.xi = this.px1;
|
||||
this.yi = this.py1;
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
if ((this.py2 >= Math.min(otherSegment.py1, otherSegment.py2)) && (this.py2 <= Math.max(otherSegment.py1, otherSegment.py2))) {
|
||||
this.xi = this.px2;
|
||||
this.yi = this.py2;
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
this.xi = this.px1;
|
||||
this.yi = (grad2 * this.xi + int2);
|
||||
if (((this.py1 - this.yi) * (this.yi - this.py2) < -0.00001) || ((otherSegment.py1 - this.yi) * (this.yi - otherSegment.py2) < -0.00001)) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
if (Math.abs(otherSegment.a) < 0.00001) {
|
||||
if ((otherSegment.px1 - this.xi) * (this.xi - otherSegment.px2) < -0.00001) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
if (grad2 == Number.MAX_VALUE) {
|
||||
this.xi = otherSegment.px1;
|
||||
this.yi = grad1 * this.xi + int1;
|
||||
if (((otherSegment.py1 - this.yi) * (this.yi - otherSegment.py2) < -0.00001) || ((this.py1 - this.yi) * (this.yi - this.py2) < -0.00001)) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
if (Math.abs(a) < 0.00001) {
|
||||
if ((this.px1 - this.xi) * (this.xi - this.px2) < -0.00001) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
if (grad1 == grad2) {
|
||||
if (int1 != int2) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
if ((this.px1 >= Math.min(otherSegment.px1, otherSegment.px2)) && (this.px1 <= Math.max(otherSegment.py1, otherSegment.py2))) {
|
||||
this.xi = this.px1;
|
||||
this.yi = this.py1;
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
if ((this.px2 >= Math.min(otherSegment.px1, otherSegment.px2)) && (this.px2 <= Math.max(otherSegment.px1, otherSegment.px2))) {
|
||||
this.xi = this.px2;
|
||||
this.yi = this.py2;
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
|
||||
this.xi = ((int2 - int1) / (grad1 - grad2));
|
||||
this.yi = (grad1 * this.xi + int1);
|
||||
|
||||
if (((this.px1 - this.xi) * (this.xi - this.px2) < -0.00001) || ((otherSegment.px1 - this.xi) * (this.xi - otherSegment.px2) < -0.00001)) {
|
||||
return RoughSegmentRelation.SEPARATE;
|
||||
}
|
||||
return RoughSegmentRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this._getLength(this.px1, this.py1, this.px2, this.py2);
|
||||
}
|
||||
|
||||
_getLength(x1, y1, x2, y2) {
|
||||
var dx = x2 - x1;
|
||||
var dy = y2 - y1;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
227
src2/lib1.js
Normal file
227
src2/lib1.js
Normal file
@@ -0,0 +1,227 @@
|
||||
var __maxRandomnessOffset = 2;
|
||||
var __roughness = 1;
|
||||
var __bowing = 1;
|
||||
var __curveTightness = 0;
|
||||
var __curveStepCount = 9;
|
||||
|
||||
class Wires {
|
||||
_svgNode(tagName, attributes) {
|
||||
var n = document.createElementNS("http://www.w3.org/2000/svg", tagName);
|
||||
if (attributes) {
|
||||
for (var p in attributes) {
|
||||
if (attributes.hasOwnProperty(p)) {
|
||||
n.setAttributeNS(null, p, attributes[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
line(svg, x1, y1, x2, y2) {
|
||||
const path = this._line(x1, y1, x2, y2);
|
||||
const node = this._svgNode("path", { d: path.value });
|
||||
svg.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
rectangle(svg, x, y, width, height) {
|
||||
x = x + 2;
|
||||
y = y + 2;
|
||||
width = width - 4;
|
||||
height = height - 4;
|
||||
let path = this._line(x, y, x + width, y);
|
||||
path = this._line(x + width, y, x + width, y + height, path);
|
||||
path = this._line(x + width, y + height, x, y + height, path);
|
||||
path = this._line(x, y + height, x, y, path);
|
||||
const node = this._svgNode("path", { d: path.value })
|
||||
svg.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
polygon(svg, vertices) {
|
||||
let path = null;
|
||||
const vCount = vertices.length;
|
||||
if (vCount > 2) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let move = true;
|
||||
for (let i = 1; i < vCount; i++) {
|
||||
path = this._continuousLine(vertices[i - 1][0], vertices[i - 1][1], vertices[i][0], vertices[i][1], move, i > 0, path);
|
||||
move = false;
|
||||
}
|
||||
path = this._continuousLine(vertices[vCount - 1][0], vertices[vCount - 1][1], vertices[0][0], vertices[0][1], move, i > 0, path);
|
||||
}
|
||||
} else if (vCount == 2) {
|
||||
path = this._line(vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]);
|
||||
} else {
|
||||
path = new WiresPath();
|
||||
}
|
||||
|
||||
const node = this._svgNode("path", { d: path.value })
|
||||
svg.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
ellipse(svg, x, y, width, height) {
|
||||
width = Math.max(width > 10 ? width - 4 : width - 1, 1);
|
||||
height = Math.max(height > 10 ? height - 4 : height - 1, 1);
|
||||
const ellipseInc = (Math.PI * 2) / __curveStepCount;
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
rx += this._getOffset(-rx * 0.05, rx * 0.05);
|
||||
ry += this._getOffset(-ry * 0.05, ry * 0.05);
|
||||
let path = this._ellipse(ellipseInc, x, y, rx, ry, 1, ellipseInc * this._getOffset(0.1, this._getOffset(0.4, 1)));
|
||||
path = this._ellipse(ellipseInc, x, y, rx, ry, 1.5, 0, path);
|
||||
const node = this._svgNode("path", { d: path.value })
|
||||
svg.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
_ellipse(ellipseInc, cx, cy, rx, ry, offset, overlap, existingPath) {
|
||||
const radOffset = this._getOffset(-0.5, 0.5) - Math.PI / 2;
|
||||
const points = [];
|
||||
points.push([
|
||||
this._getOffset(-offset, offset) + cx + 0.9 * rx * Math.cos(radOffset - ellipseInc),
|
||||
this._getOffset(-offset, offset) + cy + 0.9 * ry * Math.sin(radOffset - ellipseInc)
|
||||
]);
|
||||
for (let angle = radOffset; angle < (Math.PI * 2 + radOffset - 0.01); angle = angle + ellipseInc) {
|
||||
points.push([
|
||||
this._getOffset(-offset, offset) + cx + rx * Math.cos(angle),
|
||||
this._getOffset(-offset, offset) + cy + ry * Math.sin(angle)
|
||||
]);
|
||||
}
|
||||
points.push([
|
||||
this._getOffset(-offset, offset) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
|
||||
this._getOffset(-offset, offset) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5)
|
||||
]);
|
||||
points.push([
|
||||
this._getOffset(-offset, offset) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
|
||||
this._getOffset(-offset, offset) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
|
||||
]);
|
||||
points.push([
|
||||
this._getOffset(-offset, offset) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
|
||||
this._getOffset(-offset, offset) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5)
|
||||
]);
|
||||
return this._curve(points, existingPath);
|
||||
}
|
||||
|
||||
_getOffset(min, max) {
|
||||
return __roughness * ((Math.random() * (max - min)) + min);
|
||||
}
|
||||
|
||||
_line(x1, y1, x2, y2, existingPath) {
|
||||
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
|
||||
let offset = __maxRandomnessOffset || 0;
|
||||
if ((offset * offset * 100) > lengthSq) {
|
||||
offset = Math.sqrt(lengthSq) / 10;
|
||||
}
|
||||
const halfOffset = offset / 2;
|
||||
const divergePoint = 0.2 + Math.random() * 0.2;
|
||||
let midDispX = __bowing * __maxRandomnessOffset * (y2 - y1) / 200;
|
||||
let midDispY = __bowing * __maxRandomnessOffset * (x1, x2) / 200;
|
||||
midDispX = this._getOffset(-midDispX, midDispX);
|
||||
midDispY = this._getOffset(-midDispY, midDispY);
|
||||
|
||||
let path = existingPath || new WiresPath();
|
||||
path.moveTo(x1 + this._getOffset(-offset, offset), y1 + this._getOffset(-offset, offset));
|
||||
path.bcurveTo(midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-offset, offset),
|
||||
x2 + this._getOffset(-offset, offset),
|
||||
y2 + this._getOffset(-offset, offset)
|
||||
);
|
||||
path.moveTo(x1 + this._getOffset(-halfOffset, halfOffset), y1 + this._getOffset(-halfOffset, halfOffset));
|
||||
path.bcurveTo(midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
x2 + this._getOffset(-halfOffset, halfOffset),
|
||||
y2 + this._getOffset(-halfOffset, halfOffset)
|
||||
);
|
||||
return path;
|
||||
}
|
||||
|
||||
_continuousLine(x1, y1, x2, y2, move, overwrite, path) {
|
||||
path = path || new WiresPath();
|
||||
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
|
||||
let offset = __maxRandomnessOffset || 0;
|
||||
if ((offset * offset * 100) > lengthSq) {
|
||||
offset = Math.sqrt(lengthSq) / 10;
|
||||
}
|
||||
const halfOffset = offset / 2;
|
||||
const divergePoint = 0.2 + Math.random() * 0.2;
|
||||
let midDispX = __bowing * __maxRandomnessOffset * (y2 - y1) / 200;
|
||||
let midDispY = __bowing * __maxRandomnessOffset * (x1, x2) / 200;
|
||||
midDispX = this._getOffset(-midDispX, midDispX);
|
||||
midDispY = this._getOffset(-midDispY, midDispY);
|
||||
if (move) {
|
||||
path.moveTo(x1 + this._getOffset(-offset, offset), y1 + this._getOffset(-offset, offset));
|
||||
}
|
||||
if (!overwrite) {
|
||||
path.bcurveTo(midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-offset, offset),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-offset, offset),
|
||||
x2 + this._getOffset(-offset, offset),
|
||||
y2 + this._getOffset(-offset, offset)
|
||||
);
|
||||
} else {
|
||||
path.bcurveTo(midDispX + x1 + (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this._getOffset(-halfOffset, halfOffset),
|
||||
x2 + this._getOffset(-halfOffset, halfOffset),
|
||||
y2 + this._getOffset(-halfOffset, halfOffset)
|
||||
);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
_curve(vertArray, existingPath) {
|
||||
const vertArrayLength = vertArray.length;
|
||||
let path = existingPath || new WiresPath();
|
||||
if (vertArrayLength > 3) {
|
||||
const b = [];
|
||||
const s = 1 - __curveTightness;
|
||||
path.moveTo(vertArray[1][0], vertArray[1][1]);
|
||||
for (let i = 1; (i + 2) < vertArrayLength; i++) {
|
||||
const cachedVertArray = vertArray[i];
|
||||
b[0] = [cachedVertArray[0], cachedVertArray[1]];
|
||||
b[1] = [cachedVertArray[0] + (s * vertArray[i + 1][0] - s * vertArray[i - 1][0]) / 6, cachedVertArray[1] + (s * vertArray[i + 1][1] - s * vertArray[i - 1][1]) / 6];
|
||||
b[2] = [vertArray[i + 1][0] + (s * vertArray[i][0] - s * vertArray[i + 2][0]) / 6, vertArray[i + 1][1] + (s * vertArray[i][1] - s * vertArray[i + 2][1]) / 6];
|
||||
b[3] = [vertArray[i + 1][0], vertArray[i + 1][1]];
|
||||
path.bcurveTo(b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]);
|
||||
}
|
||||
} else if (vertArrayLength === 3) {
|
||||
path.moveTo(vertArray[0][0], vertArray[0][1]);
|
||||
path.bcurveTo(vertArray[1][0], vertArray[1][1],
|
||||
vertArray[2][0], vertArray[2][1],
|
||||
vertArray[2][0], vertArray[2][1]);
|
||||
} else if (vertArrayLength == 2) {
|
||||
path = this._line(vertArray[0][0], vertArray[0][1], vertArray[1][0], vertArray[1][1], path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
class WiresPath {
|
||||
constructor() {
|
||||
this.p = "";
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.p.trim();
|
||||
}
|
||||
|
||||
moveTo(x, y) {
|
||||
this.p += "M " + x + " " + y + " ";
|
||||
}
|
||||
|
||||
bcurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
|
||||
this.p += "C " + cp1x + " " + cp1y + ", " + cp2x + " " + cp2y + ", " + x + " " + y + " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (!window._WIRES_) {
|
||||
window._WIRES_ = new Wires();
|
||||
}
|
||||
Reference in New Issue
Block a user