mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 02:28:12 +08:00
Boolean shape operations (#470)
* added BooleanUnion message for select tool, which is sent by the BooleanUnion button
* select to dispatches a BooleanUnion message
- as far as I can tell the selected layers aren't stored anywhere
* Added neccesary messages and functions for boolean operation
* Intersection code, as yet untested
does not compile
* Updated intersection algorithm
* Fixed shapes_as_seen
- should not be effected by root transform, should be effected by the shape transform
Working line intercepts
* updated intersection algorithm
* added SubCurve struct, to reduce creation of new PathSegs and more efficiently calculate bounding boxes
* intersection algorithm modifications
* changed sub_curve to do less copying
* intersection algorithm working for ellipses
- idk why though, the algorithm isn't finding close, but nearby intersections
* Code for the sub-shape identification pieces of boolean operation algorithm
* cycle direction calculations
boolean_operation code
* final touches (a.k.a. the very begining touches before the debugging)
* removed intersection testing code to draw intersects
added code to add new shapes to document
* Bug Fix: Cycle needs to track egde origin
Bug Fix: vertex markers
Working for simple shapes
* Bug Fix: multiple intersections in same PathSeg
* Bug Fix: compare the absolute value of area magnitudes
* Bug Fix: subdivision algorithm
* cargo fmt
* comments
* Bug Fix: Difference and Subtraction operations
* simplified overlap
* changed shapes_as_seen to transformed_shapes
- modified function to use existing code for multiplying transforms and traversing layer tree
removed shape_as_seen, helper function to shapes_as_seen
* Bug Fix: selected layers must be sorted
* Changed SubFront to SubtractFront and SubBack to SubtractBack
* moved F64PRECISION to graphene::consts
* Best practices changes
intersection algorithm changes
* Added BooleanOperationError
* modified intersection algorithm to use more dynamic thresholds
* intersection algorithm modification
* Added "do_if"
* Bug Fix: properly subdivide segments with multiple intersections
* Added tests for intersection algorithm
* restructured flow control in intersection algorithm
restructured intersection algorithm to modify single vector
- should have done this yeeeaars ago
* Shapes will have the pathstyle of alpha
* collect_shapes now uses closure
* BugFix: fixed PathGraph:get_cycles, prevent multicounting cycles
* Added boolean_ops::reverse_path function
* Curves are now reversed before intersections are found
* cleanup
* BugFix: subfront and subback chose wrong shape pathstyle
BugFix: path concat should remove internal movetos and closepaths
* BugFix: prevent movetos from being added due to intersect imprecision
* Changed intersection quality threshhold to CURVE_FIDELITY
* Added functions for cubic/quadratic roots
!Does not compile!
* Added special case algorithms for line-curve intersections
Added tests for cubic root algorithm
* changed line_curve_intersections structure
* Handle intersecting shape without curve intersection case
* Behavior for SubtratFront, SubtractBack, Union, and Intersection for the no-intersect cases
* reformatted PathGraph::add_edges_from_path
Fixed bugs involving closepaths and subpaths
* Bug fix: fix refactoring error
* Bug fix: don't consider 0 length cycles
Bug Fix: dummy vertices are not intersections
* the function document_message_handler::sort_layers sorts the layers from bottom to top
- we want the reverse
* Bug Fix: ClosePath must be appended to shapes constructed from cycle
- By default PathGraph edges are not closed.
- When the input shape was not closed there would be multiple unclosed paths in the resulting shape, causing bugs
* close_path now closes all subpaths in a path
- if a shape has any subpath which is not closed boolean operations are undefined
(Also PathGraph::add_edges_from_path breaks when the path isn't closed)
* clean up
* removed unused function "intersectoin_candidates"
* comments
* removed duplicate check for valid intersection in horizontal_ray_cast
* Temporary fix for ray casting
* it wouldn't build cause one of these ->' pesky things was hanging around in the wrong neighborhood
* ignoring intersection test cases because they do exact fp comparison
* Some spelling fixes and abbreviation burnination (more needed)
* spelling fixes
close_path bug
* Fixed: local extrema were not being properly filtered out for subcurves
Fixed: reversing PathSeg removes any closepaths
* spelling
* Code review pass
* partial implementation overlapping identical curves
* Untested implementation for test for overlapping curves
- Test is not yet used in intersection algorithm
* -Removed PathGraph::intersect()
* readability improvements
changed line_t_value to be more forgiving to error
* Bug Fix: match_control_polygon didn't properly compare different degree polygons
* Added colinear() function
Added projection_on_line() function
* BugFix project_onto_line
* removed extra log
* rust fmt
Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: TrueDoctor <dennis@kobert.dev>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
TrueDoctor
parent
5a3500d256
commit
bc8a901538
@@ -1,3 +1,4 @@
|
||||
use crate::boolean_ops::boolean_operation;
|
||||
use crate::intersection::Quad;
|
||||
use crate::layers;
|
||||
use crate::layers::folder::Folder;
|
||||
@@ -8,6 +9,7 @@ use crate::layers::text::Text;
|
||||
use crate::{DocumentError, DocumentResponse, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::Affine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::max;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
@@ -95,6 +97,25 @@ impl Document {
|
||||
self.folder_mut(path)?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))
|
||||
}
|
||||
|
||||
/// Returns vector `Shape`s for each specified in `paths`.
|
||||
/// If any path is not a shape, or does not exist, `DocumentError::InvalidPath` is returned.
|
||||
fn transformed_shapes(&self, paths: &[Vec<LayerId>]) -> Result<Vec<Shape>, DocumentError> {
|
||||
let mut shapes: Vec<Shape> = Vec::new();
|
||||
let undo_viewport = self.root.transform.inverse();
|
||||
for path in paths {
|
||||
match (self.multiply_transforms(path), &self.layer(path)?.data) {
|
||||
(Ok(shape_transform), LayerDataType::Shape(shape)) => {
|
||||
let mut new_shape = shape.clone();
|
||||
new_shape.path.apply_affine(Affine::new((undo_viewport * shape_transform).to_cols_array()));
|
||||
shapes.push(new_shape);
|
||||
}
|
||||
(Ok(_), _) => return Err(DocumentError::InvalidPath),
|
||||
(Err(err), _) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(shapes)
|
||||
}
|
||||
|
||||
pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
|
||||
layers.reduce(|a, b| &a[..a.iter().zip(b.iter()).take_while(|&(a, b)| a == b).count()]).unwrap_or_default()
|
||||
}
|
||||
@@ -531,6 +552,34 @@ impl Document {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::poly_line(points, *style)), *transform), *insert_index)?;
|
||||
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(path)].concat())
|
||||
}
|
||||
Operation::BooleanOperation { operation, selected } => {
|
||||
// TODO: proper difference
|
||||
// TODO: proper style selection (done?)
|
||||
// TODO: should generate symmetrical code
|
||||
// TODO: Operations on any number of shapes
|
||||
// TODO: boolean ops on any number of shapes
|
||||
// TODO: handle overlapping identical curve case
|
||||
// TODO: precision reached without intersection bug (maybe caused by separating a closed path, or dragging handles)
|
||||
// TODO: click on shape should drag the shape
|
||||
// TODO: add ability to undo
|
||||
let mut responses = Vec::new();
|
||||
if selected.len() > 1 && selected.len() < 3 {
|
||||
// ? apparently `selected` should be reversed
|
||||
let mut shapes = self.transformed_shapes(selected)?;
|
||||
let mut shape_drain = shapes.drain(..).rev();
|
||||
let new_shapes = boolean_operation(*operation, shape_drain.next().unwrap(), shape_drain.next().unwrap())?;
|
||||
|
||||
for path in selected {
|
||||
self.delete(path)?;
|
||||
responses.push(DocumentResponse::DeletedLayer { path: path.clone() })
|
||||
}
|
||||
for new_shape in new_shapes {
|
||||
let new_id = self.add_layer(&[], Layer::new(LayerDataType::Shape(new_shape), DAffine2::IDENTITY.to_cols_array()), -1)?;
|
||||
responses.push(DocumentResponse::CreatedLayer { path: vec![new_id] })
|
||||
}
|
||||
}
|
||||
Some([vec![DocumentChanged, DocumentResponse::FolderChanged { path: vec![] }], responses].concat())
|
||||
}
|
||||
Operation::AddSpline {
|
||||
path,
|
||||
insert_index,
|
||||
|
||||
Reference in New Issue
Block a user