Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add parabola #1016

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/rnote-compose/src/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod ellipsebuilder;
mod fociellipsebuilder;
mod gridbuilder;
mod linebuilder;
mod parabolabuilder;
mod penpathcurvedbuilder;
mod penpathmodeledbuilder;
mod penpathsimplebuilder;
Expand All @@ -27,6 +28,7 @@ pub use ellipsebuilder::EllipseBuilder;
pub use fociellipsebuilder::FociEllipseBuilder;
pub use gridbuilder::GridBuilder;
pub use linebuilder::LineBuilder;
pub use parabolabuilder::ParabolaBuilder;
pub use penpathcurvedbuilder::PenPathCurvedBuilder;
pub use penpathmodeledbuilder::PenPathModeledBuilder;
pub use penpathsimplebuilder::PenPathSimpleBuilder;
Expand Down Expand Up @@ -85,6 +87,9 @@ pub enum ShapeBuilderType {
/// A polygon builder
#[serde(rename = "polygon")]
Polygon,
/// A parabola
#[serde(rename = "parabola")]
Parabola,
}

impl ShapeBuilderType {
Expand All @@ -104,6 +109,7 @@ impl ShapeBuilderType {
"shapebuilder-cubbez-symbolic" => Some(Self::CubBez),
"shapebuilder-polyline-symbolic" => Some(Self::Polyline),
"shapebuilder-polygon-symbolic" => Some(Self::Polygon),
"shapebuilder-parabola-symbolic" => Some(Self::Parabola),
_ => None,
}
}
Expand All @@ -126,6 +132,7 @@ impl ShapeBuilderType {
Self::CubBez => String::from("shapebuilder-cubbez-symbolic"),
Self::Polyline => String::from("shapebuilder-polyline-symbolic"),
Self::Polygon => String::from("shapebuilder-polygon-symbolic"),
Self::Parabola => String::from("shapebuilder-parabola-symbolic"),
}
}
}
Expand Down
89 changes: 89 additions & 0 deletions crates/rnote-compose/src/builders/parabolabuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Imports
use super::buildable::{Buildable, BuilderCreator, BuilderProgress};
use crate::eventresult::EventPropagation;
use crate::penevent::{PenEvent, PenState};
use crate::penpath::Element;
use crate::shapes::Parabola;
use crate::style::{indicators, Composer};
use crate::{Constraints, EventResult};
use crate::{Shape, Style, Transform};
use p2d::bounding_volume::{Aabb, BoundingVolume};
use p2d::shape::Cuboid;
use piet::RenderContext;
use std::time::Instant;

/// Parabola builder.
#[derive(Debug, Clone)]
pub struct ParabolaBuilder {
/// Start position.
start: na::Vector2<f64>,
/// Current position.
current: na::Vector2<f64>,
}

impl BuilderCreator for ParabolaBuilder {
fn start(element: Element, _now: Instant) -> Self {
Self {
start: element.pos,
current: element.pos,
}
}
}

impl Buildable for ParabolaBuilder {
type Emit = Shape;

fn handle_event(
&mut self,
event: PenEvent,
_now: Instant,
constraints: Constraints,
) -> EventResult<BuilderProgress<Self::Emit>> {
let progress = match event {
PenEvent::Down { element, .. } => {
self.current = constraints.constrain(element.pos - self.start) + self.start;
BuilderProgress::InProgress
}
PenEvent::Up { .. } => {
BuilderProgress::Finished(vec![Shape::Parabola(self.state_as_parabola())])
}
_ => BuilderProgress::InProgress,
};

EventResult {
handled: true,
propagate: EventPropagation::Stop,
progress,
}
}

fn bounds(&self, style: &Style, zoom: f64) -> Option<Aabb> {
Some(
self.state_as_parabola()
.composed_bounds(style)
.loosened(indicators::POS_INDICATOR_RADIUS / zoom),
)
}

fn draw_styled(&self, cx: &mut piet_cairo::CairoRenderContext, style: &Style, zoom: f64) {
cx.save().unwrap();
let parabola = self.state_as_parabola();
parabola.draw_composed(cx, style);

indicators::draw_pos_indicator(cx, PenState::Up, self.start, zoom);
indicators::draw_pos_indicator(cx, PenState::Down, self.current, zoom);
cx.restore().unwrap();
}
}

