mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
628 lines
No EOL
18 KiB
HTML
628 lines
No EOL
18 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
<title>Example</title>
|
|
<link rel="stylesheet" href="../css/style.css">
|
|
<script type="text/javascript">var root = '../../'</script>
|
|
<script type="text/javascript" src="../../src/load.js"></script>
|
|
<script type="text/javascript" src="http://www.raymondhill.net/voronoi/rhill-voronoi-core-min.js"></script>
|
|
|
|
<script type="text/paperscript" canvas="canvas">
|
|
document.currentStyle = {
|
|
strokeWidth: 1,
|
|
}
|
|
var shiftDown, altDown;
|
|
function onMouseDown(event) {
|
|
// shiftDown = Key.isDown('shift');
|
|
// altDown = Key.isDown('alt');
|
|
|
|
voronoiDrawer.initialize();
|
|
if (altDown || shiftDown)
|
|
var removed = voronoiDrawer.removePoint(event.point);
|
|
if (altDown) {
|
|
if (removed) {
|
|
voronoiDrawer.initPoints();
|
|
voronoiDrawer.render();
|
|
}
|
|
} else {
|
|
voronoiDrawer.addAndRender(event.point, true, false)
|
|
}
|
|
}
|
|
|
|
function onMouseDrag(event) {
|
|
if (!altDown) {
|
|
if (event.count == 1)
|
|
voronoiDrawer.showPointsDown();
|
|
voronoiDrawer.addAndRender(event.point, true, true)
|
|
voronoiDrawer.showPointsDrag();
|
|
}
|
|
}
|
|
|
|
function onMouseUp(event) {
|
|
if (!altDown && !shiftDown) {
|
|
voronoiDrawer.addAndRender(event.point, false, false)
|
|
}
|
|
if (shiftDown) {
|
|
voronoiDrawer.initPoints();
|
|
voronoiDrawer.render();
|
|
}
|
|
}
|
|
|
|
var voronoiDrawer = {
|
|
voronoi: new Voronoi(),
|
|
points: [],
|
|
|
|
initialize: function() {
|
|
this.getGroup();
|
|
this.setBbox();
|
|
},
|
|
|
|
getGroup: function() {
|
|
// this.group = null;
|
|
// this.vGroup = null;
|
|
// var groups = document.getItems({
|
|
// type: Group
|
|
// });
|
|
// for (var i = 0, l = groups.length; i < l; i++) {
|
|
// var group = groups[i];
|
|
// if (group.name == 'Voronoi') {
|
|
// this.group = group;
|
|
// this.vGroup = group.children['vGroup'];
|
|
// i = l;
|
|
// }
|
|
// }
|
|
if (!this.group) {
|
|
this.group = new Group();
|
|
this.group.name = 'Voronoi';
|
|
this.group.data = {};
|
|
var path = new Path();
|
|
this.group.data.path = path;
|
|
path.removeFromParent();
|
|
}
|
|
return this.group;
|
|
},
|
|
|
|
initPoints: function() {
|
|
this.points = [];
|
|
var segments = this.group.data.path.segments;
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
this.points.push(segments[i].point);
|
|
}
|
|
},
|
|
|
|
setBbox: function() {
|
|
this.bbox = {
|
|
xl: 0,
|
|
xr: document.bounds.width,
|
|
yt: 0,
|
|
yb: document.bounds.height
|
|
}
|
|
},
|
|
|
|
addPoint: function(point) {
|
|
this.group.data.path.add(point.round());
|
|
},
|
|
|
|
setPoints: function(points) {
|
|
for (var i = 0, l = points.length; i < l; i++) {
|
|
points[i] = points[i].round();
|
|
}
|
|
this.points = points;
|
|
this.group.data.path = new Path(points);
|
|
},
|
|
|
|
addRandomPoint: function() {
|
|
var point = Point.random() * document.bounds.size;
|
|
this.addPoint(point);
|
|
},
|
|
|
|
pop: function() {
|
|
this.group.data.path.segments.pop();
|
|
},
|
|
|
|
showPointsDown: function() {
|
|
var size = 2 / document.activeView.zoom;
|
|
for (var i = 0, l = this.points.length - 1; i < l; i++) {
|
|
new Path.Circle(this.points[i], size).removeOn({
|
|
up: true
|
|
});
|
|
}
|
|
},
|
|
|
|
showPointsDrag: function() {
|
|
var size = 2 / document.activeView.zoom;
|
|
new Path.Circle(this.points[this.points.length - 1], size).removeOn({
|
|
up: true,
|
|
drag: true
|
|
});
|
|
},
|
|
|
|
getOffsettedPath: function(path) {
|
|
var cloned = path.clone();
|
|
var offset = Math.min(5, Math.max(Math.abs(path.area) / 4000, 1));
|
|
path.fillColor = null;
|
|
path.strokeColor='black';
|
|
path.strokeWidth = offset * 2;
|
|
var offsetted = path.expand();
|
|
if (offsetted && offsetted.children.length) {
|
|
var compoundPath = offsetted.children[0];
|
|
if (compoundPath.children.length) {
|
|
var inner = offsetted.children[0].children[0];
|
|
inner.moveAbove(offsetted);
|
|
inner.fillColor = 'black';
|
|
}
|
|
offsetted.remove();
|
|
}
|
|
if (inner)
|
|
inner.data.path = cloned;
|
|
return inner;
|
|
},
|
|
|
|
removeSmallBits: function(path) {
|
|
var averageLength = path.length / path.segments.length;
|
|
for (var i = 0, l = path.segments.length; i < l; i++) {
|
|
var segment = path.segments[i];
|
|
var nextSegment = path.segments[i + 1 >= l ? (i + 1) % l : i + 1];
|
|
var length = (nextSegment.point - segment.point).length;
|
|
if (length / averageLength < 0.1) {
|
|
var prevSegment = path.segments[i - 1 < 0 ? l - 1 : i - 1];
|
|
var line1 = new Line(prevSegment.point, segment.point, true);
|
|
var afterSegment = path.segments[i + 2 >= l ? (i + 2) % l : i + 2];
|
|
var line2 = new Line(nextSegment.point, afterSegment.point, true);
|
|
var intersection = line1.getIntersectionPoint(line2);
|
|
path.insert(i + 1 >= l ? (i + 1) % l : i + 1, intersection);
|
|
segment.remove();
|
|
nextSegment.remove();
|
|
l = path.segments.length;
|
|
i = 0;
|
|
averageLength = path.length / path.segments.length;
|
|
};
|
|
}
|
|
},
|
|
|
|
roundPath: function(path) {
|
|
var segments = path.segments.clone();
|
|
path.segments = [];
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
var curPoint = segments[i].point;
|
|
var nextPoint = segments[i + 1 == l ? 0 : i + 1].point;
|
|
var prevPoint = segments[i - 1 < 0 ? segments.length - 1 : i - 1].point;
|
|
var nextDelta = curPoint - nextPoint;
|
|
var prevDelta = curPoint - prevPoint;
|
|
nextDelta.length *= 0.2;
|
|
prevDelta.length *= 0.2;
|
|
path.add(curPoint - prevDelta);
|
|
path.add(curPoint - nextDelta);
|
|
}
|
|
},
|
|
|
|
redrawVoronoi: function(diagram, activePoint) {
|
|
for (var i = 0, l = diagram.sites.length; i < l; i++) {
|
|
this.drawCell(diagram, i);
|
|
}
|
|
},
|
|
|
|
drawVoronoi: function(diagram, activePoint) {
|
|
|
|
if (this.points.length == 2)
|
|
this.drawCell(diagram, 0, activePoint);
|
|
|
|
var newCell = this.drawCell(diagram, this.points.length - 1, activePoint);
|
|
if (!activePoint && newCell) {
|
|
var checkPath = newCell.data.path;
|
|
for (var i = 0, l = this.vGroup.children.length; i < l; i++) {
|
|
var path = this.vGroup.children[i];
|
|
var toCheckPath = path.data.path;
|
|
if (toCheckPath && checkPath.bounds.intersects(toCheckPath.bounds)) {// && toCheckPath.intersects(checkPath)) {
|
|
var index = this.findIndex(toCheckPath.data.center);
|
|
path.remove();
|
|
this.drawCell(diagram, index, activePoint);
|
|
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
removeAllPoints: function() {
|
|
this.group.data.path.segments = [];
|
|
},
|
|
|
|
removePoint: function(point) {
|
|
|
|
var hitResult = this.vGroup.hitTest(point);
|
|
if (hitResult) {
|
|
var point = hitResult.item.data.path.data.center;
|
|
var segments = this.group.data.path.segments;
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
var segment = segments[i];
|
|
if (point == segment.point.multiply(100).round()) {
|
|
i = l;
|
|
segment.remove();
|
|
}
|
|
}
|
|
}
|
|
return !!hitResult;
|
|
},
|
|
|
|
findIndex: function(point) {
|
|
for (var i = 0, l = this.points.length; i < l; i++) {
|
|
if (point == this.points[i].multiply(100).round())
|
|
return i;
|
|
}
|
|
},
|
|
|
|
drawCell: function(diagram, index, remove) {
|
|
var cell = diagram.cells[diagram.sites[index].id];
|
|
// there is no guarantee a Voronoi cell will exist for any
|
|
// particular site
|
|
if (cell !== undefined) {
|
|
var halfedges = cell.halfedges;
|
|
var nHalfedges = halfedges.length;
|
|
if (nHalfedges < 3) return;
|
|
var path = new Path();
|
|
//path.strokeColor = 'black';
|
|
path.data = {};
|
|
if (remove) {
|
|
path.removeOn({
|
|
drag: true,
|
|
up: true
|
|
});
|
|
}
|
|
if (!remove)
|
|
this.vGroup.appendTop(path);
|
|
|
|
var startPoint = new Point(halfedges[0].getStartpoint());
|
|
path.add(startPoint);
|
|
for (var iHalfedge = 0; iHalfedge < nHalfedges; iHalfedge++) {
|
|
var curPoint = new Point(halfedges[iHalfedge].getEndpoint());
|
|
path.add(curPoint);
|
|
}
|
|
path.data.center = this.points[index].multiply(100).round();
|
|
path.closed = true;
|
|
path.fillColor = new GrayColor(1 - path.length / 4000);
|
|
|
|
if (values.drawBlobs) {
|
|
var offPath = this.getOffsettedPath(path);
|
|
if (offPath) {
|
|
this.removeSmallBits(offPath);
|
|
this.roundPath(offPath);
|
|
|
|
if (values.smoothBlobs) {
|
|
offPath.smooth();
|
|
}
|
|
if (!remove) {
|
|
this.vGroup.appendTop(offPath);
|
|
} else {
|
|
offPath.removeOn({
|
|
drag: true,
|
|
up: true
|
|
});
|
|
}
|
|
}
|
|
path = offPath;
|
|
} else {
|
|
var dataPath = new Path(path.segments);
|
|
dataPath.data = {};
|
|
dataPath.data.center = path.data.center;
|
|
path.data.path = dataPath;
|
|
dataPath.remove();
|
|
}
|
|
|
|
return path;
|
|
}
|
|
},
|
|
|
|
fastRender: function(activePoint) {
|
|
if (!this.vGroup) {
|
|
this.vGroup = new Group();
|
|
this.vGroup.name = 'vGroup';
|
|
this.group.appendTop(this.vGroup);
|
|
}
|
|
this.voronoi.setSites(this.points);
|
|
var diagram = this.voronoi.compute(this.bbox);
|
|
if (this.points.length > 1)
|
|
this.drawVoronoi(diagram, activePoint);
|
|
},
|
|
|
|
render: function() {
|
|
if (this.vGroup)
|
|
this.vGroup.remove();
|
|
this.vGroup = new Group();
|
|
this.vGroup.name = 'vGroup';
|
|
this.group.appendTop(this.vGroup);
|
|
this.voronoi.setSites(this.points);
|
|
var diagram = this.voronoi.compute(this.bbox);
|
|
this.redrawVoronoi(diagram);
|
|
},
|
|
|
|
addAndRender: function(point, preview, remove) {
|
|
|
|
if (remove) this.pop();
|
|
|
|
this.addPoint(point);
|
|
this.initPoints();
|
|
if (preview) {
|
|
this.render(point);
|
|
} else {
|
|
this.render(null, true);
|
|
}
|
|
}
|
|
};
|
|
|
|
var values = {
|
|
randomAmount: 10,
|
|
drawBlobs: false,
|
|
smoothBlobs: true
|
|
}
|
|
|
|
var drawers = {
|
|
// 'Hexagons': function() {
|
|
// var components = {
|
|
// columns: { type: 'number', label: 'Columns', value: this.columns || 5 },
|
|
// rows: { type: 'number', label: 'Rows', value: this.rows || 8 }
|
|
// };
|
|
// var vPoints = [];
|
|
// // Now we bring up the dialog
|
|
// var values = Dialog.prompt('Settings', components);
|
|
// if (values) {
|
|
// this.columns = values.columns;
|
|
// this.rows = values.rows * 2;
|
|
//
|
|
// var size = new Size(this.columns, this.rows);
|
|
// var col = document.bounds.size / size;
|
|
// for (var i = -1; i < this.columns + 1; i++) {
|
|
// for (var j = -1; j < this.rows + 1; j++) {
|
|
// var point = new Point(i, j) / new Point(size) * document.bounds.size + col / 2;
|
|
// if (j % 4 == 2 || j% 4 == 3) {
|
|
// point += new Point(col.width / 2, 0);
|
|
// }
|
|
//
|
|
// vPoints.push(point);
|
|
// }
|
|
// }
|
|
// }
|
|
// return vPoints;
|
|
// },
|
|
|
|
'Bee Hive': function() {
|
|
var components = {
|
|
columns: { type: 'number', label: 'Columns', value: this.columns || 5 },
|
|
rows: { type: 'number', label: 'Rows', value: this.rows || 8 },
|
|
loose: { type: 'checkbox', label: 'Loose', value: true }
|
|
};
|
|
var vPoints = [];
|
|
// Now we bring up the dialog
|
|
var values = Dialog.prompt('Settings', components);
|
|
if (values) {
|
|
this.columns = values.columns;
|
|
this.rows = values.rows;
|
|
|
|
var size = new Size(this.columns, this.rows);
|
|
var col = document.bounds.size / size;
|
|
for (var i = -1; i < this.columns + 1; i++) {
|
|
for (var j = -1; j < this.rows + 1; j++) {
|
|
var point = new Point(i, j) / new Point(size) * document.bounds.size + col / 2;
|
|
if (j % 2)
|
|
point += new Point(col.width / 2, 0);
|
|
if (values.loose)
|
|
point += (col / 4) * Point.random() - col / 4;
|
|
vPoints.push(point);
|
|
}
|
|
}
|
|
}
|
|
return vPoints;
|
|
},
|
|
|
|
'Teeth': function() {
|
|
var components = {
|
|
columns: { type: 'number', label: 'Columns', value: this.columns || 15 },
|
|
rows: { type: 'number', label: 'Rows', value: this.rows || 15 },
|
|
loose: { type: 'checkbox', label: 'Loose', value: true }
|
|
};
|
|
var vPoints = [];
|
|
// Now we bring up the dialog
|
|
var values = Dialog.prompt('Settings', components);
|
|
if (values) {
|
|
this.columns = values.columns;
|
|
this.rows = values.rows;
|
|
var size = new Size(this.columns, this.rows);
|
|
var col = document.bounds.size / size;
|
|
for (var i = -1; i < this.columns ; i++) {
|
|
for (var j = -1; j < this.rows; j++) {
|
|
|
|
var point = new Point(i, j) / size * document.bounds.size + col / 2;
|
|
if (j % 2)
|
|
point += new Point(col.width / 2, 0);
|
|
if (i % 2)
|
|
point += new Point(0, col.height / 2);
|
|
if (values.loose)
|
|
point += (col / 8) * Point.random() - col / 8;
|
|
vPoints.push(point);
|
|
}
|
|
}
|
|
}
|
|
return vPoints;
|
|
},
|
|
'Grid': function() {
|
|
var components = {
|
|
columns: { type: 'number', label: 'Columns', value: this.columns || 3 },
|
|
rows: { type: 'number', label: 'Rows', value: this.rows || 5 },
|
|
loose: { type: 'checkbox', label: 'Loose', value: Base.pick(this.loose, false) }
|
|
};
|
|
var vPoints = [];
|
|
// Now we bring up the dialog
|
|
var values = Dialog.prompt('Settings', components);
|
|
if (values) {
|
|
this.columns = values.columns;
|
|
this.rows = values.rows;
|
|
this.parts = values.parts;
|
|
this.loose = values.loose;
|
|
|
|
var size = new Size(this.columns, this.rows);
|
|
var col = document.bounds.size / size;
|
|
for (var i = -1; i < this.columns + 1; i++) {
|
|
for (var j = -1; j < this.rows + 1; j++) {
|
|
var point = new Point(i, j) / new Point(size) * document.bounds.size + col / 2;
|
|
if (this.loose)
|
|
point += (col / 4) * Point.random() - col / 4;
|
|
vPoints.push(point);
|
|
}
|
|
}
|
|
}
|
|
return vPoints;
|
|
},
|
|
'Citrus': function() {
|
|
var components = {
|
|
columns: { type: 'number', label: 'Columns', value: this.columns || 3 },
|
|
rows: { type: 'number', label: 'Rows', value: this.rows || 5 },
|
|
parts: { type: 'number', label: 'Rows', value: this.parts || 8 }
|
|
};
|
|
var vPoints = [];
|
|
// Now we bring up the dialog
|
|
var values = Dialog.prompt('Settings', components);
|
|
if (values) {
|
|
this.columns = values.columns;
|
|
this.rows = values.rows;
|
|
this.parts = values.parts;
|
|
|
|
var size = new Size(this.columns, this.rows);
|
|
var col = document.bounds.size / size;
|
|
for (var i = -1; i < this.columns + 1; i++) {
|
|
for (var j = -1; j < this.rows + 1; j++) {
|
|
var point = new Point(i, j) / new Point(size) * document.bounds.size + col / 2;
|
|
if (j % 2)
|
|
point += new Point(col.width / 2, 0);
|
|
vPoints.push(point);
|
|
var offset = i % 2 ? 45 : 0;
|
|
for (var z = 0; z < this.parts; z++) {
|
|
var vector = new Point(col.width / 6, 0).rotate(offset + z / this.parts * 360 * (i % 2 ? 1 : -1) * (j % 2 ? -1 : 1));
|
|
vPoints.push(point + vector);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return vPoints;
|
|
},
|
|
'Circular': function() {
|
|
var point = document.bounds.center;
|
|
var vPoints = [point];
|
|
var segments = 10;
|
|
var height = document.bounds.height * 1.5 / 10;
|
|
var vector = new Point(10, 0).normalize(height / 4);
|
|
var offset = 0;
|
|
for (var i = 0; i < 10; i++) {
|
|
segments += 2;
|
|
offset += 20;
|
|
for (var j = 0; j < segments; j++) {
|
|
vector.angle = i % 2 ? 360 / segments / 2 : 0;
|
|
vector.angle += 360 / segments * j + offset;
|
|
vPoints.push(point + vector);
|
|
}
|
|
vector.length += height / 2;
|
|
}
|
|
return vPoints;
|
|
}
|
|
}
|
|
|
|
var options = [];
|
|
Base.each(drawers, function(drawer, i) {
|
|
options.push(i);
|
|
});
|
|
|
|
values.drawer = options[0];
|
|
|
|
var components = {
|
|
ruler0: { label: 'Selected Paths', type: 'ruler' },
|
|
addPoints: {
|
|
type: 'button',
|
|
value: 'Add Selected Points',
|
|
onClick: function() {
|
|
var paths = document.getItems({
|
|
type: Path,
|
|
selected: true
|
|
});
|
|
|
|
if (paths.length) {
|
|
voronoiDrawer.initialize();
|
|
for (var i = 0, l = paths.length; i < l; i++) {
|
|
var segments = paths[i].segments;
|
|
for (var j = 0, k = segments.length; j < k; j++) {
|
|
voronoiDrawer.addPoint(segments[j].point);
|
|
}
|
|
}
|
|
voronoiDrawer.initPoints();
|
|
voronoiDrawer.render();
|
|
} else {
|
|
Dialog.alert('Please select a path first.');
|
|
}
|
|
}
|
|
},
|
|
ruler1: { label: 'Random Points', type: 'ruler' },
|
|
randomAmount: {
|
|
steppers: true,
|
|
label: 'Amount'
|
|
},
|
|
addRandomPoints: {
|
|
type: 'button',
|
|
value: 'Add Random Points',
|
|
onClick: function() {
|
|
voronoiDrawer.initialize();
|
|
for (var i = 0, l = values.randomAmount; i < l; i++) {
|
|
voronoiDrawer.addRandomPoint();
|
|
}
|
|
voronoiDrawer.initPoints();
|
|
voronoiDrawer.render();
|
|
}
|
|
},
|
|
ruler2: { label: 'Grid Generators', type: 'ruler' },
|
|
drawer: {
|
|
label: 'Type',
|
|
options: options
|
|
},
|
|
renderGrid: {
|
|
type: 'button',
|
|
value: 'Render Grid',
|
|
onClick: function() {
|
|
voronoiDrawer.initialize();
|
|
var points = drawers[values.drawer]();
|
|
if (points.length) {
|
|
var confirmed = true;
|
|
if (points.length > 800) {
|
|
confirmed = Dialog.confirm('You are about to generate a Voronoi diagram with ' + points.length + ' points. This could take a while and could potentially crash Illustrator. Do you want to live recklessly?');
|
|
}
|
|
if (confirmed) {
|
|
voronoiDrawer.setPoints(points);
|
|
voronoiDrawer.render();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
ruler3: { label: 'Settings', type: 'ruler' },
|
|
drawBlobs: {
|
|
label: 'Inset Paths',
|
|
onChange: function(value) {
|
|
components.smoothBlobs.enabled = !!value;
|
|
voronoiDrawer.initialize();
|
|
voronoiDrawer.initPoints();
|
|
if (voronoiDrawer.points.length)
|
|
voronoiDrawer.render();
|
|
}
|
|
},
|
|
smoothBlobs: {
|
|
label: 'Smooth',
|
|
onChange: function(value) {
|
|
voronoiDrawer.initialize();
|
|
voronoiDrawer.initPoints();
|
|
if (voronoiDrawer.points.length)
|
|
voronoiDrawer.render();
|
|
}
|
|
}
|
|
};
|
|
|
|
// var palette = new Palette('Voronoi Tool', components, values);
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<canvas id='canvas' resize='true'></canvas>
|
|
</body> |