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

Mesh with custom tolerance #100

Merged
merged 3 commits into from
Sep 25, 2019
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
performing a slow task.
- `ui::Image`, a simple widget to display a `graphics::Image` in your user
interface.
- `Mesh::new_with_tolerance`, which allows to control the tolerance of line
segment approximations. [#100]

### Changed
- `Mesh::stroke` now takes an `f32` as `line_width` instead of a `u16`.
Expand All @@ -37,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- any button click [#67]
- wheel movements [#67]
- the cursor leaving/entering the game window [#67]
- The `mesh` example now has a slider to control the tolerance. [#100]

### Fixed
- Hang when `Game::TICKS_PER_SECOND` is set as `0`. [#99]
Expand All @@ -48,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#78]: https://github.com/hecrj/coffee/pull/78
[#79]: https://github.com/hecrj/coffee/pull/79
[#99]: https://github.com/hecrj/coffee/pull/99
[#100]: https://github.com/hecrj/coffee/pull/100


## [0.3.2] - 2019-09-01
Expand Down
29 changes: 26 additions & 3 deletions examples/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ fn main() -> Result<()> {
struct Example {
shape: ShapeOption,
mode: ModeOption,
tolerance: f32,
stroke_width: u16,
radius: f32,
vertical_radius: f32,
color: Color,
polyline_points: Vec<Point>,

tolerance_slider: slider::State,
stroke_width_slider: slider::State,
radius_slider: slider::State,
vertical_radius_slider: slider::State,
Expand Down Expand Up @@ -58,12 +60,14 @@ impl Game for Example {
Task::succeed(move || Example {
shape: ShapeOption::Rectangle,
mode: ModeOption::Fill,
tolerance: 0.1,
color: Color::WHITE,
radius: 100.0,
vertical_radius: 50.0,
stroke_width: 2,
polyline_points: Vec::new(),

tolerance_slider: slider::State::new(),
stroke_width_slider: slider::State::new(),
radius_slider: slider::State::new(),
vertical_radius_slider: slider::State::new(),
Expand All @@ -89,7 +93,7 @@ impl Game for Example {
a: 1.0,
});

let mut mesh = Mesh::new();
let mut mesh = Mesh::new_with_tolerance(self.tolerance);

let shape = match self.shape {
ShapeOption::Rectangle => Shape::Rectangle(Rectangle {
Expand Down Expand Up @@ -138,6 +142,9 @@ impl UserInterface for Example {
Message::ModeSelected(mode) => {
self.mode = mode;
}
Message::ToleranceChanged(tolerance) => {
self.tolerance = tolerance;
}
Message::StrokeWidthChanged(stroke_width) => {
self.stroke_width = stroke_width;
}
Expand Down Expand Up @@ -206,8 +213,9 @@ impl UserInterface for Example {
}
}

controls =
controls.push(color_sliders(&mut self.color_sliders, self.color));
controls = controls
.push(color_sliders(&mut self.color_sliders, self.color))
.push(tolerance_slider(&mut self.tolerance_slider, self.tolerance));

Column::new()
.width(window.width() as u32)
Expand All @@ -225,6 +233,7 @@ impl UserInterface for Example {
enum Message {
ShapeSelected(ShapeOption),
ModeSelected(ModeOption),
ToleranceChanged(f32),
StrokeWidthChanged(u16),
RadiusChanged(f32),
VerticalRadiusChanged(f32),
Expand Down Expand Up @@ -256,6 +265,20 @@ fn shape_selector(current: ShapeOption) -> Element<'static, Message> {
.into()
}

fn tolerance_slider(
state: &mut slider::State,
tolerance: f32,
) -> Element<Message> {
slider_with_label(
"Tolerance:",
state,
0.001..=20.0,
tolerance,
&format!("{:.*}", 3, tolerance),
Message::ToleranceChanged,
)
}

fn mode_selector(current: ModeOption) -> Element<'static, Message> {
let options = [ModeOption::Fill, ModeOption::Stroke].iter().cloned().fold(
Row::new().padding(10).spacing(10),
Expand Down
59 changes: 46 additions & 13 deletions src/graphics/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,46 @@ use crate::graphics::{gpu, Color, Rectangle, Shape, Target};
use lyon_tessellation as lyon;

/// A set of shapes that can be drawn.
///
/// # Tolerance
/// When shapes contain curves or arcs, they will be approximated using line
/// segments. The `tolerance` parameter controls this approximation by
/// establishing the maximum distance between a curve and its line segments.
///
/// The lower the tolerance provided, the better a [`Mesh`] will approximate
/// a [`Shape`]. However, a lower tolerance can have a noticeable performance
/// impact. Use it wisely!
///
/// [`Mesh`]: struct.Mesh.html
/// [`Shape`]: enum.Shape.html
#[derive(Debug, Clone)]
pub struct Mesh {
tolerance: f32,
buffers: lyon::VertexBuffers<gpu::Vertex, u32>,
}

impl Mesh {
/// Creates a new empty [`Mesh`].
/// Creates a new empty [`Mesh`] with a default tolerance of `0.1`.
///
/// [`Mesh`]: struct.Mesh.html
pub fn new() -> Mesh {
Mesh {
tolerance: 0.1,
buffers: lyon::VertexBuffers::new(),
}
}

/// Creates a new empty [`Mesh`] with the given tolerance.
///
/// Providing a lower tolerance here can allow you to zoom in your [`Mesh`]
/// using a [`Transformation`] and still observe smooth curves. See
/// [Tolerance](#tolerance).
///
/// [`Mesh`]: struct.Mesh.html
/// [`Transformation`]: struct.Transformation.html
pub fn new_with_tolerance(tolerance: f32) -> Mesh {
Mesh {
tolerance,
buffers: lyon::VertexBuffers::new(),
}
}
Expand Down Expand Up @@ -45,7 +74,7 @@ impl Mesh {
}) => {
let _ = lyon::basic_shapes::fill_rectangle(
&lyon::math::rect(x, y, width, height),
&Self::fill_options(),
&Self::fill_options(self.tolerance),
&mut builder,
)
.expect("Fill rectangle");
Expand All @@ -54,7 +83,7 @@ impl Mesh {
let _ = lyon::basic_shapes::fill_circle(
lyon::math::point(center.x, center.y),
radius,
&Self::fill_options(),
&Self::fill_options(self.tolerance),
&mut builder,
)
.expect("Fill circle");
Expand All @@ -69,7 +98,7 @@ impl Mesh {
lyon::math::point(center.x, center.y),
lyon::math::vector(horizontal_radius, vertical_radius),
lyon::math::Angle::radians(rotation),
&Self::fill_options(),
&Self::fill_options(self.tolerance),
&mut builder,
)
.expect("Fill ellipse");
Expand All @@ -80,7 +109,7 @@ impl Mesh {
.iter()
.map(|point| lyon::math::point(point.x, point.y)),
&mut lyon::FillTessellator::new(),
&Self::fill_options(),
&Self::fill_options(self.tolerance),
&mut builder,
)
.expect("Fill polyline");
Expand Down Expand Up @@ -108,7 +137,7 @@ impl Mesh {
}) => {
let _ = lyon::basic_shapes::stroke_rectangle(
&lyon::math::rect(x, y, rect_width, height),
&Self::stroke_options(width),
&Self::stroke_options(self.tolerance, width),
&mut builder,
)
.expect("Stroke rectangle");
Expand All @@ -117,7 +146,7 @@ impl Mesh {
let _ = lyon::basic_shapes::stroke_circle(
lyon::math::point(center.x, center.y),
radius,
&Self::stroke_options(width),
&Self::stroke_options(self.tolerance, width),
&mut builder,
)
.expect("Stroke circle");
Expand All @@ -132,7 +161,7 @@ impl Mesh {
lyon::math::point(center.x, center.y),
lyon::math::vector(horizontal_radius, vertical_radius),
lyon::math::Angle::radians(rotation),
&Self::stroke_options(width),
&Self::stroke_options(self.tolerance, width),
&mut builder,
)
.expect("Stroke ellipse");
Expand All @@ -143,7 +172,7 @@ impl Mesh {
.iter()
.map(|point| lyon::math::point(point.x, point.y)),
false,
&Self::stroke_options(width),
&Self::stroke_options(self.tolerance, width),
&mut builder,
)
.expect("Stroke polyline");
Expand All @@ -159,12 +188,16 @@ impl Mesh {
target.draw_triangles(&self.buffers.vertices, &self.buffers.indices);
}

fn fill_options() -> lyon::FillOptions {
lyon::FillOptions::DEFAULT.with_normals(false)
fn fill_options(tolerance: f32) -> lyon::FillOptions {
lyon::FillOptions::DEFAULT
.with_tolerance(tolerance)
.with_normals(false)
}

fn stroke_options(width: f32) -> lyon::StrokeOptions {
lyon::StrokeOptions::DEFAULT.with_line_width(width)
fn stroke_options(tolerance: f32, width: f32) -> lyon::StrokeOptions {
lyon::StrokeOptions::DEFAULT
.with_tolerance(tolerance)
.with_line_width(width)
}
}

Expand Down