Files
server/src/nodes/drawable/lines.rs
2024-12-31 22:26:31 +01:00

201 lines
5.9 KiB
Rust

use super::{Line, LinesAspect};
use crate::{
bevy_plugin::{StardustExtract, TemporaryEntity, ViewLocation},
core::{client::Client, registry::Registry},
nodes::{spatial::Spatial, Node},
};
use bevy::{
app::Plugin,
asset::{Asset, Assets, RenderAssetUsages},
color::{Color, ColorToComponents, Srgba},
math::{bounding::Aabb3d, Isometry3d},
pbr::{MaterialExtension, MeshMaterial3d, StandardMaterial},
prelude::{
AlphaMode, Commands, GlobalTransform, Mesh, Mesh3d, ResMut, Single, Transform, With,
},
reflect::Reflect,
render::{
primitives::Aabb,
render_resource::{AsBindGroup, ShaderRef},
},
};
use color_eyre::eyre::Result;
use glam::{Vec3, Vec3A};
use parking_lot::Mutex;
use prisma::Lerp;
use std::{collections::VecDeque, sync::Arc};
static LINES_REGISTRY: Registry<Lines> = Registry::new();
pub struct Lines {
space: Arc<Spatial>,
data: Mutex<Vec<Line>>,
}
impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
let _ = node
.get_aspect::<Spatial>()
.unwrap()
.bounding_box_calc
.set(|node| {
if let Ok(lines) = node.get_aspect::<Lines>() {
return Aabb3d::from_point_cloud(
Isometry3d::IDENTITY,
lines
.data
.lock()
.iter()
.flat_map(|line| line.points.iter())
.map(|point| Vec3A::from(point.point)),
);
}
Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO)
});
let lines = LINES_REGISTRY.add(Lines {
space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(lines),
});
node.add_aspect_raw(lines.clone());
Ok(lines)
}
fn draw(&self, mesh: &mut Mesh, view: &GlobalTransform) -> Transform {
let transform_mat = self.space.global_transform();
let data = self.data.lock().clone();
let global_to_view = view.compute_matrix().inverse();
let local_to_view = transform_mat.inverse() * global_to_view;
let view_to_local = local_to_view.inverse();
for line in &data {
let mut points: VecDeque<BevyLinePoint> = line
.points
.iter()
.map(|p| BevyLinePoint {
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
thickness: p.thickness,
color: Srgba::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
})
.collect();
if line.cyclic && !points.is_empty() {
let first = line.points.first().unwrap();
let last = line.points.last().unwrap();
let color = Srgba {
red: first.color.c.r.lerp(&last.color.c.r, 0.5),
green: first.color.c.g.lerp(&last.color.c.g, 0.5),
blue: first.color.c.b.lerp(&last.color.c.b, 0.5),
alpha: first.color.a.lerp(&last.color.a, 0.5),
};
let connect_point = BevyLinePoint {
pt: transform_mat
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: color.into(),
};
points.push_front(connect_point);
points.push_back(connect_point);
}
let mut last_points: Option<(Vec3A, Vec3, Srgba)> = None;
let mut vertecies: Vec<[f32; 3]> = Vec::new();
let mut colors: Vec<[f32; 4]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
for point in points.into_iter() {
let pt_view = local_to_view.transform_point3a(point.pt.into());
let point1_view = pt_view + (Vec3A::Y * (point.thickness / 2.0));
let point2_view = pt_view + (Vec3A::NEG_Y * (point.thickness / 2.0));
let point1 = view_to_local.transform_point3a(point1_view);
let point2 = view_to_local.transform_point3a(point2_view);
if let Some((last1, last2, last_color)) = last_points.take() {
let normal = view_to_local.transform_vector3a(Vec3A::Z).to_array();
for _ in 0..6 {
normals.push(normal);
}
vertecies.push(last1.to_array());
vertecies.push(point1.to_array());
vertecies.push(last2.to_array());
vertecies.push(last2.to_array());
vertecies.push(point1.to_array());
vertecies.push(point2.to_array());
colors.push(last_color.to_f32_array());
colors.push(point.color.to_f32_array());
colors.push(last_color.to_f32_array());
colors.push(last_color.to_f32_array());
colors.push(point.color.to_f32_array());
colors.push(point.color.to_f32_array());
}
}
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertecies);
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}
GlobalTransform::from(transform_mat).into()
}
}
#[derive(Clone, Copy)]
struct BevyLinePoint {
pt: Vec3,
color: Srgba,
thickness: f32,
}
impl Aspect for Lines {
const NAME: &'static str = "Lines";
}
impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
let lines_aspect = node.get_aspect::<Lines>()?;
*lines_aspect.data.lock() = lines;
Ok(())
}
}
impl Drop for Lines {
fn drop(&mut self) {
LINES_REGISTRY.remove(self);
}
}
pub fn draw_all(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut cmds: Commands,
hmd: Single<&GlobalTransform, With<ViewLocation>>,
) {
let material = StandardMaterial {
base_color: Color::WHITE,
alpha_mode: AlphaMode::Blend,
..Default::default()
};
let mat_handle = materials.add(material);
for lines in LINES_REGISTRY.get_valid_contents() {
if let Some(node) = lines.space.node() {
if node.enabled() {
// Does this rebuild the mesh every frame? yes, is this problematic? probably,
// would a shader work better? yes, do i care? not right now
let mut mesh = Mesh::new(
bevy::render::mesh::PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
);
let transform = lines.draw(&mut mesh, &hmd);
let mesh_handle = meshes.add(mesh);
cmds.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(mat_handle.clone()),
TemporaryEntity,
transform,
));
}
}
}
}
pub struct BevyLinesPlugin;
impl Plugin for BevyLinesPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_systems(StardustExtract, draw_all);
}
}