Refactor to hand-mode UI and reorganize WGSL shader
Replace spatial force / stats panels with unified hand force panels; simplify app.rs wiring and reorganize shader.wgsl for the hand rendering layout.
This commit is contained in:
60
src/app.rs
60
src/app.rs
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
ui::{
|
ui::{
|
||||||
ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState,
|
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};
|
use eframe::{egui, egui_wgpu};
|
||||||
@@ -167,12 +167,7 @@ impl EskinDesktopApp {
|
|||||||
live_rates: LiveRateMeters::new(),
|
live_rates: LiveRateMeters::new(),
|
||||||
context_menu_open: false,
|
context_menu_open: false,
|
||||||
context_menu_pos: egui::pos2(24.0, 48.0),
|
context_menu_pos: egui::pos2(24.0, 48.0),
|
||||||
active_mode: ActiveMode::Finger(FingerMode {
|
active_mode: ActiveMode::Hand(HandGatewayMode { range: 0..7000 }),
|
||||||
rows: 12,
|
|
||||||
cols: 7,
|
|
||||||
range: 0..7000,
|
|
||||||
dot: true,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +185,6 @@ impl EskinDesktopApp {
|
|||||||
active_mode: self.active_mode.clone(),
|
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) {
|
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) {
|
fn draw_workspace(&mut self, ui: &mut egui::Ui) {
|
||||||
if self.breakout_visible {
|
self.draw_wgpu_background_rect(ui, ui.max_rect());
|
||||||
self.draw_split_workspace(ui);
|
|
||||||
} else {
|
|
||||||
self.draw_wgpu_background_rect(ui, ui.max_rect());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_split_workspace(&mut self, ui: &mut egui::Ui) {
|
fn draw_split_workspace(&mut self, ui: &mut egui::Ui) {
|
||||||
@@ -295,9 +285,7 @@ impl EskinDesktopApp {
|
|||||||
self.signal_history.remove(0);
|
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.matrix_config_panel.visible = false;
|
||||||
self.context_menu_open = 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(
|
if let Some(next_mode) = draw_config_panel(
|
||||||
ctx,
|
ctx,
|
||||||
&mut self.config_panel,
|
&mut self.config_panel,
|
||||||
@@ -439,39 +421,11 @@ impl EskinDesktopApp {
|
|||||||
self.live_rates.sample_rate_hz,
|
self.live_rates.sample_rate_hz,
|
||||||
self.live_rates.render_rate_hz,
|
self.live_rates.render_rate_hz,
|
||||||
) {
|
) {
|
||||||
if next_mode == SerialMode::Hand {
|
self.switch_mode(next_mode);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.config_state.mode == SerialMode::Finger {
|
self.stats_panel.visible = false;
|
||||||
self.stats_panel.visible = true;
|
draw_hand_force_panels(ctx, true, &self.hand_signal_histories);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_panel_context_menu(&mut self, ctx: &egui::Context) {
|
fn draw_panel_context_menu(&mut self, ctx: &egui::Context) {
|
||||||
|
|||||||
29
src/ui.rs
29
src/ui.rs
@@ -104,10 +104,10 @@ impl Default for ConfigPanelState {
|
|||||||
.unwrap_or_else(|| "COM3".to_owned());
|
.unwrap_or_else(|| "COM3".to_owned());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mode: SerialMode::Finger,
|
mode: SerialMode::Hand,
|
||||||
port,
|
port,
|
||||||
available_ports,
|
available_ports,
|
||||||
baud_rate: 921_600,
|
baud_rate: SerialMode::Hand.baud_rate(),
|
||||||
data_bits: 8,
|
data_bits: 8,
|
||||||
stop_bits: 1,
|
stop_bits: 1,
|
||||||
parity: Parity::None,
|
parity: Parity::None,
|
||||||
@@ -126,7 +126,7 @@ impl Default for ConnectPanelState {
|
|||||||
let port = serial_enum().unwrap_or_default();
|
let port = serial_enum().unwrap_or_default();
|
||||||
let selected_port = port.first().cloned().unwrap_or_default();
|
let selected_port = port.first().cloned().unwrap_or_default();
|
||||||
Self {
|
Self {
|
||||||
mode: SerialMode::Finger,
|
mode: SerialMode::Hand,
|
||||||
port,
|
port,
|
||||||
selected_port,
|
selected_port,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
@@ -499,27 +499,14 @@ pub fn draw_spatial_force_panel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option<SerialMode> {
|
fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option<SerialMode> {
|
||||||
let mut changed_to = None;
|
config.mode = SerialMode::Hand;
|
||||||
|
config.baud_rate = SerialMode::Hand.baud_rate();
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.colored_label(dim_text(), "模式");
|
ui.colored_label(dim_text(), "Mode");
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
ui.label(style::panel_title("Hand Gateway"));
|
||||||
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(), "重连");
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
None
|
||||||
changed_to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_connection_row(
|
fn draw_connection_row(
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ fn linear_to_srgb(linear: vec3f) -> vec3f {
|
|||||||
|
|
||||||
fn output_color(linear_rgb: vec3f, alpha: f32) -> vec4f {
|
fn output_color(linear_rgb: vec3f, alpha: f32) -> vec4f {
|
||||||
let clamped = clamp(linear_rgb, vec3f(0.0), vec3f(1.0));
|
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);
|
return vec4f(clamped, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,12 +91,12 @@ fn range_stop_color(index: u32) -> vec3f {
|
|||||||
fn sample_range_color(value: f32) -> vec3f {
|
fn sample_range_color(value: f32) -> vec3f {
|
||||||
let t = saturate(value);
|
let t = saturate(value);
|
||||||
|
|
||||||
if (t <= 0.33) {
|
if t <= 0.33 {
|
||||||
let local = smoothstep(0.0, 0.33, t);
|
let local = smoothstep(0.0, 0.33, t);
|
||||||
return mix(range_stop_color(0u), range_stop_color(1u), local);
|
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);
|
let local = smoothstep(0.33, 0.66, t);
|
||||||
return mix(range_stop_color(1u), range_stop_color(2u), local);
|
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);
|
return mix(range_stop_color(2u), range_stop_color(3u), local);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// background
|
// background
|
||||||
struct BackgroundVertexOutput {
|
struct BackgroundVertexOutput {
|
||||||
@builtin(position) clip_position: vec4f,
|
@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);
|
return output_color(vec3f(0.0, 0.0, 0.0), 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// hand image background
|
// hand image background
|
||||||
struct HandImageVertexOutput {
|
struct HandImageVertexOutput {
|
||||||
@builtin(position) clip_position: vec4f,
|
@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);
|
let image_aspect = u.image.x / max(u.image.y, 1.0);
|
||||||
|
|
||||||
var uv = in.screen_uv;
|
var uv = in.screen_uv;
|
||||||
if (viewport_aspect > image_aspect) {
|
if viewport_aspect > image_aspect {
|
||||||
let image_width = image_aspect / viewport_aspect;
|
let image_width = image_aspect / viewport_aspect;
|
||||||
uv.x = (uv.x - (1.0 - image_width) * 0.5) / image_width;
|
uv.x = (uv.x - (1.0 - image_width) * 0.5) / image_width;
|
||||||
} else {
|
} 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;
|
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;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +181,6 @@ fn fs_hand_image(in: HandImageVertexOutput) -> @location(0) vec4f {
|
|||||||
return output_color(color.rgb, color.a);
|
return output_color(color.rgb, color.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// glyph
|
// glyph
|
||||||
struct GlyphVertexInput {
|
struct GlyphVertexInput {
|
||||||
@location(0) local: vec2f,
|
@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 {
|
fn seven_segment_digit_alpha(local: vec2f, digit: u32) -> f32 {
|
||||||
var alpha = 0.0;
|
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)));
|
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)));
|
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)));
|
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)));
|
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)));
|
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)));
|
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)));
|
alpha = max(alpha, rect_alpha(local, vec2f(0.0, 0.0), vec2f(0.35, 0.075)));
|
||||||
}
|
}
|
||||||
return alpha;
|
return alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn digit_count(value: u32) -> u32 {
|
fn digit_count(value: u32) -> u32 {
|
||||||
if (value >= 1000u) {
|
if value >= 1000u {
|
||||||
return 4u;
|
return 4u;
|
||||||
}
|
}
|
||||||
if (value >= 100u) {
|
if value >= 100u {
|
||||||
return 3u;
|
return 3u;
|
||||||
}
|
}
|
||||||
if (value >= 10u) {
|
if value >= 10u {
|
||||||
return 2u;
|
return 2u;
|
||||||
}
|
}
|
||||||
return 1u;
|
return 1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn digit_at(value: u32, slot: u32, count: u32) -> u32 {
|
fn digit_at(value: u32, slot: u32, count: u32) -> u32 {
|
||||||
if (count == 4u) {
|
if count == 4u {
|
||||||
switch slot {
|
switch slot {
|
||||||
case 0u: { return (value / 1000u) % 10u; }
|
case 0u: { return (value / 1000u) % 10u; }
|
||||||
case 1u: { return (value / 100u) % 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; }
|
default: { return value % 10u; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count == 3u) {
|
if count == 3u {
|
||||||
switch slot {
|
switch slot {
|
||||||
case 0u: { return (value / 100u) % 10u; }
|
case 0u: { return (value / 100u) % 10u; }
|
||||||
case 1u: { return (value / 10u) % 10u; }
|
case 1u: { return (value / 10u) % 10u; }
|
||||||
default: { return value % 10u; }
|
default: { return value % 10u; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count == 2u) {
|
if count == 2u {
|
||||||
return select(value % 10u, (value / 10u) % 10u, slot == 0u);
|
return select(value % 10u, (value / 10u) % 10u, slot == 0u);
|
||||||
}
|
}
|
||||||
return value % 10u;
|
return value % 10u;
|
||||||
@@ -294,7 +291,7 @@ fn number_alpha(local: vec2f, display_value: f32) -> f32 {
|
|||||||
var alpha = 0.0;
|
var alpha = 0.0;
|
||||||
|
|
||||||
for (var slot = 0u; slot < 4u; slot = slot + 1u) {
|
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 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_local = vec2f((local.x - center_x) / (slot_width * 0.78), local.y / 0.92);
|
||||||
let digit = digit_at(value, slot, count);
|
let digit = digit_at(value, slot, count);
|
||||||
@@ -335,8 +332,7 @@ struct DotVertexInput {
|
|||||||
|
|
||||||
struct DotInstanceInput {
|
struct DotInstanceInput {
|
||||||
@location(1) world_position: vec4f,
|
@location(1) world_position: vec4f,
|
||||||
@location(2) style: vec4f
|
@location(2) style: vec4f}
|
||||||
}
|
|
||||||
|
|
||||||
struct DotVertexOutput {
|
struct DotVertexOutput {
|
||||||
@builtin(position) clip_position: vec4f,
|
@builtin(position) clip_position: vec4f,
|
||||||
@@ -358,7 +354,7 @@ fn hand_image_uv_to_clip(image_uv: vec2f) -> vec2f {
|
|||||||
|
|
||||||
var screen_uv = image_uv;
|
var screen_uv = image_uv;
|
||||||
|
|
||||||
if (viewport_aspect > image_aspect) {
|
if viewport_aspect > image_aspect {
|
||||||
let image_width = image_aspect / viewport_aspect;
|
let image_width = image_aspect / viewport_aspect;
|
||||||
screen_uv.x = image_uv.x * image_width + (1.0 - image_width) * 0.5;
|
screen_uv.x = image_uv.x * image_width + (1.0 - image_width) * 0.5;
|
||||||
} else {
|
} else {
|
||||||
@@ -390,8 +386,6 @@ fn fingertip_film_alpha(
|
|||||||
local: vec2f,
|
local: vec2f,
|
||||||
half_width: f32,
|
half_width: f32,
|
||||||
cap_center_y: f32,
|
cap_center_y: f32,
|
||||||
rear_edge_y: f32,
|
|
||||||
rear_bulge: f32,
|
|
||||||
softness: f32,
|
softness: f32,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
// Top/front of the film is a closed round fingertip cap.
|
// 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))
|
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));
|
* (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;
|
// The rear remains open; the carrier quad clips the membrane at its end.
|
||||||
// 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);
|
|
||||||
let side = 1.0 - smoothstep(half_width, half_width + softness, abs(local.x));
|
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_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);
|
return max(cap, body);
|
||||||
}
|
}
|
||||||
@@ -427,38 +417,77 @@ fn vs_hand_membrane(vertex: DotVertexInput, instance: DotInstanceInput) -> HandM
|
|||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f {
|
fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f {
|
||||||
// The film shape matches the fingertip: closed round front, parallel rear sides,
|
let p = in.local;
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Dense mesh: the live 12x7 pressure dots sit over this finer sensor lattice.
|
let halo_shape = fingertip_film_alpha(p, 0.72, -0.39, 0.060);
|
||||||
let uv = clamp(in.local * 0.5 + vec2f(0.5, 0.5), vec2f(0.0, 0.0), vec2f(1.0, 1.0));
|
let panel = fingertip_film_alpha(p, 0.66, -0.38, 0.040);
|
||||||
let grid = vec2f(18.0, 34.0);
|
let rim_inner = fingertip_film_alpha(p, 0.60, -0.36, 0.040);
|
||||||
let cell = uv * grid - vec2f(0.5, 0.5);
|
let clear_inner = fingertip_film_alpha(p, 0.49, -0.31, 0.180);
|
||||||
let nearest = abs(fract(cell + vec2f(0.5, 0.5)) - vec2f(0.5, 0.5));
|
|
||||||
let grid_line = max(
|
let halo = clamp(halo_shape - panel, 0.0, 1.0);
|
||||||
1.0 - smoothstep(0.014, 0.038, nearest.x),
|
let border = clamp(panel - rim_inner, 0.0, 1.0);
|
||||||
1.0 - smoothstep(0.014, 0.038, nearest.y),
|
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 grid = vec2f(18.0, 34.0);
|
||||||
let side_glow = smoothstep(0.30, 0.70, abs(in.local.x)) * 0.26;
|
let cell_uv = fract(uv * grid) - vec2f(0.5, 0.5);
|
||||||
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 membrane_color = vec3f(0.006, 0.28, 0.38);
|
let node = 1.0 - smoothstep(
|
||||||
let line_color = vec3f(0.12, 0.72, 0.88);
|
0.070,
|
||||||
let rim_color = vec3f(0.18, 0.98, 1.0);
|
0.165,
|
||||||
let color = membrane_color * (0.68 + top_light + side_glow + scan)
|
length(cell_uv * vec2f(1.0, 1.08)),
|
||||||
+ 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
|
let mesh_x = smoothstep(0.455, 0.495, abs(cell_uv.x));
|
||||||
- vec3f(0.0, 0.10, 0.16) * lift_shadow;
|
let mesh_y = smoothstep(0.455, 0.495, abs(cell_uv.y));
|
||||||
let alpha = panel * (0.24 + grid_line * 0.10 + joint * 0.23 + border * 0.42);
|
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);
|
return output_color(color, alpha);
|
||||||
}
|
}
|
||||||
@@ -621,7 +650,6 @@ fn fs_dot(in: DotVertexOutput) -> @location(0) vec4f {
|
|||||||
return output_color(color, alpha);
|
return output_color(color, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// model
|
// model
|
||||||
struct ModelVertexInput {
|
struct ModelVertexInput {
|
||||||
@location(0) position: vec3f,
|
@location(0) position: vec3f,
|
||||||
@@ -740,11 +768,11 @@ fn aces_tonemap(x: vec3f) -> vec3f {
|
|||||||
|
|
||||||
fn material_normal(in: ModelVertexOutput, front_facing: bool) -> vec3f {
|
fn material_normal(in: ModelVertexOutput, front_facing: bool) -> vec3f {
|
||||||
var n = normalize(in.world_normal);
|
var n = normalize(in.world_normal);
|
||||||
if (!front_facing && material.flags.w > 0.5) {
|
if !front_facing && material.flags.w > 0.5 {
|
||||||
n = -n;
|
n = -n;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (material.flags.z < 0.5) {
|
if material.flags.z < 0.5 {
|
||||||
return n;
|
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 base_color = base_sample * material.base_color * in.color;
|
||||||
let alpha_mode = material.emissive_alpha.w;
|
let alpha_mode = material.emissive_alpha.w;
|
||||||
var alpha = base_color.a;
|
var alpha = base_color.a;
|
||||||
if (alpha_mode < 0.5) {
|
if alpha_mode < 0.5 {
|
||||||
alpha = 1.0;
|
alpha = 1.0;
|
||||||
} else if (alpha_mode < 1.5) {
|
} else if alpha_mode < 1.5 {
|
||||||
if (alpha < material.flags.x) {
|
if alpha < material.flags.x {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
alpha = 1.0;
|
alpha = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let debug_mode = u32(u.render_options.x + 0.5);
|
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);
|
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;
|
let ambient_strength = 0.08;
|
||||||
// Temporary neutral linear ambient term until a real IBL/HDR environment is added.
|
// Temporary neutral linear ambient term until a real IBL/HDR environment is added.
|
||||||
var ambient = vec3f(0.0);
|
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_diffuse = albedo * (1.0 - metallic) * ambient_color * ambient_strength;
|
||||||
let ambient_specular = f_ambient * ambient_color * ambient_strength * (1.0 - roughness * 0.55);
|
let ambient_specular = f_ambient * ambient_color * ambient_strength * (1.0 - roughness * 0.55);
|
||||||
ambient = (ambient_diffuse + ambient_specular) * ao;
|
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);
|
let exposed_color = (ambient + key + fill + emissive) * max(u.render_options.w, 0.0);
|
||||||
var color = exposed_color;
|
var color = exposed_color;
|
||||||
if (u.render_options.z > 0.5) {
|
if u.render_options.z > 0.5 {
|
||||||
color = aces_tonemap(exposed_color);
|
color = aces_tonemap(exposed_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user