diff --git a/src/app.rs b/src/app.rs index ce5661f..684bb9f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,7 +13,7 @@ use crate::{ }, ui::{ ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState, - draw_config_panel, draw_spatial_force_panel, draw_stats_panel, panel_restore_item, + draw_config_panel, draw_hand_force_panels, panel_restore_item, }, }; use eframe::{egui, egui_wgpu}; @@ -167,12 +167,7 @@ impl EskinDesktopApp { live_rates: LiveRateMeters::new(), context_menu_open: false, context_menu_pos: egui::pos2(24.0, 48.0), - active_mode: ActiveMode::Finger(FingerMode { - rows: 12, - cols: 7, - range: 0..7000, - dot: true, - }), + active_mode: ActiveMode::Hand(HandGatewayMode { range: 0..7000 }), } } @@ -190,7 +185,6 @@ impl EskinDesktopApp { active_mode: self.active_mode.clone(), }, )); - self.paint_spatial_force_overlay(ui, rect); } fn paint_spatial_force_overlay(&self, ui: &egui::Ui, rect: egui::Rect) { @@ -223,11 +217,7 @@ impl EskinDesktopApp { } fn draw_workspace(&mut self, ui: &mut egui::Ui) { - if self.breakout_visible { - self.draw_split_workspace(ui); - } else { - self.draw_wgpu_background_rect(ui, ui.max_rect()); - } + self.draw_wgpu_background_rect(ui, ui.max_rect()); } fn draw_split_workspace(&mut self, ui: &mut egui::Ui) { @@ -295,9 +285,7 @@ impl EskinDesktopApp { self.signal_history.remove(0); } - if self.config_state.mode == SerialMode::Hand { - update_hand_signal_histories(&mut self.hand_signal_histories, &sample.matrix); - } + update_hand_signal_histories(&mut self.hand_signal_histories, &sample.matrix); } } @@ -423,12 +411,6 @@ impl EskinDesktopApp { self.matrix_config_panel.visible = false; self.context_menu_open = false; - if self.breakout_visible { - self.config_panel.visible = false; - self.stats_panel.visible = false; - return; - } - if let Some(next_mode) = draw_config_panel( ctx, &mut self.config_panel, @@ -439,39 +421,11 @@ impl EskinDesktopApp { self.live_rates.sample_rate_hz, self.live_rates.render_rate_hz, ) { - if next_mode == SerialMode::Hand { - self.config_state.mode = SerialMode::Finger; - self.connect_state.mode = SerialMode::Finger; - self.config_state.baud_rate = SerialMode::Finger.baud_rate(); - self.active_mode = ActiveMode::Finger(FingerMode { - rows: self.matrix_config.rows, - cols: self.matrix_config.cols, - range: 0..7000, - dot: true, - }); - self.breakout_visible = true; - } else { - self.switch_mode(next_mode); - } + self.switch_mode(next_mode); } - if self.config_state.mode == SerialMode::Finger { - self.stats_panel.visible = true; - draw_stats_panel( - ctx, - &mut self.stats_panel, - &self.signal_history, - self.latest_spatial_force, - ); - } else { - self.stats_panel.visible = false; - } - - if let Some(force) = self.latest_spatial_force { - self.spatial_force_panel_force = Some(force); - } - - draw_spatial_force_panel(ctx, self.spatial_force_panel_force, &self.signal_history); + self.stats_panel.visible = false; + draw_hand_force_panels(ctx, true, &self.hand_signal_histories); } fn draw_panel_context_menu(&mut self, ctx: &egui::Context) { diff --git a/src/ui.rs b/src/ui.rs index 4250187..be0769f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -104,10 +104,10 @@ impl Default for ConfigPanelState { .unwrap_or_else(|| "COM3".to_owned()); Self { - mode: SerialMode::Finger, + mode: SerialMode::Hand, port, available_ports, - baud_rate: 921_600, + baud_rate: SerialMode::Hand.baud_rate(), data_bits: 8, stop_bits: 1, parity: Parity::None, @@ -126,7 +126,7 @@ impl Default for ConnectPanelState { let port = serial_enum().unwrap_or_default(); let selected_port = port.first().cloned().unwrap_or_default(); Self { - mode: SerialMode::Finger, + mode: SerialMode::Hand, port, selected_port, duration: 10, @@ -499,27 +499,14 @@ pub fn draw_spatial_force_panel( } fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option { - let mut changed_to = None; + config.mode = SerialMode::Hand; + config.baud_rate = SerialMode::Hand.baud_rate(); ui.horizontal_wrapped(|ui| { - ui.colored_label(dim_text(), "模式"); + ui.colored_label(dim_text(), "Mode"); ui.add_space(12.0); - - if mode_button(ui, &mut config.mode, SerialMode::Finger, "指尖模块") { - changed_to = Some(SerialMode::Finger) - } - if mode_button(ui, &mut config.mode, SerialMode::Hand, "游戏体验") { - changed_to = Some(SerialMode::Hand) - } - // mode_button(ui, &mut config.mode, SerialMode::Model, "模型"); - - // Legacy reconnect controls. Current sensors run fixed 921600 baud without auto-reconnect UI. - // ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // ui.checkbox(&mut config.auto_reconnect, "自动"); - // ui.colored_label(dim_text(), "重连"); - // }); + ui.label(style::panel_title("Hand Gateway")); }); - - changed_to + None } fn draw_connection_row( diff --git a/static/wgsl/shader.wgsl b/static/wgsl/shader.wgsl index 1797361..b79f74f 100644 --- a/static/wgsl/shader.wgsl +++ b/static/wgsl/shader.wgsl @@ -63,7 +63,7 @@ fn linear_to_srgb(linear: vec3f) -> vec3f { fn output_color(linear_rgb: vec3f, alpha: f32) -> vec4f { let clamped = clamp(linear_rgb, vec3f(0.0), vec3f(1.0)); - if (u.color.w > 0.5) { + if u.color.w > 0.5 { return vec4f(clamped, alpha); } @@ -91,12 +91,12 @@ fn range_stop_color(index: u32) -> vec3f { fn sample_range_color(value: f32) -> vec3f { let t = saturate(value); - if (t <= 0.33) { + if t <= 0.33 { let local = smoothstep(0.0, 0.33, t); return mix(range_stop_color(0u), range_stop_color(1u), local); } - if (t <= 0.66) { + if t <= 0.66 { let local = smoothstep(0.33, 0.66, t); return mix(range_stop_color(1u), range_stop_color(2u), local); } @@ -105,7 +105,6 @@ fn sample_range_color(value: f32) -> vec3f { return mix(range_stop_color(2u), range_stop_color(3u), local); } - // background struct BackgroundVertexOutput { @builtin(position) clip_position: vec4f, @@ -129,7 +128,6 @@ fn fs_background(@builtin(position) frag_coord: vec4f) -> @location(0) vec4f { return output_color(vec3f(0.0, 0.0, 0.0), 1.0); } - // hand image background struct HandImageVertexOutput { @builtin(position) clip_position: vec4f, @@ -167,7 +165,7 @@ fn fs_hand_image(in: HandImageVertexOutput) -> @location(0) vec4f { let image_aspect = u.image.x / max(u.image.y, 1.0); var uv = in.screen_uv; - if (viewport_aspect > image_aspect) { + if viewport_aspect > image_aspect { let image_width = image_aspect / viewport_aspect; uv.x = (uv.x - (1.0 - image_width) * 0.5) / image_width; } else { @@ -175,7 +173,7 @@ fn fs_hand_image(in: HandImageVertexOutput) -> @location(0) vec4f { uv.y = (uv.y - (1.0 - image_height) * 0.5) / image_height; } - if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { + if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 { discard; } @@ -183,7 +181,6 @@ fn fs_hand_image(in: HandImageVertexOutput) -> @location(0) vec4f { return output_color(color.rgb, color.a); } - // glyph struct GlyphVertexInput { @location(0) local: vec2f, @@ -226,45 +223,45 @@ fn digit_segment_on(digit: u32, segment: u32) -> bool { fn seven_segment_digit_alpha(local: vec2f, digit: u32) -> f32 { var alpha = 0.0; - if (digit_segment_on(digit, 0u)) { + if digit_segment_on(digit, 0u) { alpha = max(alpha, rect_alpha(local, vec2f(0.0, 0.70), vec2f(0.38, 0.078))); } - if (digit_segment_on(digit, 1u)) { + if digit_segment_on(digit, 1u) { alpha = max(alpha, rect_alpha(local, vec2f(0.39, 0.36), vec2f(0.078, 0.335))); } - if (digit_segment_on(digit, 2u)) { + if digit_segment_on(digit, 2u) { alpha = max(alpha, rect_alpha(local, vec2f(0.39, -0.36), vec2f(0.078, 0.335))); } - if (digit_segment_on(digit, 3u)) { + if digit_segment_on(digit, 3u) { alpha = max(alpha, rect_alpha(local, vec2f(0.0, -0.70), vec2f(0.38, 0.078))); } - if (digit_segment_on(digit, 4u)) { + if digit_segment_on(digit, 4u) { alpha = max(alpha, rect_alpha(local, vec2f(-0.39, -0.36), vec2f(0.078, 0.335))); } - if (digit_segment_on(digit, 5u)) { + if digit_segment_on(digit, 5u) { alpha = max(alpha, rect_alpha(local, vec2f(-0.39, 0.36), vec2f(0.078, 0.335))); } - if (digit_segment_on(digit, 6u)) { + if digit_segment_on(digit, 6u) { alpha = max(alpha, rect_alpha(local, vec2f(0.0, 0.0), vec2f(0.35, 0.075))); } return alpha; } fn digit_count(value: u32) -> u32 { - if (value >= 1000u) { + if value >= 1000u { return 4u; } - if (value >= 100u) { + if value >= 100u { return 3u; } - if (value >= 10u) { + if value >= 10u { return 2u; } return 1u; } fn digit_at(value: u32, slot: u32, count: u32) -> u32 { - if (count == 4u) { + if count == 4u { switch slot { case 0u: { return (value / 1000u) % 10u; } case 1u: { return (value / 100u) % 10u; } @@ -272,14 +269,14 @@ fn digit_at(value: u32, slot: u32, count: u32) -> u32 { default: { return value % 10u; } } } - if (count == 3u) { + if count == 3u { switch slot { case 0u: { return (value / 100u) % 10u; } case 1u: { return (value / 10u) % 10u; } default: { return value % 10u; } } } - if (count == 2u) { + if count == 2u { return select(value % 10u, (value / 10u) % 10u, slot == 0u); } return value % 10u; @@ -294,7 +291,7 @@ fn number_alpha(local: vec2f, display_value: f32) -> f32 { var alpha = 0.0; for (var slot = 0u; slot < 4u; slot = slot + 1u) { - if (slot < count) { + if slot < count { let center_x = start_x + f32(slot) * slot_width; let digit_local = vec2f((local.x - center_x) / (slot_width * 0.78), local.y / 0.92); let digit = digit_at(value, slot, count); @@ -335,8 +332,7 @@ struct DotVertexInput { struct DotInstanceInput { @location(1) world_position: vec4f, - @location(2) style: vec4f -} + @location(2) style: vec4f} struct DotVertexOutput { @builtin(position) clip_position: vec4f, @@ -358,7 +354,7 @@ fn hand_image_uv_to_clip(image_uv: vec2f) -> vec2f { var screen_uv = image_uv; - if (viewport_aspect > image_aspect) { + if viewport_aspect > image_aspect { let image_width = image_aspect / viewport_aspect; screen_uv.x = image_uv.x * image_width + (1.0 - image_width) * 0.5; } else { @@ -390,8 +386,6 @@ fn fingertip_film_alpha( local: vec2f, half_width: f32, cap_center_y: f32, - rear_edge_y: f32, - rear_bulge: f32, softness: f32, ) -> f32 { // Top/front of the film is a closed round fingertip cap. @@ -399,14 +393,10 @@ fn fingertip_film_alpha( let cap = (1.0 - smoothstep(half_width, half_width + softness, cap_dist)) * (1.0 - smoothstep(cap_center_y - softness, cap_center_y + softness, local.y)); - // The rear half keeps nearly parallel sides and ends with a shallow arc; - // it deliberately does not converge back into another capsule end. - let x_norm = clamp(abs(local.x) / max(half_width, 0.001), 0.0, 1.0); - let rear_curve_y = rear_edge_y + rear_bulge * (1.0 - x_norm * x_norm); + // The rear remains open; the carrier quad clips the membrane at its end. let side = 1.0 - smoothstep(half_width, half_width + softness, abs(local.x)); - let rear = 1.0 - smoothstep(rear_curve_y, rear_curve_y + softness, local.y); let body_gate = smoothstep(cap_center_y - softness, cap_center_y + softness, local.y); - let body = side * rear * body_gate; + let body = side * body_gate; return max(cap, body); } @@ -427,38 +417,77 @@ fn vs_hand_membrane(vertex: DotVertexInput, instance: DotInstanceInput) -> HandM @fragment fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f { - // The film shape matches the fingertip: closed round front, parallel rear sides, - // and a shallow rear arc instead of a second capsule end. - let panel = fingertip_film_alpha(in.local, 0.66, -0.38, 0.72, 0.18, 0.045); - let inner = fingertip_film_alpha(in.local, 0.54, -0.36, 0.64, 0.12, 0.060); - let border = clamp(panel - inner * 0.52, 0.0, 1.0); + let p = in.local; - // Dense mesh: the live 12x7 pressure dots sit over this finer sensor lattice. - let uv = clamp(in.local * 0.5 + vec2f(0.5, 0.5), vec2f(0.0, 0.0), vec2f(1.0, 1.0)); - let grid = vec2f(18.0, 34.0); - let cell = uv * grid - vec2f(0.5, 0.5); - let nearest = abs(fract(cell + vec2f(0.5, 0.5)) - vec2f(0.5, 0.5)); - let grid_line = max( - 1.0 - smoothstep(0.014, 0.038, nearest.x), - 1.0 - smoothstep(0.014, 0.038, nearest.y), + let halo_shape = fingertip_film_alpha(p, 0.72, -0.39, 0.060); + let panel = fingertip_film_alpha(p, 0.66, -0.38, 0.040); + let rim_inner = fingertip_film_alpha(p, 0.60, -0.36, 0.040); + let clear_inner = fingertip_film_alpha(p, 0.49, -0.31, 0.180); + + let halo = clamp(halo_shape - panel, 0.0, 1.0); + let border = clamp(panel - rim_inner, 0.0, 1.0); + let edge_fade = pow(clamp(1.0 - clear_inner, 0.0, 1.0), 1.15) * panel; + + // Micro lattice. + let uv = clamp( + p * vec2f(0.52, 0.46) + vec2f(0.5, 0.46), + vec2f(0.0, 0.0), + vec2f(1.0, 1.0), ); - let joint = 1.0 - smoothstep(0.070, 0.150, length(nearest * vec2f(1.18, 1.0))); - let center_shadow = 1.0 - smoothstep(0.10, 0.76, length(in.local * vec2f(0.92, 0.66))); - let top_light = smoothstep(-0.52, 0.16, -in.local.y) * 0.20; - let side_glow = smoothstep(0.30, 0.70, abs(in.local.x)) * 0.26; - let lift_shadow = smoothstep(0.48, 0.86, in.local.y) * (1.0 - smoothstep(0.50, 0.86, abs(in.local.x))) * 0.13; - let scan = (0.5 + 0.5 * sin((uv.y * 76.0 + uv.x * 9.0) * 6.28318)) * 0.030; + let grid = vec2f(18.0, 34.0); + let cell_uv = fract(uv * grid) - vec2f(0.5, 0.5); - let membrane_color = vec3f(0.006, 0.28, 0.38); - let line_color = vec3f(0.12, 0.72, 0.88); - let rim_color = vec3f(0.18, 0.98, 1.0); - let color = membrane_color * (0.68 + top_light + side_glow + scan) - + line_color * (grid_line * 0.20 + joint * 0.42) - + rim_color * (border * 0.92 + side_glow * 0.18) - - vec3f(0.0, 0.16, 0.24) * center_shadow * 0.30 - - vec3f(0.0, 0.10, 0.16) * lift_shadow; - let alpha = panel * (0.24 + grid_line * 0.10 + joint * 0.23 + border * 0.42); + let node = 1.0 - smoothstep( + 0.070, + 0.165, + length(cell_uv * vec2f(1.0, 1.08)), + ); + + let mesh_x = smoothstep(0.455, 0.495, abs(cell_uv.x)); + let mesh_y = smoothstep(0.455, 0.495, abs(cell_uv.y)); + let mesh = max(mesh_x, mesh_y); + let lattice = (node * 0.90 + mesh * 0.10) * rim_inner; + + // Blue glass highlights. + let side_glow = smoothstep(0.28, 0.66, abs(p.x)) * panel; + let top_light = smoothstep(0.10, 0.78, -p.y) * panel; + + let sheen_axis = p.x * 0.88 + p.y * 0.22 + 0.16; + let sheen = (1.0 - smoothstep(0.0, 0.11, abs(sheen_axis))) * panel; + let scan = (0.5 + 0.5 * sin((uv.y * 76.0 + uv.x * 9.0) * 6.28318)) * 0.025; + + // Temporary synthetic pressure hotspot. Real hand pressure dots are drawn above this pass. + let pressure_center = vec2f(-0.10, -0.15); + let pressure_dist = length((p - pressure_center) * vec2f(1.0, 0.72)); + let pressure = (1.0 - smoothstep(0.04, 0.37, pressure_dist)) * rim_inner; + let pressure_hot = (1.0 - smoothstep(0.015, 0.13, pressure_dist)) * rim_inner; + + let membrane_color = vec3f(0.006, 0.028, 0.110); + let inner_cyanblue = vec3f(0.012, 0.110, 0.320); + let lattice_color = vec3f(0.040, 0.420, 0.760); + let rim_color = vec3f(0.120, 0.720, 0.950); + + let pressure_green = vec3f(0.00, 0.66, 0.22); + let pressure_lime = vec3f(0.18, 1.00, 0.50); + + let color = membrane_color * panel * (0.36 + top_light * 0.08 + scan) + + inner_cyanblue * edge_fade * 0.72 + + lattice_color * lattice * 0.66 + + rim_color * (border * 0.90 + edge_fade * 0.16 + halo * 0.16 + side_glow * 0.08 + sheen * 0.10) + + pressure_green * pressure * 0.70 + + pressure_lime * pressure_hot * 0.82; + + let alpha = clamp( + panel * 0.08 + + edge_fade * 0.43 + + border * 0.20 + + halo * 0.04 + + lattice * 0.055 + + pressure * 0.05, + 0.0, + 0.78, + ); return output_color(color, alpha); } @@ -621,7 +650,6 @@ fn fs_dot(in: DotVertexOutput) -> @location(0) vec4f { return output_color(color, alpha); } - // model struct ModelVertexInput { @location(0) position: vec3f, @@ -740,11 +768,11 @@ fn aces_tonemap(x: vec3f) -> vec3f { fn material_normal(in: ModelVertexOutput, front_facing: bool) -> vec3f { var n = normalize(in.world_normal); - if (!front_facing && material.flags.w > 0.5) { + if !front_facing && material.flags.w > 0.5 { n = -n; } - if (material.flags.z < 0.5) { + if material.flags.z < 0.5 { return n; } @@ -764,17 +792,17 @@ fn fs_model(in: ModelVertexOutput, @builtin(front_facing) front_facing: bool) -> let base_color = base_sample * material.base_color * in.color; let alpha_mode = material.emissive_alpha.w; var alpha = base_color.a; - if (alpha_mode < 0.5) { + if alpha_mode < 0.5 { alpha = 1.0; - } else if (alpha_mode < 1.5) { - if (alpha < material.flags.x) { + } else if alpha_mode < 1.5 { + if alpha < material.flags.x { discard; } alpha = 1.0; } let debug_mode = u32(u.render_options.x + 0.5); - if (debug_mode == 1u) { + if debug_mode == 1u { return output_color(base_color.rgb, alpha); } @@ -840,7 +868,7 @@ fn fs_model(in: ModelVertexOutput, @builtin(front_facing) front_facing: bool) -> let ambient_strength = 0.08; // Temporary neutral linear ambient term until a real IBL/HDR environment is added. var ambient = vec3f(0.0); - if (u.render_options.y > 0.5) { + if u.render_options.y > 0.5 { let ambient_diffuse = albedo * (1.0 - metallic) * ambient_color * ambient_strength; let ambient_specular = f_ambient * ambient_color * ambient_strength * (1.0 - roughness * 0.55); ambient = (ambient_diffuse + ambient_specular) * ao; @@ -848,7 +876,7 @@ fn fs_model(in: ModelVertexOutput, @builtin(front_facing) front_facing: bool) -> let exposed_color = (ambient + key + fill + emissive) * max(u.render_options.w, 0.0); var color = exposed_color; - if (u.render_options.z > 0.5) { + if u.render_options.z > 0.5 { color = aces_tonemap(exposed_color); }