use super::{heatmap_color, PhotometricData};
use bevy::asset::RenderAssetUsages;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum PhotometricMeshResolution {
Low,
#[default]
Medium,
High,
Custom {
c_step: f64,
g_step: f64,
},
}
impl PhotometricMeshResolution {
pub fn steps(&self) -> (f64, f64) {
match self {
Self::Low => (20.0, 10.0),
Self::Medium => (10.0, 5.0),
Self::High => (5.0, 2.5),
Self::Custom { c_step, g_step } => (*c_step, *g_step),
}
}
}
pub fn photometric_solid_mesh<T: PhotometricData>(
data: &T,
resolution: PhotometricMeshResolution,
scale: f32,
) -> Mesh {
let (c_step, g_step) = resolution.steps();
let num_c = (360.0 / c_step) as usize;
let num_g = (180.0 / g_step) as usize + 1;
let max_intensity = data.max_intensity();
if max_intensity <= 0.0 {
return Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
);
}
let mut positions = Vec::with_capacity(num_c * num_g);
let mut normals = Vec::with_capacity(num_c * num_g);
let mut colors = Vec::with_capacity(num_c * num_g);
let mut indices = Vec::with_capacity(num_c * (num_g - 1) * 6);
for ci in 0..num_c {
let c_angle = ci as f64 * c_step;
let c_rad = c_angle.to_radians() as f32;
for gi in 0..num_g {
let g_angle = gi as f64 * g_step;
let normalized = data.sample(c_angle, g_angle) / max_intensity;
let r = normalized as f32 * scale;
let g_rad = g_angle.to_radians() as f32;
let x = r * g_rad.sin() * c_rad.cos();
let z = r * g_rad.sin() * c_rad.sin();
let y = -r * g_rad.cos();
positions.push([x, y, z]);
let len = (x * x + y * y + z * z).sqrt().max(0.001);
normals.push([x / len, y / len, z / len]);
let (cr, cg, cb) = heatmap_color(normalized);
colors.push([cr, cg, cb, 0.7]); }
}
for c in 0..num_c {
let next_c = (c + 1) % num_c;
for g in 0..(num_g - 1) {
let v0 = (c * num_g + g) as u32;
let v1 = (next_c * num_g + g) as u32;
let v2 = (next_c * num_g + (g + 1)) as u32;
let v3 = (c * num_g + (g + 1)) as u32;
indices.push(v0);
indices.push(v1);
indices.push(v2);
indices.push(v0);
indices.push(v2);
indices.push(v3);
}
}
let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
mesh.insert_indices(Indices::U32(indices));
mesh
}
pub fn luminaire_mesh<T: PhotometricData>(data: &T) -> Mesh {
let (width, length, height) = data.dimensions();
if data.is_cylindrical() {
let radius = length.max(0.1) / 2.0;
Cylinder::new(radius, height).into()
} else {
Cuboid::new(width.max(0.1), height, length.max(0.1)).into()
}
}
pub fn luminaire_material(light_color: Color) -> StandardMaterial {
let linear = light_color.to_linear();
StandardMaterial {
base_color: Color::srgb(0.3, 0.3, 0.3),
emissive: LinearRgba::new(linear.red * 2.0, linear.green * 2.0, linear.blue * 2.0, 1.0),
metallic: 0.8,
perceptual_roughness: 0.3,
..default()
}
}
pub fn photometric_solid_material() -> StandardMaterial {
StandardMaterial {
base_color: Color::WHITE,
alpha_mode: AlphaMode::Blend,
double_sided: true,
cull_mode: None,
..default()
}
}