use ash::vk;
use egui::Pos2;
use hecs::{PreparedQuery, With, World};
use nalgebra::{
point, vector, Isometry3, Orthographic3, Point3, Quaternion, Translation3, UnitQuaternion,
Vector2,
};
use rapier3d::{
math::Point,
prelude::{InteractionGroups, Ray},
};
const POSITION_OFFSET: [f32; 3] = [0., 0.071173, -0.066082];
const ROTATION_OFFSET: Quaternion<f32> =
Quaternion::new(-0.558_149_8, 0.827_491_2, 0.034_137_9, -0.050_611_5);
use crate::{
components::{hand::Handedness, panel::PanelInput, Info, Panel, Pointer, Transform, Visible},
resources::{PhysicsContext, XrContext},
util::{is_space_valid, posef_to_isometry},
};
pub fn pointers_system(
query: &mut PreparedQuery<With<Visible, (&mut Pointer, &mut Transform)>>,
world: &mut World,
xr_context: &XrContext,
physics_context: &mut PhysicsContext,
) {
for (_, (pointer, transform)) in query.query(world).iter() {
let time = xr_context.frame_state.predicted_display_time;
let (space, path) = match pointer.handedness {
Handedness::Left => (
&xr_context.left_hand_space,
xr_context.left_hand_subaction_path,
),
Handedness::Right => (
&xr_context.right_hand_space,
xr_context.right_hand_subaction_path,
),
};
let space = space.locate(&xr_context.reference_space, time).unwrap();
if !is_space_valid(&space) {
return;
}
let pose = space.pose;
let mut position = posef_to_isometry(pose);
apply_grip_offset(&mut position);
transform.translation = position.translation.vector;
transform.rotation = position.rotation;
let trigger_value =
openxr::ActionInput::get(&xr_context.trigger_action, &xr_context.session, path)
.unwrap()
.current_state;
pointer.trigger_value = trigger_value;
let ray_direction = transform.rotation.transform_vector(&vector![0., 1.0, 0.]);
let ray = Ray::new(Point::from(transform.translation), ray_direction);
let max_toi = 40.0;
let solid = true;
let groups = InteractionGroups::new(0b10, 0b10);
let filter = None;
if let Some((handle, toi)) = physics_context.query_pipeline.cast_ray(
&physics_context.colliders,
&ray,
max_toi,
solid,
groups,
filter,
) {
let hit_point = ray.point_at(toi); let hit_collider = physics_context.colliders.get(handle).unwrap();
let entity = unsafe { world.find_entity_from_id(hit_collider.user_data as _) };
match world.get_mut::<Panel>(entity) {
Ok(mut panel) => {
let panel_transform = hit_collider.position();
let cursor_location = get_cursor_location_for_panel(
&hit_point,
panel_transform,
&panel.resolution,
&panel.world_size,
);
panel.input = Some(PanelInput {
cursor_location,
trigger_value,
});
}
Err(_) => {
let info = world.get::<Info>(entity).map(|i| format!("{:?}", *i));
println!("[HOTHAM_POINTERS] Ray collided with object that does not have a panel: {:?} - {:?}", entity, info);
}
}
}
}
}
pub(crate) fn apply_grip_offset(position: &mut Isometry3<f32>) {
let updated_rotation = position.rotation.quaternion() * ROTATION_OFFSET;
let updated_translation = position.translation.vector
- vector!(POSITION_OFFSET[0], POSITION_OFFSET[1], POSITION_OFFSET[2]);
position.rotation = UnitQuaternion::from_quaternion(updated_rotation);
position.translation = Translation3::from(updated_translation);
}
fn get_cursor_location_for_panel(
hit_point: &Point3<f32>,
panel_transform: &Isometry3<f32>,
panel_extent: &vk::Extent2D,
panel_world_size: &Vector2<f32>,
) -> Pos2 {
let projected_hit_point = ray_to_panel_space(hit_point, panel_transform, panel_world_size);
let transformed_hit_point = panel_transform
.rotation
.transform_point(&projected_hit_point);
let x = (transformed_hit_point.x + 1.) * 0.5;
let y = ((transformed_hit_point.y * -1.) * 0.5) + 0.5;
let x_points = x * panel_extent.width as f32;
let y_points = y * panel_extent.height as f32;
Pos2::new(x_points, y_points)
}
fn ray_to_panel_space(
hit_point: &Point3<f32>,
panel_transform: &Isometry3<f32>,
panel_world_size: &Vector2<f32>,
) -> Point3<f32> {
let (extent_x, extent_y) = (panel_world_size.x / 2., panel_world_size.y / 2.);
let translated_extents = panel_transform * point![extent_x, extent_y, 0.];
let left = translated_extents.x - 1.;
let right = translated_extents.x;
let bottom = translated_extents.y - 1.;
let top = translated_extents.y;
let panel_projection = Orthographic3::new(left, right, bottom, top, 0., 1.);
panel_projection.project_point(hit_point)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use ash::vk;
#[cfg(target_os = "windows")]
#[test]
pub fn test_pointers_system() {
use crate::{
components::{Collider, Panel, Transform},
resources::physics_context::{DEFAULT_COLLISION_GROUP, PANEL_COLLISION_GROUP},
texture::Texture,
};
use nalgebra::vector;
use rapier3d::prelude::ColliderBuilder;
let (mut xr_context, mut vulkan_context) = XrContext::new().unwrap();
let mut physics_context = PhysicsContext::default();
let mut world = World::default();
let panel = Panel {
resolution: vk::Extent2D {
width: 300,
height: 300,
},
world_size: [1.0, 1.0].into(),
texture: Texture::empty(&mut vulkan_context).unwrap(),
input: None,
};
let panel_entity = world.spawn((panel,));
let collider = ColliderBuilder::cuboid(0.5, 0.5, 0.0)
.sensor(true)
.collision_groups(InteractionGroups::new(
PANEL_COLLISION_GROUP,
PANEL_COLLISION_GROUP,
))
.translation(vector![-0.2, 2., -0.433918])
.rotation(vector![(3. * std::f32::consts::PI) * 0.5, 0., 0.])
.user_data(panel_entity.id() as _)
.build();
let handle = physics_context.colliders.insert(collider);
let collider = Collider {
collisions_this_frame: Vec::new(),
handle,
};
world.insert_one(panel_entity, collider).unwrap();
let collider = ColliderBuilder::cuboid(0.1, 0.1, 0.1)
.sensor(true)
.collision_groups(InteractionGroups::new(
DEFAULT_COLLISION_GROUP,
DEFAULT_COLLISION_GROUP,
))
.translation(vector![-0.2, 1.5, -0.433918])
.rotation(vector![(3. * std::f32::consts::PI) * 0.5, 0., 0.])
.build();
let handle = physics_context.colliders.insert(collider);
let collider = Collider {
collisions_this_frame: Vec::new(),
handle,
};
world.spawn((collider,));
let pointer_entity = world.spawn((
Visible {},
Pointer {
handedness: Handedness::Left,
trigger_value: 0.0,
},
Transform::default(),
));
schedule(&mut physics_context, &mut world, &mut xr_context);
let transform = world.get_mut::<Transform>(pointer_entity).unwrap();
assert_relative_eq!(transform.translation, vector![-0.2, 1.328827, -0.433918]);
let panel = world.get_mut::<Panel>(panel_entity).unwrap();
let input = panel.input.clone().unwrap();
assert_relative_eq!(input.cursor_location.x, 150.);
assert_relative_eq!(input.cursor_location.y, 88.473129);
assert_eq!(input.trigger_value, 0.);
}
#[cfg(target_os = "windows")]
fn schedule(
physics_context: &mut PhysicsContext,
world: &mut hecs::World,
xr_context: &mut XrContext,
) -> () {
physics_context.update();
pointers_system(&mut Default::default(), world, xr_context, physics_context)
}
#[test]
pub fn test_get_cursor_location_for_panel() {
let panel_transform = Isometry3::new(nalgebra::zero(), nalgebra::zero());
let panel_extent = vk::Extent2D {
width: 100,
height: 100,
};
let panel_world_size = vector![1.0, 1.0];
let result = get_cursor_location_for_panel(
&point![0., 0., 0.],
&panel_transform,
&panel_extent,
&panel_world_size,
);
assert_relative_eq!(result.x, 50.);
assert_relative_eq!(result.y, 50.);
let result = get_cursor_location_for_panel(
&point![-0.5, 0.5, 0.],
&panel_transform,
&panel_extent,
&panel_world_size,
);
assert_relative_eq!(result.x, 0.);
assert_relative_eq!(result.y, 0.);
let result = get_cursor_location_for_panel(
&point![0.5, 0.5, 0.],
&panel_transform,
&panel_extent,
&panel_world_size,
);
assert_relative_eq!(result.x, 100.);
assert_relative_eq!(result.y, 0.);
let result = get_cursor_location_for_panel(
&point![0.5, -0.5, 0.],
&panel_transform,
&panel_extent,
&panel_world_size,
);
assert_relative_eq!(result.x, 100.);
assert_relative_eq!(result.y, 100.);
let result = get_cursor_location_for_panel(
&point![-0.5, -0.5, 0.],
&panel_transform,
&panel_extent,
&panel_world_size,
);
assert_relative_eq!(result.x, 0.);
assert_relative_eq!(result.y, 100.);
}
#[test]
pub fn test_ray_to_panel_space() {
let panel_transform = Isometry3::new(nalgebra::zero(), nalgebra::zero());
let panel_world_size = vector![1.0, 1.0];
let result = ray_to_panel_space(&point![0., 0., 0.], &panel_transform, &panel_world_size);
assert_relative_eq!(result, point![0.0, 0.0, -1.0]);
let result =
ray_to_panel_space(&point![-0.5, 0.5, 0.], &panel_transform, &panel_world_size);
assert_relative_eq!(result, point![-1.0, 1.0, -1.0]);
let result = ray_to_panel_space(&point![0.5, 0.5, 0.], &panel_transform, &panel_world_size);
assert_relative_eq!(result, point![1.0, 1.0, -1.0]);
let result =
ray_to_panel_space(&point![0.5, -0.5, 0.], &panel_transform, &panel_world_size);
assert_relative_eq!(result, point![1.0, -1.0, -1.0]);
let result =
ray_to_panel_space(&point![-0.5, -0.5, 0.], &panel_transform, &panel_world_size);
assert_relative_eq!(result, point![-1.0, -1.0, -1.0]);
}
}