lots of rendering work & the font pain begins
This commit is contained in:
parent
95f27171f8
commit
7df3f8b3a8
|
@ -26,6 +26,7 @@ winit = { version = "0.29", features = ["rwh_05"], default-features = false }
|
||||||
wgpu = { version = "0.18", features = ["webgl"] }
|
wgpu = { version = "0.18", features = ["webgl"] }
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
glyphon = { git = "https://github.com/grovesNL/glyphon" }
|
||||||
|
|
||||||
[dependencies.js-sys]
|
[dependencies.js-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
use nexrad2::message31::MOMENT_DATA_FOLDED;
|
|
||||||
use crate::mode::Mode;
|
use crate::mode::Mode;
|
||||||
|
use nexrad2::message31::MOMENT_DATA_FOLDED;
|
||||||
|
|
||||||
pub fn correlation_coefficient(val: f32) -> &'static str {
|
pub fn correlation_coefficient(val: f32) -> &'static str {
|
||||||
let gradient = [
|
let gradient = [
|
||||||
|
@ -24,7 +24,7 @@ pub fn correlation_coefficient(val: f32) -> &'static str {
|
||||||
(1.02, "purple"),
|
(1.02, "purple"),
|
||||||
(1.03, "mediumvioletred"),
|
(1.03, "mediumvioletred"),
|
||||||
(1.045, "pink"),
|
(1.045, "pink"),
|
||||||
(1.05, "lavenderblush")
|
(1.05, "lavenderblush"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (threshold, color) in gradient {
|
for (threshold, color) in gradient {
|
||||||
|
@ -61,7 +61,7 @@ pub fn spectrum_width(val: f32) -> &'static str {
|
||||||
(32.0, "white"),
|
(32.0, "white"),
|
||||||
(35.0, "yellow"),
|
(35.0, "yellow"),
|
||||||
(60.0, "lime"),
|
(60.0, "lime"),
|
||||||
(1000.0, "purple")
|
(1000.0, "purple"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (threshold, color) in gradient {
|
for (threshold, color) in gradient {
|
||||||
|
@ -96,7 +96,7 @@ pub fn differential_reflectivity(val: f32) -> &'static str {
|
||||||
(6.0, "maroon"),
|
(6.0, "maroon"),
|
||||||
(7.0, "hotpink"),
|
(7.0, "hotpink"),
|
||||||
(10.0, "pink"),
|
(10.0, "pink"),
|
||||||
(999.0, "white")
|
(999.0, "white"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (threshold, color) in gradient {
|
for (threshold, color) in gradient {
|
||||||
|
@ -183,7 +183,13 @@ pub fn velocity(vel: f32) -> &'static str {
|
||||||
"#520106", // 140
|
"#520106", // 140
|
||||||
];
|
];
|
||||||
|
|
||||||
let i = scale_int((vel.floor()) as i32, 140, -140, (colors.len() - 1) as i32, 0);
|
let i = scale_int(
|
||||||
|
(vel.floor()) as i32,
|
||||||
|
140,
|
||||||
|
-140,
|
||||||
|
(colors.len() - 1) as i32,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
colors[i as usize]
|
colors[i as usize]
|
||||||
}
|
}
|
||||||
|
@ -201,6 +207,6 @@ pub fn color_scheme(product: Mode, value: f32) -> &'static str {
|
||||||
Mode::DifferentialPhase => correlation_coefficient(value),
|
Mode::DifferentialPhase => correlation_coefficient(value),
|
||||||
Mode::CorrelationCoefficient => correlation_coefficient(value),
|
Mode::CorrelationCoefficient => correlation_coefficient(value),
|
||||||
Mode::ClutterFilterPowerRemoved => dbz_noaa(value),
|
Mode::ClutterFilterPowerRemoved => dbz_noaa(value),
|
||||||
Mode::RadarInoperative => "#ff0000"
|
Mode::RadarInoperative => "#ff0000",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use web_sys::FileReader;
|
|
||||||
use crate::loadar2;
|
use crate::loadar2;
|
||||||
use crate::mode::Mode;
|
use crate::mode::Mode;
|
||||||
use crate::scope::ScopeState;
|
use crate::scope::ScopeState;
|
||||||
|
use web_sys::FileReader;
|
||||||
|
|
||||||
pub fn should_newline(state: &mut ScopeState) -> bool {
|
pub fn should_newline(state: &mut ScopeState) -> bool {
|
||||||
state.command_buf.starts_with("MODE SET")
|
state.command_buf.starts_with("MODE SET") || state.command_buf.starts_with("ELEVATION SET")
|
||||||
|| state.command_buf.starts_with("ELEVATION SET")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(state: &mut ScopeState, command: String) {
|
pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
|
@ -20,7 +19,7 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
if tokens.len() < 3 {
|
if tokens.len() < 3 {
|
||||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
} else {
|
} else {
|
||||||
let mode = tokens[2];
|
let mode = tokens[2];
|
||||||
if let Some(ar2) = &state.ar2 {
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
@ -30,7 +29,7 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
if !valid_modes.contains(&&mode.to_string()) {
|
if !valid_modes.contains(&&mode.to_string()) {
|
||||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.scope_mode = match mode {
|
state.scope_mode = match mode {
|
||||||
|
@ -41,19 +40,19 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
"PHI" => Mode::DifferentialPhase,
|
"PHI" => Mode::DifferentialPhase,
|
||||||
"RHO" => Mode::CorrelationCoefficient,
|
"RHO" => Mode::CorrelationCoefficient,
|
||||||
"CFP" => Mode::ClutterFilterPowerRemoved,
|
"CFP" => Mode::ClutterFilterPowerRemoved,
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if command.starts_with("ELEVATION SET") {
|
} else if command.starts_with("ELEVATION SET") {
|
||||||
if tokens.len() < 3 {
|
if tokens.len() < 3 {
|
||||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
} else {
|
} else {
|
||||||
let elev = tokens[2].to_string();
|
let elev = tokens[2].to_string();
|
||||||
if let Some(ar2) = &state.ar2 {
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
@ -64,7 +63,7 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
if !valid_elevs.contains(&elev) {
|
if !valid_elevs.contains(&elev) {
|
||||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let number: usize = elev.parse().unwrap();
|
let number: usize = elev.parse().unwrap();
|
||||||
|
|
||||||
|
@ -74,7 +73,7 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
} else {
|
} else {
|
||||||
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if command == "`T" {
|
} else if command == "`T" {
|
||||||
|
@ -83,4 +82,4 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
state.command_buf = "UNRECOGNIZED COMMAND".to_string();
|
state.command_buf = "UNRECOGNIZED COMMAND".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,8 @@ pub const EARTH_RADIUS_MILES: f64 = 3958.8;
|
||||||
pub fn latlong_to_xy(lat: f64, long: f64) -> (f64, f64) {
|
pub fn latlong_to_xy(lat: f64, long: f64) -> (f64, f64) {
|
||||||
// x = r λ cos(φ0)
|
// x = r λ cos(φ0)
|
||||||
// y = r φ
|
// y = r φ
|
||||||
(EARTH_RADIUS_MILES * long * lat.cos(), EARTH_RADIUS_MILES * lat)
|
(
|
||||||
}
|
EARTH_RADIUS_MILES * long * lat.cos(),
|
||||||
|
EARTH_RADIUS_MILES * lat,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
pub mod utils;
|
pub mod colors;
|
||||||
|
pub mod command;
|
||||||
|
pub mod equirectangular;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
pub mod equirectangular;
|
|
||||||
pub mod sites;
|
pub mod sites;
|
||||||
pub mod command;
|
pub mod text;
|
||||||
|
pub mod utils;
|
||||||
pub mod vcp;
|
pub mod vcp;
|
||||||
pub mod colors;
|
|
||||||
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use js_sys::Uint8Array;
|
|
||||||
use log::{debug, error, info};
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
|
||||||
use wgpu::SurfaceError;
|
|
||||||
use winit::dpi::PhysicalSize;
|
|
||||||
use winit::event::{ElementState, Event, WindowEvent};
|
|
||||||
use winit::event::WindowEvent::KeyboardInput;
|
|
||||||
use winit::event_loop::{EventLoop, EventLoopWindowTarget};
|
|
||||||
use winit::keyboard::KeyCode;
|
|
||||||
use winit::window::WindowBuilder;
|
|
||||||
use winit::platform::web::{WindowExtWebSys, WindowBuilderExtWebSys, EventLoopExtWebSys};
|
|
||||||
use crate::command::{exec, should_newline};
|
use crate::command::{exec, should_newline};
|
||||||
use crate::mode::Mode;
|
use crate::mode::Mode;
|
||||||
use crate::mode::Mode::Reflectivity;
|
use crate::mode::Mode::Reflectivity;
|
||||||
use crate::scope::{Preferences, ScopeState, WgpuState};
|
use crate::scope::{Preferences, ScopeState, WgpuState};
|
||||||
|
use glyphon::{Metrics, TextBounds};
|
||||||
|
use js_sys::Uint8Array;
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
|
use wgpu::SurfaceError;
|
||||||
|
use winit::dpi::PhysicalSize;
|
||||||
|
use winit::event::WindowEvent::KeyboardInput;
|
||||||
|
use winit::event::{ElementState, Event, WindowEvent};
|
||||||
|
use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget};
|
||||||
|
use winit::keyboard::KeyCode;
|
||||||
|
use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys};
|
||||||
|
use winit::window::WindowBuilder;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -33,22 +34,31 @@ extern "C" {
|
||||||
fn loadar2(c: &str);
|
fn loadar2(c: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AbiScopeState(ScopeState);
|
pub enum RenderMessage {
|
||||||
|
Resize { new_w: u32, new_h: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiScopeState {
|
pub struct AbiProxy(EventLoopProxy<RenderMessage>);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
utils::set_panic_hook();
|
utils::set_panic_hook();
|
||||||
|
|
||||||
info!("wgpu setup");
|
info!("wgpu setup");
|
||||||
|
|
||||||
let event_loop = EventLoop::new().expect("event loop creation failed");
|
let event_loop = EventLoopBuilder::<RenderMessage>::with_user_event()
|
||||||
let window = WindowBuilder::new().with_prevent_default(false).build(&event_loop).unwrap();
|
.build()
|
||||||
|
.expect("event loop creation failed");
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_prevent_default(false)
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
window.set_min_inner_size(Some(PhysicalSize::new(w, h)));
|
window.set_min_inner_size(Some(PhysicalSize::new(w, h)));
|
||||||
|
|
||||||
|
|
||||||
web_sys::window()
|
web_sys::window()
|
||||||
.and_then(|win| win.document())
|
.and_then(|win| win.document())
|
||||||
.and_then(|doc| {
|
.and_then(|doc| {
|
||||||
|
@ -62,70 +72,115 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiScopeState {
|
||||||
|
|
||||||
let mut render_state = WgpuState::new(window, PhysicalSize::new(w, h)).await;
|
let mut render_state = WgpuState::new(window, PhysicalSize::new(w, h)).await;
|
||||||
|
|
||||||
|
|
||||||
// If you see an error here, your IDE is not compiling for webassembly
|
|
||||||
event_loop.spawn(move |event: Event<_>, window: &EventLoopWindowTarget<_>| {
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { ref event, window_id} => {
|
|
||||||
match event {
|
|
||||||
KeyboardInput { event, .. } => {
|
|
||||||
debug!("{:?}", event.physical_key);
|
|
||||||
},
|
|
||||||
WindowEvent::CloseRequested => { window.exit(); },
|
|
||||||
WindowEvent::Resized(physical_size) => {
|
|
||||||
render_state.reconfigure(*physical_size);
|
|
||||||
},
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
render_state.update();
|
|
||||||
match render_state.render() {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(SurfaceError::Lost) => render_state.reconfigure(render_state.size),
|
|
||||||
Err(SurfaceError::OutOfMemory) => {
|
|
||||||
error!("out of memory!");
|
|
||||||
window.exit();
|
|
||||||
},
|
|
||||||
Err(e) => error!("transient rendering error: {}", e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::AboutToWait => {
|
|
||||||
render_state.window().request_redraw();
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
info!("initializing the scope");
|
info!("initializing the scope");
|
||||||
|
|
||||||
let document = web_sys::window().expect("window should exist").document().expect("document should exist");
|
let document = web_sys::window()
|
||||||
|
.expect("window should exist")
|
||||||
|
.document()
|
||||||
|
.expect("document should exist");
|
||||||
|
|
||||||
info!("finding form input element");
|
info!("finding form input element");
|
||||||
|
|
||||||
let file = document.get_element_by_id("file").expect("file element should exist");
|
let file = document
|
||||||
let file: web_sys::HtmlInputElement = file.dyn_into::<web_sys::HtmlInputElement>().map_err(|_| ()).expect("file input is not an input");
|
.get_element_by_id("file")
|
||||||
|
.expect("file element should exist");
|
||||||
|
let file: web_sys::HtmlInputElement = file
|
||||||
|
.dyn_into::<web_sys::HtmlInputElement>()
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("file input is not an input");
|
||||||
|
|
||||||
let scope_state = ScopeState {
|
let mut scope_state = ScopeState {
|
||||||
ar2: None,
|
ar2: None,
|
||||||
scope_mode: Mode::RadarInoperative,
|
scope_mode: Mode::RadarInoperative,
|
||||||
file_input: file,
|
file_input: file,
|
||||||
lat: 30.48500, // jacksonville
|
lat: 30.48500, // jacksonville
|
||||||
long: -81.70200, // jacksonville
|
long: -81.70200, // jacksonville
|
||||||
prefs: Preferences {
|
prefs: Preferences { fcs: 20 },
|
||||||
fcs: 20
|
|
||||||
},
|
|
||||||
command_buf: String::new(),
|
command_buf: String::new(),
|
||||||
command_buf_response_mode: false,
|
command_buf_response_mode: false,
|
||||||
new_data_available: false,
|
new_data_available: false,
|
||||||
selected_elevation: 1,
|
selected_elevation: 1,
|
||||||
show_ui: false
|
show_ui: false,
|
||||||
|
wgpu: render_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope_state.wgpu.add_textarea(
|
||||||
|
"status_bar".to_string(),
|
||||||
|
Metrics::new(30.0, 42.0),
|
||||||
|
10.0,
|
||||||
|
10.0,
|
||||||
|
1.0,
|
||||||
|
TextBounds {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 600,
|
||||||
|
bottom: 160,
|
||||||
|
},
|
||||||
|
glyphon::Color::rgb(0x4a, 0xf6, 0x26),
|
||||||
|
"NEXRAD INOP 00:00:00Z",
|
||||||
|
);
|
||||||
|
|
||||||
|
let event_proxy: EventLoopProxy<RenderMessage> = event_loop.create_proxy();
|
||||||
|
|
||||||
|
// If you see an error here, your IDE is not compiling for webassembly
|
||||||
|
event_loop.spawn(
|
||||||
|
move |event: Event<_>, window: &EventLoopWindowTarget<_>| match event {
|
||||||
|
Event::UserEvent(e) => match e {
|
||||||
|
RenderMessage::Resize { new_w, new_h } => {
|
||||||
|
scope_state
|
||||||
|
.wgpu
|
||||||
|
.reconfigure(PhysicalSize::new(new_w, new_h));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::WindowEvent {
|
||||||
|
ref event,
|
||||||
|
window_id,
|
||||||
|
} => match event {
|
||||||
|
KeyboardInput { event, .. } => {
|
||||||
|
debug!("{:?}", event.physical_key);
|
||||||
|
}
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
window.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::Resized(physical_size) => {
|
||||||
|
scope_state.wgpu.reconfigure(*physical_size);
|
||||||
|
}
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
scope_state.wgpu.update();
|
||||||
|
match scope_state.wgpu.render() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(SurfaceError::Lost) => {
|
||||||
|
scope_state.wgpu.reconfigure(scope_state.wgpu.size)
|
||||||
|
}
|
||||||
|
Err(SurfaceError::OutOfMemory) => {
|
||||||
|
error!("out of memory!");
|
||||||
|
window.exit();
|
||||||
|
}
|
||||||
|
Err(e) => error!("transient rendering error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Event::AboutToWait => {
|
||||||
|
scope_state.wgpu.window().request_redraw();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
info!("nexrad-browser initialized successfully");
|
info!("nexrad-browser initialized successfully");
|
||||||
|
|
||||||
AbiScopeState(scope_state)
|
AbiProxy(event_proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn resize_abi(w: u32, h: u32, proxy: &mut AbiProxy) {
|
||||||
|
proxy
|
||||||
|
.0
|
||||||
|
.send_event(RenderMessage::Resize { new_w: w, new_h: h })
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn render_abi(state: &mut AbiScopeState) {
|
pub fn render_abi(state: &mut AbiScopeState) {
|
||||||
|
@ -133,7 +188,7 @@ pub fn render_abi(state: &mut AbiScopeState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) {
|
pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) {
|
||||||
let array = Uint8Array::new(buf);
|
let array = Uint8Array::new(buf);
|
||||||
|
@ -174,4 +229,6 @@ pub fn keydown(state: &mut AbiScopeState, key: String) {
|
||||||
} else if key.len() == 1 {
|
} else if key.len() == 1 {
|
||||||
state.0.command_buf += &key.to_uppercase();
|
state.0.command_buf += &key.to_uppercase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub enum Mode {
|
||||||
/// CFP
|
/// CFP
|
||||||
ClutterFilterPowerRemoved,
|
ClutterFilterPowerRemoved,
|
||||||
/// RADR INOP
|
/// RADR INOP
|
||||||
RadarInoperative
|
RadarInoperative,
|
||||||
}
|
}
|
||||||
impl Mode {
|
impl Mode {
|
||||||
pub fn unselected(&self) -> &str {
|
pub fn unselected(&self) -> &str {
|
||||||
|
@ -27,7 +27,7 @@ impl Mode {
|
||||||
Self::DifferentialPhase => " PHI ",
|
Self::DifferentialPhase => " PHI ",
|
||||||
Self::CorrelationCoefficient => " RHO ",
|
Self::CorrelationCoefficient => " RHO ",
|
||||||
Self::ClutterFilterPowerRemoved => " CFP ",
|
Self::ClutterFilterPowerRemoved => " CFP ",
|
||||||
Self::RadarInoperative => " RADR INOP"
|
Self::RadarInoperative => " RADR INOP",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn selected(&self) -> &str {
|
pub fn selected(&self) -> &str {
|
||||||
|
@ -39,10 +39,10 @@ impl Mode {
|
||||||
Self::DifferentialPhase => ">PHI<",
|
Self::DifferentialPhase => ">PHI<",
|
||||||
Self::CorrelationCoefficient => ">RHO<",
|
Self::CorrelationCoefficient => ">RHO<",
|
||||||
Self::ClutterFilterPowerRemoved => ">CFP<",
|
Self::ClutterFilterPowerRemoved => ">CFP<",
|
||||||
Self::RadarInoperative => ">RADR INOP<"
|
Self::RadarInoperative => ">RADR INOP<",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rname(&self) -> &str {
|
pub fn rname(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Reflectivity => "REF",
|
Self::Reflectivity => "REF",
|
||||||
|
@ -52,7 +52,7 @@ impl Mode {
|
||||||
Self::DifferentialPhase => "PHI",
|
Self::DifferentialPhase => "PHI",
|
||||||
Self::CorrelationCoefficient => "RHO",
|
Self::CorrelationCoefficient => "RHO",
|
||||||
Self::ClutterFilterPowerRemoved => "CFP",
|
Self::ClutterFilterPowerRemoved => "CFP",
|
||||||
Self::RadarInoperative => "NOP"
|
Self::RadarInoperative => "NOP",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
use std::f64::consts::PI;
|
|
||||||
use chrono::{DateTime, Timelike, Utc};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use log::{debug, info};
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD;
|
|
||||||
use crate::colors::color_scheme;
|
use crate::colors::color_scheme;
|
||||||
use crate::mode::Mode;
|
use crate::mode::Mode;
|
||||||
use crate::mode::Mode::{ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, RadarInoperative, Reflectivity, SpectrumWidth, Velocity};
|
use crate::mode::Mode::{
|
||||||
|
ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity,
|
||||||
|
RadarInoperative, Reflectivity, SpectrumWidth, Velocity,
|
||||||
|
};
|
||||||
use crate::rescaleCanvas;
|
use crate::rescaleCanvas;
|
||||||
use crate::scope::ScopeState;
|
use crate::scope::ScopeState;
|
||||||
use crate::utils::parse_date;
|
use crate::utils::parse_date;
|
||||||
use crate::vcp::vcp_string;
|
use crate::vcp::vcp_string;
|
||||||
|
use chrono::{DateTime, Timelike, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::{debug, info};
|
||||||
|
use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD;
|
||||||
|
use std::f64::consts::PI;
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
pub const TEXT_COLOR_RED: &str = "#ef0000";
|
pub const TEXT_COLOR_RED: &str = "#ef0000";
|
||||||
pub const TEXT_COLOR_GREEN: &str = "#4af626";
|
pub const TEXT_COLOR_GREEN: &str = "#4af626";
|
||||||
|
@ -52,24 +55,48 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
|
|
||||||
// render out the scope id line
|
// render out the scope id line
|
||||||
if state.show_ui {
|
if state.show_ui {
|
||||||
ctx.fill_text(&format!("NEXRAD {} {}", state.ar2.as_ref().map(|u| u.volume_header_record.icao.as_str()).unwrap_or("INOP"), zulu(time)), 50.0, 50.0)?;
|
ctx.fill_text(
|
||||||
|
&format!(
|
||||||
|
"NEXRAD {} {}",
|
||||||
|
state
|
||||||
|
.ar2
|
||||||
|
.as_ref()
|
||||||
|
.map(|u| u.volume_header_record.icao.as_str())
|
||||||
|
.unwrap_or("INOP"),
|
||||||
|
zulu(time)
|
||||||
|
),
|
||||||
|
50.0,
|
||||||
|
50.0,
|
||||||
|
)?;
|
||||||
if state.ar2.is_none() {
|
if state.ar2.is_none() {
|
||||||
// inop alert
|
// inop alert
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
|
||||||
ctx.fill_text("RADAR INOPERATIVE NO DATA LOADED", 50.0, 50.0 + (state.prefs.fcs as f64))?;
|
ctx.fill_text(
|
||||||
|
"RADAR INOPERATIVE NO DATA LOADED",
|
||||||
|
50.0,
|
||||||
|
50.0 + (state.prefs.fcs as f64),
|
||||||
|
)?;
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
}
|
}
|
||||||
if state.new_data_available {
|
if state.new_data_available {
|
||||||
// inop alert
|
// inop alert
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
|
||||||
ctx.fill_text("NEW DATA AVAIL RLD RQD", 50.0, 50.0 + (state.prefs.fcs as f64) * 2.0)?;
|
ctx.fill_text(
|
||||||
|
"NEW DATA AVAIL RLD RQD",
|
||||||
|
50.0,
|
||||||
|
50.0 + (state.prefs.fcs as f64) * 2.0,
|
||||||
|
)?;
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the command buffer
|
// render the command buffer
|
||||||
for (line_no, line) in state.command_buf.split('\n').enumerate() {
|
for (line_no, line) in state.command_buf.split('\n').enumerate() {
|
||||||
ctx.fill_text(line, 50.0, (canvas.height() / 3) as f64 + (state.prefs.fcs * line_no) as f64)?;
|
ctx.fill_text(
|
||||||
|
line,
|
||||||
|
50.0,
|
||||||
|
(canvas.height() / 3) as f64 + (state.prefs.fcs * line_no) as f64,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the radar site info
|
// render the radar site info
|
||||||
|
@ -77,7 +104,16 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
ctx.set_text_align("right");
|
ctx.set_text_align("right");
|
||||||
ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?;
|
ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?;
|
||||||
if let Some(ar2) = &state.ar2 {
|
if let Some(ar2) = &state.ar2 {
|
||||||
ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?;
|
ctx.fill_text(
|
||||||
|
&format!(
|
||||||
|
"{} VCP {} {}",
|
||||||
|
ar2.volume_header_record.icao,
|
||||||
|
ar2.meta_rda_status_data.vcp,
|
||||||
|
vcp_string(ar2.meta_rda_status_data.vcp)
|
||||||
|
),
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
50.0 + (state.prefs.fcs as f64),
|
||||||
|
)?;
|
||||||
let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
|
let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
|
||||||
let delay = time - recorded;
|
let delay = time - recorded;
|
||||||
|
|
||||||
|
@ -85,18 +121,39 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
let minutes = (delay.num_seconds() / 60) % 60;
|
let minutes = (delay.num_seconds() / 60) % 60;
|
||||||
let hours = delay.num_seconds() / 3600;
|
let hours = delay.num_seconds() / 3600;
|
||||||
|
|
||||||
ctx.fill_text(&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?;
|
ctx.fill_text(
|
||||||
|
&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds),
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
50.0 + (state.prefs.fcs as f64 * 2.0),
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?;
|
ctx.fill_text(
|
||||||
ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?;
|
"SITE INFORMATION UNAVAILABLE",
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
50.0 + state.prefs.fcs as f64,
|
||||||
|
)?;
|
||||||
|
ctx.fill_text(
|
||||||
|
"DELAY INFORMATION UNAVAILABLE",
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
50.0 + (state.prefs.fcs * 2) as f64,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mode info
|
// mode info
|
||||||
ctx.fill_text("MODE", (canvas.width() - 50) as f64, (canvas.height() / 3) as f64)?;
|
ctx.fill_text(
|
||||||
|
"MODE",
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
(canvas.height() / 3) as f64,
|
||||||
|
)?;
|
||||||
let modelines = [
|
let modelines = [
|
||||||
[Reflectivity, Velocity, SpectrumWidth].as_slice(),
|
[Reflectivity, Velocity, SpectrumWidth].as_slice(),
|
||||||
[DifferentialReflectivity, DifferentialPhase, CorrelationCoefficient].as_slice(),
|
[
|
||||||
[ClutterFilterPowerRemoved, RadarInoperative].as_slice()
|
DifferentialReflectivity,
|
||||||
|
DifferentialPhase,
|
||||||
|
CorrelationCoefficient,
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
[ClutterFilterPowerRemoved, RadarInoperative].as_slice(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut available_modes = vec![];
|
let mut available_modes = vec![];
|
||||||
|
@ -114,7 +171,11 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
let pad_end = 2 - item_no;
|
let pad_end = 2 - item_no;
|
||||||
let pad_start = " ".repeat(pad_start * 6);
|
let pad_start = " ".repeat(pad_start * 6);
|
||||||
let pad_end = " ".repeat(pad_end * 6);
|
let pad_end = " ".repeat(pad_end * 6);
|
||||||
let middle = if state.scope_mode == *item { item.selected() } else { item.unselected() };
|
let middle = if state.scope_mode == *item {
|
||||||
|
item.selected()
|
||||||
|
} else {
|
||||||
|
item.unselected()
|
||||||
|
};
|
||||||
|
|
||||||
if state.scope_mode == *item {
|
if state.scope_mode == *item {
|
||||||
// display selected color
|
// display selected color
|
||||||
|
@ -130,7 +191,6 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if available_modes.contains(&&item.rname().to_string()) {
|
if available_modes.contains(&&item.rname().to_string()) {
|
||||||
ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?;
|
ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?;
|
||||||
}
|
}
|
||||||
|
@ -142,26 +202,56 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fill_text("ELEVATIONS", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64)?;
|
ctx.fill_text(
|
||||||
|
"ELEVATIONS",
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
((canvas.height() / 7) * 4) as f64,
|
||||||
|
)?;
|
||||||
if let Some(ar2) = &state.ar2 {
|
if let Some(ar2) = &state.ar2 {
|
||||||
let mut line_no = 0;
|
let mut line_no = 0;
|
||||||
|
|
||||||
for chunk in &ar2.elevations.iter().sorted_by(|a, b| {
|
for chunk in &ar2
|
||||||
Ord::cmp(&a.0, &b.0)
|
.elevations
|
||||||
}).enumerate().chunks(4) {
|
.iter()
|
||||||
|
.sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
|
||||||
|
.enumerate()
|
||||||
|
.chunks(4)
|
||||||
|
{
|
||||||
for (no_in_line, (elevation_no, scans)) in chunk {
|
for (no_in_line, (elevation_no, scans)) in chunk {
|
||||||
if state.selected_elevation != *elevation_no {
|
if state.selected_elevation != *elevation_no {
|
||||||
ctx.fill_text(&format!(" {:0>3} {}", elevation_no, " ".repeat(5 * (3 - (no_in_line % 4)))), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
|
ctx.fill_text(
|
||||||
|
&format!(
|
||||||
|
" {:0>3} {}",
|
||||||
|
elevation_no,
|
||||||
|
" ".repeat(5 * (3 - (no_in_line % 4)))
|
||||||
|
),
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
((canvas.height() / 7) * 4) as f64
|
||||||
|
+ (state.prefs.fcs * (line_no + 1)) as f64,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
|
||||||
ctx.fill_text(&format!(">{:0>3}<{}", elevation_no, " ".repeat(5 * (3 - (no_in_line % 4)))), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
|
ctx.fill_text(
|
||||||
|
&format!(
|
||||||
|
">{:0>3}<{}",
|
||||||
|
elevation_no,
|
||||||
|
" ".repeat(5 * (3 - (no_in_line % 4)))
|
||||||
|
),
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
((canvas.height() / 7) * 4) as f64
|
||||||
|
+ (state.prefs.fcs * (line_no + 1)) as f64,
|
||||||
|
)?;
|
||||||
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
line_no += 1;
|
line_no += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.fill_text("ELEVATION INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64)?;
|
ctx.fill_text(
|
||||||
|
"ELEVATION INFORMATION UNAVAILABLE",
|
||||||
|
(canvas.width() - 50) as f64,
|
||||||
|
((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,8 +263,21 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
|
|
||||||
let radials = ar2.elevations.get(&state.selected_elevation).unwrap();
|
let radials = ar2.elevations.get(&state.selected_elevation).unwrap();
|
||||||
|
|
||||||
let first_gate_px = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range) as f32 / 1000.0 * px_per_km;
|
let first_gate_px = (radials[0]
|
||||||
let gate_interval_km = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range_sample_interval) as f32 / 1000.0;
|
.available_data
|
||||||
|
.get("REF")
|
||||||
|
.expect("reflectivity is missing!")
|
||||||
|
.gdm
|
||||||
|
.data_moment_range) as f32
|
||||||
|
/ 1000.0
|
||||||
|
* px_per_km;
|
||||||
|
let gate_interval_km = (radials[0]
|
||||||
|
.available_data
|
||||||
|
.get("REF")
|
||||||
|
.expect("reflectivity is missing!")
|
||||||
|
.gdm
|
||||||
|
.data_moment_range_sample_interval) as f32
|
||||||
|
/ 1000.0;
|
||||||
let gate_width_px = gate_interval_km * px_per_km;
|
let gate_width_px = gate_interval_km * px_per_km;
|
||||||
|
|
||||||
for radial in radials {
|
for radial in radials {
|
||||||
|
@ -188,7 +291,7 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
|
|
||||||
let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
|
let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
|
||||||
1 => 0.5,
|
1 => 0.5,
|
||||||
_ => 1.0
|
_ => 1.0,
|
||||||
};
|
};
|
||||||
if (azimuth_angle + azimuth_spacing).floor() > azimuth {
|
if (azimuth_angle + azimuth_spacing).floor() > azimuth {
|
||||||
azimuth += azimuth_spacing;
|
azimuth += azimuth_spacing;
|
||||||
|
@ -205,26 +308,60 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
// line width
|
// line width
|
||||||
// line cap
|
// line cap
|
||||||
|
|
||||||
let gates = radial.available_data.get(state.scope_mode.rname()).expect("selected unavailable product").scaled_data();
|
let gates = radial
|
||||||
|
.available_data
|
||||||
|
.get(state.scope_mode.rname())
|
||||||
|
.expect("selected unavailable product")
|
||||||
|
.scaled_data();
|
||||||
|
|
||||||
let num_gates = gates.len();
|
let num_gates = gates.len();
|
||||||
|
|
||||||
for (num, value) in gates.iter().enumerate() {
|
for (num, value) in gates.iter().enumerate() {
|
||||||
if *value != MOMENT_DATA_BELOW_THRESHOLD {
|
if *value != MOMENT_DATA_BELOW_THRESHOLD {
|
||||||
|
ctx.move_to(
|
||||||
ctx.move_to(xc as f64 + start_angle.cos() * distance_x as f64, yc as f64 + start_angle.sin() * distance_y as f64);
|
xc as f64 + start_angle.cos() * distance_x as f64,
|
||||||
|
yc as f64 + start_angle.sin() * distance_y as f64,
|
||||||
|
);
|
||||||
|
|
||||||
ctx.begin_path();
|
ctx.begin_path();
|
||||||
|
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle - 0.001, end_angle + 0.001)?;
|
ctx.ellipse(
|
||||||
|
xc as f64,
|
||||||
|
yc as f64,
|
||||||
|
distance_x as f64,
|
||||||
|
distance_y as f64,
|
||||||
|
0.0,
|
||||||
|
start_angle - 0.001,
|
||||||
|
end_angle + 0.001,
|
||||||
|
)?;
|
||||||
} else if num == num_gates - 1 {
|
} else if num == num_gates - 1 {
|
||||||
ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle)?;
|
ctx.ellipse(
|
||||||
|
xc as f64,
|
||||||
|
yc as f64,
|
||||||
|
distance_x as f64,
|
||||||
|
distance_y as f64,
|
||||||
|
0.0,
|
||||||
|
start_angle,
|
||||||
|
end_angle,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle + 0.001)?;
|
ctx.ellipse(
|
||||||
|
xc as f64,
|
||||||
|
yc as f64,
|
||||||
|
distance_x as f64,
|
||||||
|
distance_y as f64,
|
||||||
|
0.0,
|
||||||
|
start_angle,
|
||||||
|
end_angle + 0.001,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.set_stroke_style(&JsValue::from_str(&format!("{}px {}", gate_width_px + 1.0, color_scheme(state.scope_mode, *value))));
|
ctx.set_stroke_style(&JsValue::from_str(&format!(
|
||||||
|
"{}px {}",
|
||||||
|
gate_width_px + 1.0,
|
||||||
|
color_scheme(state.scope_mode, *value)
|
||||||
|
)));
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,4 +374,4 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
|
use crate::mode::Mode;
|
||||||
|
use crate::text::OTextArea;
|
||||||
|
use glyphon::{
|
||||||
|
Attrs, Buffer, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea,
|
||||||
|
TextAtlas, TextBounds, TextRenderer,
|
||||||
|
};
|
||||||
|
use nexrad2::Nexrad2Chunk;
|
||||||
|
use raw_window_handle::HasRawDisplayHandle;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use glyphon::fontdb::Source;
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement};
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement};
|
||||||
use wgpu::{Backends, Color, CommandEncoderDescriptor, Device, Features, Instance, InstanceDescriptor, Limits, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureUsages, TextureViewDescriptor};
|
use wgpu::DeviceDescriptor;
|
||||||
|
use wgpu::{
|
||||||
|
Backends, Color, CommandEncoderDescriptor, Device, Features, Instance, InstanceDescriptor,
|
||||||
|
Limits, LoadOp, MultisampleState, Operations, Queue, RenderPassColorAttachment,
|
||||||
|
RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureUsages,
|
||||||
|
TextureViewDescriptor,
|
||||||
|
};
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use raw_window_handle::HasRawDisplayHandle;
|
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
use nexrad2::Nexrad2Chunk;
|
|
||||||
use crate::mode::Mode;
|
|
||||||
use wgpu::DeviceDescriptor;
|
|
||||||
|
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub ar2: Option<Nexrad2Chunk>,
|
pub ar2: Option<Nexrad2Chunk>,
|
||||||
|
@ -20,7 +33,8 @@ pub struct ScopeState {
|
||||||
pub command_buf_response_mode: bool,
|
pub command_buf_response_mode: bool,
|
||||||
pub new_data_available: bool,
|
pub new_data_available: bool,
|
||||||
pub selected_elevation: usize,
|
pub selected_elevation: usize,
|
||||||
pub show_ui: bool
|
pub show_ui: bool,
|
||||||
|
pub wgpu: WgpuState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WgpuState {
|
pub struct WgpuState {
|
||||||
|
@ -29,7 +43,12 @@ pub struct WgpuState {
|
||||||
pub queue: Queue,
|
pub queue: Queue,
|
||||||
pub surface_config: SurfaceConfiguration,
|
pub surface_config: SurfaceConfiguration,
|
||||||
pub size: PhysicalSize<u32>,
|
pub size: PhysicalSize<u32>,
|
||||||
pub window: Window
|
pub window: Window,
|
||||||
|
pub font_system: FontSystem,
|
||||||
|
pub font_cache: SwashCache,
|
||||||
|
pub atlas: TextAtlas,
|
||||||
|
pub text_renderer: TextRenderer,
|
||||||
|
pub text_buffers: HashMap<String, OTextArea>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WgpuState {
|
impl WgpuState {
|
||||||
|
@ -40,30 +59,34 @@ impl WgpuState {
|
||||||
dx12_shader_compiler: Default::default(),
|
dx12_shader_compiler: Default::default(),
|
||||||
gles_minor_version: Default::default(),
|
gles_minor_version: Default::default(),
|
||||||
});
|
});
|
||||||
let surface = unsafe {
|
let surface = unsafe { instance.create_surface(&window).unwrap() };
|
||||||
instance.create_surface(&window).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let adapter = instance.request_adapter(
|
let adapter = instance
|
||||||
&wgpu::RequestAdapterOptions {
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
power_preference: wgpu::PowerPreference::default(),
|
power_preference: wgpu::PowerPreference::default(),
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
},
|
})
|
||||||
).await.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let (device, queue) = adapter.request_device(
|
let (device, queue) = adapter
|
||||||
&DeviceDescriptor {
|
.request_device(
|
||||||
features: Features::empty(),
|
&DeviceDescriptor {
|
||||||
limits: Limits::downlevel_webgl2_defaults(),
|
features: Features::empty(),
|
||||||
label: None,
|
limits: Limits::downlevel_webgl2_defaults(),
|
||||||
},
|
label: None,
|
||||||
None, // Trace path
|
},
|
||||||
).await.unwrap();
|
None, // Trace path
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let surface_capabilities = surface.get_capabilities(&adapter);
|
let surface_capabilities = surface.get_capabilities(&adapter);
|
||||||
|
|
||||||
let surface_format = surface_capabilities.formats.iter()
|
let surface_format = surface_capabilities
|
||||||
|
.formats
|
||||||
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|f| f.is_srgb())
|
.find(|f| f.is_srgb())
|
||||||
.unwrap_or(surface_capabilities.formats[0]);
|
.unwrap_or(surface_capabilities.formats[0]);
|
||||||
|
@ -80,13 +103,27 @@ impl WgpuState {
|
||||||
|
|
||||||
surface.configure(&device, &surface_config);
|
surface.configure(&device, &surface_config);
|
||||||
|
|
||||||
|
let mut fonts = vec![];
|
||||||
|
fonts.push(Source::Binary(Arc::new(include_bytes!("./VT323-Regular.ttf"))));
|
||||||
|
|
||||||
|
let mut font_system = FontSystem::new_with_fonts(fonts.into_iter());
|
||||||
|
let mut font_cache = SwashCache::new();
|
||||||
|
let mut atlas = TextAtlas::new(&device, &queue, surface_format);
|
||||||
|
let mut text_renderer =
|
||||||
|
TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
surface,
|
surface,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
surface_config,
|
surface_config,
|
||||||
size
|
size,
|
||||||
|
font_system,
|
||||||
|
font_cache,
|
||||||
|
atlas,
|
||||||
|
text_renderer,
|
||||||
|
text_buffers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,29 +144,70 @@ impl WgpuState {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&mut self) -> Result<(), SurfaceError> {
|
pub fn render(&mut self) -> Result<(), SurfaceError> {
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output.texture.create_view(&TextureViewDescriptor::default());
|
let view = output
|
||||||
let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor {
|
.texture
|
||||||
label: Some("Render Encoder"),
|
.create_view(&TextureViewDescriptor::default());
|
||||||
});
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&CommandEncoderDescriptor {
|
||||||
|
label: Some("Render Encoder"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let physical_width = (self.surface_config.width as f64 * self.window.scale_factor()) as f32;
|
||||||
|
let physical_height =
|
||||||
|
(self.surface_config.height as f64 * self.window.scale_factor()) as f32;
|
||||||
|
|
||||||
|
for textarea in self.text_buffers.values_mut() {
|
||||||
|
// resize the buf
|
||||||
|
textarea
|
||||||
|
.buffer
|
||||||
|
.set_size(&mut self.font_system, physical_width, physical_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
let textareas = self
|
||||||
|
.text_buffers
|
||||||
|
.values()
|
||||||
|
.map(|u| TextArea {
|
||||||
|
buffer: &u.buffer,
|
||||||
|
left: u.left.clone() as f32,
|
||||||
|
top: u.top.clone() as f32,
|
||||||
|
scale: u.scale.clone() as f32,
|
||||||
|
bounds: u.bounds.clone(),
|
||||||
|
default_color: u.default_color.clone(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.text_renderer
|
||||||
|
.prepare(
|
||||||
|
&self.device,
|
||||||
|
&self.queue,
|
||||||
|
&mut self.font_system,
|
||||||
|
&mut self.atlas,
|
||||||
|
Resolution {
|
||||||
|
width: self.surface_config.width,
|
||||||
|
height: self.surface_config.height,
|
||||||
|
},
|
||||||
|
textareas,
|
||||||
|
&mut self.font_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// LOCK BLOCK
|
// LOCK BLOCK
|
||||||
{
|
{
|
||||||
let render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
||||||
label: Some("Render Pass"),
|
label: Some("Render Pass"),
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
view: &view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: Operations {
|
ops: Operations {
|
||||||
load: LoadOp::Clear(Color {
|
load: LoadOp::Clear(Color {
|
||||||
r: 0.1,
|
r: 0.0,
|
||||||
g: 0.2,
|
g: 0.0,
|
||||||
b: 0.3,
|
b: 0.0,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
}),
|
}),
|
||||||
store: StoreOp::Store,
|
store: StoreOp::Store,
|
||||||
|
@ -139,16 +217,61 @@ impl WgpuState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.text_renderer
|
||||||
|
.render(&self.atlas, &mut render_pass)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.queue.submit(iter::once(encoder.finish()));
|
self.queue.submit(iter::once(encoder.finish()));
|
||||||
output.present();
|
output.present();
|
||||||
|
|
||||||
|
self.atlas.trim();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_textarea(
|
||||||
|
&mut self,
|
||||||
|
id: String,
|
||||||
|
metrics: Metrics,
|
||||||
|
left: f64,
|
||||||
|
top: f64,
|
||||||
|
scale: f64,
|
||||||
|
bounds: TextBounds,
|
||||||
|
default_color: glyphon::Color,
|
||||||
|
start_text: &str,
|
||||||
|
) -> &mut OTextArea {
|
||||||
|
let mut buffer = Buffer::new(&mut self.font_system, metrics);
|
||||||
|
|
||||||
|
let physical_width = (self.surface_config.width as f64 * self.window.scale_factor()) as f32;
|
||||||
|
let physical_height =
|
||||||
|
(self.surface_config.height as f64 * self.window.scale_factor()) as f32;
|
||||||
|
|
||||||
|
buffer.set_size(&mut self.font_system, physical_width, physical_height);
|
||||||
|
buffer.set_text(
|
||||||
|
&mut self.font_system,
|
||||||
|
start_text,
|
||||||
|
Attrs::new().family(Family::Monospace),
|
||||||
|
Shaping::Basic,
|
||||||
|
);
|
||||||
|
buffer.shape_until_scroll(&mut self.font_system);
|
||||||
|
|
||||||
|
let textarea = OTextArea {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
scale,
|
||||||
|
bounds,
|
||||||
|
default_color,
|
||||||
|
buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.text_buffers.insert(id.clone(), textarea);
|
||||||
|
|
||||||
|
self.text_buffers.get_mut(&id).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Preferences {
|
pub struct Preferences {
|
||||||
pub fcs: usize
|
pub fcs: usize,
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
||||||
|
use glyphon::{Buffer, Color, TextArea, TextBounds};
|
||||||
|
|
||||||
|
pub struct OTextArea {
|
||||||
|
pub left: f64,
|
||||||
|
pub top: f64,
|
||||||
|
pub scale: f64,
|
||||||
|
pub bounds: TextBounds,
|
||||||
|
pub default_color: Color,
|
||||||
|
pub buffer: Buffer,
|
||||||
|
}
|
|
@ -18,4 +18,4 @@ pub fn parse_date(julian: u32, millis: u32) -> DateTime<Utc> {
|
||||||
date += Duration::days(julian as i64 - 1);
|
date += Duration::days(julian as i64 - 1);
|
||||||
date += Duration::milliseconds(millis as i64);
|
date += Duration::milliseconds(millis as i64);
|
||||||
date
|
date
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,4 @@ pub const fn vcp_string(vcp: i16) -> &'static str {
|
||||||
} else {
|
} else {
|
||||||
"UNKNOWN"
|
"UNKNOWN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import * as wasm from "./wasm/nexrad_browser.js";
|
import * as wasm from "./wasm/nexrad_browser.js";
|
||||||
await wasm.default();
|
await wasm.default();
|
||||||
let global_context = wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight);
|
let abi_proxy = await wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight);
|
||||||
|
|
||||||
|
window.onresize = async () => {
|
||||||
|
console.log("window resized!");
|
||||||
|
await wasm.resize_abi(window.innerWidth, window.innerHeight, abi_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function rescaleCanvas(canvas, ctx) {
|
function rescaleCanvas(canvas, ctx) {
|
||||||
var dpr = window.devicePixelRatio || 1;
|
var dpr = window.devicePixelRatio || 1;
|
||||||
|
|
Loading…
Reference in New Issue