Integrate hand gateway and spatial force rendering

This commit is contained in:
lenn
2026-06-29 18:55:42 +08:00
parent f30ebcf20b
commit d4f160af75
14 changed files with 1041 additions and 98 deletions

View File

@@ -13,11 +13,13 @@ use std::ops::Range;
pub const PRESSURE_CELL_COUNT: usize =
(crate::matrix::MATRIX_ROWS * crate::matrix::MATRIX_COLS) as usize;
pub type PressureFrame = [[f32; 2]; PRESSURE_CELL_COUNT];
pub type PressureSamples = Vec<[f32; 2]>;
pub struct WgpuBackgroundCallback {
pub width: f32,
pub height: f32,
pub pressure: PressureFrame,
pub hand_pressure: PressureSamples,
pub active_mode: ActiveMode,
}
@@ -85,6 +87,10 @@ 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;
// 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.
// size_px keeps the same 7:12 aspect as the Finger-mode 7 columns x 12 rows matrix.
@@ -114,7 +120,13 @@ impl egui_wgpu::CallbackTrait for WgpuBackgroundCallback {
resources: &mut egui_wgpu::CallbackResources,
) -> Vec<wgpu::CommandBuffer> {
let resources: &mut BackgroundRenderResources = resources.get_mut().unwrap();
resources.prepare(queue, self.width, self.height, &self.pressure);
resources.prepare(
queue,
self.width,
self.height,
&self.pressure,
&self.hand_pressure,
);
Vec::new()
}
@@ -596,7 +608,14 @@ impl BackgroundRenderResources {
}
}
fn prepare(&mut self, queue: &wgpu::Queue, width: f32, height: f32, pressure: &PressureFrame) {
fn prepare(
&mut self,
queue: &wgpu::Queue,
width: f32,
height: f32,
pressure: &PressureFrame,
hand_pressure: &[[f32; 2]],
) {
let aspect = width / height.max(1.0);
self.uniform = MatrixUniform::new(
width,
@@ -628,14 +647,20 @@ impl BackgroundRenderResources {
bytemuck::cast_slice(&self.glyph_instances),
);
// Hand mode uses five UV-anchored fingertip matrices over hand.png.
let hand_pressure = if hand_pressure.is_empty() {
pressure.as_slice()
} else {
hand_pressure
};
// Hand mode uses UV-anchored fingertip matrices over hand.png.
// Rebuild their instance positions here so pressure colors update every frame.
self.hand_dot_instances = build_hand_dot_instances(
self.rows,
self.cols,
self.hand_image_texture.width as f32,
self.hand_image_texture.height as f32,
pressure,
hand_pressure,
);
queue.write_buffer(
&self.hand_dot_instance_buffer,
@@ -650,7 +675,7 @@ impl BackgroundRenderResources {
self.cols,
self.hand_image_texture.width as f32,
self.hand_image_texture.height as f32,
pressure,
hand_pressure,
);
queue.write_buffer(
&self.hand_palm_dot_instance_buffer,
@@ -1158,19 +1183,22 @@ fn build_hand_dot_instances(
cols: u32,
image_width: f32,
image_height: f32,
pressure: &PressureFrame,
pressure: &[[f32; 2]],
) -> Vec<GlyphInstance> {
let mut instances = Vec::with_capacity(HAND_TIP_MATRICES.len() * rows as usize * cols as usize);
for tip in HAND_TIP_MATRICES {
for (tip_index, tip) in HAND_TIP_MATRICES.into_iter().enumerate() {
let cos = tip.angle_rad.cos();
let sin = tip.angle_rad.sin();
for row in 0..rows {
for col in 0..cols {
let index = (row * cols + col) as usize;
let [normalized, display_value] =
pressure.get(index).copied().unwrap_or([0.0, 0.0]);
let [normalized, display_value] = sample_pressure_at(
pressure,
tip_index * HAND_FINGER_SENSOR_CELLS + index,
index,
);
// 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];
@@ -1195,11 +1223,11 @@ fn build_hand_dot_instances(
}
fn build_hand_palm_dot_instances(
rows: u32,
cols: u32,
_rows: u32,
_cols: u32,
image_width: f32,
image_height: f32,
pressure: &PressureFrame,
pressure: &[[f32; 2]],
) -> Vec<GlyphInstance> {
let chip_dot_count: usize = HAND_PALM_CHIPS
.iter()
@@ -1207,7 +1235,7 @@ fn build_hand_palm_dot_instances(
.sum();
let mut instances = Vec::with_capacity(chip_dot_count);
for chip in HAND_PALM_CHIPS {
for (chip_index, chip) in HAND_PALM_CHIPS.into_iter().enumerate() {
let cos = chip.angle_rad.cos();
let sin = chip.angle_rad.sin();
// Leave a bevel around the chip so the matrix reads as embedded pixels.
@@ -1215,13 +1243,13 @@ fn build_hand_palm_dot_instances(
for row in 0..chip.rows {
for col in 0..chip.cols {
// Current live data is still the device pressure frame. Sample it by
// proportion so the palm's 5x14 and 11x4 layouts get coherent values.
let source_row = resample_index(row, chip.rows, rows);
let source_col = resample_index(col, chip.cols, cols);
let index = (source_row * cols + source_col) as usize;
let index = (row * chip.cols + col) as usize;
let offset = match chip_index {
0 => HAND_PALM_HORIZONTAL_OFFSET,
_ => HAND_PALM_VERTICAL_OFFSET,
};
let [normalized, display_value] =
pressure.get(index).copied().unwrap_or([0.0, 0.0]);
sample_pressure_at(pressure, offset + index, index);
let local_x =
(col as f32 - chip.cols as f32 / 2.0 + 0.5) / chip.cols as f32 * active_size[0];
@@ -1247,13 +1275,12 @@ fn build_hand_palm_dot_instances(
instances
}
fn resample_index(index: u32, source_count: u32, target_count: u32) -> u32 {
if source_count <= 1 || target_count <= 1 {
return 0;
}
let mapped = index as f32 / (source_count - 1) as f32 * (target_count - 1) as f32;
mapped.round().clamp(0.0, (target_count - 1) as f32) as u32
fn sample_pressure_at(pressure: &[[f32; 2]], index: usize, fallback_index: usize) -> [f32; 2] {
pressure
.get(index)
.or_else(|| pressure.get(fallback_index))
.copied()
.unwrap_or([0.0, 0.0])
}
fn build_glyph_instances(