fix: 修复打砖块游戏碰撞穿透bug,添加渐进提速机制

This commit is contained in:
lenn
2026-04-29 15:43:56 +08:00
parent 26533f6916
commit 326f07ed4f
23 changed files with 786 additions and 376 deletions

View File

@@ -11,6 +11,8 @@ pub mod model;
pub mod serial;
pub mod record;
pub mod utils;
#[cfg(feature = "multi-dim")]
pub mod multi_dim_force;
pub type TestRecording = Recording<TestFrame>;
pub type TactileARecording = Recording<TactileAFrame>;

View File

@@ -0,0 +1,122 @@
use ndarray::Array2;
const TOTAL_PRESSURE_LOW_THRESHOLD: usize = 500;
const COP_STABILITY_FRAMES_REQUIRED: usize = 5;
const SENSOR_ROWS: usize = 12;
const SENSOR_COLS: usize = 7;
pub struct PztProcessor {
first_frame: Option<Vec<f32>>,
first_contact_cop_x: Option<f32>,
first_contact_cop_y: Option<f32>,
contact_initialized: bool,
total_pressure_low_counter: usize,
}
impl PztProcessor {
pub fn new() -> Self {
Self {
first_frame: None,
first_contact_cop_x: None,
first_contact_cop_y: None,
contact_initialized: false,
total_pressure_low_counter: 0,
}
}
fn subtract_baseline(&mut self, current_frame: &[f32]) -> Vec<f32> {
if self.first_frame.is_none() {
self.first_frame = Some(current_frame.to_vec());
}
let baseline = self.first_frame.as_ref().unwrap();
current_frame
.iter()
.zip(baseline.iter())
.map(|(c, b)| (c - b).max(0.0))
.collect()
}
fn reset_cop_state(&mut self) {
self.first_contact_cop_x = None;
self.first_contact_cop_y = None;
self.contact_initialized = false;
self.total_pressure_low_counter = 0;
}
fn compute_pressure_direction(&mut self, frame: &[f32]) -> (f32, f32) {
let frame2d = Array2::from_shape_vec((SENSOR_ROWS, SENSOR_COLS), frame.to_vec()).unwrap();
let total_pressure: f32 = frame2d.sum();
if total_pressure < TOTAL_PRESSURE_LOW_THRESHOLD as f32 {
self.total_pressure_low_counter += 1;
} else {
self.total_pressure_low_counter = 0;
}
if self.total_pressure_low_counter >= COP_STABILITY_FRAMES_REQUIRED {
self.reset_cop_state();
return (0.0, 0.0);
}
if total_pressure == 0.0 {
return (0.0, 0.0);
}
let mut sum_x = 0.0;
let mut sum_y = 0.0;
for r in 0..SENSOR_ROWS {
for c in 0..SENSOR_COLS {
let val = frame2d[(r, c)];
sum_x += val * c as f32;
sum_y += val * r as f32;
}
}
let cop_x = sum_x / total_pressure;
let cop_y = sum_y / total_pressure;
if !self.contact_initialized {
self.first_contact_cop_x = Some(cop_x);
self.first_contact_cop_y = Some(cop_y);
self.contact_initialized = true;
return (0.0, 0.0);
}
let dx = cop_x - self.first_contact_cop_x.unwrap();
let dy = cop_y - self.first_contact_cop_y.unwrap();
(dx, dy)
}
fn compute_vector_angle(x: f32, y: f32) -> (f32, f32) {
let epsilon = 1e-8;
let mag = (x * x + y * y).sqrt();
let mut angle = (y).atan2(x + epsilon).to_degrees();
if angle < 0.0 {
angle += 360.0;
}
(angle, mag)
}
fn compute_pzt_angle(px: f32, py: f32) -> (f32, f32) {
Self::compute_vector_angle(px, -py)
}
pub fn get_pzt_angle(&mut self, adc_data: &[f32]) -> Result<f32, &'static str> {
if adc_data.len() != 84 {
return Err("ADC data length must be 84");
}
let baseline = self.subtract_baseline(adc_data);
let (dx, dy) = self.compute_pressure_direction(&baseline);
let (angle, _) = Self::compute_pzt_angle(dx, dy);
Ok(angle)
}
pub fn reset_baseline(&mut self) {
self.first_frame = None;
self.reset_cop_state();
}
}

View File

