use gizmo_math::Vec3;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use wgpu::util::DeviceExt;
#[derive(Clone)]
pub struct Mesh {
pub vbuf: Arc<wgpu::Buffer>,
pub vertex_count: u32,
pub ibuf: Option<Arc<wgpu::Buffer>>,
pub index_count: u32,
pub index_format: wgpu::IndexFormat,
pub center_offset: Vec3,
pub source: String,
pub bounds: gizmo_math::Aabb,
pub cpu_vertices: Arc<Vec<Vec3>>,
pub lod_vbufs: Vec<Arc<wgpu::Buffer>>,
pub lod_vertex_counts: Vec<u32>,
}
impl Mesh {
#[cfg_attr(target_arch = "wasm32", allow(unused_variables, unused_mut))]
pub fn new(
device: &wgpu::Device,
vbuf: Arc<wgpu::Buffer>,
vertices: &[crate::gpu_types::Vertex],
center_offset: Vec3,
source: String,
) -> Self {
debug_assert!(
!vertices.is_empty(),
"Kullanım hatası: Normal kullanımlarda vertices boş olamaz. Boş (fallback) mesh için Mesh::empty() kullanın."
);
let vertex_count = vertices.len() as u32;
debug_assert_eq!(
vertex_count as usize * std::mem::size_of::<crate::gpu_types::Vertex>(),
vbuf.size() as usize
);
let bounds = gizmo_math::Aabb::from_points(vertices.iter().map(|v| v.position));
let cpu_vertices = Arc::new(vertices.iter().map(|v| Vec3::from(v.position)).collect());
let mut lod_vbufs = Vec::new();
let mut lod_vertex_counts = Vec::new();
#[cfg(not(target_arch = "wasm32"))]
if vertex_count > 20000 {
let (unique_count, indices) = meshopt::generate_vertex_remap(vertices, None);
let mut unique_vertices = vec![crate::gpu_types::Vertex::default(); unique_count];
for (i, &new_idx) in indices.iter().enumerate() {
unique_vertices[new_idx as usize] = vertices[i];
}
let adapter = meshopt::VertexDataAdapter::new(
bytemuck::cast_slice(&unique_vertices),
std::mem::size_of::<crate::gpu_types::Vertex>(),
0,
)
.unwrap();
let target_count = (indices.len() as f32 * 0.5) as usize; let lod1_indices = meshopt::simplify(
&indices,
&adapter,
target_count,
0.1, meshopt::SimplifyOptions::empty(),
None,
);
if !lod1_indices.is_empty() && lod1_indices.len() < indices.len() {
let mut lod_flat = Vec::with_capacity(lod1_indices.len());
for &idx in &lod1_indices {
lod_flat.push(unique_vertices[idx as usize]);
}
let lod_vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("LOD1 VBuf: {}", source)),
contents: bytemuck::cast_slice(&lod_flat),
usage: wgpu::BufferUsages::VERTEX,
});
lod_vbufs.push(Arc::new(lod_vbuf));
lod_vertex_counts.push(lod_flat.len() as u32);
}
}
Self {
vbuf,
vertex_count,
ibuf: None,
index_count: 0,
index_format: wgpu::IndexFormat::Uint32,
center_offset,
source,
bounds,
cpu_vertices,
lod_vbufs,
lod_vertex_counts,
}
}
pub fn new_indexed(
device: &wgpu::Device,
vertices: &[crate::gpu_types::Vertex],
center_offset: Vec3,
source: String,
) -> Self {
#[cfg(target_arch = "wasm32")]
{
use wgpu::util::DeviceExt;
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("VBuf (flat, no meshopt): {source}")),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
});
Mesh::new(device, Arc::new(vbuf), vertices, center_offset, source)
}
#[cfg(not(target_arch = "wasm32"))]
{
let (unique_count, indices) = meshopt::generate_vertex_remap(vertices, None);
let mut unique_vertices = vec![crate::gpu_types::Vertex::default(); unique_count];
for (i, &new_idx) in indices.iter().enumerate() {
unique_vertices[new_idx as usize] = vertices[i];
}
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("VBuf (indexed): {source}")),
contents: bytemuck::cast_slice(&unique_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let (index_format, index_bytes): (wgpu::IndexFormat, Vec<u8>) = if unique_count <= 65536 {
let narrow: Vec<u16> = indices.iter().map(|&i| i as u16).collect();
(
wgpu::IndexFormat::Uint16,
bytemuck::cast_slice(&narrow).to_vec(),
)
} else {
(
wgpu::IndexFormat::Uint32,
bytemuck::cast_slice(&indices).to_vec(),
)
};
let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("IBuf: {source}")),
contents: &index_bytes,
usage: wgpu::BufferUsages::INDEX,
});
let mut mesh = Mesh::new(
device,
Arc::new(vbuf),
&unique_vertices,
center_offset,
source,
);
mesh.index_count = indices.len() as u32;
mesh.index_format = index_format;
mesh.ibuf = Some(Arc::new(ibuf));
mesh.cpu_vertices = Arc::new(vertices.iter().map(|v| Vec3::from(v.position)).collect());
mesh
}
}
pub fn empty(vbuf: Arc<wgpu::Buffer>, source: String) -> Self {
Self {
vbuf,
vertex_count: 0,
ibuf: None,
index_count: 0,
index_format: wgpu::IndexFormat::Uint32,
center_offset: Vec3::ZERO,
source,
bounds: gizmo_math::Aabb::empty(),
cpu_vertices: Arc::new(Vec::new()),
lod_vbufs: Vec::new(),
lod_vertex_counts: Vec::new(),
}
}
pub fn from_vertices(
device: &wgpu::Device,
vertices: &[crate::gpu_types::Vertex],
source: impl Into<String>,
) -> Self {
use wgpu::util::DeviceExt;
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("ProcMesh VBuf"),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
Mesh::new(device, Arc::new(vbuf), vertices, Vec3::ZERO, source.into())
}
pub fn update_vertices(&self, queue: &wgpu::Queue, vertices: &[crate::gpu_types::Vertex]) {
let bytes: &[u8] = bytemuck::cast_slice(vertices);
debug_assert!(bytes.len() as u64 <= self.vbuf.size());
queue.write_buffer(&self.vbuf, 0, bytes);
}
}
#[derive(Clone)]
pub struct MeshRenderer {
pub lod_bias: f32,
pub shadows: ShadowCasting,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ShadowCasting {
#[default]
On,
Off,
Only,
}
impl ShadowCasting {
pub fn casts(self) -> bool {
matches!(self, Self::On | Self::Only)
}
pub fn visible(self) -> bool {
matches!(self, Self::On | Self::Off)
}
}
impl MeshRenderer {
pub fn new() -> Self {
Self { lod_bias: 1.0, shadows: ShadowCasting::On }
}
pub fn with_lod_bias(mut self, bias: f32) -> Self {
self.lod_bias = bias;
self
}
pub fn with_shadows(mut self, shadows: ShadowCasting) -> Self {
self.shadows = shadows;
self
}
}
pub fn effective_lod_distance(distance: f32, bias: f32) -> f32 {
if bias.is_finite() && bias > 0.0 {
distance / bias
} else {
distance
}
}
impl Default for MeshRenderer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod lod_bias_tests {
use super::effective_lod_distance;
#[test]
fn a_higher_bias_holds_detail_further_out() {
assert_eq!(effective_lod_distance(100.0, 2.0), 50.0);
assert_eq!(effective_lod_distance(100.0, 0.5), 200.0);
assert_eq!(effective_lod_distance(100.0, 1.0), 100.0, "1.0 is a no-op");
}
#[test]
fn a_bias_that_cannot_mean_anything_is_ignored() {
assert_eq!(effective_lod_distance(100.0, 0.0), 100.0);
assert_eq!(effective_lod_distance(100.0, -2.0), 100.0);
assert_eq!(effective_lod_distance(100.0, f32::NAN), 100.0);
assert_eq!(effective_lod_distance(100.0, f32::INFINITY), 100.0);
}
}