impl ParabolaBuilder {
/// The current state as a parabola.
pub fn state_as_parabola(&self) -> Parabola {
let center = (self.start + self.current) * 0.5;
let transform = Transform::new_w_isometry(na::Isometry2::new(center, 0.0));
let half_extents = (self.current - self.start) * 0.5;
let cuboid = Cuboid::new(half_extents);

Parabola { cuboid, transform }
}
}
3 changes: 3 additions & 0 deletions crates/rnote-compose/src/shapes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub mod cubbez;
pub mod ellipse;
/// Line
pub mod line;
/// Parabola
pub mod parabola;
/// Polygon
pub mod polygon;
/// Polyline
Expand All @@ -25,6 +27,7 @@ pub use arrow::Arrow;
pub use cubbez::CubicBezier;
pub use ellipse::Ellipse;
pub use line::Line;
pub use parabola::Parabola;
pub use polygon::Polygon;
pub use polyline::Polyline;
pub use quadbez::QuadraticBezier;
Expand Down
153 changes: 153 additions & 0 deletions crates/rnote-compose/src/shapes/parabola.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Imports
use super::Line;
use crate::ext::{AabbExt, KurboShapeExt, Vector2Ext};
use crate::shapes::Shapeable;
use crate::transform::Transformable;
use crate::Transform;
use kurbo::Shape;
use p2d::bounding_volume::Aabb;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(default, rename = "parabola")]
/// A parabola.
pub struct Parabola {
#[serde(rename = "cuboid", with = "crate::serialize::p2d_cuboid_dp3")]
/// The cuboid, specifies the extents.
pub cuboid: p2d::shape::Cuboid,
#[serde(rename = "transform")]
/// The transform of the center of the cuboid.
pub transform: Transform,
}

impl Default for Parabola {
fn default() -> Self {
Self {
cuboid: p2d::shape::Cuboid::new(na::Vector2::zeros()),
transform: Transform::default(),
}
}
}

impl Shapeable for Parabola {
fn bounds(&self) -> Aabb {
let tl = self.transform.affine
* na::point![-self.cuboid.half_extents[0], -self.cuboid.half_extents[1]];
let tr = self.transform.affine
* na::point![self.cuboid.half_extents[0], -self.cuboid.half_extents[1]];
let bm = self.transform.affine * na::point![0.0, 3.0 * self.cuboid.half_extents[1]];

let bez = kurbo::QuadBez::new(
na::Vector3::from(tl).xy().to_kurbo_point(),
na::Vector3::from(bm).xy().to_kurbo_point(),
na::Vector3::from(tr).xy().to_kurbo_point(),
);

bez.bounds_to_p2d_aabb()
}

fn hitboxes(&self) -> Vec<Aabb> {
self.outline_lines()
.into_iter()
.flat_map(|line| line.hitboxes())
.collect()
}

fn outline_path(&self) -> kurbo::BezPath {
let tl = self.transform.affine
* na::point![-self.cuboid.half_extents[0], -self.cuboid.half_extents[1]];
let tr = self.transform.affine
* na::point![self.cuboid.half_extents[0], -self.cuboid.half_extents[1]];
let bm = self.transform.affine * na::point![0.0, 3.0 * self.cuboid.half_extents[1]];

kurbo::QuadBez::new(
na::Vector3::from(tl).xy().to_kurbo_point(),
na::Vector3::from(bm).xy().to_kurbo_point(),
na::Vector3::from(tr).xy().to_kurbo_point(),
)
.to_path(0.25)
}
}

impl Transformable for Parabola {
fn translate(&mut self, offset: na::Vector2<f64>) {
self.transform.append_translation_mut(offset);
}

fn rotate(&mut self, angle: f64, center: na::Point2<f64>) {
self.transform.append_rotation_wrt_point_mut(angle, center)
}

fn scale(&mut self, scale: na::Vector2<f64>) {
self.transform.append_scale_mut(scale);
}
}