@@ -2,12 +2,17 @@ use crate::serial_core::codec::Codec;
use crate::serial_core::codecs::tactile_a::TactileACodec;
use crate::serial_core::frame::{FrameHandler, TactileAFrame, TestFrame};
use crate::serial_core::model::{HudChartState, HudPacket};
#[cfg(feature = "multi-dim")]
use crate::serial_core::multi_dim_force::PztProcessor;
use crate::serial_core::record::Recording;
use crate::serial_core::record::{FrameTiming, RecordedFrame};
#[cfg(feature = "devkit")]
use crate::devkit::{proto::SensorFrame, DevKitState};
use anyhow::Result;
use log::debug;
use std::future::pending;
#[cfg(feature = "devkit")]
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tauri::{AppHandle, Emitter};
@@ -17,14 +22,19 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{self, Duration, MissedTickBehavior};
use tokio_serial::SerialStream;
use tokio_util::sync::CancellationToken;
#[cfg(feature = "devkit")]
use std::sync::atomic::Ordering;
const AUTO_SUB_INTERVAL: Duration = Duration::from_nanos(16_666_667);
pub enum PollMode<F> {
Disable,
Enabled(Box<dyn PollRequester<F>>),
}
struct PendingSubFrame<F> {
frame: F,
values: Vec<i32>,
}
pub trait SerialFrame: Clone + Send + 'static {
fn dts_ms(&self) -> u64;
@@ -215,10 +225,15 @@ where
it.set_missed_tick_behavior(MissedTickBehavior::Skip);
it
});
let mut poll_sub_interval = time::interval(AUTO_SUB_INTERVAL);
poll_sub_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
let mut chart_state = HudChartState::new();
let mut buffer = [0u8; 1024];
let mut prune_interval = time::interval(Duration::from_millis(450));
#[cfg(feature = "multi-dim")]
let mut pzt_processor = PztProcessor::new();
let mut pending_sub_frame: Option<PendingSubFrame<F>> = None;
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
loop {
@@ -246,6 +261,21 @@ where
app.emit("hud_stream", packet)?;
}
}
_ = poll_sub_interval.tick() => {
if let Some(pending) = pending_sub_frame.take() {
let display_values = build_display_values(
&mut chart_state,
pending.values.as_slice(),
);
if let Some(packet) = pending
.frame
.to_hud_packet(&mut chart_state, display_values.as_deref())
{
app.emit("hud_stream", packet)?;
}
}
}
read_result = port.read(&mut buffer) => {
let n = read_result?;
if n == 0 {
@@ -266,25 +296,38 @@ where
.await?
.map(|vals| vals.into_iter().map(Into::into).collect::<Vec<i32>>());
let mut record = recording.lock().map_err(|_| anyhow::anyhow!("recording state poisoned"))?;
record.push(RecordedFrame{
timing: FrameTiming { pts_ms: None, dts_ms: frame.dts_ms() },
let mut record = recording
.lock()
.map_err(|_| anyhow::anyhow!("recording state poisoned"))?;
record.push(RecordedFrame {
timing: FrameTiming {
pts_ms: None,
dts_ms: frame.dts_ms(),
},
frame: frame.clone(),
});
drop(record);
let display_values = if let Some(vals) = decode_res.as_ref() {
let summary = vals.iter().copied().sum::<i32>();
let force = raw_to_g1(summary as u32);
chart_state.record_summary(force as f32);
chart_state.record_pressure_matrix(vals.as_slice());
if let Some(vals) = decode_res {
#[cfg(feature = "multi-dim")]
{
let pzt_values = vals.iter().map(|value| *value as f32).collect::<Vec<f32>>();
if let Ok(angle) = pzt_processor.get_pzt_angle(&pzt_values) {
// debug!("pzt angle: {:.2}", angle);
}
}
#[cfg(feature = "devkit")]
push_devkit_frame(&app, vals.as_slice(), frame.dts_ms(), force);
Some(vec![summary])
} else {
None
};
{
let summary = vals.iter().copied().sum::<i32>();
let force = raw_to_g1(summary as u32);
push_devkit_frame(&app, vals.as_slice(), frame.dts_ms(), force);
}
if let Some(packet) = frame.to_hud_packet(&mut chart_state, display_values.as_deref()) {
pending_sub_frame = Some(PendingSubFrame {
frame: frame.clone(),
values: vals,
});
} else if let Some(packet) = frame.to_hud_packet(&mut chart_state, None) {
app.emit("hud_stream", packet)?;
}
}
@@ -294,6 +337,14 @@ where
Ok(())
}
fn build_display_values(chart_state: &mut HudChartState, values: &[i32]) -> Option<Vec<i32>> {
let summary = values.iter().copied().sum::<i32>();
let force = raw_to_g1(summary as u32);
chart_state.record_summary(force as f32);
chart_state.record_pressure_matrix(values);
Some(vec![summary])
}
#[cfg(feature = "devkit")]
fn push_devkit_frame(app: &AppHandle, values: &[i32], dts_ms: u64, resultant_force: f64) {
let devkit_state = app.state::<DevKitState>();