diff --git a/Cargo.lock b/Cargo.lock index 76ce557..130df0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,6 +233,20 @@ name = "bytemuck" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] [[package]] name = "byteorder" @@ -581,6 +595,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -732,8 +758,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -758,6 +786,9 @@ name = "glam" version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +dependencies = [ + "bytemuck", +] [[package]] name = "glow" @@ -808,7 +839,7 @@ dependencies = [ "log", "presser", "thiserror", - "windows 0.56.0", + "windows 0.58.0", ] [[package]] @@ -1415,6 +1446,12 @@ dependencies = [ name = "mp_elements" version = "0.1.0" dependencies = [ + "bytemuck", + "flume", + "glam", + "mp_core", + "pollster", + "regex", "wgpu", ] @@ -1439,6 +1476,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "napi-derive-backend-ohos" version = "0.0.7" @@ -1711,6 +1757,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.9.0" @@ -2144,6 +2196,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" diff --git a/mp_elements/Cargo.toml b/mp_elements/Cargo.toml index 5e6db05..380f11e 100644 --- a/mp_elements/Cargo.toml +++ b/mp_elements/Cargo.toml @@ -4,4 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +bytemuck = { version = "1.19.0", features = ["derive"] } +glam = { version = "0.29.2", features = ["bytemuck"] } +regex = "1.11.1" wgpu = "23.0.0" +mp_core = { path = "../mp_core", version = "*" } +flume = "0.11.1" +pollster = "0.4.0" diff --git a/mp_elements/shaders/colormap.wgsl b/mp_elements/shaders/colormap.wgsl new file mode 100644 index 0000000..4d02b5e --- /dev/null +++ b/mp_elements/shaders/colormap.wgsl @@ -0,0 +1,29 @@ +@group(1) @binding(0) var color_map_texture: texture_2d; +@group(1) @binding(1) var color_map_sampler: sampler; +@group(1) @binding(2) var color_map_params: ColorMapParams; + +struct ColorMapParams { + color_count: u32, + value_min: f32, + value_max: f32 + invalid_value: f32 +} + +fn find_idx(ratio: f32) -> f32 { + var sum = 0.0; + var i = 0.0; + var count = (color_map_params.color_count - 1) as f32; + while (ratio > sum) { + sum += textureSample(color_map_texture, color_map_sampler, vec2(i / count, 0.0)).r; + i += 1.0; + } + return i / count; +} + + +fn linear_colormap(value: f32) -> vec4f { + var v = clamp((value - color_map_params.value_min) / (color_map_params.value_max - color_map_params.value_min), 0.0, 1.0); + float idx = find_idx(v); + let c0: vec3f = textureSample(color_map_texture, color_map_sampler, vec2(idx, 0.0)).rgb; + return vec4f(c0, 1.0); +} \ No newline at end of file diff --git a/mp_elements/shaders/constants.wgsl b/mp_elements/shaders/constants.wgsl new file mode 100644 index 0000000..f477611 --- /dev/null +++ b/mp_elements/shaders/constants.wgsl @@ -0,0 +1,5 @@ +const PI:f32 = 3.14159265358979323846264338327950288; +const TWO_PI:f32 = 6.28318530717958647692528676655900576; +const HALF_PI:f32 = 1.57079632679489661923132169163975144; +const LOG2:f32 = 0.693147180559945309417232121458176568; +const LOG10:f32 = 2.30258509299404568401799145468436421; diff --git a/mp_elements/shaders/ppi.wgsl b/mp_elements/shaders/ppi.wgsl new file mode 100644 index 0000000..d808497 --- /dev/null +++ b/mp_elements/shaders/ppi.wgsl @@ -0,0 +1,65 @@ +#include "constants.wgsl"; +#include "colormap.wgsl"; + +@group(2) @binding(0) var params: UniformParams; +@group(2) @binding(1) var data_buffer: texture_3d; + +struct UniformParams { + origin: vec3f +} + +struct VertexOutput { + @location(0) position: vec4f, + @location(1) r_range: vec2f, + @location(2) idx: vec3f +} + +struct UniformParams { + // Model-View-Projection matrix + mvp: mat4x4f, + origin: vec3f +} + +@vertex +fn vertex( + @location(0) position: vec3f, + @location(1) r_range: vec2f, + @location(2) idx: vec3f +) -> VertexOutput { + + var out: VertexOutput; + + // Transform position + out.position = params.mvp * vec4f(position, 1.0); + out.r_range = r_range; + out.idx = idx; + + return out; +} + +fn polar_forward(cartesian: vec3f) -> vec3f { + let r = length(cartesian.xy - params.origin.xy); + let theta = atan2(cartesian.y, cartesian.x); + let z = cartesian.z; + return vec3f(r, theta, z); +} + +@fragment +fn fragment(input: VertexOutput) -> location(0) vec4f { + // Sample data texture + let value = textureSample(data_texture, data_sampler, input.idx).r; + let ear = polar_forward(input.position); + var color = linear_colormap(value); + + let r = ear.z; + // Valid range + let r_range = input.r_range; + + let outside_lower_bound = step(r, r_range.x); + let outside_upper_bound = step(r_range.y, r); + let is_outside = outside_lower_bound + outside_upper_bound; + color.a *= 1.0 - is_outside; + + return color; +} + diff --git a/mp_elements/src/app.rs b/mp_elements/src/app.rs index 089d1bb..90131b4 100644 --- a/mp_elements/src/app.rs +++ b/mp_elements/src/app.rs @@ -1,18 +1,31 @@ -use wgpu::{core::instance, Backends, Instance, RequestAdapterOptions}; -pub struct App {} +use crate::elements::{ElementAttach, Elements}; +use crate::elementvec::ElementVec; +use wgpu::{Backends, Instance}; const BACKENDS_DEFAULT: u32 = Backends::DX12.bits() | Backends::METAL.bits() | Backends::GL.bits() | Backends::BROWSER_WEBGPU.bits(); +pub struct App { + device: wgpu::Device, + queue: wgpu::Queue, + common_utils: CommonUtils, + _texture: wgpu::Texture, + texture_view: wgpu::TextureView, + pipelines: Option, +} + impl App { - pub async fn inistant() -> Self { + pub async fn instant() -> Self { + // 修复方法名称拼写错误 + // Instance is a handle to the backend. It is used to create adapters. let instance = Instance::new(wgpu::InstanceDescriptor { backends: Backends::from_bits(BACKENDS_DEFAULT).unwrap(), ..Default::default() }); + // Request an adapter, which is a handle to a physical device. let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), @@ -22,27 +35,251 @@ impl App { .await .unwrap(); + // Request a device and a queue from the adapter. The device is the handle to the GPU, and the queue is used to submit commands to the GPU. let (device, queue) = adapter .request_device(&Default::default(), None) .await .unwrap(); - let texture_size = 256u32; - let texture_desc = wgpu::TextureDescriptor { + // Create a new instance of the common utils struct. This struct contains the bind group layout, bind group, and uniform buffer. + let common_utils = CommonUtils::new(&device); + + // Create a new texture. This texture will be used as the output texture for the render pass. + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("output texture"), size: wgpu::Extent3d { - width: texture_size, - height: texture_size, + width: 800, + height: 600, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, - label: None, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, view_formats: &[], - }; + }); - Self {} + // Create a texture view from the texture. This texture view will be used as the output texture for the render pass. + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + Self { + device, + queue, + common_utils, + pipelines: None, + _texture: texture, + texture_view, + } + } + + // Create a new context struct. This struct contains references to the device, queue, bind group layout, and bind group. + pub fn ctx(&self) -> Ctx { + Ctx::new(self) + } + + // Initialize the app. This method creates the pipelines and initializes the elements. + pub async fn init(&mut self) { + self.pipelines = Some(ElementVec::init(&Ctx::new(self))); + } + + // Get a reference to the pipelines. + pub fn pipelines(&self) -> &ElementVec { + self.pipelines.as_ref().unwrap() + } + + pub fn common_utils_mut(&mut self) -> &mut CommonUtils { + &mut self.common_utils + } + + // Draw the elements in the draw list. + pub fn draw(&self, draw_list: DrawList) { + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("draw_command_encoder"), + }); + + { + let render_pass_desc = wgpu::RenderPassDescriptor { + label: Some("Some Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + ..Default::default() + }; + let mut render_pass = encoder.begin_render_pass(&render_pass_desc); + + // Set the common utils bind group. + let common_utils = &self.common_utils.bind_group; + + // Draw each element in the draw list. + for (attach, element) in draw_list.elements { + let pipeline = element.pipeline(); + // Set the pipeline for the render pass. + render_pass.set_pipeline(pipeline); + + // Set the common utils bind group for the render pass. + render_pass.set_bind_group(0, common_utils, &[]); + + // Set the bind group for the render pass. + for (index, bind_group) in attach.bind_group.iter() { + render_pass.set_bind_group(*index, bind_group, &[]); + } + + // Set the bind group for the render pass. + attach.bind(&mut render_pass); + + // Draw the element. + element.draw(attach, &mut render_pass); + } + } + self.queue.submit(Some(encoder.finish())); + } +} + +pub struct Ctx<'a> { + pub device: &'a wgpu::Device, + pub queue: &'a wgpu::Queue, + pub common_tools_bind_group_layout: &'a wgpu::BindGroupLayout, + pub common_tools_bind_group: &'a wgpu::BindGroup, +} + +impl<'a> Ctx<'a> { + pub fn bind_group_layout(&self) -> Vec { + vec![] + } + + pub fn new(app: &'a App) -> Self { + Self { + device: &app.device, + queue: &app.queue, + common_tools_bind_group_layout: &app.common_utils.bind_group_layout, + common_tools_bind_group: &app.common_utils.bind_group, + } + } +} + +pub struct DrawList<'a> { + pub elements: Vec<(&'a ElementAttach, &'a Elements)>, // 修复字段访问权限 +} + +impl<'a> DrawList<'a> { + pub fn new() -> Self { + Self { elements: vec![] } + } + + pub fn push>(&mut self, element: Ele, attach: &'a ElementAttach) { + self.elements.push((attach, element.into())); + } +} + +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct CommonUniform { + pub model_matrix: glam::Mat4, + pub view_matrix: glam::Mat4, + pub proj_matrix: glam::Mat4, + pub camera_position: glam::Vec3, + pub camera_front: glam::Vec3, + pub camera_up: glam::Vec3, + pub light_position: glam::Vec3, + pub light_color: glam::Vec3, + pub light_intensity: f32, +} + +pub struct CommonUtils { + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, + pub uniform_buffer: wgpu::Buffer, +} + +impl CommonUtils { + fn new(device: &wgpu::Device) -> Self { + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("common_utils_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::all(), + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("common_utils_buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("common_utils_bind_group"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + Self { + bind_group_layout, + bind_group, + uniform_buffer: buffer, + } + } +} + +pub struct Elementi {} + +mod test { + use mp_core::{PluginManager, RadarGridData}; + + use crate::elements::{Element, PPI}; + + use super::*; + #[test] + fn test_app() { + let plugin_manager = PluginManager::new("/Users/tsuki/projects/mp/loaders").unwrap(); + + let data = plugin_manager.try_load_data( + "/Users/tsuki/Desktop/Z_RADR_I_X5775_20230726180000_O_DOR-XPD-CAP-FMT.BIN.zip", + ); + + pollster::block_on(async { + let mut app = App::instant().await; + + if let Ok(data) = data { + let first_block = data.first().unwrap(); + + if let Ok(data) = <&mp_core::Data as TryInto<&RadarGridData>>::try_into(first_block) + { + } + } + // app.init().await; + + // let pipelines = app.pipelines(); + // let ppi = pipelines.ppi(); + + // let ctx = app.ctx(); + + // let attachment = ppi.new_attachment(&ctx); + + // ppi.bake() + + // attachment.update_data(&ctx, data); + + // let mut draw_list = DrawList::new(); + // draw_list.push(pipelines.ppi()); + }) } } diff --git a/mp_elements/src/elements/mod.rs b/mp_elements/src/elements/mod.rs new file mode 100644 index 0000000..0cb0501 --- /dev/null +++ b/mp_elements/src/elements/mod.rs @@ -0,0 +1,85 @@ +pub mod ppi; +use crate::app::Ctx; +pub use ppi::PPI; +use std::any::Any; +use wgpu::util::DeviceExt; + +macro_rules! elements { + ($(($element_name:ident,$element: ty),)+) => { + pub enum Elements { + $($element_name($element),)+ + } + + $( + impl From<$element> for Elements { + fn from(element: $element) -> Self { + Elements::$element_name(element) + } + } + )+ + + impl Elements { + pub fn pipeline(&self) -> &wgpu::RenderPipeline { + match self { + $(Elements::$element_name(element) => element.pipeline(),)+ + } + } + + pub fn draw(&self, attach: &ElementAttach, render_pass: &mut wgpu::RenderPass) { + match self { + $(Elements::$element_name(element) => element.draw(attach, render_pass),)+ + } + } + } + + }; +} + +pub trait Element { + type Vertex; + type Uniform; + type Data; + + fn new(ctx: &Ctx) -> Self; + + fn new_attachment(&self, ctx: &Ctx) -> ElementAttach; + + // Bake the data into vertices and indices + fn bake(&self, data: &Self::Data) -> (Vec, Option>); + + fn supports(&self, _data: &Self::Data) -> bool { + true + } + + fn pipeline(&self) -> &wgpu::RenderPipeline; + + fn draw(&self, attach: &ElementAttach, render_pass: &mut wgpu::RenderPass); +} + +pub struct ElementAttach { + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: Option, + pub num_indices: u32, + pub uniform_buffer: Option, + pub bind_group: Vec<(u32, wgpu::BindGroup)>, +} + +impl ElementAttach { + pub fn update_data(&self, ctx: &Ctx, data: (Vec, Option>)) + where + T: bytemuck::Zeroable + bytemuck::Pod, + { + let device = ctx.device; + } + + pub fn bind(&self, render_pass: &mut wgpu::RenderPass) { + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + if let Some(index_buffer) = &self.index_buffer { + render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32); + } + } +} + +elements! { + (PPI, PPI), +} diff --git a/mp_elements/src/elements/ppi.rs b/mp_elements/src/elements/ppi.rs new file mode 100644 index 0000000..ef0d885 --- /dev/null +++ b/mp_elements/src/elements/ppi.rs @@ -0,0 +1,374 @@ +use std::ops::Sub; + +use crate::{app::Ctx, utils::merge_shader}; +use bytemuck; +use glam::Vec3; +use mp_core::{data::CoordType, RadarGridData}; + +use super::{Element, ElementAttach}; + +const EMAXNUM: u64 = 50; +const AMAXNUM: u64 = 360; +const RMAXNUM: u64 = 50; + +pub struct PPI { + bind_group_layout: wgpu::BindGroupLayout, + pipeline: wgpu::RenderPipeline, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct PPIUniform { + origin: Vec3, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct PPIVertex { + position: Vec3, + r_ranges: [f32; 2], + idx: [u32; 3], +} + +impl PPIVertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Uint32x3, + }, + ], + } + } +} + +impl Element for PPI { + type Vertex = PPIVertex; + type Uniform = PPIUniform; + type Data = RadarGridData; + + fn new(ctx: &Ctx) -> Self { + let device = ctx.device; + // Group Layout + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("PPI Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + // Shader Code + let shader = Self::init_shader(device); + + // Render Pipeline Layout + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("PPI Render Pipeline Layout"), + bind_group_layouts: &[ + // common_tools + &ctx.common_tools_bind_group_layout, + // ppi + &bind_group_layout, + ], + push_constant_ranges: &[], + }); + + // Render Pipeline + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("PPI Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + compilation_options: Default::default(), + entry_point: Some("vertex"), + buffers: &[PPIVertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + compilation_options: Default::default(), + entry_point: Some("fragment"), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + PPI { + bind_group_layout: bind_group_layout, + pipeline: render_pipeline, + } + } + + fn new_attachment(&self, ctx: &Ctx) -> ElementAttach { + let device = ctx.device; + + // Buffers + let data_buffer = Self::create_data_buffer(device); + let uniform_buffer = Self::create_uniform_buffer(device); + + // Bind Group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: data_buffer.as_entire_binding(), + }, + ], + label: Some("PPI Bind Group"), + }); + + // Vertex Buffer + let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("PPI Vertex Buffer"), + mapped_at_creation: false, + size: std::mem::size_of::() as wgpu::BufferAddress * EMAXNUM * AMAXNUM, + usage: wgpu::BufferUsages::VERTEX, + }); + + // Index Buffer + let index_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("PPI Index Buffer"), + mapped_at_creation: false, + size: EMAXNUM + * AMAXNUM + * RMAXNUM + * 6 + * std::mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::INDEX, + }); + + ElementAttach { + vertex_buffer, + index_buffer: Some(index_buffer), + num_indices: 0, + uniform_buffer: Some(uniform_buffer), + bind_group: vec![(1, bind_group)], + } + } + + fn bake(&self, data: &Self::Data) -> (Vec, Option>) { + let coord_typ = data.coord_type().unwrap(); + + if let CoordType::Polar { + range, + azimuth, + elevation, + } = coord_typ + { + let e = elevation.map(|v| *v.first().unwrap_or(&0.0)).unwrap_or(0.0); + let (vertices, indices) = Self::bake_vi(e, azimuth, &range); + + (vertices, Some(indices)) + } else { + panic!("PPI only supports polar coordinates"); + } + } + + fn pipeline(&self) -> &wgpu::RenderPipeline { + &self.pipeline + } + + fn draw(&self, attach: &ElementAttach, render_pass: &mut wgpu::RenderPass) { + render_pass.draw_indexed(0..attach.num_indices, 0, 0..1); + } +} + +impl PPI { + fn init_shader(device: &wgpu::Device) -> wgpu::ShaderModule { + let shader_str = merge_shader("../../shaders/ppi.wgsl"); + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("PPI Shader Module"), + source: wgpu::ShaderSource::Wgsl(shader_str.into()), + }); + shader + } + + fn create_uniform_buffer(device: &wgpu::Device) -> wgpu::Buffer { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("PPI Uniform Buffer"), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE, + size: 0, + mapped_at_creation: false, + }); + + buffer + } + + fn create_data_buffer(device: &wgpu::Device) -> wgpu::Buffer { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("PPI Texture Buffer"), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE, + size: 0, + mapped_at_creation: false, + }); + + buffer + } + + fn bake_vi(e: f64, a: &Vec, r: &Vec) -> (Vec, Vec) { + // Sort azimuth + let mut sorted_azimuth = a.clone(); + sorted_azimuth.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater)); + + // Calculate step + let azi_step = max_step(&sorted_azimuth, f64::MIN) / 2.0; + let r_step = min_step(&r, f64::MAX) / (r[r.len() - 1]) / 2.0; + let r_step_f32 = r_step as f32; + + // One cell has 4 vertices + let mut vertexs = Vec::with_capacity(a.len() * r.len() * 4); + for (a_idx, _a) in sorted_azimuth.into_iter().enumerate() { + for (r_idx, _r) in r.iter().enumerate() { + let r_ranges = [*_r as f32 - r_step_f32, *_r as f32 + r_step_f32]; + let idx = [0, a_idx as u32, r_idx as u32]; + // Left Top + let lt = polar_to_cartesian(*_r + r_step, _a - azi_step, e); + vertexs.push(PPIVertex { + position: Vec3::new(lt.0, lt.1, lt.2), + r_ranges, + idx, + }); + // Left Bot + let lb = polar_to_cartesian(*_r - r_step, _a - azi_step, e); + vertexs.push(PPIVertex { + position: Vec3::new(lb.0, lb.1, lb.2), + r_ranges, + idx, + }); + // Right Top + let rt = polar_to_cartesian(*_r + r_step, _a + azi_step, e); + vertexs.push(PPIVertex { + position: Vec3::new(rt.0, rt.1, rt.2), + r_ranges, + idx, + }); + // Right Bot + let rb = polar_to_cartesian(*_r - r_step, _a + azi_step, e); + vertexs.push(PPIVertex { + position: Vec3::new(rb.0, rb.1, rb.2), + r_ranges, + idx, + }); + } + } + + // index buffer + let mut indice_buffer = Vec::with_capacity(a.len() * r.len() * 6); + for a_idx in 0..a.len() - 1 { + for r_idx in 0..r.len() - 1 { + let lt = (a_idx * r.len() + r_idx) as u32; + let lb = (a_idx * r.len() + r_idx + 1) as u32; + let rt = ((a_idx + 1) * r.len() + r_idx) as u32; + let rb = ((a_idx + 1) * r.len() + r_idx + 1) as u32; + + indice_buffer.push(lt); + indice_buffer.push(lb); + indice_buffer.push(rt); + + indice_buffer.push(lb); + indice_buffer.push(rb); + indice_buffer.push(rt); + } + } + return (vertexs, indice_buffer); + } +} + +fn min_step + Copy>(data: &Vec, gap: T) -> T +where + ::Output: PartialOrd, +{ + // 计算相邻元素之间的间距 + let mut min_gap = gap; + for window in data.windows(2) { + if let [a, b] = window { + let gap = *b - *a; + if gap < min_gap { + min_gap = gap; + } + } + } + return min_gap; +} + +fn max_step>(data: &Vec, gap: T) -> T +where + ::Output: PartialOrd, +{ + // 计算相邻元素之间的间距 + let mut max_gap = gap; + for window in data.windows(2) { + if let [a, b] = window { + let gap = *b - *a; + if gap > max_gap { + max_gap = gap; + } + } + } + return max_gap; +} + +fn polar_to_cartesian(r: f64, azimuth: f64, elevation: f64) -> (f32, f32, f32) { + let x = r * azimuth.cos() * elevation.cos(); + let y = r * azimuth.sin() * elevation.cos(); + let z = r * elevation.sin(); + (x as f32, y as f32, z as f32) +} diff --git a/mp_elements/src/elementvec.rs b/mp_elements/src/elementvec.rs new file mode 100644 index 0000000..be75368 --- /dev/null +++ b/mp_elements/src/elementvec.rs @@ -0,0 +1,27 @@ +use crate::app::Ctx; +use crate::elements::*; +macro_rules! elementvec { + ($({$element: ident, $element_ty: ty})+) => { + pub struct ElementVec { + $($element: $element_ty,)+ + } + impl ElementVec { + pub fn init(ctx: &Ctx) -> Self { + // Compile the shaders, create the pipelines, etc. + Self { + $($element: <$element_ty>::new(ctx),)+ + } + } + + $(pub fn $element(&self) -> &$element_ty { + &self.$element + })+ + + } + }; + () => {}; +} + +elementvec!({ + ppi, PPI +}); diff --git a/mp_elements/src/lib.rs b/mp_elements/src/lib.rs index 309be62..8b260ce 100644 --- a/mp_elements/src/lib.rs +++ b/mp_elements/src/lib.rs @@ -1 +1,5 @@ pub mod app; +pub mod elements; +pub mod elementvec; +pub mod renderer; +mod utils; diff --git a/mp_elements/src/renderer/camera.rs b/mp_elements/src/renderer/camera.rs new file mode 100644 index 0000000..5f5a980 --- /dev/null +++ b/mp_elements/src/renderer/camera.rs @@ -0,0 +1,30 @@ +use glam::*; +pub struct Camera { + pub position: Vec3, + pub center: Vec3, + pub up: Vec3, +} + +impl Default for Camera { + fn default() -> Self { + Self { + position: Vec3::new(0.0, 0.0, 0.0), + center: Vec3::new(0.0, 0.0, 0.0), + up: Vec3::new(0.0, 0.0, 0.0), + } + } +} + +impl Camera { + pub fn new(position: Vec3, center: Vec3, up: Vec3) -> Self { + Self { + position, + center, + up, + } + } + + pub fn calc_matrix(&self) -> Mat4 { + Mat4::look_at_rh(self.position, self.center, self.up) + } +} diff --git a/mp_elements/src/renderer/mod.rs b/mp_elements/src/renderer/mod.rs new file mode 100644 index 0000000..d8fb977 --- /dev/null +++ b/mp_elements/src/renderer/mod.rs @@ -0,0 +1,2 @@ +pub mod camera; +pub mod projection; diff --git a/mp_elements/src/renderer/projection.rs b/mp_elements/src/renderer/projection.rs new file mode 100644 index 0000000..d09ece8 --- /dev/null +++ b/mp_elements/src/renderer/projection.rs @@ -0,0 +1,26 @@ +use glam::*; +pub struct Projection { + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Projection { + pub fn new(width: u32, height: u32, fovy: f32, z_near: f32, z_far: f32) -> Self { + Self { + aspect: width as f32 / height as f32, + fovy, + znear: z_near, + zfar: z_far, + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + self.aspect = width as f32 / height as f32; + } + + pub fn calc_matrix(&self) -> Mat4 { + Mat4::perspective_rh(self.fovy, self.aspect, self.znear, self.zfar) + } +} diff --git a/mp_elements/src/utils.rs b/mp_elements/src/utils.rs new file mode 100644 index 0000000..11cae92 --- /dev/null +++ b/mp_elements/src/utils.rs @@ -0,0 +1,96 @@ +use std::path::PathBuf; + +use regex::Regex; +pub(crate) fn merge_shader<'a>(shader: &'a str) -> String { + const TOOLS: &'static str = r#"// This is a tool that merges the shader code with the shader code from the shader module. + +struct UniformCommonTools { + model_matrix: mat4, + view_matrix: mat4, + proj_matrix: mat4, + camera_position: vec3, + camera_front: vec3, + camera_up: vec3, + light_position: vec3, + light_color: vec3, + light_intensity: float, +} + +@group(0) @binding(0) var common_tools: UniformCommonTools; +"#; + + let mut visited_files = std::collections::HashSet::new(); + let path: PathBuf = PathBuf::from(shader); + + let base = match path.canonicalize() { + Ok(path) => path.parent().unwrap().to_owned(), + Err(e) => { + panic!("Failed to canonicalize path: {}", e); + } + }; + + let mut result = process_file(&path, &mut visited_files, &base); + + // 将工具代码插入到 shader 代码的开头 + result.insert_str(0, TOOLS); + + return result; +} + +fn process_file<'a, P: AsRef + 'a, B: AsRef>( + file_path: P, + visited_files: &mut std::collections::HashSet, + base_path: B, +) -> String { + let base_path = base_path.as_ref(); + let file_path = file_path.as_ref(); + + // 如果该文件已经处理过,则避免死循环 + if visited_files.contains(file_path) { + return String::new(); // 返回空字符串表示已经处理过 + } + + visited_files.insert(file_path.to_path_buf()); + + let content = std::fs::read_to_string(file_path).unwrap_or_else(|_| { + panic!("Failed to read file: {}", file_path.display()); + }); + + let re = Regex::new(r#"#include\s+\"([^\"]+\.wgsl)\";"#).unwrap(); + + let result = re.replace_all(&content, |caps: ®ex::Captures| { + let included_file = &caps[1]; // 获取文件名 + + let included_path = resolve_included_file(included_file, base_path.as_ref()); + + let included_content = process_file( + &included_path, + visited_files, + base_path.parent().unwrap_or(base_path), + ); + + included_content + }); + + result.to_string() +} + +fn resolve_included_file(included_file: &str, base_path: &std::path::Path) -> std::path::PathBuf { + let included_path = std::path::Path::new(included_file); + + if included_path.is_relative() { + base_path.join(included_path) + } else { + included_path.to_path_buf() + } +} + +mod test { + use super::merge_shader; + + #[test] + fn test() { + let merged = merge_shader("/Users/tsuki/projects/mp/mp_elements/shaders/ppi.wgsl"); + println!("{}", merged); + } +}