fix: 修复打砖块游戏碰撞穿透bug,添加渐进提速机制
This commit is contained in:
@@ -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>;
|
||||
|
||||
122
src-tauri/src/serial_core/multi_dim_force.rs
Normal file
122
src-tauri/src/serial_core/multi_dim_force.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user