use crate::context::DebugId;
use crate::error::GameError;
use crate::graphics::*;
use gfx::traits::FactoryExt;
use lyon::tessellation as t;
use lyon::{self, math::Point as LPoint};
pub use self::t::{FillOptions, FillRule, LineCap, LineJoin, StrokeOptions};
#[derive(Debug, Clone)]
pub struct MeshBuilder {
buffer: t::geometry_builder::VertexBuffers<Vertex, u32>,
image: Option<Image>,
}
impl Default for MeshBuilder {
fn default() -> Self {
Self {
buffer: t::VertexBuffers::new(),
image: None,
}
}
}
impl MeshBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn line<P>(&mut self, points: &[P], width: f32, color: Color) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
{
self.polyline(DrawMode::stroke(width), points, color)
}
pub fn circle<P>(
&mut self,
mode: DrawMode,
point: P,
radius: f32,
tolerance: f32,
color: Color,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>>,
{
assert!(
tolerance > 0.0,
"Tolerances <= 0 are invalid, see https://github.com/ggez/ggez/issues/892"
);
{
let point = point.into();
let buffers = &mut self.buffer;
let vb = VertexBuilder {
color: LinearColor::from(color),
};
match mode {
DrawMode::Fill(fill_options) => {
let _ = t::basic_shapes::fill_circle(
t::math::point(point.x, point.y),
radius,
&fill_options.with_tolerance(tolerance),
&mut t::BuffersBuilder::new(buffers, vb),
);
}
DrawMode::Stroke(options) => {
let _ = t::basic_shapes::stroke_circle(
t::math::point(point.x, point.y),
radius,
&options.with_tolerance(tolerance),
&mut t::BuffersBuilder::new(buffers, vb),
);
}
};
}
Ok(self)
}
pub fn ellipse<P>(
&mut self,
mode: DrawMode,
point: P,
radius1: f32,
radius2: f32,
tolerance: f32,
color: Color,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>>,
{
assert!(
tolerance > 0.0,
"Tolerances <= 0 are invalid, see https://github.com/ggez/ggez/issues/892"
);
{
let buffers = &mut self.buffer;
let point = point.into();
let vb = VertexBuilder {
color: LinearColor::from(color),
};
match mode {
DrawMode::Fill(_fill_options) => {
unimplemented!()
}
DrawMode::Stroke(options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ = t::basic_shapes::stroke_ellipse(
t::math::point(point.x, point.y),
t::math::vector(radius1, radius2),
t::math::Angle { radians: 0.0 },
&options.with_tolerance(tolerance),
builder,
);
}
};
}
Ok(self)
}
pub fn polyline<P>(
&mut self,
mode: DrawMode,
points: &[P],
color: Color,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
{
if points.len() < 2 {
return Err(GameError::LyonError(
"MeshBuilder::polyline() got a list of < 2 points".to_string(),
));
}
self.polyline_inner(mode, points, false, color)
}
pub fn polygon<P>(
&mut self,
mode: DrawMode,
points: &[P],
color: Color,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
{
if points.len() < 3 {
return Err(GameError::LyonError(
"MeshBuilder::polygon() got a list of < 3 points".to_string(),
));
}
self.polyline_inner(mode, points, true, color)
}
fn polyline_inner<P>(
&mut self,
mode: DrawMode,
points: &[P],
is_closed: bool,
color: Color,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
{
let vb = VertexBuilder {
color: LinearColor::from(color),
};
self.polyline_with_vertex_builder(mode, points, is_closed, vb)
}
pub fn polyline_with_vertex_builder<P, V>(
&mut self,
mode: DrawMode,
points: &[P],
is_closed: bool,
vb: V,
) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
V: t::BasicVertexConstructor<Vertex>
+ t::StrokeVertexConstructor<Vertex>
+ t::FillVertexConstructor<Vertex>,
{
{
assert!(points.len() > 1);
let buffers = &mut self.buffer;
let points = points.iter().cloned().map(|p| {
let mint_point: mint::Point2<f32> = p.into();
t::math::point(mint_point.x, mint_point.y)
});
match mode {
DrawMode::Fill(options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let tessellator = &mut t::FillTessellator::new();
let _ = t::basic_shapes::fill_polyline(points, tessellator, &options, builder)?;
}
DrawMode::Stroke(options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ = t::basic_shapes::stroke_polyline(points, is_closed, &options, builder);
}
};
}
Ok(self)
}
pub fn rectangle(
&mut self,
mode: DrawMode,
bounds: Rect,
color: Color,
) -> GameResult<&mut Self> {
{
let buffers = &mut self.buffer;
let rect = t::math::rect(bounds.x, bounds.y, bounds.w, bounds.h);
let vb = VertexBuilder {
color: LinearColor::from(color),
};
match mode {
DrawMode::Fill(fill_options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ = t::basic_shapes::fill_rectangle(&rect, &fill_options, builder);
}
DrawMode::Stroke(options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ = t::basic_shapes::stroke_rectangle(&rect, &options, builder);
}
};
}
Ok(self)
}
pub fn rounded_rectangle(
&mut self,
mode: DrawMode,
bounds: Rect,
radius: f32,
color: Color,
) -> GameResult<&mut Self> {
{
let buffers = &mut self.buffer;
let rect = t::math::rect(bounds.x, bounds.y, bounds.w, bounds.h);
let radii = t::basic_shapes::BorderRadii::new_all_same(radius);
let vb = VertexBuilder {
color: LinearColor::from(color),
};
match mode {
DrawMode::Fill(fill_options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ = t::basic_shapes::fill_rounded_rectangle(
&rect,
&radii,
&fill_options,
builder,
);
}
DrawMode::Stroke(options) => {
let builder = &mut t::BuffersBuilder::new(buffers, vb);
let _ =
t::basic_shapes::stroke_rounded_rectangle(&rect, &radii, &options, builder);
}
};
}
Ok(self)
}
pub fn triangles<P>(&mut self, triangles: &[P], color: Color) -> GameResult<&mut Self>
where
P: Into<mint::Point2<f32>> + Clone,
{
{
if (triangles.len() % 3) != 0 {
return Err(GameError::LyonError(String::from(
"Called Mesh::triangles() with points that have a length not a multiple of 3.",
)));
}
let tris = triangles
.iter()
.cloned()
.map(|p| {
let mint_point = p.into();
lyon::math::point(mint_point.x, mint_point.y)
})
.collect::<Vec<_>>();
let tris = tris.chunks(3);
let vb = VertexBuilder {
color: LinearColor::from(color),
};
let builder: &mut t::BuffersBuilder<_, _, _> =
&mut t::BuffersBuilder::new(&mut self.buffer, vb);
use lyon::tessellation::BasicGeometryBuilder;
for tri in tris {
assert!(tri.len() == 3);
let fst = tri[0];
let snd = tri[1];
let thd = tri[2];
let _i1 = builder.add_vertex(fst)?;
let _i2 = builder.add_vertex(snd)?;
let _i3 = builder.add_vertex(thd)?;
}
}
Ok(self)
}
pub fn texture(&mut self, texture: Image) -> GameResult<&mut Self> {
self.image = Some(texture);
Ok(self)
}
pub fn raw<V>(
&mut self,
verts: &[V],
indices: &[u32],
texture: Option<Image>,
) -> GameResult<&mut Self>
where
V: Into<Vertex> + Clone,
{
assert!(self.buffer.vertices.len() + verts.len() < (std::u32::MAX as usize));
assert!(self.buffer.indices.len() + indices.len() < (std::u32::MAX as usize));
let next_idx = self.buffer.vertices.len() as u32;
let vertices = verts.iter().cloned().map(|v: V| -> Vertex { v.into() });
let indices = indices.iter().map(|i| (*i) + next_idx);
self.buffer.vertices.extend(vertices);
self.buffer.indices.extend(indices);
self.image = texture;
Ok(self)
}
pub fn build(&self, ctx: &mut Context) -> GameResult<Mesh> {
Mesh::from_raw(
ctx,
&self.buffer.vertices,
&self.buffer.indices,
self.image.clone(),
)
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
struct VertexBuilder {
color: LinearColor,
}
impl t::BasicVertexConstructor<Vertex> for VertexBuilder {
fn new_vertex(&mut self, position: LPoint) -> Vertex {
Vertex {
pos: [position.x, position.y],
uv: [position.x, position.y],
color: self.color.into(),
}
}
}
impl t::StrokeVertexConstructor<Vertex> for VertexBuilder {
fn new_vertex(&mut self, position: LPoint, _attributes: t::StrokeAttributes) -> Vertex {
Vertex {
pos: [position.x, position.y],
uv: [0.0, 0.0],
color: self.color.into(),
}
}
}
impl t::FillVertexConstructor<Vertex> for VertexBuilder {
fn new_vertex(&mut self, position: LPoint, _attributes: t::FillAttributes) -> Vertex {
Vertex {
pos: [position.x, position.y],
uv: [0.0, 0.0],
color: self.color.into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mesh {
buffer: gfx::handle::Buffer<gfx_device_gl::Resources, Vertex>,
slice: gfx::Slice<gfx_device_gl::Resources>,
blend_mode: Option<BlendMode>,
image: Image,
debug_id: DebugId,
rect: Rect,
}
impl Mesh {
pub fn new_line<P>(
ctx: &mut Context,
points: &[P],
width: f32,
color: Color,
) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>> + Clone,
{
let mut mb = MeshBuilder::new();
let _ = mb.polyline(DrawMode::stroke(width), points, color);
mb.build(ctx)
}
pub fn new_circle<P>(
ctx: &mut Context,
mode: DrawMode,
point: P,
radius: f32,
tolerance: f32,
color: Color,
) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>>,
{
let mut mb = MeshBuilder::new();
let _ = mb.circle(mode, point, radius, tolerance, color);
mb.build(ctx)
}
pub fn new_ellipse<P>(
ctx: &mut Context,
mode: DrawMode,
point: P,
radius1: f32,
radius2: f32,
tolerance: f32,
color: Color,
) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>>,
{
let mut mb = MeshBuilder::new();
let _ = mb.ellipse(mode, point, radius1, radius2, tolerance, color);
mb.build(ctx)
}
pub fn new_polyline<P>(
ctx: &mut Context,
mode: DrawMode,
points: &[P],
color: Color,
) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>> + Clone,
{
let mut mb = MeshBuilder::new();
let _ = mb.polyline(mode, points, color);
mb.build(ctx)
}
pub fn new_polygon<P>(
ctx: &mut Context,
mode: DrawMode,
points: &[P],
color: Color,
) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>> + Clone,
{
if points.len() < 3 {
return Err(GameError::LyonError(
"Mesh::new_polygon() got a list of < 3 points".to_string(),
));
}
let mut mb = MeshBuilder::new();
let _ = mb.polygon(mode, points, color);
mb.build(ctx)
}
pub fn new_rectangle(
ctx: &mut Context,
mode: DrawMode,
bounds: Rect,
color: Color,
) -> GameResult<Mesh> {
let mut mb = MeshBuilder::new();
let _ = mb.rectangle(mode, bounds, color);
mb.build(ctx)
}
pub fn new_rounded_rectangle(
ctx: &mut Context,
mode: DrawMode,
bounds: Rect,
radius: f32,
color: Color,
) -> GameResult<Mesh> {
let mut mb = MeshBuilder::new();
let _ = mb.rounded_rectangle(mode, bounds, radius, color);
mb.build(ctx)
}
pub fn from_triangles<P>(ctx: &mut Context, triangles: &[P], color: Color) -> GameResult<Mesh>
where
P: Into<mint::Point2<f32>> + Clone,
{
let mut mb = MeshBuilder::new();
let _ = mb.triangles(triangles, color);
mb.build(ctx)
}
pub fn from_raw<V>(
ctx: &mut Context,
verts: &[V],
indices: &[u32],
texture: Option<Image>,
) -> GameResult<Mesh>
where
V: Into<Vertex> + Clone,
{
if verts.len() > (std::u32::MAX as usize) {
let msg = format!(
"Tried to build a mesh with {} vertices, max is u32::MAX",
verts.len()
);
return Err(GameError::LyonError(msg));
}
if indices.len() > (std::u32::MAX as usize) {
let msg = format!(
"Tried to build a mesh with {} indices, max is u32::MAX",
indices.len()
);
return Err(GameError::LyonError(msg));
}
if verts.len() < 3 {
let msg = String::from("Trying to build mesh with < 3 vertices, this is usually due to invalid input to a `Mesh` or MeshBuilder`.");
return Err(GameError::LyonError(msg));
}
if indices.len() < 3 {
let msg = format!("Trying to build mesh with < 3 indices, this is usually due to invalid input to a `Mesh` or MeshBuilder`. Indices:\n {:#?}", indices);
return Err(GameError::LyonError(msg));
}
if indices.len() % 3 != 0 {
let msg = String::from("Trying to build mesh with an array of indices that is not a multiple of 3, this is usually due to invalid input to a `Mesh` or MeshBuilder`.");
return Err(GameError::LyonError(msg));
}
let verts: Vec<Vertex> = verts.iter().cloned().map(Into::into).collect();
let rect = bbox_for_vertices(&verts).expect(
"No vertices in MeshBuilder; should never happen since we already checked this",
);
let (vbuf, slice) = ctx
.gfx_context
.factory
.create_vertex_buffer_with_slice(&verts[..], indices);
Ok(Mesh {
buffer: vbuf,
slice,
blend_mode: None,
image: texture.unwrap_or_else(|| ctx.gfx_context.white_image.clone()),
debug_id: DebugId::get(ctx),
rect,
})
}
pub fn set_vertices(&mut self, ctx: &mut Context, verts: &[Vertex], indices: &[u32]) {
let (vbuf, slice) = ctx
.gfx_context
.factory
.create_vertex_buffer_with_slice(verts, indices);
self.buffer = vbuf;
self.slice = slice;
}
pub fn get_slice(&self) -> &gfx::Slice<gfx_device_gl::Resources> {
&self.slice
}
pub fn get_vertex_buffer(&self) -> gfx::handle::Buffer<gfx_device_gl::Resources, Vertex> {
self.buffer.clone()
}
}
impl Drawable for Mesh {
fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
self.debug_id.assert(ctx);
let gfx = &mut ctx.gfx_context;
gfx.update_instance_properties(param)?;
gfx.data.vbuf = self.buffer.clone();
let texture = self.image.texture.clone();
let sampler = gfx
.samplers
.get_or_insert(self.image.sampler_info, gfx.factory.as_mut());
let typed_thingy = gfx.backend_spec.raw_to_typed_shader_resource(texture);
gfx.data.tex = (typed_thingy, sampler);
gfx.draw(Some(&self.slice))?;
Ok(())
}
fn dimensions(&self, _ctx: &mut Context) -> Option<Rect> {
Some(self.rect)
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
}
fn blend_mode(&self) -> Option<BlendMode> {
self.blend_mode
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MeshIdx(pub usize);
#[derive(Debug)]
pub struct MeshBatch {
mesh: Mesh,
instance_params: Vec<DrawParam>,
instance_buffer: Option<gfx::handle::Buffer<gfx_device_gl::Resources, InstanceProperties>>,
instance_buffer_dirty: bool,
}
impl MeshBatch {
pub fn new(mesh: Mesh) -> GameResult<MeshBatch> {
Ok(MeshBatch {
mesh,
instance_params: Vec::new(),
instance_buffer: None,
instance_buffer_dirty: true,
})
}
pub fn clear(&mut self) {
self.instance_params.clear();
self.instance_buffer_dirty = true;
}
pub fn get_instance_params(&self) -> &[DrawParam] {
&self.instance_params
}
pub fn get_instance_params_mut(&mut self) -> &mut [DrawParam] {
&mut self.instance_params
}
pub fn add<P>(&mut self, param: P) -> MeshIdx
where
P: Into<DrawParam>,
{
self.instance_params.push(param.into());
self.instance_buffer_dirty = true;
MeshIdx(self.instance_params.len() - 1)
}
pub fn set<P>(&mut self, handle: MeshIdx, param: P) -> GameResult
where
P: Into<DrawParam>,
{
if handle.0 < self.instance_params.len() {
self.instance_params[handle.0] = param.into();
self.instance_buffer_dirty = true;
Ok(())
} else {
Err(GameError::RenderError(String::from("Index out of bounds")))
}
}
pub fn set_range<P>(&mut self, first_handle: MeshIdx, params: &[P]) -> GameResult
where
P: Into<DrawParam> + Copy,
{
let first_param = first_handle.0;
let num_params = params.len();
if first_param < self.instance_params.len()
&& (first_param + num_params) <= self.instance_params.len()
{
for (i, item) in params.iter().enumerate().take(num_params) {
self.instance_params[first_param + i] = (*item).into();
}
self.instance_buffer_dirty = true;
Ok(())
} else {
Err(GameError::RenderError(String::from("Range out of bounds")))
}
}
pub fn flush_range(
&mut self,
ctx: &mut Context,
first_handle: MeshIdx,
count: usize,
) -> GameResult {
let first_param = first_handle.0;
let slice_len = first_param + count;
if first_param < self.instance_params.len() && slice_len <= self.instance_params.len() {
let needs_new_buffer = self.instance_buffer == None
|| self.instance_buffer.as_ref().unwrap().len() < slice_len;
let slice = if needs_new_buffer {
&self.instance_params
} else {
&self.instance_params[first_param..slice_len]
};
let new_properties: Vec<InstanceProperties> = slice
.iter()
.map(|param| param.to_instance_properties(ctx.gfx_context.is_srgb()))
.collect();
if needs_new_buffer {
let new_buffer = ctx.gfx_context.factory.create_buffer(
new_properties.len(),
gfx::buffer::Role::Vertex,
gfx::memory::Usage::Dynamic,
gfx::memory::Bind::TRANSFER_DST,
)?;
self.instance_buffer = Some(new_buffer);
ctx.gfx_context.encoder.update_buffer(
&self.instance_buffer.as_ref().expect("Can never fail"),
new_properties.as_slice(),
0,
)?;
} else {
ctx.gfx_context.encoder.update_buffer(
&self.instance_buffer.as_ref().expect("Should never fail"),
new_properties.as_slice(),
first_param,
)?;
}
self.instance_buffer_dirty = false;
Ok(())
} else {
Err(GameError::RenderError(String::from("Range out of bounds")))
}
}
pub fn flush(&mut self, ctx: &mut Context) -> GameResult {
self.flush_range(ctx, MeshIdx(0), self.instance_params.len())
}
pub fn draw(&mut self, ctx: &mut Context, param: DrawParam) -> GameResult {
if !self.instance_params.is_empty() {
self.mesh.debug_id.assert(ctx);
if !self.instance_params.is_empty() && self.instance_buffer_dirty {
self.flush(ctx)?;
}
let mut slice = self.mesh.slice.clone();
slice.instances = Some((self.instance_params.len() as u32, 0));
let gfx = &mut ctx.gfx_context;
let batch_transform = Matrix4::from(param.trans.to_bare_matrix());
gfx.set_global_mvp(batch_transform)?;
let instance_buffer = self.instance_buffer.as_ref().expect("Should never fail");
let old_instance_buffer = gfx.data.rect_instance_properties.clone();
gfx.data.rect_instance_properties = instance_buffer.clone();
gfx.data.vbuf = self.mesh.buffer.clone();
let texture = self.mesh.image.texture.clone();
let sampler = gfx
.samplers
.get_or_insert(self.mesh.image.sampler_info, gfx.factory.as_mut());
let typed_thingy = gfx.backend_spec.raw_to_typed_shader_resource(texture);
gfx.data.tex = (typed_thingy, sampler);
gfx.draw(Some(&slice))?;
gfx.data.rect_instance_properties = old_instance_buffer;
gfx.set_global_mvp(Matrix4::identity())?;
}
Ok(())
}
pub fn dimensions(&self, ctx: &mut Context) -> Option<Rect> {
self.mesh.dimensions(ctx)
}
pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.mesh.set_blend_mode(mode)
}
pub fn blend_mode(&self) -> Option<BlendMode> {
self.mesh.blend_mode()
}
}
fn bbox_for_vertices(verts: &[Vertex]) -> Option<Rect> {
if verts.is_empty() {
return None;
}
let [x0, y0] = verts[0].pos;
let mut x_max = x0;
let mut x_min = x0;
let mut y_max = y0;
let mut y_min = y0;
for v in verts {
let x = v.pos[0];
let y = v.pos[1];
x_max = f32::max(x_max, x);
x_min = f32::min(x_min, x);
y_max = f32::max(y_max, y);
y_min = f32::min(y_min, y);
}
Some(Rect {
w: x_max - x_min,
h: y_max - y_min,
x: x_min,
y: y_min,
})
}