Refactor internal shape and reduce reliance on Kurbo (#617)

* Dissolve Points from path

* Add handling for removing the first anchor

* Add function to turn handles into bez_paths

* Created overlay manager, wip

* WIP Refactor of VectorShape / Overlays / ShapeEditor

* WIP stripping vector shape, anchor, point.

* WIP Removed kurbo deps from vector shape, anchor, point

* WIP Further work to make vector shapes / anchors / points more standalone.

* WIP more pruning

* WIP Progress on overlay_renderer

* WIP more overlay_renderer work

* WIP more pruning, cleared warnings

* WIP decided ShapeRenderer wasn't an accurate name, ShapeAdapter now. Error squashing continues.

* WIP squashed more errors, now need to decide if anchors should have unique IDs

* WIP Errors squashed, now to actually make it work.

* WIP Moved vector structs to graphene, beginning to remove bezpath from shape_layer

* Refactoring: disentangle kurbo from apply_affine

* Refactor internal shape and remove reliance on Kurbo (PR #617) - Disentangle Kurbo (#619)

* Refactoring: disentangle kurbo from apply_affine

* Broke boolean operations, refactor in state which compiles

* "fixed" boolean operation refactor related errors

* fixed apply_affine, which would not have applied any type of affine

* Small Cleanup, readability

* Fix issue with overlay styles no longer showing selection state.

* Resolved error with point option

* WIP, figuring out how to have one source of truth for VectorShape. Trying to avoid cloning.

* WIP work on single source of truth vectorshapes

* More steps toward single source of truth VectorShape

* Continued wip on making VectorShapes mutably accessible without cloning

* Wip using paths to reference vectorshapes instead, need to restructure ShapeEditor

* Decided to allow temporary copies of vectorshapes.

* Removed HashSet for selected shape indices

* Added @TrueDoctor's id_storage.rs with some heavy modification. Added it to VectorShape. Isn't yet used for folders.

* Integrated UniqueElements<T> with VectorShape to store VectorAnchors

* Improved storage_id.rs perf and cleaned up it's interface

* Iterator Implementations and fixes (#637)

* Refactoring: disentangle kurbo from apply_affine

* Broke boolean operations, refactor in state which compiles

* "fixed" boolean operation refactor related errors

* fixed apply_affine, which would not have applied any type of affine

* implemented transforms for VectorAnchors
implemented Not for VectorControlPointType

* started adding Vector Shape implementations of shape prototypes

* added several useful implemtations to UniqueElements

* added another implemnation for UniqueElements to make working with iterators easier, fixed vector-shape errors

* package-lock.json

* clean up rebase, added back Layer paths

* added deref implementation for VectorShape

* unnecesary variable

* simplify code by removing levels of indirection

* fixed errors

* merge cleanup

* removed package-lock.json

* Removed .selected from VectorShape, it isn't needed as layers are selected not shapes specifically.

* Removed transform and layer_path from VectorShape

* Auto-saving tentitively working. Work toward Overlay transform issues.

* Overlays properly hiding and caching. Not clearing cache yet and some tool switching issues remain, but progress.

* Putting layers in folders changes their unique ID. This is problematic. Assumed this was not the case.

* Removed need for closed bool, changed VectorShape to a tuple struct.

* WIP Switched to layer paths as opposed to VectorShapes. Next up add messages for changing VectorShapes.

* Added initial messages to edit VectorShape points.

* DeleteSelectedPoints messages implemented, selection isn't working currently though.

* Selection messages arriving in document, but transform is wrong.

* Selection, Deselection working, delete working for first point.

* Working towards moving points again

* Removed extra vec from UniqueElements, attempting to squash ordering bug. Still appears to occur though.

* Delete more stable, clean up, renamed to HandleIn, HandleOut

* Further vec_unique cleanup

* Further cleanup

* Removed Deref / DerefMut from VectorShape

* Document version++, will likely revert before merge into master

* Seleting / deleting handles tentitively working again.

* Version number bump, fixed tests.

* Fixed comment in VecUnique

* Improved VecUnique descriptor comment

* Renamed VecUnique to IdBackedVec to further clarify usage.

* Resolved formatting.

* WIP Fixing dragging points

* Fixed an instance where an OverlayMessage could be sent to the main document incorrectly.

* Deleting all of a shapes points now gracefully deletes the layer instead of crashing.

* Fixed handle configurations that would panic on deletion

* Single anchor dragging restored with multi-dragging next plus handles

* sides.into()

* Handle and Multi-point dragging working

* WIP Handle symmetry working again

* Handle mirroring functional again.

* Cleaned up warnings

* Fixed overlay outline not matching shape

* Git branch fix of compatibility with new master

* Fixed closed shape bug, replaced kurbo ellipse

* Removed unused func, updated comments

* Deleting points can undo, multiple shape selection deletes now working

* Removed AddOverlay* operations

* Partial fix for select drift, added helpers

* Don't snap against dragging points

* Properly cleanup path outline with multiple shapes

* Clear all points in other selected shapes

* Actually don't snap against dragging points

* Fix path tool & add snap angle and break handle

* Fix handle being set to NaN causing render issues

* Fix cached overlays not showing line -> curve

* Add operations for modifying paths

* Remove kurbo from pen tool

* Do not snap against handles when anchor selected

* Fix overlays not being cleaned up on path tool

* Fix handle position after dragging

* Use `Anchor` for text & no kurbo in operations

* Replace kurbo to_svg function

* Ngon no longer center scales by default, still some weird behaviour when holding alt

* Cleanup overlays

* Fix render and bounding box doctests

* Fix fun to_svg error

* Fix compile error

* Some code review

* Remove legacy `SelectPoint` on doubleclick

* Remove font from test document

* Fix the pen tool selection changed

* Reorder imports

Co-authored-by: Dennis <dennis@kobert.dev>
Co-authored-by: Caleb Dennis <caleb.dennis429@gmail.com>
Co-authored-by: caleb <56044292+caleb-ad@users.noreply.github.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com>
This commit is contained in:
Oliver Davies
2022-07-05 15:02:18 -07:00
committed by Keavon Chambers
co-authored by Dennis Caleb Dennis caleb Keavon Chambers 0hypercube 0HyperCube
parent 3c2fff4465
commit 58675eb64d
48 changed files with 2461 additions and 1807 deletions
+25 -116
View File
@@ -1,18 +1,14 @@
use super::layer_info::LayerData;
use super::style::{self, PathStyle, RenderData, ViewMode};
use super::vector::vector_shape::VectorShape;
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2};
use kurbo::{Affine, BezPath, Shape as KurboShape};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
fn glam_to_kurbo(transform: DAffine2) -> Affine {
Affine::new(transform.to_cols_array())
}
/// A generic SVG element defined using Bezier paths.
/// Shapes are rendered as
/// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
@@ -21,21 +17,19 @@ fn glam_to_kurbo(transform: DAffine2) -> Affine {
/// group that the transformation matrix is applied to.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ShapeLayer {
/// A Bezier path.
pub path: BezPath,
/// The geometry of the layer.
pub shape: VectorShape,
/// The visual style of the shape.
pub style: style::PathStyle,
// TODO: We might be able to remove this in a future refactor
pub render_index: i32,
/// Whether or not the [path](ShapeLayer::path) connects to itself.
pub closed: bool,
}
impl LayerData for ShapeLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) {
let mut path = self.path.clone();
let mut vector_shape = self.shape.clone();
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
let layer_bounds = [(x0, y0).into(), (x1, y1).into()];
let transform = self.transform(transforms, render_data.view_mode);
@@ -44,9 +38,9 @@ impl LayerData for ShapeLayer {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return;
}
path.apply_affine(glam_to_kurbo(transform));
vector_shape.apply_affine(transform);
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
let transformed_bounds = [(x0, y0).into(), (x1, y1).into()];
let _ = writeln!(svg, r#"<g transform="matrix("#);
@@ -57,33 +51,37 @@ impl LayerData for ShapeLayer {
let _ = write!(
svg,
r#"<path d="{}" {} />"#,
path.to_svg(),
vector_shape.to_svg(),
self.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds)
);
let _ = svg.write_str("</g>");
}
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
use kurbo::Shape;
let mut path = self.path.clone();
let mut vector_shape = self.shape.clone();
if transform.matrix2 == DMat2::ZERO {
return None;
}
path.apply_affine(glam_to_kurbo(transform));
vector_shape.apply_affine(transform);
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
if intersect_quad_bez_path(quad, &self.path, self.style.fill().is_some()) {
let filled = self.style.fill().is_some() || self.shape.anchors().last().filter(|anchor| anchor.is_close()).is_some();
if intersect_quad_bez_path(quad, &(&self.shape).into(), filled) {
intersections.push(path.clone());
}
}
}
impl ShapeLayer {
/// Construct a new [ShapeLayer] with the specified [VectorShape] and [PathStyle]
pub fn new(shape: VectorShape, style: PathStyle) -> Self {
Self { shape, style, render_index: 1 }
}
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
let start = match (mode, self.render_index) {
(ViewMode::Outline, _) => 0,
@@ -93,15 +91,7 @@ impl ShapeLayer {
transforms.iter().skip(start).fold(DAffine2::IDENTITY, |a, b| a * *b)
}
pub fn from_bez_path(bez_path: BezPath, style: PathStyle, closed: bool) -> Self {
Self {
path: bez_path,
style,
render_index: 1,
closed,
}
}
/// TODO The behavior of ngon changed from the previous iteration slightly, match original behavior
/// Create an N-gon.
///
/// # Panics
@@ -132,136 +122,55 @@ impl ShapeLayer {
path.close_path();
Self {
path,
shape: VectorShape::new_ngon(DVec2::new(0., 0.), sides.into(), 1.),
style,
render_index: 1,
closed: true,
}
}
/// Create a rectangular shape.
pub fn rectangle(style: PathStyle) -> Self {
Self {
path: kurbo::Rect::new(0., 0., 1., 1.).to_path(0.01),
shape: VectorShape::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
closed: true,
}
}
/// Create an elliptical shape.
pub fn ellipse(style: PathStyle) -> Self {
Self {
path: kurbo::Ellipse::from_rect(kurbo::Rect::new(0., 0., 1., 1.)).to_path(0.01),
shape: VectorShape::new_ellipse(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
closed: true,
}
}
/// Create a straight line from (0, 0) to (1, 0).
pub fn line(style: PathStyle) -> Self {
Self {
path: kurbo::Line::new((0., 0.), (1., 0.)).to_path(0.01),
shape: VectorShape::new_line(DVec2::new(0., 0.), DVec2::new(1., 0.)),
style,
render_index: 1,
closed: false,
}
}
/// Create a polygonal line that visits each provided point.
pub fn poly_line(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
let mut path = kurbo::BezPath::new();
points
.into_iter()
.map(|v| v.into())
.map(|v: DVec2| kurbo::Point { x: v.x, y: v.y })
.enumerate()
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
Self {
path,
shape: VectorShape::new_poly_line(points),
style,
render_index: 0,
closed: false,
}
}
/// Creates a smooth bezier spline that passes through all given points.
/// The algorithm used in this implementation is described here: <https://www.particleincell.com/2012/bezier-splines/>
pub fn spline(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
let mut path = kurbo::BezPath::new();
// Creating a bezier spline is only necessary for 3 or more points.
// For 2 given points a line segment is created instead.
if points.len() > 2 {
let points: Vec<_> = points.into_iter().map(|v| v.into()).map(|v: DVec2| kurbo::Vec2 { x: v.x, y: v.y }).collect();
// Number of bezier segments
let n = points.len() - 1;
// Control points for each bezier segment
let mut p1 = vec![kurbo::Vec2::ZERO; n];
let mut p2 = vec![kurbo::Vec2::ZERO; n];
// Tri-diagonal matrix coefficients a, b and c (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
let mut a = vec![1.0; n];
a[0] = 0.0;
a[n - 1] = 2.0;
let mut b = vec![4.0; n];
b[0] = 2.0;
b[n - 1] = 7.0;
let mut c = vec![1.0; n];
c[n - 1] = 0.0;
let mut r: Vec<_> = (0..n).map(|i| 4.0 * points[i] + 2.0 * points[i + 1]).collect();
r[0] = points[0] + (2.0 * points[1]);
r[n - 1] = 8.0 * points[n - 1] + points[n];
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
for i in 1..n {
let m = a[i] / b[i - 1];
b[i] -= m * c[i - 1];
let last_iteration_r = r[i - 1];
r[i] -= m * last_iteration_r;
}
// Determine first control point for each segment
p1[n - 1] = r[n - 1] / b[n - 1];
for i in (0..n - 1).rev() {
p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i];
}
// Determine second control point per segment from first
for i in 0..n - 1 {
p2[i] = 2.0 * points[i + 1] - p1[i + 1];
}
p2[n - 1] = 0.5 * (points[n] + p1[n - 1]);
// Create bezier path from given points and computed control points
points.into_iter().enumerate().for_each(|(i, p)| {
if i == 0 {
path.move_to(p.to_point())
} else {
path.curve_to(p1[i - 1].to_point(), p2[i - 1].to_point(), p.to_point())
}
});
} else {
points
.into_iter()
.map(|v| v.into())
.map(|v: DVec2| kurbo::Point { x: v.x, y: v.y })
.enumerate()
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
}
Self {
path,
shape: VectorShape::new_spline(points),
style,
render_index: 0,
closed: false,
}
}
}