mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
add segment brush
This commit is contained in:
parent
4a5c9f0b54
commit
27f7102b06
3 changed files with 110 additions and 75 deletions
|
@ -1,7 +1,13 @@
|
|||
import paper from 'paper';
|
||||
import log from '../log/log';
|
||||
import broadBrushHelper from './broad-brush-helper';
|
||||
import segmentBrushHelper from './segment-brush-helper';
|
||||
|
||||
/**
|
||||
* Shared code for the brush and eraser tool. Adds functions on the paper tool object
|
||||
* to handle mouse events, which are delegated to broad-brush-helper and segment-brush-helper
|
||||
* based on the brushSize in the state.
|
||||
*/
|
||||
class BlobTool {
|
||||
|
||||
static get BROAD () {
|
||||
|
@ -15,7 +21,7 @@ class BlobTool {
|
|||
// Segment brush has performance issues at low threshold, but broad brush has weird corners
|
||||
// which get more obvious the bigger it is
|
||||
static get THRESHOLD () {
|
||||
return 100000;
|
||||
return 9;
|
||||
}
|
||||
|
||||
setOptions (options) {
|
||||
|
@ -62,10 +68,8 @@ class BlobTool {
|
|||
path.strokeWidth = 1;
|
||||
}
|
||||
} else {
|
||||
// TODO keep a separate active toolbar style for brush vs pen?
|
||||
//path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
|
||||
//TODO FIX
|
||||
// TODO: Add back brush styling. Keep a separate active toolbar style for brush vs pen.
|
||||
// path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
|
||||
path.fillColor = 'black';
|
||||
if (path === this.cursorPreview) {
|
||||
|
@ -80,8 +84,7 @@ class BlobTool {
|
|||
tool.fixedDistance = 1;
|
||||
|
||||
broadBrushHelper(tool);
|
||||
// TODO add
|
||||
//pg.segmentbrushhelper(tool, options);
|
||||
segmentBrushHelper(tool);
|
||||
|
||||
tool.onMouseMove = function (event) {
|
||||
tool.resizeCursorIfNeeded(event.point);
|
||||
|
@ -111,10 +114,10 @@ class BlobTool {
|
|||
if (event.event.button > 0) return; // only first mouse button
|
||||
if (this.brush === BlobTool.BROAD) {
|
||||
this.onBroadMouseDrag(event);
|
||||
} else if (this.brush === Blob.SEGMENT) {
|
||||
} else if (this.brush === BlobTool.SEGMENT) {
|
||||
this.onSegmentMouseDrag(event);
|
||||
} else {
|
||||
log.warning(`Brush type does not exist: ${this.brush}`);
|
||||
log.warn(`Brush type does not exist: ${this.brush}`);
|
||||
}
|
||||
|
||||
this.cursorPreview.bringToFront();
|
||||
|
@ -132,7 +135,7 @@ class BlobTool {
|
|||
} else if (this.brush === BlobTool.SEGMENT) {
|
||||
lastPath = this.onSegmentMouseUp(event);
|
||||
} else {
|
||||
log.warning(`Brush type does not exist: ${this.brush}`);
|
||||
log.warn(`Brush type does not exist: ${this.brush}`);
|
||||
}
|
||||
|
||||
if (isEraser) {
|
||||
|
@ -195,8 +198,8 @@ class BlobTool {
|
|||
paths.splice(i, 1);
|
||||
}
|
||||
}
|
||||
// TODO FIX
|
||||
//pg.undo.snapshot('broadbrush');
|
||||
// TODO: Add back undo
|
||||
// pg.undo.snapshot('broadbrush');
|
||||
};
|
||||
|
||||
tool.mergeEraser = function (lastPath) {
|
||||
|
@ -210,8 +213,8 @@ class BlobTool {
|
|||
// Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset
|
||||
// and deselect the selection
|
||||
if (items.length === 0) {
|
||||
// TODO FIX
|
||||
//pg.selection.clearSelection();
|
||||
// TODO: Add back selection handling
|
||||
// pg.selection.clearSelection();
|
||||
items = paper.project.getItems({
|
||||
match: function (item) {
|
||||
return tool.isMergeable(lastPath, item) && tool.touches(lastPath, item);
|
||||
|
@ -225,7 +228,7 @@ class BlobTool {
|
|||
|
||||
// Gather path segments
|
||||
const subpaths = [];
|
||||
// TODO handle compound path
|
||||
// TODO: Handle compound path
|
||||
if (items[i] instanceof paper.Path && !items[i].closed) {
|
||||
const firstSeg = items[i].clone();
|
||||
const intersections = firstSeg.getIntersections(lastPath);
|
||||
|
@ -290,8 +293,8 @@ class BlobTool {
|
|||
items[i].remove();
|
||||
}
|
||||
lastPath.remove();
|
||||
// TODO FIX
|
||||
//pg.undo.snapshot('eraser');
|
||||
// TODO: Add back undo handling
|
||||
// pg.undo.snapshot('eraser');
|
||||
};
|
||||
|
||||
tool.colorMatch = function (existingPath, addedPath) {
|
||||
|
@ -318,7 +321,7 @@ class BlobTool {
|
|||
path1.hitTest(path2.firstSegment.point)) {
|
||||
return true;
|
||||
}
|
||||
// TODO clean up these no point paths
|
||||
// TODO: clean up these no point paths
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -338,4 +341,4 @@ class BlobTool {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = BlobTool;
|
||||
export default BlobTool;
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
||||
const paper = require('paper');
|
||||
import paper from 'paper';
|
||||
|
||||
/**
|
||||
* Applies segment brush functions to the tool.
|
||||
* Applies broad brush functions to the tool. Call them when the corresponding mouse event happens
|
||||
* to get the broad brush behavior.
|
||||
*
|
||||
* Broad brush draws strokes by drawing points equidistant from the mouse event, perpendicular to the
|
||||
* direction of motion. Shortcomings are that this path can cross itself, and 180 degree turns result
|
||||
* in a flat edge.
|
||||
*
|
||||
* @param {!Tool} tool paper.js mouse object
|
||||
*/
|
||||
const broadBrushHelper = function (tool) {
|
||||
|
@ -102,4 +108,4 @@ const broadBrushHelper = function (tool) {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports = broadBrushHelper;
|
||||
export default broadBrushHelper;
|
||||
|
|
|
@ -1,59 +1,85 @@
|
|||
// Applies segment brush functions to the tool
|
||||
pg.segmentbrushhelper = function(tool, options) {
|
||||
var lastPoint, finalPath;
|
||||
import paper from 'paper';
|
||||
|
||||
tool.onSegmentMouseDown = function(event) {
|
||||
tool.minDistance = options.brushSize/4;
|
||||
tool.maxDistance = options.brushSize;
|
||||
|
||||
finalPath = new Path.Circle({
|
||||
center: event.point,
|
||||
radius: options.brushSize/2
|
||||
});
|
||||
tool.stylePath(finalPath);
|
||||
lastPoint = event.point;
|
||||
};
|
||||
|
||||
tool.onSegmentMouseDrag = function(event) {
|
||||
var step = (event.delta).normalize(options.brushSize/2);
|
||||
var handleVec = step.clone();
|
||||
handleVec.length = options.brushSize/2;
|
||||
handleVec.angle += 90;
|
||||
/**
|
||||
* Applies segment brush functions to the tool. Call them when the corresponding mouse event happens
|
||||
* to get the broad brush behavior.
|
||||
*
|
||||
* Segment brush draws by creating a rounded rectangle for each mouse move event and merging all of
|
||||
* those shapes. Unlike the broad brush, the resulting shape will not self-intersect and when you make
|
||||
* 180 degree turns, you will get a rounded point as expected. Shortcomings include that performance is
|
||||
* worse, especially as the number of segments to join increase, and that there are problems in paper.js
|
||||
* with union on shapes with curves, so that chunks of the union tend to disappear.
|
||||
* (https://github.com/paperjs/paper.js/issues/1321)
|
||||
*
|
||||
* @param {!Tool} tool paper.js mouse object
|
||||
*/
|
||||
const segmentBrushHelper = function (tool) {
|
||||
let lastPoint;
|
||||
let finalPath;
|
||||
|
||||
var path = new Path();
|
||||
path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
path.strokeColor = null;
|
||||
// Add handles to round the end caps
|
||||
path.add(new Segment(lastPoint - step, -handleVec, handleVec));
|
||||
step.angle += 90;
|
||||
tool.onSegmentMouseDown = function (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
|
||||
path.add(event.lastPoint + step);
|
||||
path.insert(0, event.lastPoint - step);
|
||||
path.add(event.point + step);
|
||||
path.insert(0, event.point - step);
|
||||
tool.minDistance = this.options.brushSize / 4;
|
||||
tool.maxDistance = this.options.brushSize;
|
||||
|
||||
finalPath = new paper.Path.Circle({
|
||||
center: event.point,
|
||||
radius: this.options.brushSize / 2
|
||||
});
|
||||
tool.stylePath(finalPath);
|
||||
lastPoint = event.point;
|
||||
};
|
||||
|
||||
tool.onSegmentMouseDrag = function (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
|
||||
// Add end cap
|
||||
step.angle -= 90;
|
||||
path.add(new Segment(event.point + step, handleVec, -handleVec));
|
||||
path.closed = true;
|
||||
// The unite function on curved paths does not always work (sometimes deletes half the path)
|
||||
// so we have to flatten.
|
||||
path.flatten(options.brushSize/5);
|
||||
|
||||
lastPoint = event.point;
|
||||
var newPath = finalPath.unite(path);
|
||||
path.remove();
|
||||
finalPath.remove();
|
||||
finalPath = newPath;
|
||||
};
|
||||
const step = (event.delta).normalize(this.options.brushSize / 2);
|
||||
const handleVec = step.clone();
|
||||
handleVec.length = this.options.brushSize / 2;
|
||||
handleVec.angle += 90;
|
||||
|
||||
tool.onSegmentMouseUp = function(event) {
|
||||
// TODO: This smoothing tends to cut off large portions of the path! Would like to eventually
|
||||
// add back smoothing, maybe a custom implementation that only applies to a subset of the line?
|
||||
const path = new paper.Path();
|
||||
|
||||
// TODO: Add back brush styling
|
||||
// path = pg.stylebar.applyActiveToolbarStyle(path);
|
||||
path.fillColor = 'black';
|
||||
|
||||
// Smooth the path.
|
||||
finalPath.simplify(2);
|
||||
// console.log(finalPath.segments);
|
||||
return finalPath;
|
||||
};
|
||||
}
|
||||
// Add handles to round the end caps
|
||||
path.add(new paper.Segment(lastPoint.subtract(step), handleVec.multiply(-1), handleVec));
|
||||
step.angle += 90;
|
||||
|
||||
path.add(event.lastPoint.add(step));
|
||||
path.insert(0, event.lastPoint.subtract(step));
|
||||
path.add(event.point.add(step));
|
||||
path.insert(0, event.point.subtract(step));
|
||||
|
||||
// Add end cap
|
||||
step.angle -= 90;
|
||||
path.add(new paper.Segment(event.point.add(step), handleVec, handleVec.multiply(-1)));
|
||||
path.closed = true;
|
||||
// The unite function on curved paths does not always work (sometimes deletes half the path)
|
||||
// so we have to flatten.
|
||||
path.flatten(this.options.brushSize / 5);
|
||||
|
||||
lastPoint = event.point;
|
||||
const newPath = finalPath.unite(path);
|
||||
path.remove();
|
||||
finalPath.remove();
|
||||
finalPath = newPath;
|
||||
};
|
||||
|
||||
tool.onSegmentMouseUp = function (event) {
|
||||
if (event.event.button > 0) return; // only first mouse button
|
||||
|
||||
// TODO: This smoothing tends to cut off large portions of the path! Would like to eventually
|
||||
// add back smoothing, maybe a custom implementation that only applies to a subset of the line?
|
||||
|
||||
// Smooth the path.
|
||||
finalPath.simplify(2);
|
||||
// console.log(finalPath.segments);
|
||||
return finalPath;
|
||||
};
|
||||
};
|
||||
|
||||
export default segmentBrushHelper;
|
||||
|
|
Loading…
Reference in a new issue