use crate::{
CameraView, DrawOrder, GpuRenderer, GraphicsError, Index, Mesh2DVertex,
OtherError, Vec2, Vec3, Vec4, VertexBuilder, vbo::OrderedIndex,
};
use cosmic_text::Color;
use lyon::{
lyon_tessellation::{FillOptions, StrokeOptions},
math::Point as LPoint,
path::Polygon,
tessellation as tess,
};
#[derive(Debug, Copy, Clone)]
pub enum DrawMode {
Stroke(StrokeOptions),
Fill(FillOptions),
}
impl DrawMode {
pub fn stroke(width: f32) -> DrawMode {
DrawMode::Stroke(StrokeOptions::default().with_line_width(width))
}
pub fn fill() -> DrawMode {
DrawMode::Fill(FillOptions::default())
}
}
#[derive(Debug)]
pub struct Mesh2D {
pub pos: Vec3,
pub size: Vec2,
pub vertices: Vec<Mesh2DVertex>,
pub indices: Vec<u32>,
pub vbo_store_id: Index,
pub order: DrawOrder,
pub high_index: u32,
pub changed: bool,
}
impl Mesh2D {
pub fn new(
renderer: &mut GpuRenderer,
pos: Vec3,
order_layer: u32,
) -> Self {
Self {
pos,
size: Vec2::default(),
vbo_store_id: renderer.default_vbo_store(),
order: DrawOrder::new(false, Vec3::default(), order_layer),
changed: true,
vertices: Vec::new(),
indices: Vec::new(),
high_index: 0,
}
}
pub fn with_capacity(
renderer: &mut GpuRenderer,
pos: Vec3,
order_layer: u32,
capacity: usize,
) -> Self {
Self {
pos,
size: Vec2::default(),
vbo_store_id: renderer.default_vbo_store(),
order: DrawOrder::new(false, Vec3::default(), order_layer),
changed: true,
vertices: Vec::with_capacity(capacity),
indices: Vec::with_capacity(capacity),
high_index: 0,
}
}
pub fn unload(self, renderer: &mut GpuRenderer) {
renderer.remove_vbo_store(self.vbo_store_id);
}
pub fn set_order_pos(&mut self, order_override: Vec3) -> &mut Self {
self.order.set_pos(order_override);
self
}
pub fn set_order_layer(&mut self, order_layer: u32) -> &mut Self {
self.order.order_layer = order_layer;
self
}
pub fn set_order_alpha(&mut self, alpha: bool) -> &mut Self {
self.order.alpha = alpha;
self
}
pub fn from_builder(&mut self, builder: &Mesh2DBuilder) {
let mut alpha = false;
self.vertices.clear();
self.indices.clear();
self.size = Vec2::new(
builder.bounds.z - builder.bounds.x,
builder.bounds.w - builder.bounds.y,
);
self.vertices.reserve(builder.buffer.vertices.len());
for vertex in &builder.buffer.vertices {
let mut vertex = *vertex;
vertex.pos[0] += builder.offset.x;
vertex.pos[1] += builder.offset.y;
if Color(vertex.color).a() < 255 {
alpha = true
}
self.vertices.push(vertex);
}
self.indices.extend_from_slice(&builder.buffer.indices);
self.order.alpha = alpha;
self.high_index = builder.high_index;
self.changed = true;
}
pub fn append_from_builder(&mut self, builder: &Mesh2DBuilder) {
let mut alpha = self.order.alpha;
let mut new_high_index = self.high_index;
let size = Vec2::new(
builder.bounds.z - builder.bounds.x,
builder.bounds.w - builder.bounds.y,
);
self.size = self.size.max(size);
self.vertices.reserve(builder.buffer.vertices.len());
for vertex in &builder.buffer.vertices {
let mut vertex = *vertex;
vertex.pos[0] += builder.offset.x;
vertex.pos[1] += builder.offset.y;
if Color(vertex.color).a() < 255 {
alpha = true
}
self.vertices.push(vertex);
}
for index in &builder.buffer.indices {
let i = index
+ if self.high_index == 0 {
0
} else {
self.high_index + 1
};
new_high_index = new_high_index.max(i);
self.indices.push(i);
}
self.order.alpha = alpha;
self.high_index = new_high_index;
self.changed = true;
}
pub fn clear(&mut self) {
self.vertices.clear();
self.indices.clear();
self.high_index = 0;
self.order.alpha = false;
self.changed = true;
}
pub fn set_pos(&mut self, pos: Vec3) -> &mut Self {
self.pos = pos;
self.order.set_pos(pos);
self.changed = true;
self
}
pub fn set_size(&mut self, size: Vec2) -> &mut Self {
self.size = size;
self.changed = true;
self
}
pub fn create_quad(&mut self, renderer: &mut GpuRenderer) {
if let Some(store) = renderer.get_vbo_store_mut(self.vbo_store_id) {
let mut verticies = Vec::with_capacity(self.vertices.len());
for vertex in &self.vertices {
let mut v = *vertex;
v.pos[0] += self.pos.x;
v.pos[1] += self.pos.y;
verticies.push(v);
}
let vertex_bytes: &[u8] = bytemuck::cast_slice(&verticies);
let index_bytes: &[u8] = bytemuck::cast_slice(&self.indices);
if vertex_bytes.len() != store.store.len() {
store.store.resize_with(vertex_bytes.len(), || 0);
}
if index_bytes.len() != store.indexs.len() {
store.indexs.resize_with(index_bytes.len(), || 0);
}
store.store.copy_from_slice(vertex_bytes);
store.indexs.copy_from_slice(index_bytes);
store.changed = true;
}
}
pub fn update(&mut self, renderer: &mut GpuRenderer) -> OrderedIndex {
if self.changed {
self.create_quad(renderer);
self.changed = false;
}
OrderedIndex::new(self.order, self.vbo_store_id, self.high_index)
}
pub fn check_mouse_bounds(&self, mouse_pos: Vec2) -> bool {
mouse_pos[0] > self.pos.x
&& mouse_pos[0] < self.pos.x + self.size.x
&& mouse_pos[1] > self.pos.y
&& mouse_pos[1] < self.pos.y + self.size.y
}
}
#[derive(Debug, Clone)]
pub struct Mesh2DBuilder {
pub buffer: tess::geometry_builder::VertexBuffers<Mesh2DVertex, u32>,
pub offset: Vec2,
pub bounds: Vec4,
pub size: Vec2,
pub z: f32,
pub high_index: u32,
pub camera_view: CameraView,
}
impl Default for Mesh2DBuilder {
fn default() -> Self {
Self {
buffer: tess::VertexBuffers::new(),
offset: Vec2::default(),
bounds: Vec4::new(0.0, 0.0, 0.0, 0.0),
size: Vec2::new(0.0, 0.0),
z: 1.0,
high_index: 0,
camera_view: CameraView::default(),
}
}
}
impl Mesh2DBuilder {
pub fn clear(&mut self) {
self.buffer.clear();
self.bounds = Vec4::new(0.0, 0.0, 0.0, 0.0);
self.size = Vec2::new(0.0, 0.0);
self.z = 1.0;
self.high_index = 0;
self.camera_view = CameraView::default();
}
pub fn with_camera_view(camera_view: CameraView) -> Self {
Self {
camera_view,
..Self::default()
}
}
pub fn set_offset(&mut self, offset: Vec2) -> &mut Self {
self.offset = offset;
self
}
pub fn finalize(&mut self) -> &mut Self {
let [minx, miny, maxx, maxy, minz] = self.buffer.vertices.iter().fold(
[f32::MAX, f32::MAX, f32::MIN, f32::MIN, 1.0],
|[minx, miny, maxx, maxy, minz], vert| {
let [x, y, z] = vert.pos;
[
minx.min(x),
miny.min(y),
maxx.max(x),
maxy.max(y),
minz.min(z),
]
},
);
let high_index = self
.buffer
.indices
.iter()
.fold(0, |max, index| max.max(*index));
self.bounds = Vec4::new(minx, miny, maxx, maxy);
self.size = Vec2::new(maxx - minx, maxy - miny);
self.z = minz;
self.high_index = high_index;
self
}
pub fn line(
&mut self,
points: &[Vec2],
z: f32,
width: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
self.polyline(DrawMode::stroke(width), points, z, color)
}
pub fn circle(
&mut self,
mode: DrawMode,
point: Vec2,
radius: f32,
tolerance: f32,
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
assert!(tolerance > 0.0, "Tolerances <= 0 are invalid");
{
let buffers = &mut self.buffer;
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
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)
}
#[allow(clippy::too_many_arguments)]
pub fn ellipse(
&mut self,
mode: DrawMode,
point: Vec2,
radius1: f32,
radius2: f32,
tolerance: f32,
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
assert!(tolerance > 0.0, "Tolerances <= 0 are invalid");
{
let buffers = &mut self.buffer;
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
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(
&mut self,
mode: DrawMode,
points: &[Vec2],
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
if points.len() < 2 {
return Err(GraphicsError::Other(OtherError::new(
"MeshBuilder::polyline() got a list of < 2 points",
)));
}
self.polyline_inner(mode, points, false, z, color)
}
pub fn polygon(
&mut self,
mode: DrawMode,
points: &[Vec2],
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
if points.len() < 3 {
return Err(GraphicsError::Other(OtherError::new(
"MeshBuilder::polygon() got a list of < 3 points",
)));
}
self.polyline_inner(mode, points, true, z, color)
}
fn polyline_inner(
&mut self,
mode: DrawMode,
points: &[Vec2],
is_closed: bool,
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
self.polyline_with_vertex_builder(mode, points, is_closed, vb)
}
pub fn polyline_with_vertex_builder<V>(
&mut self,
mode: DrawMode,
points: &[Vec2],
is_closed: bool,
vb: V,
) -> Result<&mut Self, GraphicsError>
where
V: tess::StrokeVertexConstructor<Mesh2DVertex>
+ tess::FillVertexConstructor<Mesh2DVertex>,
{
{
assert!(points.len() > 1);
let buffers = &mut self.buffer;
let points: Vec<LPoint> = points
.iter()
.cloned()
.map(|p| tess::math::point(p.x, p.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: Vec4,
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
{
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.z, bounds.w),
);
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
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: Vec4,
z: f32,
radius: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
{
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.z, bounds.w),
);
let radii = tess::path::builder::BorderRadii::new(radius);
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
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(
&mut self,
triangles: &[Vec2],
z: f32,
color: Color,
) -> Result<&mut Self, GraphicsError> {
{
if !triangles.len().is_multiple_of(3) {
return Err(GraphicsError::Other(OtherError::new(
"Called MeshBuilder::triangles() with points that have a length not a multiple of 3.",
)));
}
let tris = triangles
.iter()
.cloned()
.map(|p| lyon::math::point(p.x, p.y))
.collect::<Vec<_>>();
let tris = tris.chunks(3);
let vb = VertexBuilder {
z,
color,
camera: self.camera_view as u32,
};
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)
}
}