impl Parabola {
/// Construct from center and half extents
pub fn from_half_extents(center: na::Vector2<f64>, half_extents: na::Vector2<f64>) -> Self {
let cuboid = p2d::shape::Cuboid::new(half_extents);
let transform = Transform::new_w_isometry(na::Isometry2::new(center, 0.0));

Self { cuboid, transform }
}

/// Construct from corners across from each other.
pub fn from_corners(first: na::Vector2<f64>, second: na::Vector2<f64>) -> Self {
let half_extents = (second - first).abs() * 0.5;
let center = first + (second - first) * 0.5;

let cuboid = p2d::shape::Cuboid::new(half_extents);
let transform = Transform::new_w_isometry(na::Isometry2::new(center, 0.0));

Self { cuboid, transform }
}

/// Construct from bounds.
pub fn from_p2d_aabb(mut bounds: Aabb) -> Self {
bounds.ensure_positive();
let cuboid = p2d::shape::Cuboid::new(bounds.half_extents());
let transform = Transform::new_w_isometry(na::Isometry2::new(bounds.center().coords, 0.0));

Self { cuboid, transform }
}

/// The outlines of the parabola.
pub fn outline_lines(&self) -> [Line; 4] {
let upper_left = self.transform.transform_point(na::point![
-self.cuboid.half_extents[0],
-self.cuboid.half_extents[1]
]);
let upper_right = self.transform.transform_point(na::point![
self.cuboid.half_extents[0],
-self.cuboid.half_extents[1]
]);
let lower_left = self.transform.transform_point(na::point![
-self.cuboid.half_extents[0],
self.cuboid.half_extents[1]
]);
let lower_right = self.transform.transform_point(na::point![
self.cuboid.half_extents[0],
self.cuboid.half_extents[1]
]);

[
Line {
start: upper_left.coords,
end: lower_left.coords,
},
Line {
start: lower_left.coords,
end: lower_right.coords,
},
Line {
start: lower_right.coords,
end: upper_right.coords,
},
Line {
start: upper_right.coords,
end: upper_left.coords,
},
]
}
}
18 changes: 17 additions & 1 deletion crates/rnote-compose/src/shapes/shape.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Imports
use super::{
Arrow, CubicBezier, Ellipse, Line, Polygon, Polyline, QuadraticBezier, Rectangle, Shapeable,
Arrow, CubicBezier, Ellipse, Line, Parabola, Polygon, Polyline, QuadraticBezier, Rectangle,
Shapeable,
};
use crate::transform::Transformable;
use p2d::bounding_volume::Aabb;
Expand Down Expand Up @@ -34,6 +35,9 @@ pub enum Shape {
/// A polygon shape.
#[serde(rename = "polygon")]
Polygon(Polygon),
/// A parabola shape
#[serde(rename = "parabola")]
Parabola(Parabola),
}

impl Default for Shape {
Expand Down Expand Up @@ -69,6 +73,9 @@ impl Transformable for Shape {
Self::Polygon(polygon) => {
polygon.translate(offset);
}
Self::Parabola(parabola) => {
parabola.translate(offset);
}
}
}

Expand Down Expand Up @@ -98,6 +105,9 @@ impl Transformable for Shape {
Self::Polygon(polygon) => {
polygon.rotate(angle, center);
}
Self::Parabola(parabola) => {
parabola.rotate(angle, center);
}
}
}

Expand Down Expand Up @@ -127,6 +137,9 @@ impl Transformable for Shape {
Self::Polygon(polygon) => {
polygon.scale(scale);
}
Self::Parabola(parabola) => {
parabola.scale(scale);
}
}
}
}
Expand All @@ -142,6 +155,7 @@ impl Shapeable for Shape {
Self::CubicBezier(cubbez) => cubbez.bounds(),
Self::Polyline(polyline) => polyline.bounds(),
Self::Polygon(polygon) => polygon.bounds(),
Self::Parabola(parabola) => parabola.bounds(),
}
}

Expand All @@ -155,6 +169,7 @@ impl Shapeable for Shape {
Self::CubicBezier(cubbez) => cubbez.hitboxes(),
Self::Polyline(polyline) => polyline.hitboxes(),
Self::Polygon(polygon) => polygon.hitboxes(),
Self::Parabola(parabola) => parabola.hitboxes(),
}
}

Expand All @@ -168,6 +183,7 @@ impl Shapeable for Shape {
Self::CubicBezier(cubbez) => cubbez.outline_path(),
Self::Polyline(polyline) => polyline.outline_path(),
Self::Polygon(polygon) => polygon.outline_path(),
Self::Parabola(parabola) => parabola.outline_path(),
}
}
}
Loading
Loading