use std::{
any::TypeId,
ops::{Deref, Range},
sync::{Arc, RwLock},
};
use self::primitive::Primitive;
pub use self::{
background::Background,
drawing::{Drawing, DrawingContext},
theme::Theme,
};
use crate::{
draw::{indirect::Indirect, instanced::Instanced, mesh::MeshExt},
render::{DefaultNannouShaderModel, ErasedShaderModel, ShaderModel},
text::font::SharedTextCx,
};
use bevy::{
asset::UntypedAssetId,
platform::collections::{HashMap, HashSet},
prelude::*,
render::{
render_resource as wgpu,
render_resource::{BlendComponent, BlendState},
storage::ShaderBuffer,
},
};
use lyon::path::PathEvent;
use uuid::Uuid;
pub mod background;
mod drawing;
pub mod indirect;
pub mod instanced;
pub mod mesh;
pub mod primitive;
pub mod properties;
pub(crate) mod render;
pub mod theme;
#[derive(Component, Clone)]
pub struct Draw {
pub state: Arc<RwLock<State>>,
context: DrawContext,
pub(crate) shader_model: UntypedAssetId,
pub(crate) window: Entity,
pub(crate) text_cx: SharedTextCx,
}
#[derive(Clone)]
pub enum DrawRef<'a> {
Borrowed(&'a Draw),
Owned(Draw),
}
impl<'a> Deref for DrawRef<'a> {
type Target = Draw;
fn deref(&self) -> &Self::Target {
match self {
DrawRef::Borrowed(draw) => *draw,
DrawRef::Owned(draw) => draw,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DrawContext {
pub transform: Mat4,
}
impl Default for DrawContext {
fn default() -> Self {
Self {
transform: Mat4::IDENTITY,
}
}
}
#[derive(Clone, Debug)]
pub enum DrawCommand {
Primitive(Primitive),
Instanced(Primitive, Range<u32>),
Indirect(Primitive, Handle<ShaderBuffer>),
Context(DrawContext),
ShaderModel(UntypedAssetId),
BackgroundColor(Color),
}
pub struct State {
last_shader_model: Option<UntypedAssetId>,
last_draw_context: Option<DrawContext>,
background_color: Option<Color>,
drawing: HashMap<usize, Primitive>,
pub(crate) shader_models: HashMap<UntypedAssetId, Box<dyn ErasedShaderModel>>,
ignored_drawings: HashSet<usize>,
pub(crate) draw_commands: Vec<Option<DrawCommand>>,
pub(crate) intermediary_state: Arc<RwLock<IntermediaryState>>,
pub(crate) theme: Theme,
}
#[derive(Clone, Debug)]
pub struct IntermediaryState {
pub intermediary_mesh: Mesh,
pub path_event_buffer: Vec<PathEvent>,
pub path_points_vertex_buffer: Vec<(Vec2, Color, Vec2)>,
pub text_buffer: String,
}
impl IntermediaryState {
pub fn reset(&mut self) {
self.intermediary_mesh.clear();
self.path_event_buffer.clear();
self.path_points_vertex_buffer.clear();
self.text_buffer.clear();
}
}
impl State {
fn reset(&mut self) {
self.last_shader_model = None;
self.last_draw_context = None;
self.background_color = None;
self.drawing.clear();
self.shader_models.clear();
self.draw_commands.clear();
self.intermediary_state.write().unwrap().reset();
}
fn finish_remaining_drawings(&mut self) {
let mut drawing = std::mem::replace(&mut self.drawing, Default::default());
for (index, primitive) in drawing.drain() {
self.insert_draw_command(index, primitive);
}
std::mem::swap(&mut self.drawing, &mut drawing);
}
pub(crate) fn finish_drawing(&mut self, index: usize) {
if self.ignored_drawings.contains(&index) {
return;
}
if let Some(primitive) = self.drawing.remove(&index) {
self.insert_draw_command(index, primitive);
}
}
fn insert_draw_command(&mut self, index: usize, prim: Primitive) {
if let Some(elem) = self.draw_commands.get_mut(index) {
*elem = Some(DrawCommand::Primitive(prim));
}
}
}
impl Draw {
pub fn new(window: Entity, text_cx: SharedTextCx) -> Self {
let mut state = State::default();
let context = DrawContext::default();
let model = DefaultNannouShaderModel::default();
let model_id = UntypedAssetId::Uuid {
type_id: TypeId::of::<DefaultNannouShaderModel>(),
uuid: Uuid::new_v4(),
};
state.shader_models.insert(model_id, Box::new(model));
Draw {
state: Arc::new(RwLock::new(state)),
context,
shader_model: model_id,
window,
text_cx,
}
}
pub fn reset(&mut self) {
self.state.write().unwrap().reset();
self.insert_default_shader_model();
}
fn insert_default_shader_model(&mut self) {
let mut state = self.state.write().unwrap();
let model = DefaultNannouShaderModel::default();
let model_id = UntypedAssetId::Uuid {
type_id: TypeId::of::<DefaultNannouShaderModel>(),
uuid: Uuid::new_v4(),
};
state.shader_models.insert(model_id, Box::new(model));
self.shader_model = model_id;
}
pub fn transform(&self, transform_matrix: Mat4) -> Self {
let mut context = self.context.clone();
context.transform = context.transform * transform_matrix;
self.context(context)
}
pub fn translate(&self, v: Vec3) -> Self {
self.transform(Mat4::from_translation(v))
}
pub fn xyz(&self, v: Vec3) -> Self {
self.translate(v)
}
pub fn xy(&self, v: Vec2) -> Self {
self.xyz(v.extend(0.0))
}
pub fn x_y_z(&self, x: f32, y: f32, z: f32) -> Self {
self.xyz([x, y, z].into())
}
pub fn x_y(&self, x: f32, y: f32) -> Self {
self.xy([x, y].into())
}
pub fn x(&self, x: f32) -> Self {
self.x_y(x, 0.0)
}
pub fn y(&self, y: f32) -> Self {
self.x_y(0.0, y)
}
pub fn z(&self, z: f32) -> Self {
self.x_y_z(0.0, 0.0, z)
}
pub fn scale(&self, s: f32) -> Self {
self.scale_axes(Vec3::new(s, s, s))
}
pub fn scale_axes(&self, v: Vec3) -> Self {
self.transform(Mat4::from_scale(v))
}
pub fn scale_x(&self, s: f32) -> Self {
self.scale_axes(Vec3::new(s, 1.0, 1.0))
}
pub fn scale_y(&self, s: f32) -> Self {
self.scale_axes(Vec3::new(1.0, s, 1.0))
}
pub fn scale_z(&self, s: f32) -> Self {
self.scale_axes(Vec3::new(1.0, 1.0, s))
}
pub fn euler(&self, euler: Vec3) -> Self {
self.transform(Mat4::from_euler(EulerRot::XYZ, euler.x, euler.y, euler.z))
}
pub fn quaternion(&self, q: Quat) -> Self {
self.transform(Mat4::from_quat(q))
}
pub fn radians(&self, v: Vec3) -> Self {
self.euler(v)
}
pub fn x_radians(&self, x: f32) -> Self {
self.radians(Vec3::new(x, 0.0, 0.0))
}
pub fn y_radians(&self, y: f32) -> Self {
self.radians(Vec3::new(0.0, y, 0.0))
}
pub fn z_radians(&self, z: f32) -> Self {
self.radians(Vec3::new(0.0, 0.0, z))
}
pub fn degrees(&self, v: Vec3) -> Self {
self.radians(Vec3::new(
v.x.to_radians(),
v.y.to_radians(),
v.z.to_radians(),
))
}
pub fn x_degrees(&self, x: f32) -> Self {
self.x_radians(x.to_radians())
}
pub fn y_degrees(&self, y: f32) -> Self {
self.y_radians(y.to_radians())
}
pub fn z_degrees(&self, z: f32) -> Self {
self.z_radians(z.to_radians())
}
pub fn turns(&self, v: Vec3) -> Self {
self.radians(v * std::f32::consts::TAU)
}
pub fn x_turns(&self, x: f32) -> Self {
self.x_radians(x * std::f32::consts::TAU)
}
pub fn y_turns(&self, y: f32) -> Self {
self.y_radians(y * std::f32::consts::TAU)
}
pub fn z_turns(&self, z: f32) -> Self {
self.z_radians(z * std::f32::consts::TAU)
}
pub fn pitch(&self, pitch: f32) -> Self {
self.x_radians(pitch)
}
pub fn yaw(&self, yaw: f32) -> Self {
self.y_radians(yaw)
}
pub fn roll(&self, roll: f32) -> Self {
self.z_radians(roll)
}
pub fn rotate(&self, radians: f32) -> Self {
self.z_radians(radians)
}
fn context(&self, context: DrawContext) -> Draw {
let state = self.state.clone();
let shader_model = self.shader_model.clone();
let window = self.window;
let text_cx = self.text_cx.clone();
Draw {
state,
context,
shader_model,
window,
text_cx,
}
}
fn clone_default_shader_model(&self) -> Option<DefaultNannouShaderModel> {
let state = self.state.read().unwrap();
let shader_model = state.shader_models.get(&self.shader_model)?;
shader_model
.as_any()
.downcast_ref::<DefaultNannouShaderModel>()
.cloned()
}
pub fn shader_model<SM: ShaderModel>(&self, model: SM) -> Draw {
let context = self.context.clone();
let DrawContext { transform, .. } = context;
let context = DrawContext { transform };
let state = self.state.clone();
let window = self.window;
let text_cx = self.text_cx.clone();
let model_id = UntypedAssetId::Uuid {
type_id: TypeId::of::<SM>(),
uuid: Uuid::new_v4(),
};
state
.write()
.unwrap()
.shader_models
.insert(model_id, Box::new(model));
Draw {
state,
context,
shader_model: model_id,
window,
text_cx,
}
}
pub fn background<'a>(&'a self) -> Background<'a> {
background::new(self)
}
pub fn instanced<'a>(&'a self) -> Instanced<'a> {
instanced::new(self)
}
pub fn indirect<'a>(&'a self) -> Indirect<'a> {
indirect::new(self)
}
pub fn a<T>(&self, primitive: T) -> Drawing<'_, T>
where
T: Into<Primitive>,
{
let (index, model_index) = self.insert_primitive(primitive.into());
drawing::new(self, index, model_index)
}
fn insert_primitive(&self, primitive: Primitive) -> (usize, usize) {
let mut state = self.state.write().unwrap();
if state.last_draw_context.as_ref() != Some(&self.context) {
state
.draw_commands
.push(Some(DrawCommand::Context(self.context.clone())));
state.last_draw_context = Some(self.context.clone());
}
let id = &self.shader_model;
if state.last_shader_model.as_ref() != Some(id) {
state
.draw_commands
.push(Some(DrawCommand::ShaderModel(id.clone())));
state.last_shader_model = Some(id.clone());
}
let shader_model_index = state.draw_commands.len();
state.draw_commands.push(None);
let index = state.draw_commands.len();
state.draw_commands.push(None);
state.drawing.insert(index, primitive);
(index, shader_model_index)
}
pub fn path(&self) -> Drawing<'_, primitive::PathInit> {
self.a(Default::default())
}
pub fn ellipse(&self) -> Drawing<'_, primitive::Ellipse> {
self.a(Default::default())
}
pub fn line(&self) -> Drawing<'_, primitive::Line> {
self.a(Default::default())
}
pub fn arrow(&self) -> Drawing<'_, primitive::Arrow> {
self.a(Default::default())
}
pub fn quad(&self) -> Drawing<'_, primitive::Quad> {
self.a(Default::default())
}
pub fn rect(&self) -> Drawing<'_, primitive::Rect> {
self.a(Default::default())
}
pub fn tri(&self) -> Drawing<'_, primitive::Tri> {
self.a(Default::default())
}
pub fn polygon(&self) -> Drawing<'_, primitive::PolygonInit> {
self.a(Default::default())
}
pub fn mesh(&self) -> Drawing<'_, primitive::mesh::Vertexless> {
self.a(Default::default())
}
pub fn polyline(&self) -> Drawing<'_, primitive::PathStroke> {
self.path().stroke()
}
pub fn text(&self, s: &str) -> Drawing<'_, primitive::Text> {
let text = {
let state = self.state.read().expect("lock poisoned");
let mut intermediary_state = state.intermediary_state.write().expect("lock poisoned");
let ctxt = DrawingContext::from_intermediary_state(&mut *intermediary_state);
primitive::text::Text::new(ctxt, s)
};
self.a(text)
}
pub fn text_layout<'b>(&self, s: &'b str) -> crate::text::Builder<'b> {
crate::text::Builder::new(s, self.text_cx.clone())
}
pub fn drain_commands(&self) -> impl Iterator<Item = DrawCommand> {
self.finish_remaining_drawings();
let cmds = {
let mut state = self.state.write().unwrap();
let empty = Vec::with_capacity(state.draw_commands.len());
std::mem::replace(&mut state.draw_commands, empty)
};
cmds.into_iter().filter_map(|opt| opt)
}
pub fn finish_remaining_drawings(&self) {
let mut state = self.state.write().unwrap();
state.finish_remaining_drawings()
}
fn map_default_shader_model<F>(&self, map: F) -> Self
where
F: FnOnce(&mut DefaultNannouShaderModel),
{
match self.clone_default_shader_model() {
Some(mut model) => {
map(&mut model);
self.shader_model(model)
}
None => {
bevy::log::warn_once!(
"this operation only applies to the default nannou shader model; \
a custom shader model is active, so it has been skipped"
);
self.clone()
}
}
}
pub fn alpha_blend(&self, blend_descriptor: wgpu::BlendComponent) -> Self {
self.map_default_shader_model(|model| {
model.blend = Some(BlendState {
color: BlendComponent::REPLACE,
alpha: blend_descriptor,
});
})
}
pub fn color_blend(&self, blend_descriptor: wgpu::BlendComponent) -> Self {
self.map_default_shader_model(|model| {
model.blend = Some(BlendState {
color: blend_descriptor,
alpha: BlendComponent::REPLACE,
});
})
}
pub fn blend(&self, blend_descriptor: wgpu::BlendComponent) -> Self {
self.color_blend(blend_descriptor)
}
pub fn polygon_mode(&self, polygon_mode: wgpu::PolygonMode) -> Self {
self.map_default_shader_model(|model| model.polygon_mode = polygon_mode)
}
}
impl Default for IntermediaryState {
fn default() -> Self {
let intermediary_mesh = Mesh::init();
let path_event_buffer = Default::default();
let path_points_vertex_buffer = Default::default();
let text_buffer = Default::default();
IntermediaryState {
intermediary_mesh,
path_event_buffer,
path_points_vertex_buffer,
text_buffer,
}
}
}
impl Default for State {
fn default() -> Self {
let last_shader_model = None;
let last_draw_context = None;
let background_color = Default::default();
let draw_commands = Default::default();
let drawing = Default::default();
let intermediary_state = Arc::new(Default::default());
let theme = Default::default();
State {
last_shader_model,
last_draw_context,
draw_commands,
drawing,
intermediary_state,
theme,
background_color,
ignored_drawings: Default::default(),
shader_models: Default::default(),
}
}
}