use bevy::app::{App, Startup};
use bevy::asset::Assets;
use bevy::camera::visibility::NoFrustumCulling;
use bevy::color::{Color, LinearRgba};
use bevy::math::Vec3;
use bevy::prelude::{
AmbientLight, ColorToComponents, Commands, Cuboid, Mesh, Mesh3d, ResMut, Transform,
};
use bevy::DefaultPlugins;
use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin};
use bevy_voxel_plot::{InstanceData, InstanceMaterialData, VoxelMaterialPlugin};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
fn jet_colormap(value: f32) -> (f32, f32, f32) {
let four_value = 4.0 * value;
let r = (four_value - 1.5).clamp(0.0, 1.0);
let g = (four_value - 0.5).clamp(0.0, 1.0) - (four_value - 2.5).clamp(0.0, 1.0);
let b = 1.0 - (four_value - 1.5).clamp(0.0, 1.0);
(r, g, b)
}
fn load_pcd_file(path: &Path) -> (Vec<InstanceData>, f32, f32, f32) {
let file = File::open(path).expect("Failed to open PCD file");
let reader = BufReader::new(file);
let mut instances = Vec::new();
let mut in_data = false;
for line in reader.lines() {
let line = line.expect("Failed to read line");
if line.starts_with("#") || line.trim().is_empty() {
continue;
}
if line.trim() == "DATA ascii" {
in_data = true;
continue;
}
if in_data {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
continue;
}
let x: f32 = parts[0].parse().unwrap_or(0.0);
let y: f32 = parts[1].parse().unwrap_or(0.0);
let z: f32 = parts[2].parse().unwrap_or(0.0);
let (r, g, b) = jet_colormap(z * 10.0);
let instance = InstanceData {
position: [x, y, z], scale: 1.0,
color: LinearRgba::from(Color::srgba(r, g, b, 0.01)).to_f32_array(), };
instances.push(instance);
}
}
let cube_width = 0.02;
let cube_height = 0.02;
let cube_depth = 0.02;
(instances, cube_width, cube_height, cube_depth)
}
fn voxel_plot_setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
let (instances, cube_width, cube_height, cube_depth) =
load_pcd_file(Path::new("assets/files/bunny.pcd"));
let mut instances: Vec<InstanceData> = instances.into_iter().collect();
instances.sort_by(|a, b| {
b.color[3]
.partial_cmp(&a.color[3])
.unwrap_or(std::cmp::Ordering::Equal)
});
const MAX_INSTANCES: usize = 2_000_000;
if instances.len() > MAX_INSTANCES {
instances.truncate(MAX_INSTANCES);
}
if instances.len() == MAX_INSTANCES {
let threshold = instances.last().unwrap().color[3];
println!("Auto threshold for opacity was: {}", threshold);
}
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(cube_width, cube_height, cube_depth))),
InstanceMaterialData { instances },
NoFrustumCulling,
));
commands.spawn(AmbientLight {
color: Color::WHITE,
brightness: 2.0, affects_lightmapped_meshes: false,
});
commands.spawn((
Transform::from_translation(Vec3::new(1.0, 0.0, 1.0)),
PanOrbitCamera::default(),
));
}
fn main() {
App::new()
.add_plugins((DefaultPlugins, VoxelMaterialPlugin, PanOrbitCameraPlugin))
.add_systems(Startup, voxel_plot_setup)
.run();
}