Tune hand-tip and palm chip layout coordinates

Adjust HandTipMatrix / HandPalmChip center, angle, and size in render.rs and propagate the spacing tweak through shader.wgsl.
This commit is contained in:
lenn
2026-07-03 15:30:25 +08:00
parent a999d9c064
commit 5264f0c247
2 changed files with 124 additions and 85 deletions

View File

@@ -44,43 +44,43 @@ pub struct HandGatewayMode {
const HAND_TIP_MATRICES: [HandTipMatrix; 5] = [
HandTipMatrix {
center_px: [260.0, 490.0],
center_px: [265.0, 495.0],
size_px: [70.0, 120.0],
angle_rad: -0.45,
angle_rad: -0.40,
},
HandTipMatrix {
center_px: [360.0, 255.0],
center_px: [359.0, 260.0],
size_px: [70.0, 120.0],
angle_rad: -0.05,
angle_rad: -0.17,
},
HandTipMatrix {
center_px: [485.0, 225.0],
center_px: [482.0, 228.0],
size_px: [70.0, 120.0],
angle_rad: 0.0,
angle_rad: -0.03,
},
HandTipMatrix {
center_px: [595.0, 260.0],
center_px: [596.0, 265.0],
size_px: [70.0, 120.0],
angle_rad: 0.12,
angle_rad: 0.10,
},
HandTipMatrix {
center_px: [705.0, 385.0],
center_px: [693.0, 370.0],
size_px: [70.0, 120.0],
angle_rad: 0.28,
angle_rad: 0.22,
},
];
const HAND_PALM_CHIPS: [HandPalmChip; 2] = [
HandPalmChip {
center_px: [538.0, 608.0],
size_px: [248.0, 82.0],
angle_rad: 0.06,
center_px: [530.0, 593.0],
size_px: [224.0, 68.0],
angle_rad: 0.12,
rows: 5,
cols: 14,
},
HandPalmChip {
center_px: [606.0, 780.0],
size_px: [72.0, 214.0],
center_px: [610.0, 778.0],
size_px: [60.0, 190.0],
angle_rad: 0.05,
rows: 11,
cols: 4,
@@ -90,6 +90,7 @@ const HAND_PALM_CHIPS: [HandPalmChip; 2] = [
const HAND_FINGER_SENSOR_CELLS: usize = 12 * 7;
const HAND_PALM_HORIZONTAL_OFFSET: usize = HAND_FINGER_SENSOR_CELLS * 5;
const HAND_PALM_VERTICAL_OFFSET: usize = HAND_PALM_HORIZONTAL_OFFSET + 5 * 14;
const HAND_TIP_DOT_LOCAL_Y_OFFSET_PX: f32 = 14.0;
// Each entry pins one miniature matrix to a fingertip in hand.png.
// Coordinates are authored in source-image pixels so they are easy to tune by eye.
@@ -1144,7 +1145,7 @@ fn build_hand_membrane_instances(image_width: f32, image_height: f32) -> Vec<Gly
.map(|tip| {
let uv_x = tip.center_px[0] / image_width.max(1.0);
let uv_y = tip.center_px[1] / image_height.max(1.0);
let membrane_size = [tip.size_px[0] * 1.62, tip.size_px[1] * 1.56];
let membrane_size = [tip.size_px[0] * 1.44, tip.size_px[1] * 1.36];
GlyphInstance {
// Membrane shaders read xy as hand.png UV.
@@ -1202,7 +1203,9 @@ fn build_hand_dot_instances(
// Lay out a rows x cols matrix in fingertip-local pixel space.
let local_x = (col as f32 - cols as f32 / 2.0 + 0.5) / cols as f32 * tip.size_px[0];
let local_y = (row as f32 - rows as f32 / 2.0 + 0.5) / rows as f32 * tip.size_px[1];
let local_y = (row as f32 - rows as f32 / 2.0 + 0.5) / rows as f32
* tip.size_px[1]
+ HAND_TIP_DOT_LOCAL_Y_OFFSET_PX;
// Rotate the local matrix so it follows the direction of the finger.
let x = tip.center_px[0] + local_x * cos - local_y * sin;

View File

@@ -382,6 +382,11 @@ fn rounded_rect_alpha(local: vec2f, radius: f32, softness: f32) -> f32 {
return 1.0 - smoothstep(0.0, softness, dist);
}
fn dot_matrix(uv: vec2f, grid: vec2f, dot_radius: f32, dot_softness: f32) -> f32 {
let cell = fract(uv * grid) - vec2f(0.5, 0.5);
return 1.0 - smoothstep(dot_radius, dot_radius + dot_softness, length(cell));
}
fn fingertip_film_alpha(
local: vec2f,
half_width: f32,
@@ -419,14 +424,28 @@ fn vs_hand_membrane(vertex: DotVertexInput, instance: DotInstanceInput) -> HandM
fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f {
let p = in.local;
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_shape = fingertip_film_alpha(p, 0.72, -0.20, 0.055);
let panel = fingertip_film_alpha(p, 0.66, -0.22, 0.035);
let inner = fingertip_film_alpha(p, 0.58, -0.25, 0.045);
let clear_inner = fingertip_film_alpha(p, 0.48, -0.24, 0.160);
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;
let rim = clamp(panel - inner, 0.0, 1.0);
let edge_fade = pow(clamp(1.0 - clear_inner, 0.0, 1.0), 1.20) * panel;
// Strong pseudo extrusion / bevel.
let depth_offset = vec2f(0.045, 0.055);
let back_shape_1 = fingertip_film_alpha(p - depth_offset * 0.55, 0.66, -0.22, 0.045);
let back_shape_2 = fingertip_film_alpha(p - depth_offset, 0.66, -0.22, 0.060);
let extrusion = clamp(max(back_shape_1, back_shape_2) - panel, 0.0, 1.0);
let bevel_offset = vec2f(0.028, 0.034);
let shifted_down_right = fingertip_film_alpha(p - bevel_offset, 0.66, -0.22, 0.035);
let bevel_light = clamp(panel - shifted_down_right, 0.0, 1.0);
let shifted_up_left = fingertip_film_alpha(p + bevel_offset, 0.66, -0.22, 0.035);
let bevel_dark = clamp(panel - shifted_up_left, 0.0, 1.0);
let broad_bevel = edge_fade * clamp(0.58 - p.x * 0.20 - p.y * 0.18, 0.0, 1.0);
// Micro lattice.
let uv = clamp(
@@ -437,56 +456,58 @@ fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f {
let grid = vec2f(18.0, 34.0);
let cell_uv = fract(uv * grid) - vec2f(0.5, 0.5);
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 dot_dist = length(cell_uv * vec2f(1.0, 1.06));
let dot_aa = max(fwidth(dot_dist) * 1.35, 0.006);
let dots = (1.0 - smoothstep(0.105 - dot_aa, 0.105 + dot_aa, dot_dist)) * inner;
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;
let sheen = (1.0 - smoothstep(0.018, 0.105, abs(sheen_axis))) * panel;
let shell_sheen = sheen * (0.26 + edge_fade * 0.74);
// 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 pressure_center = vec2f(-0.08, -0.12);
let pressure_dist = length((p - pressure_center) * vec2f(1.0, 0.74));
let pressure = (1.0 - smoothstep(0.05, 0.36, pressure_dist)) * inner;
let pressure_hot = (1.0 - smoothstep(0.02, 0.13, pressure_dist)) * 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 glass_base = vec3f(0.006, 0.055, 0.105);
let glass_cyan = vec3f(0.025, 0.42, 0.58);
let rim_color = vec3f(0.10, 0.88, 1.00);
let extrusion_color = vec3f(0.004, 0.080, 0.110);
let extrusion_edge_color = vec3f(0.015, 0.30, 0.38);
let bevel_highlight = vec3f(0.48, 1.00, 1.00);
let bevel_dark_color = vec3f(0.004, 0.035, 0.060);
let dot_color = vec3f(0.08, 0.48, 0.58);
let pressure_green = vec3f(0.01, 0.78, 0.23);
let pressure_hot_color = vec3f(0.20, 1.00, 0.42);
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 color = extrusion_color * extrusion * 0.90
+ extrusion_edge_color * extrusion * halo_shape * 0.32
+ glass_base * panel * 0.26
+ glass_cyan * edge_fade * 0.46
+ glass_cyan * broad_bevel * 0.24
+ rim_color * rim * 0.88
+ bevel_highlight * bevel_light * 1.35
+ bevel_dark_color * bevel_dark * 0.95
+ bevel_highlight * shell_sheen * 0.28
+ rim_color * halo * 0.16
+ dot_color * dots * 0.68
+ pressure_green * dots * pressure * 0.82
+ pressure_hot_color * dots * pressure_hot * 0.90;
let alpha = clamp(
panel * 0.08
+ edge_fade * 0.43
+ border * 0.20
+ halo * 0.04
+ lattice * 0.055
+ pressure * 0.05,
extrusion * 0.44
+ panel * 0.055
+ edge_fade * 0.30
+ rim * 0.34
+ bevel_light * 0.34
+ bevel_dark * 0.20
+ shell_sheen * 0.09
+ halo * 0.045
+ dots * 0.09
+ dots * pressure * 0.10,
0.0,
0.78,
0.90,
);
return output_color(color, alpha);
@@ -561,32 +582,47 @@ fn vs_hand_palm_chip(vertex: DotVertexInput, instance: DotInstanceInput) -> Hand
@fragment
fn fs_hand_palm_chip(in: HandPalmChipVertexOutput) -> @location(0) vec4f {
// Dark rounded tile: this is the inset chip body sitting inside the palm surface.
let panel = rounded_rect_alpha(in.local, 0.10, 0.040);
let inset = rounded_rect_alpha(in.local * vec2f(1.10, 1.08), 0.08, 0.052);
let rim = clamp(panel - inset * 0.72, 0.0, 1.0);
// Inactive palm matrix: only independent dots, with no filled patch underneath.
let panel = rounded_rect_alpha(in.local, 0.11, 0.040);
let inner = rounded_rect_alpha(in.local * vec2f(1.08, 1.08), 0.09, 0.052);
// Inactive chip pixels use the chip's real hand layout:
// horizontal 14 columns x 5 rows, or vertical 4 columns x 11 rows.
let uv = clamp(in.local * 0.5 + vec2f(0.5, 0.5), vec2f(0.0, 0.0), vec2f(1.0, 1.0));
let cell = abs(fract(uv * in.grid) - vec2f(0.5, 0.5));
let micro_pixel = 1.0 - smoothstep(0.105, 0.178, length(cell * vec2f(1.04, 0.94)));
let patch_mask = smoothstep(0.0, 0.36, inner) * panel;
let top_bevel = smoothstep(-0.96, -0.18, -in.local.y) * 0.16;
let lower_shadow = smoothstep(0.20, 0.92, in.local.y) * 0.22;
let side_bevel = smoothstep(0.58, 0.96, abs(in.local.x)) * 0.12;
let scan = (0.5 + 0.5 * sin((uv.y * 36.0 + uv.x * 7.0) * 6.28318)) * 0.026;
let grid = max(in.grid * 1.70, vec2f(1.0, 1.0));
let cell_uv = fract(uv * grid) - vec2f(0.5, 0.5);
let dot_dist = length(cell_uv);
let dot_aa = max(fwidth(dot_dist) * 1.5, 0.008);
let dots = 1.0 - smoothstep(0.135 - dot_aa, 0.135 + dot_aa, dot_dist);
let dot_glow = 1.0 - smoothstep(0.14, 0.28, dot_dist);
let visible_dots = dots * patch_mask;
let visible_glow = dot_glow * patch_mask;
let base = vec3f(0.004, 0.012, 0.018);
let glass = vec3f(0.012, 0.048, 0.064);
let pixel_color = vec3f(0.075, 0.300, 0.360);
let rim_color = vec3f(0.060, 0.560, 0.670);
let color = base * (0.92 - lower_shadow)
+ glass * (0.52 + top_bevel + side_bevel + scan)
+ pixel_color * micro_pixel * 0.70
+ rim_color * rim * 0.60;
// Synthetic low-level pressure only changes nearby dots; it does not fill the patch.
let horizontal = step(in.grid.y, in.grid.x);
let pressure_center = mix(vec2f(0.0, 0.0), vec2f(-0.10, 0.0), horizontal);
let pressure_scale = mix(vec2f(1.00, 0.82), vec2f(0.82, 1.00), horizontal);
let pressure_dist = length((in.local - pressure_center) * pressure_scale);
let pressure = (1.0 - smoothstep(0.06, 0.32, pressure_dist)) * patch_mask;
let pressure_hot = (1.0 - smoothstep(0.02, 0.14, pressure_dist)) * patch_mask;
let alpha = panel * (0.64 + micro_pixel * 0.18 + rim * 0.20);
let idle_dot_color = vec3f(0.18, 0.32, 0.38);
let active_dot_color = vec3f(0.02, 0.88, 0.34);
let hot_dot_color = vec3f(0.28, 1.00, 0.52);
let dot_color = mix(idle_dot_color, active_dot_color, pressure);
let color = dot_color * visible_dots * 1.25
+ hot_dot_color * visible_dots * pressure_hot * 0.85
+ active_dot_color * visible_glow * pressure * 0.14;
let alpha = clamp(
visible_dots * (0.46 + pressure * 0.38)
+ visible_dots * pressure_hot * 0.22
+ visible_glow * pressure * 0.045,
0.0,
0.96,
);
return output_color(color, alpha);
}