use super::{
context::GraphicsContext, gpu::arc::ArcBuffer, Canvas, Color, Draw, DrawMode, DrawParam,
Drawable, LinearColor, Rect, WgpuContext,
};
use crate::{context::Has, GameError, GameResult};
use lyon::{math::Point as LPoint, path::Polygon, tessellation as tess};
use wgpu::util::DeviceExt;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Zeroable, bytemuck::Pod)]
pub struct Vertex {
pub position: [f32; 2],
pub uv: [f32; 2],
pub color: [f32; 4],
}
impl Vertex {
pub(crate) const fn layout() -> wgpu::VertexBufferLayout<'static> {
const ATTRIBUTES: [wgpu::VertexAttribute; 3] = [
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 8,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: 16,
shader_location: 2,
},
];
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &ATTRIBUTES,
}
}
}
#[derive(Debug, Clone)]
pub struct Mesh {
pub(crate) verts: ArcBuffer,
pub(crate) inds: ArcBuffer,
pub(crate) vertex_count: usize,
pub(crate) index_count: usize,
pub(crate) bounds: Rect,
}
impl Mesh {
pub fn from_data(gfx: &impl Has<GraphicsContext>, raw: MeshData) -> Self {
let gfx = gfx.retrieve();
Self::from_data_wgpu(&gfx.wgpu, raw)
}
pub(crate) fn from_data_wgpu(wgpu: &WgpuContext, raw: MeshData) -> Self {
let [minx, miny, maxx, maxy] = raw.vertices.iter().fold(
[f32::MAX, f32::MAX, f32::MIN, f32::MIN],
|[minx, miny, maxx, maxy], vert| {
let [x, y] = vert.position;
[minx.min(x), miny.min(y), maxx.max(x), maxy.max(y)]
},
);
Mesh {
verts: Self::create_verts(wgpu, raw.vertices),
inds: Self::create_inds(wgpu, raw.indices),
vertex_count: raw.vertices.len(),
index_count: raw.indices.len(),
bounds: Rect {
x: minx,
y: miny,
w: maxx - minx,
h: maxy - miny,
},
}
}
pub fn new_line(
gfx: &impl Has<GraphicsContext>,
points: &[impl Into<mint::Point2<f32>> + Clone],
width: f32,
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new()
.polyline(DrawMode::stroke(width), points, color)?
.build(),
))
}
pub fn new_circle(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
point: impl Into<mint::Point2<f32>>,
radius: f32,
tolerance: f32,
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new()
.circle(mode, point, radius, tolerance, color)?
.build(),
))
}
pub fn new_ellipse(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
point: impl Into<mint::Point2<f32>>,
radius1: f32,
radius2: f32,
tolerance: f32,
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new()
.ellipse(mode, point, radius1, radius2, tolerance, color)?
.build(),
))
}
pub fn new_polyline(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
points: &[impl Into<mint::Point2<f32>> + Clone],
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new().polyline(mode, points, color)?.build(),
))
}
pub fn new_polygon(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
points: &[impl Into<mint::Point2<f32>> + Clone],
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new().polygon(mode, points, color)?.build(),
))
}
pub fn new_rectangle(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
bounds: Rect,
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new().rectangle(mode, bounds, color)?.build(),
))
}
pub fn new_rounded_rectangle(
gfx: &impl Has<GraphicsContext>,
mode: DrawMode,
bounds: Rect,
radius: f32,
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new()
.rounded_rectangle(mode, bounds, radius, color)?
.build(),
))
}
pub fn from_triangles(
gfx: &impl Has<GraphicsContext>,
triangles: &[impl Into<mint::Point2<f32>> + Clone],
color: Color,
) -> GameResult<Self> {
Ok(Mesh::from_data(
gfx,
MeshBuilder::new().triangles(triangles, color)?.build(),
))
}
#[inline]
pub fn wgpu(&self) -> (&wgpu::Buffer, &wgpu::Buffer) {
(&self.verts, &self.inds)
}
#[inline]
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
#[inline]
pub fn index_count(&self) -> usize {
self.index_count
}
#[allow(unsafe_code)]
fn create_verts(wgpu: &WgpuContext, vertices: &[Vertex]) -> ArcBuffer {
ArcBuffer::new(
wgpu.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
}),
)
}
#[allow(unsafe_code)]
fn create_inds(wgpu: &WgpuContext, indices: &[u32]) -> ArcBuffer {
ArcBuffer::new(
wgpu.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
}),
)
}
}
impl Drawable for Mesh {
fn draw(&self, canvas: &mut Canvas, param: impl Into<DrawParam>) {
canvas.push_draw(
Draw::Mesh {
mesh: self.clone(),
image: canvas.default_resources().image.clone(),
scale: false,
},
param.into(),
);
}
fn dimensions(&self, _gfx: &impl Has<GraphicsContext>) -> Option<Rect> {
Some(self.bounds)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Quad;
impl Drawable for Quad {
fn draw(&self, canvas: &mut Canvas, param: impl Into<DrawParam>) {
canvas.default_resources().mesh.clone().draw(canvas, param);
}
fn dimensions(&self, _gfx: &impl Has<GraphicsContext>) -> Option<Rect> {
Some(Rect::one())
}
}
#[derive(Debug, Clone)]
pub struct MeshData<'a> {
pub vertices: &'a [Vertex],
pub indices: &'a [u32],
}
#[derive(Debug, Clone)]
pub struct MeshBuilder {
buffer: tess::geometry_builder::VertexBuffers<Vertex, u32>,
}
impl Default for MeshBuilder {
fn default() -> Self {
Self {
buffer: tess::VertexBuffers::new(),
}
}
}
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 mut tessellator = tess::FillTessellator::new();
tessellator.tessellate_circle(
tess::math::point(point.x, point.y),
radius,
&fill_options.with_tolerance(tolerance),
&mut tess::BuffersBuilder::new(buffers, vb),
)?;
}
DrawMode::Stroke(options) => {
let mut tessellator = tess::StrokeTessellator::new();
tessellator.tessellate_circle(
tess::math::point(point.x, point.y),
radius,
&options.with_tolerance(tolerance),
&mut tess::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) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::FillTessellator::new();
tessellator.tessellate_ellipse(
tess::math::point(point.x, point.y),
tess::math::vector(radius1, radius2),
tess::math::Angle { radians: 0.0 },
tess::path::Winding::Positive,
&fill_options.with_tolerance(tolerance),
builder,
)?;
}
DrawMode::Stroke(options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::StrokeTessellator::new();
tessellator.tessellate_ellipse(
tess::math::point(point.x, point.y),
tess::math::vector(radius1, radius2),
tess::math::Angle { radians: 0.0 },
tess::path::Winding::Positive,
&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: tess::StrokeVertexConstructor<Vertex> + tess::FillVertexConstructor<Vertex>,
{
{
assert!(points.len() > 1);
let buffers = &mut self.buffer;
let points: Vec<LPoint> = points
.iter()
.cloned()
.map(|p| {
let mint_point: mint::Point2<f32> = p.into();
tess::math::point(mint_point.x, mint_point.y)
})
.collect();
let polygon = Polygon {
points: &points,
closed: is_closed,
};
match mode {
DrawMode::Fill(options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let tessellator = &mut tess::FillTessellator::new();
tessellator.tessellate_polygon(polygon, &options, builder)?;
}
DrawMode::Stroke(options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let tessellator = &mut tess::StrokeTessellator::new();
tessellator.tessellate_polygon(polygon, &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 = tess::math::Box2D::from_origin_and_size(
tess::math::point(bounds.x, bounds.y),
tess::math::size(bounds.w, bounds.h),
);
let vb = VertexBuilder {
color: LinearColor::from(color),
};
match mode {
DrawMode::Fill(fill_options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::FillTessellator::new();
tessellator.tessellate_rectangle(&rect, &fill_options, builder)?;
}
DrawMode::Stroke(options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::StrokeTessellator::new();
tessellator.tessellate_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 = tess::math::Box2D::from_origin_and_size(
tess::math::point(bounds.x, bounds.y),
tess::math::size(bounds.w, bounds.h),
);
let radii = tess::path::builder::BorderRadii::new(radius);
let vb = VertexBuilder {
color: LinearColor::from(color),
};
let mut path_builder = tess::path::Path::builder();
path_builder.add_rounded_rectangle(&rect, &radii, tess::path::Winding::Positive);
let path = path_builder.build();
match mode {
DrawMode::Fill(fill_options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::FillTessellator::new();
tessellator.tessellate_path(&path, &fill_options, builder)?;
}
DrawMode::Stroke(options) => {
let builder = &mut tess::BuffersBuilder::new(buffers, vb);
let mut tessellator = tess::StrokeTessellator::new();
tessellator.tessellate_path(&path, &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),
};
for tri in tris {
assert!(tri.len() == 3);
let first_index: u32 = self.buffer.vertices.len().try_into().unwrap(); self.buffer.vertices.push(vb.new_vertex(tri[0]));
self.buffer.vertices.push(vb.new_vertex(tri[1]));
self.buffer.vertices.push(vb.new_vertex(tri[2]));
self.buffer.indices.push(first_index);
self.buffer.indices.push(first_index + 1);
self.buffer.indices.push(first_index + 2);
}
}
Ok(self)
}
pub fn build(&self) -> MeshData {
MeshData {
vertices: &self.buffer.vertices,
indices: &self.buffer.indices,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
struct VertexBuilder {
color: LinearColor,
}
impl VertexBuilder {
fn new_vertex(self, position: LPoint) -> Vertex {
Vertex {
position: [position.x, position.y],
uv: [position.x, position.y],
color: self.color.into(),
}
}
}
impl tess::StrokeVertexConstructor<Vertex> for VertexBuilder {
fn new_vertex(&mut self, vertex: tess::StrokeVertex) -> Vertex {
let position = vertex.position();
Vertex {
position: [position.x, position.y],
uv: [0.0, 0.0],
color: self.color.into(),
}
}
}
impl tess::FillVertexConstructor<Vertex> for VertexBuilder {
fn new_vertex(&mut self, vertex: tess::FillVertex) -> Vertex {
let position = vertex.position();
Vertex {
position: [position.x, position.y],
uv: [0.0, 0.0],
color: self.color.into(),
}
}
}