mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Filter out webidl files based on special comments, and feature-gate webxr interfaces. (#34348)
* Filter out webidl files based on skip-if directives. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Don't build XR functionality without webxr feature. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Fix tidy. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Adjust imports for file movement. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Fix clippy. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Formatting. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Clean up webxr module import. Co-authored-by: Samson <16504129+sagudev@users.noreply.github.com> Signed-off-by: Josh Matthews <josh@joshmatthews.net> --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net> Co-authored-by: Samson <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
parent
e956f3124c
commit
3faed9b921
94 changed files with 206 additions and 53 deletions
368
components/script/dom/webxr/fakexrdevice.rs
Normal file
368
components/script/dom/webxr/fakexrdevice.rs
Normal file
|
@ -0,0 +1,368 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{Point2D, Point3D, Rect, RigidTransform3D, Rotation3D, Size2D, Transform3D, Vector3D};
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use profile_traits::ipc;
|
||||
use webxr_api::{
|
||||
EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion,
|
||||
MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility,
|
||||
};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
|
||||
use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{
|
||||
FakeXRBoundsPoint, FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit,
|
||||
FakeXRViewInit, FakeXRWorldInit,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit;
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
|
||||
XRHandedness, XRTargetRayMode,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState;
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::refcounted::TrustedPromise;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::fakexrinputcontroller::{init_to_mock_buttons, FakeXRInputController};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::CanGc;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct FakeXRDevice {
|
||||
reflector: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in ipc-channel"]
|
||||
#[no_trace]
|
||||
sender: IpcSender<MockDeviceMsg>,
|
||||
#[ignore_malloc_size_of = "defined in webxr-api"]
|
||||
#[no_trace]
|
||||
next_input_id: Cell<InputId>,
|
||||
}
|
||||
|
||||
impl FakeXRDevice {
|
||||
pub fn new_inherited(sender: IpcSender<MockDeviceMsg>) -> FakeXRDevice {
|
||||
FakeXRDevice {
|
||||
reflector: Reflector::new(),
|
||||
sender,
|
||||
next_input_id: Cell::new(InputId(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, sender: IpcSender<MockDeviceMsg>) -> DomRoot<FakeXRDevice> {
|
||||
reflect_dom_object(Box::new(FakeXRDevice::new_inherited(sender)), global)
|
||||
}
|
||||
|
||||
pub fn disconnect(&self, sender: IpcSender<()>) {
|
||||
let _ = self.sender.send(MockDeviceMsg::Disconnect(sender));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> {
|
||||
if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 {
|
||||
return Err(Error::Type("Incorrectly sized array".into()));
|
||||
}
|
||||
|
||||
let mut proj = [0.; 16];
|
||||
let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect();
|
||||
proj.copy_from_slice(&v);
|
||||
let projection = Transform3D::from_array(proj);
|
||||
|
||||
// spec defines offsets as origins, but mock API expects the inverse transform
|
||||
let transform = get_origin(&view.viewOffset)?.inverse();
|
||||
|
||||
let size = Size2D::new(view.resolution.width, view.resolution.height);
|
||||
let origin = match view.eye {
|
||||
XREye::Right => Point2D::new(size.width, 0),
|
||||
_ => Point2D::zero(),
|
||||
};
|
||||
let viewport = Rect::new(origin, size);
|
||||
|
||||
let fov = view.fieldOfView.as_ref().map(|fov| {
|
||||
(
|
||||
fov.leftDegrees.to_radians(),
|
||||
fov.rightDegrees.to_radians(),
|
||||
fov.upDegrees.to_radians(),
|
||||
fov.downDegrees.to_radians(),
|
||||
)
|
||||
});
|
||||
|
||||
Ok(MockViewInit {
|
||||
projection,
|
||||
transform,
|
||||
viewport,
|
||||
fov,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> {
|
||||
match views.len() {
|
||||
1 => Ok(MockViewsInit::Mono(view(&views[0])?)),
|
||||
2 => {
|
||||
let (left, right) = match (views[0].eye, views[1].eye) {
|
||||
(XREye::Left, XREye::Right) => (&views[0], &views[1]),
|
||||
(XREye::Right, XREye::Left) => (&views[1], &views[0]),
|
||||
_ => return Err(Error::NotSupported),
|
||||
};
|
||||
Ok(MockViewsInit::Stereo(view(left)?, view(right)?))
|
||||
},
|
||||
_ => Err(Error::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_origin<T, U>(
|
||||
origin: &FakeXRRigidTransformInit,
|
||||
) -> Fallible<RigidTransform3D<f32, T, U>> {
|
||||
if origin.position.len() != 3 || origin.orientation.len() != 4 {
|
||||
return Err(Error::Type("Incorrectly sized array".into()));
|
||||
}
|
||||
let p = Vector3D::new(
|
||||
*origin.position[0],
|
||||
*origin.position[1],
|
||||
*origin.position[2],
|
||||
);
|
||||
let o = Rotation3D::unit_quaternion(
|
||||
*origin.orientation[0],
|
||||
*origin.orientation[1],
|
||||
*origin.orientation[2],
|
||||
*origin.orientation[3],
|
||||
);
|
||||
|
||||
Ok(RigidTransform3D::new(o, p))
|
||||
}
|
||||
|
||||
pub fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> {
|
||||
Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast()
|
||||
}
|
||||
|
||||
pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
|
||||
let regions = world
|
||||
.hitTestRegions
|
||||
.iter()
|
||||
.map(|region| {
|
||||
let ty = region.type_.into();
|
||||
let faces = region
|
||||
.faces
|
||||
.iter()
|
||||
.map(|face| {
|
||||
if face.vertices.len() != 3 {
|
||||
return Err(Error::Type(
|
||||
"Incorrectly sized array for triangle list".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Triangle {
|
||||
first: get_point(&face.vertices[0]),
|
||||
second: get_point(&face.vertices[1]),
|
||||
third: get_point(&face.vertices[2]),
|
||||
})
|
||||
})
|
||||
.collect::<Fallible<Vec<_>>>()?;
|
||||
Ok(MockRegion { faces, ty })
|
||||
})
|
||||
.collect::<Fallible<Vec<_>>>()?;
|
||||
|
||||
Ok(MockWorld { regions })
|
||||
}
|
||||
|
||||
impl From<FakeXRRegionType> for EntityType {
|
||||
fn from(x: FakeXRRegionType) -> Self {
|
||||
match x {
|
||||
FakeXRRegionType::Point => EntityType::Point,
|
||||
FakeXRRegionType::Plane => EntityType::Plane,
|
||||
FakeXRRegionType::Mesh => EntityType::Mesh,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
|
||||
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
|
||||
fn SetViews(
|
||||
&self,
|
||||
views: Vec<FakeXRViewInit>,
|
||||
_secondary_views: Option<Vec<FakeXRViewInit>>,
|
||||
) -> Fallible<()> {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(MockDeviceMsg::SetViews(get_views(&views)?));
|
||||
// TODO: Support setting secondary views for mock backend
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setviewerorigin>
|
||||
fn SetViewerOrigin(
|
||||
&self,
|
||||
origin: &FakeXRRigidTransformInit,
|
||||
_emulated_position: bool,
|
||||
) -> Fallible<()> {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearviewerorigin>
|
||||
fn ClearViewerOrigin(&self) {
|
||||
let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearfloororigin>
|
||||
fn ClearFloorOrigin(&self) {
|
||||
let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setfloororigin>
|
||||
fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearworld>
|
||||
fn ClearWorld(&self) {
|
||||
let _ = self.sender.send(MockDeviceMsg::ClearWorld);
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setworld>
|
||||
fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> {
|
||||
let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulatevisibilitychange>
|
||||
fn SimulateVisibilityChange(&self, v: XRVisibilityState) {
|
||||
let v = match v {
|
||||
XRVisibilityState::Visible => Visibility::Visible,
|
||||
XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred,
|
||||
XRVisibilityState::Hidden => Visibility::Hidden,
|
||||
};
|
||||
let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateinputsourceconnection>
|
||||
fn SimulateInputSourceConnection(
|
||||
&self,
|
||||
init: &FakeXRInputSourceInit,
|
||||
) -> Fallible<DomRoot<FakeXRInputController>> {
|
||||
let id = self.next_input_id.get();
|
||||
self.next_input_id.set(InputId(id.0 + 1));
|
||||
|
||||
let handedness = init.handedness.into();
|
||||
let target_ray_mode = init.targetRayMode.into();
|
||||
|
||||
let pointer_origin = Some(get_origin(&init.pointerOrigin)?);
|
||||
|
||||
let grip_origin = if let Some(ref g) = init.gripOrigin {
|
||||
Some(get_origin(g)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let profiles = init.profiles.iter().cloned().map(String::from).collect();
|
||||
|
||||
let mut supported_buttons = vec![];
|
||||
if let Some(ref buttons) = init.supportedButtons {
|
||||
supported_buttons.extend(init_to_mock_buttons(buttons));
|
||||
}
|
||||
|
||||
let source = InputSource {
|
||||
handedness,
|
||||
target_ray_mode,
|
||||
id,
|
||||
supports_grip: true,
|
||||
profiles,
|
||||
hand_support: None,
|
||||
};
|
||||
|
||||
let init = MockInputInit {
|
||||
source,
|
||||
pointer_origin,
|
||||
grip_origin,
|
||||
supported_buttons,
|
||||
};
|
||||
|
||||
let global = self.global();
|
||||
let _ = self.sender.send(MockDeviceMsg::AddInputSource(init));
|
||||
|
||||
let controller = FakeXRInputController::new(&global, self.sender.clone(), id);
|
||||
|
||||
Ok(controller)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-disconnect>
|
||||
fn Disconnect(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||
let global = self.global();
|
||||
let p = Promise::new(&global, can_gc);
|
||||
let mut trusted = Some(TrustedPromise::new(p.clone()));
|
||||
let (task_source, canceller) = global
|
||||
.as_window()
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source_with_canceller();
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |_| {
|
||||
let trusted = trusted
|
||||
.take()
|
||||
.expect("disconnect callback called multiple times");
|
||||
let _ = task_source.queue_with_canceller(trusted.resolve_task(()), &canceller);
|
||||
}),
|
||||
);
|
||||
self.disconnect(sender);
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setboundsgeometry>
|
||||
fn SetBoundsGeometry(&self, bounds_coodinates: Vec<FakeXRBoundsPoint>) -> Fallible<()> {
|
||||
if bounds_coodinates.len() < 3 {
|
||||
return Err(Error::Type(
|
||||
"Bounds geometry must contain at least 3 points".into(),
|
||||
));
|
||||
}
|
||||
let coords = bounds_coodinates
|
||||
.iter()
|
||||
.map(|coord| {
|
||||
let x = *coord.x.unwrap() as f32;
|
||||
let y = *coord.z.unwrap() as f32;
|
||||
Point2D::new(x, y)
|
||||
})
|
||||
.collect();
|
||||
let _ = self.sender.send(MockDeviceMsg::SetBoundsGeometry(coords));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateresetpose>
|
||||
fn SimulateResetPose(&self) {
|
||||
let _ = self.sender.send(MockDeviceMsg::SimulateResetPose);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<XRHandedness> for Handedness {
|
||||
fn from(h: XRHandedness) -> Self {
|
||||
match h {
|
||||
XRHandedness::None => Handedness::None,
|
||||
XRHandedness::Left => Handedness::Left,
|
||||
XRHandedness::Right => Handedness::Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<XRTargetRayMode> for TargetRayMode {
|
||||
fn from(t: XRTargetRayMode) -> Self {
|
||||
match t {
|
||||
XRTargetRayMode::Gaze => TargetRayMode::Gaze,
|
||||
XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
|
||||
XRTargetRayMode::Screen => TargetRayMode::Screen,
|
||||
XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
|
||||
}
|
||||
}
|
||||
}
|
194
components/script/dom/webxr/fakexrinputcontroller.rs
Normal file
194
components/script/dom/webxr/fakexrinputcontroller.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use webxr_api::{
|
||||
Handedness, InputId, MockButton, MockButtonType, MockDeviceMsg, MockInputMsg, SelectEvent,
|
||||
SelectKind, TargetRayMode,
|
||||
};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::FakeXRRigidTransformInit;
|
||||
use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::{
|
||||
FakeXRButtonStateInit, FakeXRButtonType, FakeXRInputControllerMethods,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
|
||||
XRHandedness, XRTargetRayMode,
|
||||
};
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::fakexrdevice::get_origin;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct FakeXRInputController {
|
||||
reflector: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in ipc-channel"]
|
||||
#[no_trace]
|
||||
sender: IpcSender<MockDeviceMsg>,
|
||||
#[ignore_malloc_size_of = "defined in webxr-api"]
|
||||
#[no_trace]
|
||||
id: InputId,
|
||||
}
|
||||
|
||||
impl FakeXRInputController {
|
||||
pub fn new_inherited(sender: IpcSender<MockDeviceMsg>, id: InputId) -> FakeXRInputController {
|
||||
FakeXRInputController {
|
||||
reflector: Reflector::new(),
|
||||
sender,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
sender: IpcSender<MockDeviceMsg>,
|
||||
id: InputId,
|
||||
) -> DomRoot<FakeXRInputController> {
|
||||
reflect_dom_object(
|
||||
Box::new(FakeXRInputController::new_inherited(sender, id)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
fn send_message(&self, msg: MockInputMsg) {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(MockDeviceMsg::MessageInputSource(self.id, msg));
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeXRInputControllerMethods<crate::DomTypeHolder> for FakeXRInputController {
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setpointerorigin>
|
||||
fn SetPointerOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
|
||||
self.send_message(MockInputMsg::SetPointerOrigin(Some(get_origin(origin)?)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setgriporigin>
|
||||
fn SetGripOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
|
||||
self.send_message(MockInputMsg::SetGripOrigin(Some(get_origin(origin)?)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-cleargriporigin>
|
||||
fn ClearGripOrigin(&self) {
|
||||
self.send_message(MockInputMsg::SetGripOrigin(None))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-disconnect>
|
||||
fn Disconnect(&self) {
|
||||
self.send_message(MockInputMsg::Disconnect)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-reconnect>
|
||||
fn Reconnect(&self) {
|
||||
self.send_message(MockInputMsg::Reconnect)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-startselection>
|
||||
fn StartSelection(&self) {
|
||||
self.send_message(MockInputMsg::TriggerSelect(
|
||||
SelectKind::Select,
|
||||
SelectEvent::Start,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-endselection>
|
||||
fn EndSelection(&self) {
|
||||
self.send_message(MockInputMsg::TriggerSelect(
|
||||
SelectKind::Select,
|
||||
SelectEvent::End,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-simulateselect>
|
||||
fn SimulateSelect(&self) {
|
||||
self.send_message(MockInputMsg::TriggerSelect(
|
||||
SelectKind::Select,
|
||||
SelectEvent::Select,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-sethandedness>
|
||||
fn SetHandedness(&self, handedness: XRHandedness) {
|
||||
let h = match handedness {
|
||||
XRHandedness::None => Handedness::None,
|
||||
XRHandedness::Left => Handedness::Left,
|
||||
XRHandedness::Right => Handedness::Right,
|
||||
};
|
||||
self.send_message(MockInputMsg::SetHandedness(h));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-settargetraymode>
|
||||
fn SetTargetRayMode(&self, target_ray_mode: XRTargetRayMode) {
|
||||
let t = match target_ray_mode {
|
||||
XRTargetRayMode::Gaze => TargetRayMode::Gaze,
|
||||
XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
|
||||
XRTargetRayMode::Screen => TargetRayMode::Screen,
|
||||
XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
|
||||
};
|
||||
self.send_message(MockInputMsg::SetTargetRayMode(t));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setprofiles>
|
||||
fn SetProfiles(&self, profiles: Vec<DOMString>) {
|
||||
let t = profiles.into_iter().map(String::from).collect();
|
||||
self.send_message(MockInputMsg::SetProfiles(t));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setsupportedbuttons>
|
||||
fn SetSupportedButtons(&self, supported_buttons: Vec<FakeXRButtonStateInit>) {
|
||||
let supported = init_to_mock_buttons(&supported_buttons);
|
||||
self.send_message(MockInputMsg::SetSupportedButtons(supported));
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-updatebuttonstate>
|
||||
fn UpdateButtonState(&self, button_state: &FakeXRButtonStateInit) -> Fallible<()> {
|
||||
// https://immersive-web.github.io/webxr-test-api/#validate-a-button-state
|
||||
if (button_state.pressed || *button_state.pressedValue > 0.0) && !button_state.touched {
|
||||
return Err(Error::Type("Pressed button must also be touched".into()));
|
||||
}
|
||||
if *button_state.pressedValue < 0.0 {
|
||||
return Err(Error::Type("Pressed value must be non-negative".into()));
|
||||
}
|
||||
|
||||
// TODO: Steps 3-5 of updateButtonState
|
||||
// Passing the one WPT test that utilizes this will require additional work
|
||||
// to specify gamepad button/axes list lengths, as well as passing that info
|
||||
// to the constructor of XRInputSource
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FakeXRButtonType> for MockButtonType {
|
||||
fn from(b: FakeXRButtonType) -> Self {
|
||||
match b {
|
||||
FakeXRButtonType::Grip => MockButtonType::Grip,
|
||||
FakeXRButtonType::Touchpad => MockButtonType::Touchpad,
|
||||
FakeXRButtonType::Thumbstick => MockButtonType::Thumbstick,
|
||||
FakeXRButtonType::Optional_button => MockButtonType::OptionalButton,
|
||||
FakeXRButtonType::Optional_thumbstick => MockButtonType::OptionalThumbstick,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr-test-api/#parse-supported-buttons>
|
||||
pub fn init_to_mock_buttons(buttons: &[FakeXRButtonStateInit]) -> Vec<MockButton> {
|
||||
let supported: Vec<MockButton> = buttons
|
||||
.iter()
|
||||
.map(|b| MockButton {
|
||||
button_type: b.buttonType.into(),
|
||||
pressed: b.pressed,
|
||||
touched: b.touched,
|
||||
pressed_value: *b.pressedValue,
|
||||
x_value: *b.xValue,
|
||||
y_value: *b.yValue,
|
||||
})
|
||||
.collect();
|
||||
supported
|
||||
}
|
44
components/script/dom/webxr/mod.rs
Normal file
44
components/script/dom/webxr/mod.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod fakexrdevice;
|
||||
pub mod fakexrinputcontroller;
|
||||
pub mod xrboundedreferencespace;
|
||||
pub mod xrcompositionlayer;
|
||||
pub mod xrcubelayer;
|
||||
pub mod xrcylinderlayer;
|
||||
pub mod xrequirectlayer;
|
||||
pub mod xrframe;
|
||||
pub mod xrhand;
|
||||
pub mod xrhittestresult;
|
||||
pub mod xrhittestsource;
|
||||
pub mod xrinputsource;
|
||||
pub mod xrinputsourcearray;
|
||||
pub mod xrinputsourceevent;
|
||||
pub mod xrinputsourceschangeevent;
|
||||
pub mod xrjointpose;
|
||||
pub mod xrjointspace;
|
||||
pub mod xrlayer;
|
||||
pub mod xrlayerevent;
|
||||
pub mod xrmediabinding;
|
||||
pub mod xrpose;
|
||||
pub mod xrprojectionlayer;
|
||||
pub mod xrquadlayer;
|
||||
pub mod xrray;
|
||||
pub mod xrreferencespace;
|
||||
pub mod xrreferencespaceevent;
|
||||
pub mod xrrenderstate;
|
||||
pub mod xrrigidtransform;
|
||||
pub mod xrsession;
|
||||
pub mod xrsessionevent;
|
||||
pub mod xrspace;
|
||||
pub mod xrsubimage;
|
||||
pub mod xrsystem;
|
||||
pub mod xrtest;
|
||||
pub mod xrview;
|
||||
pub mod xrviewerpose;
|
||||
pub mod xrviewport;
|
||||
pub mod xrwebglbinding;
|
||||
pub mod xrwebgllayer;
|
||||
pub mod xrwebglsubimage;
|
91
components/script/dom/webxr/xrboundedreferencespace.rs
Normal file
91
components/script/dom/webxr/xrboundedreferencespace.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::MutableHandleValue;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRBoundedReferenceSpaceBinding::XRBoundedReferenceSpaceMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::utils::to_frozen_array;
|
||||
use crate::dom::dompointreadonly::DOMPointReadOnly;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrreferencespace::XRReferenceSpace;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRBoundedReferenceSpace {
|
||||
reference_space: XRReferenceSpace,
|
||||
offset: Dom<XRRigidTransform>,
|
||||
}
|
||||
|
||||
impl XRBoundedReferenceSpace {
|
||||
pub fn new_inherited(
|
||||
session: &XRSession,
|
||||
offset: &XRRigidTransform,
|
||||
) -> XRBoundedReferenceSpace {
|
||||
XRBoundedReferenceSpace {
|
||||
reference_space: XRReferenceSpace::new_inherited(
|
||||
session,
|
||||
offset,
|
||||
XRReferenceSpaceType::Bounded_floor,
|
||||
),
|
||||
offset: Dom::from_ref(offset),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRBoundedReferenceSpace> {
|
||||
let offset = XRRigidTransform::identity(global, can_gc);
|
||||
Self::new_offset(global, session, &offset)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new_offset(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
offset: &XRRigidTransform,
|
||||
) -> DomRoot<XRBoundedReferenceSpace> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRBoundedReferenceSpace::new_inherited(session, offset)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reference_space(&self) -> &XRReferenceSpace {
|
||||
&self.reference_space
|
||||
}
|
||||
}
|
||||
|
||||
impl XRBoundedReferenceSpaceMethods<crate::DomTypeHolder> for XRBoundedReferenceSpace {
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrboundedreferencespace-boundsgeometry>
|
||||
fn BoundsGeometry(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
|
||||
if let Some(bounds) = self.reference_space.get_bounds() {
|
||||
let points: Vec<DomRoot<DOMPointReadOnly>> = bounds
|
||||
.into_iter()
|
||||
.map(|point| {
|
||||
DOMPointReadOnly::new(
|
||||
&self.global(),
|
||||
point.x.into(),
|
||||
0.0,
|
||||
point.y.into(),
|
||||
1.0,
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
to_frozen_array(&points, cx, retval)
|
||||
} else {
|
||||
to_frozen_array::<DomRoot<DOMPointReadOnly>>(&[], cx, retval)
|
||||
}
|
||||
}
|
||||
}
|
12
components/script/dom/webxr/xrcompositionlayer.rs
Normal file
12
components/script/dom/webxr/xrcompositionlayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrlayer::XRLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRCompositionLayer {
|
||||
xr_layer: XRLayer,
|
||||
}
|
12
components/script/dom/webxr/xrcubelayer.rs
Normal file
12
components/script/dom/webxr/xrcubelayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRCubeLayer {
|
||||
composition_layer: XRCompositionLayer,
|
||||
}
|
12
components/script/dom/webxr/xrcylinderlayer.rs
Normal file
12
components/script/dom/webxr/xrcylinderlayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRCylinderLayer {
|
||||
composition_layer: XRCompositionLayer,
|
||||
}
|
12
components/script/dom/webxr/xrequirectlayer.rs
Normal file
12
components/script/dom/webxr/xrequirectlayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XREquirectLayer {
|
||||
composition_layer: XRCompositionLayer,
|
||||
}
|
294
components/script/dom/webxr/xrframe.rs
Normal file
294
components/script/dom/webxr/xrframe.rs
Normal file
|
@ -0,0 +1,294 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::gc::CustomAutoRooterGuard;
|
||||
use js::typedarray::Float32Array;
|
||||
use webxr_api::{Frame, LayerId, SubImages};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRFrameBinding::XRFrameMethods;
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrhittestresult::XRHitTestResult;
|
||||
use crate::dom::xrhittestsource::XRHitTestSource;
|
||||
use crate::dom::xrjointpose::XRJointPose;
|
||||
use crate::dom::xrjointspace::XRJointSpace;
|
||||
use crate::dom::xrpose::XRPose;
|
||||
use crate::dom::xrreferencespace::XRReferenceSpace;
|
||||
use crate::dom::xrsession::{ApiPose, XRSession};
|
||||
use crate::dom::xrspace::XRSpace;
|
||||
use crate::dom::xrviewerpose::XRViewerPose;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRFrame {
|
||||
reflector_: Reflector,
|
||||
session: Dom<XRSession>,
|
||||
#[ignore_malloc_size_of = "defined in webxr_api"]
|
||||
#[no_trace]
|
||||
data: Frame,
|
||||
active: Cell<bool>,
|
||||
animation_frame: Cell<bool>,
|
||||
}
|
||||
|
||||
impl XRFrame {
|
||||
fn new_inherited(session: &XRSession, data: Frame) -> XRFrame {
|
||||
XRFrame {
|
||||
reflector_: Reflector::new(),
|
||||
session: Dom::from_ref(session),
|
||||
data,
|
||||
active: Cell::new(false),
|
||||
animation_frame: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, session: &XRSession, data: Frame) -> DomRoot<XRFrame> {
|
||||
reflect_dom_object(Box::new(XRFrame::new_inherited(session, data)), global)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#xrframe-active>
|
||||
pub fn set_active(&self, active: bool) {
|
||||
self.active.set(active);
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#xrframe-animationframe>
|
||||
pub fn set_animation_frame(&self, animation_frame: bool) {
|
||||
self.animation_frame.set(animation_frame);
|
||||
}
|
||||
|
||||
pub fn get_pose(&self, space: &XRSpace) -> Option<ApiPose> {
|
||||
space.get_pose(&self.data)
|
||||
}
|
||||
|
||||
pub fn get_sub_images(&self, layer_id: LayerId) -> Option<&SubImages> {
|
||||
self.data
|
||||
.sub_images
|
||||
.iter()
|
||||
.find(|sub_images| sub_images.layer_id == layer_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRFrameMethods<crate::DomTypeHolder> for XRFrame {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrframe-session>
|
||||
fn Session(&self) -> DomRoot<XRSession> {
|
||||
DomRoot::from_ref(&self.session)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrframe-predicteddisplaytime>
|
||||
fn PredictedDisplayTime(&self) -> Finite<f64> {
|
||||
// TODO: If inline, return the same value
|
||||
// as the timestamp passed to XRFrameRequestCallback
|
||||
Finite::new(self.data.predicted_display_time)
|
||||
.expect("Failed to create predictedDisplayTime")
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getviewerpose>
|
||||
fn GetViewerPose(
|
||||
&self,
|
||||
reference: &XRReferenceSpace,
|
||||
can_gc: CanGc,
|
||||
) -> Result<Option<DomRoot<XRViewerPose>>, Error> {
|
||||
if self.session != reference.upcast::<XRSpace>().session() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
if !self.active.get() || !self.animation_frame.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
let to_base = if let Some(to_base) = reference.get_base_transform(&self.data) {
|
||||
to_base
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let viewer_pose = if let Some(pose) = self.data.pose.as_ref() {
|
||||
pose
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(XRViewerPose::new(
|
||||
&self.global(),
|
||||
&self.session,
|
||||
to_base,
|
||||
viewer_pose,
|
||||
can_gc,
|
||||
)))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
|
||||
fn GetPose(
|
||||
&self,
|
||||
space: &XRSpace,
|
||||
base_space: &XRSpace,
|
||||
can_gc: CanGc,
|
||||
) -> Result<Option<DomRoot<XRPose>>, Error> {
|
||||
if self.session != space.session() || self.session != base_space.session() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
if !self.active.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
let space = if let Some(space) = self.get_pose(space) {
|
||||
space
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let base_space = if let Some(r) = self.get_pose(base_space) {
|
||||
r
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let pose = space.then(&base_space.inverse());
|
||||
Ok(Some(XRPose::new(&self.global(), pose, can_gc)))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
|
||||
fn GetJointPose(
|
||||
&self,
|
||||
space: &XRJointSpace,
|
||||
base_space: &XRSpace,
|
||||
can_gc: CanGc,
|
||||
) -> Result<Option<DomRoot<XRJointPose>>, Error> {
|
||||
if self.session != space.upcast::<XRSpace>().session() ||
|
||||
self.session != base_space.session()
|
||||
{
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
if !self.active.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
let joint_frame = if let Some(frame) = space.frame(&self.data) {
|
||||
frame
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let base_space = if let Some(r) = self.get_pose(base_space) {
|
||||
r
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let pose = joint_frame.pose.then(&base_space.inverse());
|
||||
Ok(Some(XRJointPose::new(
|
||||
&self.global(),
|
||||
pose.cast_unit(),
|
||||
Some(joint_frame.radius),
|
||||
can_gc,
|
||||
)))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrframe-gethittestresults>
|
||||
fn GetHitTestResults(&self, source: &XRHitTestSource) -> Vec<DomRoot<XRHitTestResult>> {
|
||||
self.data
|
||||
.hit_test_results
|
||||
.iter()
|
||||
.filter(|r| r.id == source.id())
|
||||
.map(|r| XRHitTestResult::new(&self.global(), *r, self))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
/// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-filljointradii>
|
||||
fn FillJointRadii(
|
||||
&self,
|
||||
joint_spaces: Vec<DomRoot<XRJointSpace>>,
|
||||
mut radii: CustomAutoRooterGuard<Float32Array>,
|
||||
) -> Result<bool, Error> {
|
||||
if !self.active.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
for joint_space in &joint_spaces {
|
||||
if self.session != joint_space.upcast::<XRSpace>().session() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
}
|
||||
|
||||
if joint_spaces.len() > radii.len() {
|
||||
return Err(Error::Type(
|
||||
"Length of radii does not match length of joint spaces".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut radii_vec = radii.to_vec();
|
||||
let mut all_valid = true;
|
||||
radii_vec.iter_mut().enumerate().for_each(|(i, radius)| {
|
||||
if let Some(joint_frame) = joint_spaces
|
||||
.get(i)
|
||||
.and_then(|joint_space| joint_space.frame(&self.data))
|
||||
{
|
||||
*radius = joint_frame.radius;
|
||||
} else {
|
||||
all_valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
if !all_valid {
|
||||
radii_vec.fill(f32::NAN);
|
||||
}
|
||||
|
||||
radii.update(&radii_vec);
|
||||
|
||||
Ok(all_valid)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
/// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-fillposes>
|
||||
fn FillPoses(
|
||||
&self,
|
||||
spaces: Vec<DomRoot<XRSpace>>,
|
||||
base_space: &XRSpace,
|
||||
mut transforms: CustomAutoRooterGuard<Float32Array>,
|
||||
) -> Result<bool, Error> {
|
||||
if !self.active.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
for space in &spaces {
|
||||
if self.session != space.session() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
}
|
||||
|
||||
if self.session != base_space.session() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
if spaces.len() * 16 > transforms.len() {
|
||||
return Err(Error::Type(
|
||||
"Transforms array length does not match 16 * spaces length".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut transforms_vec = transforms.to_vec();
|
||||
let mut all_valid = true;
|
||||
spaces.iter().enumerate().for_each(|(i, space)| {
|
||||
let Some(joint_pose) = self.get_pose(space) else {
|
||||
all_valid = false;
|
||||
return;
|
||||
};
|
||||
let Some(base_pose) = self.get_pose(base_space) else {
|
||||
all_valid = false;
|
||||
return;
|
||||
};
|
||||
let pose = joint_pose.then(&base_pose.inverse());
|
||||
let elements = pose.to_transform();
|
||||
let elements_arr = elements.to_array();
|
||||
transforms_vec[i * 16..(i + 1) * 16].copy_from_slice(&elements_arr);
|
||||
});
|
||||
|
||||
if !all_valid {
|
||||
transforms_vec.fill(f32::NAN);
|
||||
}
|
||||
|
||||
transforms.update(&transforms_vec);
|
||||
|
||||
Ok(all_valid)
|
||||
}
|
||||
}
|
176
components/script/dom/webxr/xrhand.rs
Normal file
176
components/script/dom/webxr/xrhand.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use webxr_api::{FingerJoint, Hand, Joint};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRHandBinding::{XRHandJoint, XRHandMethods};
|
||||
use crate::dom::bindings::iterable::Iterable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrinputsource::XRInputSource;
|
||||
use crate::dom::xrjointspace::XRJointSpace;
|
||||
|
||||
const JOINT_SPACE_MAP: [(XRHandJoint, Joint); 25] = [
|
||||
(XRHandJoint::Wrist, Joint::Wrist),
|
||||
(XRHandJoint::Thumb_metacarpal, Joint::ThumbMetacarpal),
|
||||
(
|
||||
XRHandJoint::Thumb_phalanx_proximal,
|
||||
Joint::ThumbPhalanxProximal,
|
||||
),
|
||||
(XRHandJoint::Thumb_phalanx_distal, Joint::ThumbPhalanxDistal),
|
||||
(XRHandJoint::Thumb_tip, Joint::ThumbPhalanxTip),
|
||||
(
|
||||
XRHandJoint::Index_finger_metacarpal,
|
||||
Joint::Index(FingerJoint::Metacarpal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Index_finger_phalanx_proximal,
|
||||
Joint::Index(FingerJoint::PhalanxProximal),
|
||||
),
|
||||
(XRHandJoint::Index_finger_phalanx_intermediate, {
|
||||
Joint::Index(FingerJoint::PhalanxIntermediate)
|
||||
}),
|
||||
(
|
||||
XRHandJoint::Index_finger_phalanx_distal,
|
||||
Joint::Index(FingerJoint::PhalanxDistal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Index_finger_tip,
|
||||
Joint::Index(FingerJoint::PhalanxTip),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Middle_finger_metacarpal,
|
||||
Joint::Middle(FingerJoint::Metacarpal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Middle_finger_phalanx_proximal,
|
||||
Joint::Middle(FingerJoint::PhalanxProximal),
|
||||
),
|
||||
(XRHandJoint::Middle_finger_phalanx_intermediate, {
|
||||
Joint::Middle(FingerJoint::PhalanxIntermediate)
|
||||
}),
|
||||
(
|
||||
XRHandJoint::Middle_finger_phalanx_distal,
|
||||
Joint::Middle(FingerJoint::PhalanxDistal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Middle_finger_tip,
|
||||
Joint::Middle(FingerJoint::PhalanxTip),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Ring_finger_metacarpal,
|
||||
Joint::Ring(FingerJoint::Metacarpal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Ring_finger_phalanx_proximal,
|
||||
Joint::Ring(FingerJoint::PhalanxProximal),
|
||||
),
|
||||
(XRHandJoint::Ring_finger_phalanx_intermediate, {
|
||||
Joint::Ring(FingerJoint::PhalanxIntermediate)
|
||||
}),
|
||||
(
|
||||
XRHandJoint::Ring_finger_phalanx_distal,
|
||||
Joint::Ring(FingerJoint::PhalanxDistal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Ring_finger_tip,
|
||||
Joint::Ring(FingerJoint::PhalanxTip),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Pinky_finger_metacarpal,
|
||||
Joint::Little(FingerJoint::Metacarpal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Pinky_finger_phalanx_proximal,
|
||||
Joint::Little(FingerJoint::PhalanxProximal),
|
||||
),
|
||||
(XRHandJoint::Pinky_finger_phalanx_intermediate, {
|
||||
Joint::Little(FingerJoint::PhalanxIntermediate)
|
||||
}),
|
||||
(
|
||||
XRHandJoint::Pinky_finger_phalanx_distal,
|
||||
Joint::Little(FingerJoint::PhalanxDistal),
|
||||
),
|
||||
(
|
||||
XRHandJoint::Pinky_finger_tip,
|
||||
Joint::Little(FingerJoint::PhalanxTip),
|
||||
),
|
||||
];
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRHand {
|
||||
reflector_: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in webxr"]
|
||||
source: Dom<XRInputSource>,
|
||||
#[ignore_malloc_size_of = "partially defind in webxr"]
|
||||
#[custom_trace]
|
||||
spaces: Hand<Dom<XRJointSpace>>,
|
||||
}
|
||||
|
||||
impl XRHand {
|
||||
fn new_inherited(source: &XRInputSource, spaces: &Hand<DomRoot<XRJointSpace>>) -> XRHand {
|
||||
XRHand {
|
||||
reflector_: Reflector::new(),
|
||||
source: Dom::from_ref(source),
|
||||
spaces: spaces.map(|j, _| j.as_ref().map(|j| Dom::from_ref(&**j))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, source: &XRInputSource, support: Hand<()>) -> DomRoot<XRHand> {
|
||||
let id = source.id();
|
||||
let session = source.session();
|
||||
let spaces = support.map(|field, joint| {
|
||||
let hand_joint = JOINT_SPACE_MAP
|
||||
.iter()
|
||||
.find(|&&(_, value)| value == joint)
|
||||
.map(|&(hand_joint, _)| hand_joint)
|
||||
.expect("Invalid joint name");
|
||||
field.map(|_| XRJointSpace::new(global, session, id, joint, hand_joint))
|
||||
});
|
||||
reflect_dom_object(Box::new(XRHand::new_inherited(source, &spaces)), global)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRHandMethods<crate::DomTypeHolder> for XRHand {
|
||||
/// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
|
||||
fn Size(&self) -> u32 {
|
||||
XRHandJoint::Pinky_finger_tip as u32 + 1
|
||||
}
|
||||
|
||||
/// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
|
||||
fn Get(&self, joint_name: XRHandJoint) -> DomRoot<XRJointSpace> {
|
||||
let joint = JOINT_SPACE_MAP
|
||||
.iter()
|
||||
.find(|&&(key, _)| key == joint_name)
|
||||
.map(|&(_, joint)| joint)
|
||||
.expect("Invalid joint name");
|
||||
self.spaces
|
||||
.get(joint)
|
||||
.map(|j| DomRoot::from_ref(&**j))
|
||||
.expect("Failed to get joint pose")
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterable for XRHand {
|
||||
type Key = XRHandJoint;
|
||||
type Value = DomRoot<XRJointSpace>;
|
||||
|
||||
fn get_iterable_length(&self) -> u32 {
|
||||
JOINT_SPACE_MAP.len() as u32
|
||||
}
|
||||
|
||||
fn get_value_at_index(&self, n: u32) -> DomRoot<XRJointSpace> {
|
||||
let joint = JOINT_SPACE_MAP[n as usize].1;
|
||||
self.spaces
|
||||
.get(joint)
|
||||
.map(|j| DomRoot::from_ref(&**j))
|
||||
.expect("Failed to get joint pose")
|
||||
}
|
||||
|
||||
fn get_key_at_index(&self, n: u32) -> XRHandJoint {
|
||||
JOINT_SPACE_MAP[n as usize].0
|
||||
}
|
||||
}
|
54
components/script/dom/webxr/xrhittestresult.rs
Normal file
54
components/script/dom/webxr/xrhittestresult.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use webxr_api::HitTestResult;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRHitTestResultBinding::XRHitTestResultMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrframe::XRFrame;
|
||||
use crate::dom::xrpose::XRPose;
|
||||
use crate::dom::xrspace::XRSpace;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRHitTestResult {
|
||||
reflector_: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in webxr"]
|
||||
#[no_trace]
|
||||
result: HitTestResult,
|
||||
frame: Dom<XRFrame>,
|
||||
}
|
||||
|
||||
impl XRHitTestResult {
|
||||
fn new_inherited(result: HitTestResult, frame: &XRFrame) -> XRHitTestResult {
|
||||
XRHitTestResult {
|
||||
reflector_: Reflector::new(),
|
||||
result,
|
||||
frame: Dom::from_ref(frame),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
result: HitTestResult,
|
||||
frame: &XRFrame,
|
||||
) -> DomRoot<XRHitTestResult> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRHitTestResult::new_inherited(result, frame)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRHitTestResultMethods<crate::DomTypeHolder> for XRHitTestResult {
|
||||
// https://immersive-web.github.io/hit-test/#dom-xrhittestresult-getpose
|
||||
fn GetPose(&self, base: &XRSpace, can_gc: CanGc) -> Option<DomRoot<XRPose>> {
|
||||
let base = self.frame.get_pose(base)?;
|
||||
let pose = self.result.space.then(&base.inverse());
|
||||
Some(XRPose::new(&self.global(), pose.cast_unit(), can_gc))
|
||||
}
|
||||
}
|
53
components/script/dom/webxr/xrhittestsource.rs
Normal file
53
components/script/dom/webxr/xrhittestsource.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use webxr_api::HitTestId;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestSourceMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRHitTestSource {
|
||||
reflector_: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in webxr"]
|
||||
#[no_trace]
|
||||
id: HitTestId,
|
||||
session: Dom<XRSession>,
|
||||
}
|
||||
|
||||
impl XRHitTestSource {
|
||||
fn new_inherited(id: HitTestId, session: &XRSession) -> XRHitTestSource {
|
||||
XRHitTestSource {
|
||||
reflector_: Reflector::new(),
|
||||
id,
|
||||
session: Dom::from_ref(session),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
id: HitTestId,
|
||||
session: &XRSession,
|
||||
) -> DomRoot<XRHitTestSource> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRHitTestSource::new_inherited(id, session)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> HitTestId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl XRHitTestSourceMethods<crate::DomTypeHolder> for XRHitTestSource {
|
||||
// https://immersive-web.github.io/hit-test/#dom-xrhittestsource-cancel
|
||||
fn Cancel(&self) {
|
||||
self.session.with_session(|s| s.cancel_hit_test(self.id));
|
||||
}
|
||||
}
|
186
components/script/dom/webxr/xrinputsource.rs
Normal file
186
components/script/dom/webxr/xrinputsource.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::conversions::ToJSValConvertible;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsval::{JSVal, UndefinedValue};
|
||||
use js::rust::MutableHandleValue;
|
||||
use script_traits::GamepadSupportedHapticEffects;
|
||||
use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
|
||||
XRHandedness, XRInputSourceMethods, XRTargetRayMode,
|
||||
};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::gamepad::Gamepad;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrhand::XRHand;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::dom::xrspace::XRSpace;
|
||||
use crate::realms::enter_realm;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRInputSource {
|
||||
reflector: Reflector,
|
||||
session: Dom<XRSession>,
|
||||
#[ignore_malloc_size_of = "Defined in rust-webxr"]
|
||||
#[no_trace]
|
||||
info: InputSource,
|
||||
target_ray_space: MutNullableDom<XRSpace>,
|
||||
grip_space: MutNullableDom<XRSpace>,
|
||||
hand: MutNullableDom<XRHand>,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
profiles: Heap<JSVal>,
|
||||
gamepad: DomRoot<Gamepad>,
|
||||
}
|
||||
|
||||
impl XRInputSource {
|
||||
pub fn new_inherited(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
info: InputSource,
|
||||
can_gc: CanGc,
|
||||
) -> XRInputSource {
|
||||
// <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences>
|
||||
let gamepad = Gamepad::new(
|
||||
global,
|
||||
0,
|
||||
"".into(),
|
||||
"xr-standard".into(),
|
||||
(-1.0, 1.0),
|
||||
(0.0, 1.0),
|
||||
GamepadSupportedHapticEffects {
|
||||
supports_dual_rumble: false,
|
||||
supports_trigger_rumble: false,
|
||||
},
|
||||
true,
|
||||
can_gc,
|
||||
);
|
||||
XRInputSource {
|
||||
reflector: Reflector::new(),
|
||||
session: Dom::from_ref(session),
|
||||
info,
|
||||
target_ray_space: Default::default(),
|
||||
grip_space: Default::default(),
|
||||
hand: Default::default(),
|
||||
profiles: Heap::default(),
|
||||
gamepad,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
info: InputSource,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRInputSource> {
|
||||
let source = reflect_dom_object(
|
||||
Box::new(XRInputSource::new_inherited(global, session, info, can_gc)),
|
||||
global,
|
||||
);
|
||||
|
||||
let _ac = enter_realm(global);
|
||||
let cx = GlobalScope::get_cx();
|
||||
unsafe {
|
||||
rooted!(in(*cx) let mut profiles = UndefinedValue());
|
||||
source.info.profiles.to_jsval(*cx, profiles.handle_mut());
|
||||
source.profiles.set(profiles.get());
|
||||
}
|
||||
source
|
||||
}
|
||||
|
||||
pub fn id(&self) -> InputId {
|
||||
self.info.id
|
||||
}
|
||||
|
||||
pub fn session(&self) -> &XRSession {
|
||||
&self.session
|
||||
}
|
||||
|
||||
pub fn update_gamepad_state(&self, frame: InputFrame) {
|
||||
frame
|
||||
.button_values
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, value)| {
|
||||
self.gamepad.map_and_normalize_buttons(i, *value as f64);
|
||||
});
|
||||
frame.axis_values.iter().enumerate().for_each(|(i, value)| {
|
||||
self.gamepad.map_and_normalize_axes(i, *value as f64);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn gamepad(&self) -> &DomRoot<Gamepad> {
|
||||
&self.gamepad
|
||||
}
|
||||
}
|
||||
|
||||
impl XRInputSourceMethods<crate::DomTypeHolder> for XRInputSource {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-handedness>
|
||||
fn Handedness(&self) -> XRHandedness {
|
||||
match self.info.handedness {
|
||||
Handedness::None => XRHandedness::None,
|
||||
Handedness::Left => XRHandedness::Left,
|
||||
Handedness::Right => XRHandedness::Right,
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetraymode>
|
||||
fn TargetRayMode(&self) -> XRTargetRayMode {
|
||||
match self.info.target_ray_mode {
|
||||
TargetRayMode::Gaze => XRTargetRayMode::Gaze,
|
||||
TargetRayMode::TrackedPointer => XRTargetRayMode::Tracked_pointer,
|
||||
TargetRayMode::Screen => XRTargetRayMode::Screen,
|
||||
TargetRayMode::TransientPointer => XRTargetRayMode::Transient_pointer,
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace>
|
||||
fn TargetRaySpace(&self) -> DomRoot<XRSpace> {
|
||||
self.target_ray_space.or_init(|| {
|
||||
let global = self.global();
|
||||
XRSpace::new_inputspace(&global, &self.session, self, false)
|
||||
})
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace>
|
||||
fn GetGripSpace(&self) -> Option<DomRoot<XRSpace>> {
|
||||
if self.info.supports_grip {
|
||||
Some(self.grip_space.or_init(|| {
|
||||
let global = self.global();
|
||||
XRSpace::new_inputspace(&global, &self.session, self, true)
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles
|
||||
fn Profiles(&self, _cx: JSContext, mut retval: MutableHandleValue) {
|
||||
retval.set(self.profiles.get())
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrinputsource-skiprendering>
|
||||
fn SkipRendering(&self) -> bool {
|
||||
// Servo is not currently supported anywhere that would allow for skipped
|
||||
// controller rendering.
|
||||
false
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrinputsource-interface>
|
||||
fn GetGamepad(&self) -> Option<DomRoot<Gamepad>> {
|
||||
Some(DomRoot::from_ref(&*self.gamepad))
|
||||
}
|
||||
|
||||
// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md
|
||||
fn GetHand(&self) -> Option<DomRoot<XRHand>> {
|
||||
self.info.hand_support.as_ref().map(|hand| {
|
||||
self.hand
|
||||
.or_init(|| XRHand::new(&self.global(), self, hand.clone()))
|
||||
})
|
||||
}
|
||||
}
|
152
components/script/dom/webxr/xrinputsourcearray.rs
Normal file
152
components/script/dom/webxr/xrinputsourcearray.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use webxr_api::{InputId, InputSource};
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourceArrayBinding::XRInputSourceArrayMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrinputsource::XRInputSource;
|
||||
use crate::dom::xrinputsourceschangeevent::XRInputSourcesChangeEvent;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRInputSourceArray {
|
||||
reflector_: Reflector,
|
||||
input_sources: DomRefCell<Vec<Dom<XRInputSource>>>,
|
||||
}
|
||||
|
||||
impl XRInputSourceArray {
|
||||
fn new_inherited() -> XRInputSourceArray {
|
||||
XRInputSourceArray {
|
||||
reflector_: Reflector::new(),
|
||||
input_sources: DomRefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<XRInputSourceArray> {
|
||||
reflect_dom_object(Box::new(XRInputSourceArray::new_inherited()), global)
|
||||
}
|
||||
|
||||
pub fn add_input_sources(&self, session: &XRSession, inputs: &[InputSource], can_gc: CanGc) {
|
||||
let global = self.global();
|
||||
|
||||
let mut added = vec![];
|
||||
for info in inputs {
|
||||
// This is quadratic, but won't be a problem for the only case
|
||||
// where we add multiple input sources (the initial input sources case)
|
||||
debug_assert!(
|
||||
!self
|
||||
.input_sources
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|i| i.id() == info.id),
|
||||
"Should never add a duplicate input id!"
|
||||
);
|
||||
let input = XRInputSource::new(&global, session, info.clone(), can_gc);
|
||||
self.input_sources.borrow_mut().push(Dom::from_ref(&input));
|
||||
added.push(input);
|
||||
}
|
||||
|
||||
let event = XRInputSourcesChangeEvent::new(
|
||||
&global,
|
||||
atom!("inputsourceschange"),
|
||||
false,
|
||||
true,
|
||||
session,
|
||||
&added,
|
||||
&[],
|
||||
can_gc,
|
||||
);
|
||||
event.upcast::<Event>().fire(session.upcast(), can_gc);
|
||||
}
|
||||
|
||||
pub fn remove_input_source(&self, session: &XRSession, id: InputId, can_gc: CanGc) {
|
||||
let global = self.global();
|
||||
let removed = if let Some(i) = self.input_sources.borrow().iter().find(|i| i.id() == id) {
|
||||
i.gamepad().update_connected(false, false, can_gc);
|
||||
[DomRoot::from_ref(&**i)]
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let event = XRInputSourcesChangeEvent::new(
|
||||
&global,
|
||||
atom!("inputsourceschange"),
|
||||
false,
|
||||
true,
|
||||
session,
|
||||
&[],
|
||||
&removed,
|
||||
can_gc,
|
||||
);
|
||||
self.input_sources.borrow_mut().retain(|i| i.id() != id);
|
||||
event.upcast::<Event>().fire(session.upcast(), can_gc);
|
||||
}
|
||||
|
||||
pub fn add_remove_input_source(
|
||||
&self,
|
||||
session: &XRSession,
|
||||
id: InputId,
|
||||
info: InputSource,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let global = self.global();
|
||||
let root;
|
||||
let removed = if let Some(i) = self.input_sources.borrow().iter().find(|i| i.id() == id) {
|
||||
i.gamepad().update_connected(false, false, can_gc);
|
||||
root = [DomRoot::from_ref(&**i)];
|
||||
&root as &[_]
|
||||
} else {
|
||||
warn!("Could not find removed input source with id {:?}", id);
|
||||
&[]
|
||||
};
|
||||
self.input_sources.borrow_mut().retain(|i| i.id() != id);
|
||||
let input = XRInputSource::new(&global, session, info, can_gc);
|
||||
self.input_sources.borrow_mut().push(Dom::from_ref(&input));
|
||||
|
||||
let added = [input];
|
||||
|
||||
let event = XRInputSourcesChangeEvent::new(
|
||||
&global,
|
||||
atom!("inputsourceschange"),
|
||||
false,
|
||||
true,
|
||||
session,
|
||||
&added,
|
||||
removed,
|
||||
can_gc,
|
||||
);
|
||||
event.upcast::<Event>().fire(session.upcast(), can_gc);
|
||||
}
|
||||
|
||||
pub fn find(&self, id: InputId) -> Option<DomRoot<XRInputSource>> {
|
||||
self.input_sources
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|x| x.id() == id)
|
||||
.map(|x| DomRoot::from_ref(&**x))
|
||||
}
|
||||
}
|
||||
|
||||
impl XRInputSourceArrayMethods<crate::DomTypeHolder> for XRInputSourceArray {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrinputsourcearray-length>
|
||||
fn Length(&self) -> u32 {
|
||||
self.input_sources.borrow().len() as u32
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#xrinputsourcearray>
|
||||
fn IndexedGetter(&self, n: u32) -> Option<DomRoot<XRInputSource>> {
|
||||
self.input_sources
|
||||
.borrow()
|
||||
.get(n as usize)
|
||||
.map(|x| DomRoot::from_ref(&**x))
|
||||
}
|
||||
}
|
116
components/script/dom/webxr/xrinputsourceevent.rs
Normal file
116
components/script/dom/webxr/xrinputsourceevent.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourceEventBinding::{
|
||||
self, XRInputSourceEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrframe::XRFrame;
|
||||
use crate::dom::xrinputsource::XRInputSource;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRInputSourceEvent {
|
||||
event: Event,
|
||||
frame: Dom<XRFrame>,
|
||||
source: Dom<XRInputSource>,
|
||||
}
|
||||
|
||||
impl XRInputSourceEvent {
|
||||
#[allow(crown::unrooted_must_root)]
|
||||
fn new_inherited(frame: &XRFrame, source: &XRInputSource) -> XRInputSourceEvent {
|
||||
XRInputSourceEvent {
|
||||
event: Event::new_inherited(),
|
||||
frame: Dom::from_ref(frame),
|
||||
source: Dom::from_ref(source),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
frame: &XRFrame,
|
||||
source: &XRInputSource,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRInputSourceEvent> {
|
||||
Self::new_with_proto(
|
||||
global, None, type_, bubbles, cancelable, frame, source, can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
frame: &XRFrame,
|
||||
source: &XRInputSource,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRInputSourceEvent> {
|
||||
let trackevent = reflect_dom_object_with_proto(
|
||||
Box::new(XRInputSourceEvent::new_inherited(frame, source)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
);
|
||||
{
|
||||
let event = trackevent.upcast::<Event>();
|
||||
event.init_event(type_, bubbles, cancelable);
|
||||
}
|
||||
trackevent
|
||||
}
|
||||
}
|
||||
|
||||
impl XRInputSourceEventMethods<crate::DomTypeHolder> for XRInputSourceEvent {
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceevent-xrinputsourceevent
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &XRInputSourceEventBinding::XRInputSourceEventInit,
|
||||
) -> Fallible<DomRoot<XRInputSourceEvent>> {
|
||||
Ok(XRInputSourceEvent::new_with_proto(
|
||||
&window.global(),
|
||||
proto,
|
||||
Atom::from(type_),
|
||||
init.parent.bubbles,
|
||||
init.parent.cancelable,
|
||||
&init.frame,
|
||||
&init.inputSource,
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-frame
|
||||
fn Frame(&self) -> DomRoot<XRFrame> {
|
||||
DomRoot::from_ref(&*self.frame)
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-inputsource
|
||||
fn InputSource(&self) -> DomRoot<XRInputSource> {
|
||||
DomRoot::from_ref(&*self.source)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
140
components/script/dom/webxr/xrinputsourceschangeevent.rs
Normal file
140
components/script/dom/webxr/xrinputsourceschangeevent.rs
Normal file
|
@ -0,0 +1,140 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsval::JSVal;
|
||||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRInputSourcesChangeEventBinding::{
|
||||
self, XRInputSourcesChangeEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::utils::to_frozen_array;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrinputsource::XRInputSource;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::realms::enter_realm;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRInputSourcesChangeEvent {
|
||||
event: Event,
|
||||
session: Dom<XRSession>,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
added: Heap<JSVal>,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
removed: Heap<JSVal>,
|
||||
}
|
||||
|
||||
impl XRInputSourcesChangeEvent {
|
||||
#[allow(crown::unrooted_must_root)]
|
||||
fn new_inherited(session: &XRSession) -> XRInputSourcesChangeEvent {
|
||||
XRInputSourcesChangeEvent {
|
||||
event: Event::new_inherited(),
|
||||
session: Dom::from_ref(session),
|
||||
added: Heap::default(),
|
||||
removed: Heap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
session: &XRSession,
|
||||
added: &[DomRoot<XRInputSource>],
|
||||
removed: &[DomRoot<XRInputSource>],
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRInputSourcesChangeEvent> {
|
||||
Self::new_with_proto(
|
||||
global, None, type_, bubbles, cancelable, session, added, removed, can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
session: &XRSession,
|
||||
added: &[DomRoot<XRInputSource>],
|
||||
removed: &[DomRoot<XRInputSource>],
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRInputSourcesChangeEvent> {
|
||||
let changeevent = reflect_dom_object_with_proto(
|
||||
Box::new(XRInputSourcesChangeEvent::new_inherited(session)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
);
|
||||
{
|
||||
let event = changeevent.upcast::<Event>();
|
||||
event.init_event(type_, bubbles, cancelable);
|
||||
}
|
||||
let _ac = enter_realm(global);
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut frozen_val: JSVal);
|
||||
to_frozen_array(added, cx, frozen_val.handle_mut());
|
||||
changeevent.added.set(*frozen_val);
|
||||
to_frozen_array(removed, cx, frozen_val.handle_mut());
|
||||
changeevent.removed.set(*frozen_val);
|
||||
changeevent
|
||||
}
|
||||
}
|
||||
|
||||
impl XRInputSourcesChangeEventMethods<crate::DomTypeHolder> for XRInputSourcesChangeEvent {
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-xrinputsourceschangeevent
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &XRInputSourcesChangeEventBinding::XRInputSourcesChangeEventInit,
|
||||
) -> DomRoot<XRInputSourcesChangeEvent> {
|
||||
XRInputSourcesChangeEvent::new_with_proto(
|
||||
&window.global(),
|
||||
proto,
|
||||
Atom::from(type_),
|
||||
init.parent.bubbles,
|
||||
init.parent.cancelable,
|
||||
&init.session,
|
||||
&init.added,
|
||||
&init.removed,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-session
|
||||
fn Session(&self) -> DomRoot<XRSession> {
|
||||
DomRoot::from_ref(&*self.session)
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-added
|
||||
fn Added(&self, _cx: JSContext, mut retval: MutableHandleValue) {
|
||||
retval.set(self.added.get())
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-removed
|
||||
fn Removed(&self, _cx: JSContext, mut retval: MutableHandleValue) {
|
||||
retval.set(self.removed.get())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
51
components/script/dom/webxr/xrjointpose.rs
Normal file
51
components/script/dom/webxr/xrjointpose.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRJointPoseBinding::XRJointPoseMethods;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrpose::XRPose;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::ApiRigidTransform;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRJointPose {
|
||||
pose: XRPose,
|
||||
radius: Option<f32>,
|
||||
}
|
||||
|
||||
impl XRJointPose {
|
||||
fn new_inherited(transform: &XRRigidTransform, radius: Option<f32>) -> XRJointPose {
|
||||
XRJointPose {
|
||||
pose: XRPose::new_inherited(transform),
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
pose: ApiRigidTransform,
|
||||
radius: Option<f32>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRJointPose> {
|
||||
let transform = XRRigidTransform::new(global, pose, can_gc);
|
||||
reflect_dom_object(
|
||||
Box::new(XRJointPose::new_inherited(&transform, radius)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRJointPoseMethods<crate::DomTypeHolder> for XRJointPose {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-XRJointPose-views>
|
||||
fn GetRadius(&self) -> Option<Finite<f32>> {
|
||||
self.radius.map(Finite::wrap)
|
||||
}
|
||||
}
|
83
components/script/dom/webxr/xrjointspace.rs
Normal file
83
components/script/dom/webxr/xrjointspace.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::RigidTransform3D;
|
||||
use webxr_api::{BaseSpace, Frame, InputId, Joint, JointFrame, Space};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRHandBinding::XRHandJoint;
|
||||
use crate::dom::bindings::codegen::Bindings::XRJointSpaceBinding::XRJointSpaceMethods;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrsession::{ApiPose, XRSession};
|
||||
use crate::dom::xrspace::XRSpace;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRJointSpace {
|
||||
xrspace: XRSpace,
|
||||
#[ignore_malloc_size_of = "defined in rust-webxr"]
|
||||
#[no_trace]
|
||||
input: InputId,
|
||||
#[ignore_malloc_size_of = "defined in rust-webxr"]
|
||||
#[no_trace]
|
||||
joint: Joint,
|
||||
hand_joint: XRHandJoint,
|
||||
}
|
||||
|
||||
impl XRJointSpace {
|
||||
pub fn new_inherited(
|
||||
session: &XRSession,
|
||||
input: InputId,
|
||||
joint: Joint,
|
||||
hand_joint: XRHandJoint,
|
||||
) -> XRJointSpace {
|
||||
XRJointSpace {
|
||||
xrspace: XRSpace::new_inherited(session),
|
||||
input,
|
||||
joint,
|
||||
hand_joint,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
input: InputId,
|
||||
joint: Joint,
|
||||
hand_joint: XRHandJoint,
|
||||
) -> DomRoot<XRJointSpace> {
|
||||
reflect_dom_object(
|
||||
Box::new(Self::new_inherited(session, input, joint, hand_joint)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn space(&self) -> Space {
|
||||
let base = BaseSpace::Joint(self.input, self.joint);
|
||||
let offset = RigidTransform3D::identity();
|
||||
Space { base, offset }
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&self, frame: &'a Frame) -> Option<&'a JointFrame> {
|
||||
frame
|
||||
.inputs
|
||||
.iter()
|
||||
.find(|i| i.id == self.input)
|
||||
.and_then(|i| i.hand.as_ref())
|
||||
.and_then(|h| h.get(self.joint))
|
||||
}
|
||||
|
||||
pub fn get_pose(&self, frame: &Frame) -> Option<ApiPose> {
|
||||
self.frame(frame).map(|f| f.pose).map(|t| t.cast_unit())
|
||||
}
|
||||
}
|
||||
|
||||
impl XRJointSpaceMethods<crate::DomTypeHolder> for XRJointSpace {
|
||||
/// <https://www.w3.org/TR/webxr-hand-input-1/#xrjointspace-jointname>
|
||||
fn JointName(&self) -> XRHandJoint {
|
||||
self.hand_joint
|
||||
}
|
||||
}
|
77
components/script/dom/webxr/xrlayer.rs
Normal file
77
components/script/dom/webxr/xrlayer.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use canvas_traits::webgl::WebGLContextId;
|
||||
use dom_struct::dom_struct;
|
||||
use webxr_api::LayerId;
|
||||
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::root::Dom;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
|
||||
use crate::dom::xrframe::XRFrame;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::dom::xrwebgllayer::XRWebGLLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRLayer {
|
||||
event_target: EventTarget,
|
||||
session: Dom<XRSession>,
|
||||
context: Dom<WebGLRenderingContext>,
|
||||
/// If none, the session is inline (the composition disabled flag is true)
|
||||
/// and this is a XRWebGLLayer.
|
||||
#[ignore_malloc_size_of = "Layer ids don't heap-allocate"]
|
||||
#[no_trace]
|
||||
layer_id: Option<LayerId>,
|
||||
}
|
||||
|
||||
impl XRLayer {
|
||||
#[allow(dead_code)]
|
||||
pub fn new_inherited(
|
||||
session: &XRSession,
|
||||
context: &WebGLRenderingContext,
|
||||
layer_id: Option<LayerId>,
|
||||
) -> XRLayer {
|
||||
XRLayer {
|
||||
event_target: EventTarget::new_inherited(),
|
||||
session: Dom::from_ref(session),
|
||||
context: Dom::from_ref(context),
|
||||
layer_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn layer_id(&self) -> Option<LayerId> {
|
||||
self.layer_id
|
||||
}
|
||||
|
||||
pub(crate) fn context_id(&self) -> WebGLContextId {
|
||||
self.context.context_id()
|
||||
}
|
||||
|
||||
pub(crate) fn context(&self) -> &WebGLRenderingContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub(crate) fn session(&self) -> &XRSession {
|
||||
&self.session
|
||||
}
|
||||
|
||||
pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> {
|
||||
// TODO: Implement this for other layer types
|
||||
if let Some(this) = self.downcast::<XRWebGLLayer>() {
|
||||
this.begin_frame(frame)
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&self, frame: &XRFrame) -> Option<()> {
|
||||
// TODO: Implement this for other layer types
|
||||
if let Some(this) = self.downcast::<XRWebGLLayer>() {
|
||||
this.end_frame(frame)
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
77
components/script/dom/webxr/xrlayerevent.rs
Normal file
77
components/script/dom/webxr/xrlayerevent.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRLayerEventBinding::{
|
||||
XRLayerEventInit, XRLayerEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrlayer::XRLayer;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://w3c.github.io/uievents/#interface-uievent
|
||||
#[dom_struct]
|
||||
pub struct XRLayerEvent {
|
||||
event: Event,
|
||||
layer: Dom<XRLayer>,
|
||||
}
|
||||
|
||||
impl XRLayerEvent {
|
||||
pub fn new_inherited(layer: &XRLayer) -> XRLayerEvent {
|
||||
XRLayerEvent {
|
||||
event: Event::new_inherited(),
|
||||
layer: Dom::from_ref(layer),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
layer: &XRLayer,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRLayerEvent> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(XRLayerEvent::new_inherited(layer)),
|
||||
window,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRLayerEventMethods<crate::DomTypeHolder> for XRLayerEvent {
|
||||
// https://immersive-web.github.io/layers/#dom-xrlayerevent-xrlayerevent
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &XRLayerEventInit,
|
||||
) -> DomRoot<XRLayerEvent> {
|
||||
let event = XRLayerEvent::new(window, proto, &init.layer, can_gc);
|
||||
let type_ = Atom::from(type_);
|
||||
let bubbles = init.parent.bubbles;
|
||||
let cancelable = init.parent.cancelable;
|
||||
event.event.init_event(type_, bubbles, cancelable);
|
||||
event
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/layers/#dom-xrlayerevent-layer
|
||||
fn Layer(&self) -> DomRoot<XRLayer> {
|
||||
DomRoot::from_ref(&self.layer)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
101
components/script/dom/webxr/xrmediabinding.rs
Normal file
101
components/script/dom/webxr/xrmediabinding.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaBinding_Binding::XRMediaBindingMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaLayerInit;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::htmlvideoelement::HTMLVideoElement;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrcylinderlayer::XRCylinderLayer;
|
||||
use crate::dom::xrequirectlayer::XREquirectLayer;
|
||||
use crate::dom::xrquadlayer::XRQuadLayer;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRMediaBinding {
|
||||
reflector: Reflector,
|
||||
session: Dom<XRSession>,
|
||||
}
|
||||
|
||||
impl XRMediaBinding {
|
||||
pub fn new_inherited(session: &XRSession) -> XRMediaBinding {
|
||||
XRMediaBinding {
|
||||
reflector: Reflector::new(),
|
||||
session: Dom::from_ref(session),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
global: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
session: &XRSession,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRMediaBinding> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(XRMediaBinding::new_inherited(session)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRMediaBindingMethods<crate::DomTypeHolder> for XRMediaBinding {
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-xrmediabinding>
|
||||
fn Constructor(
|
||||
global: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
session: &XRSession,
|
||||
) -> Fallible<DomRoot<XRMediaBinding>> {
|
||||
// Step 1.
|
||||
if session.is_ended() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
if !session.is_immersive() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
// Steps 3-5.
|
||||
Ok(XRMediaBinding::new(global, proto, session, can_gc))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createquadlayer>
|
||||
fn CreateQuadLayer(
|
||||
&self,
|
||||
_: &HTMLVideoElement,
|
||||
_: &XRMediaLayerInit,
|
||||
) -> Fallible<DomRoot<XRQuadLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createcylinderlayer>
|
||||
fn CreateCylinderLayer(
|
||||
&self,
|
||||
_: &HTMLVideoElement,
|
||||
_: &XRMediaLayerInit,
|
||||
) -> Fallible<DomRoot<XRCylinderLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createequirectlayer>
|
||||
fn CreateEquirectLayer(
|
||||
&self,
|
||||
_: &HTMLVideoElement,
|
||||
_: &XRMediaLayerInit,
|
||||
) -> Fallible<DomRoot<XREquirectLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
}
|
65
components/script/dom/webxr/xrpose.rs
Normal file
65
components/script/dom/webxr/xrpose.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRPoseBinding::XRPoseMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::dompointreadonly::DOMPointReadOnly;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::ApiRigidTransform;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRPose {
|
||||
reflector_: Reflector,
|
||||
transform: Dom<XRRigidTransform>,
|
||||
}
|
||||
|
||||
impl XRPose {
|
||||
pub fn new_inherited(transform: &XRRigidTransform) -> XRPose {
|
||||
XRPose {
|
||||
reflector_: Reflector::new(),
|
||||
transform: Dom::from_ref(transform),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
transform: ApiRigidTransform,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRPose> {
|
||||
let transform = XRRigidTransform::new(global, transform, can_gc);
|
||||
reflect_dom_object(Box::new(XRPose::new_inherited(&transform)), global)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRPoseMethods<crate::DomTypeHolder> for XRPose {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrpose-transform>
|
||||
fn Transform(&self) -> DomRoot<XRRigidTransform> {
|
||||
DomRoot::from_ref(&self.transform)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrpose-linearvelocity>
|
||||
fn GetLinearVelocity(&self) -> Option<DomRoot<DOMPointReadOnly>> {
|
||||
// TODO: Expose from webxr crate
|
||||
None
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrpose-angularvelocity>
|
||||
fn GetAngularVelocity(&self) -> Option<DomRoot<DOMPointReadOnly>> {
|
||||
// TODO: Expose from webxr crate
|
||||
None
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrpose-emulatedposition>
|
||||
fn EmulatedPosition(&self) -> bool {
|
||||
// There are currently no instances in which we would need to rely
|
||||
// on emulation for reporting pose, so return false.
|
||||
false
|
||||
}
|
||||
}
|
12
components/script/dom/webxr/xrprojectionlayer.rs
Normal file
12
components/script/dom/webxr/xrprojectionlayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRProjectionLayer {
|
||||
composition_layer: XRCompositionLayer,
|
||||
}
|
12
components/script/dom/webxr/xrquadlayer.rs
Normal file
12
components/script/dom/webxr/xrquadlayer.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRQuadLayer {
|
||||
composition_layer: XRCompositionLayer,
|
||||
}
|
174
components/script/dom/webxr/xrray.rs
Normal file
174
components/script/dom/webxr/xrray.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{Angle, RigidTransform3D, Rotation3D, Vector3D};
|
||||
use js::rust::HandleObject;
|
||||
use js::typedarray::{Float32, Float32Array};
|
||||
use webxr_api::{ApiSpace, Ray};
|
||||
|
||||
use crate::dom::bindings::buffer_source::HeapBufferSource;
|
||||
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
|
||||
use crate::dom::bindings::codegen::Bindings::XRRayBinding::{XRRayDirectionInit, XRRayMethods};
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::dompointreadonly::DOMPointReadOnly;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRRay {
|
||||
reflector_: Reflector,
|
||||
#[ignore_malloc_size_of = "defined in webxr"]
|
||||
#[no_trace]
|
||||
ray: Ray<ApiSpace>,
|
||||
#[ignore_malloc_size_of = "defined in mozjs"]
|
||||
matrix: HeapBufferSource<Float32>,
|
||||
}
|
||||
|
||||
impl XRRay {
|
||||
fn new_inherited(ray: Ray<ApiSpace>) -> XRRay {
|
||||
XRRay {
|
||||
reflector_: Reflector::new(),
|
||||
ray,
|
||||
matrix: HeapBufferSource::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
ray: Ray<ApiSpace>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRRay> {
|
||||
reflect_dom_object_with_proto(Box::new(XRRay::new_inherited(ray)), global, proto, can_gc)
|
||||
}
|
||||
|
||||
pub fn ray(&self) -> Ray<ApiSpace> {
|
||||
self.ray
|
||||
}
|
||||
}
|
||||
|
||||
impl XRRayMethods<crate::DomTypeHolder> for XRRay {
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray>
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
origin: &DOMPointInit,
|
||||
direction: &XRRayDirectionInit,
|
||||
) -> Fallible<DomRoot<Self>> {
|
||||
if origin.w != 1.0 {
|
||||
return Err(Error::Type("Origin w coordinate must be 1".into()));
|
||||
}
|
||||
if *direction.w != 0.0 {
|
||||
return Err(Error::Type("Direction w coordinate must be 0".into()));
|
||||
}
|
||||
if *direction.x == 0.0 && *direction.y == 0.0 && *direction.z == 0.0 {
|
||||
return Err(Error::Type(
|
||||
"Direction vector cannot have zero length".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let origin = Vector3D::new(origin.x as f32, origin.y as f32, origin.z as f32);
|
||||
let direction = Vector3D::new(
|
||||
*direction.x as f32,
|
||||
*direction.y as f32,
|
||||
*direction.z as f32,
|
||||
)
|
||||
.normalize();
|
||||
|
||||
Ok(Self::new(
|
||||
&window.global(),
|
||||
proto,
|
||||
Ray { origin, direction },
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray-transform>
|
||||
fn Constructor_(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
transform: &XRRigidTransform,
|
||||
) -> Fallible<DomRoot<Self>> {
|
||||
let transform = transform.transform();
|
||||
let origin = transform.translation;
|
||||
let direction = transform
|
||||
.rotation
|
||||
.transform_vector3d(Vector3D::new(0., 0., -1.));
|
||||
|
||||
Ok(Self::new(
|
||||
&window.global(),
|
||||
proto,
|
||||
Ray { origin, direction },
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrray-origin>
|
||||
fn Origin(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
|
||||
DOMPointReadOnly::new(
|
||||
&self.global(),
|
||||
self.ray.origin.x as f64,
|
||||
self.ray.origin.y as f64,
|
||||
self.ray.origin.z as f64,
|
||||
1.,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrray-direction>
|
||||
fn Direction(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
|
||||
DOMPointReadOnly::new(
|
||||
&self.global(),
|
||||
self.ray.direction.x as f64,
|
||||
self.ray.direction.y as f64,
|
||||
self.ray.direction.z as f64,
|
||||
0.,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/hit-test/#dom-xrray-matrix>
|
||||
fn Matrix(&self, _cx: JSContext) -> Float32Array {
|
||||
// https://immersive-web.github.io/hit-test/#xrray-obtain-the-matrix
|
||||
if !self.matrix.is_initialized() {
|
||||
// Step 1
|
||||
let z = Vector3D::new(0., 0., -1.);
|
||||
// Step 2
|
||||
let axis = z.cross(self.ray.direction);
|
||||
// Step 3
|
||||
let cos_angle = z.dot(self.ray.direction);
|
||||
// Step 4
|
||||
let rotation = if cos_angle > -1. && cos_angle < 1. {
|
||||
Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
|
||||
} else if cos_angle == -1. {
|
||||
let axis = Vector3D::new(1., 0., 0.);
|
||||
Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
|
||||
} else {
|
||||
Rotation3D::identity()
|
||||
};
|
||||
// Step 5
|
||||
let translation = self.ray.origin;
|
||||
// Step 6
|
||||
// According to the spec all matrices are column-major,
|
||||
// however euclid uses row vectors so we use .to_array()
|
||||
let arr = RigidTransform3D::new(rotation, translation)
|
||||
.to_transform()
|
||||
.to_array();
|
||||
self.matrix
|
||||
.set_data(_cx, &arr)
|
||||
.expect("Failed to set matrix data on XRRAy.")
|
||||
}
|
||||
|
||||
self.matrix
|
||||
.get_buffer()
|
||||
.expect("Failed to get matrix from XRRay.")
|
||||
}
|
||||
}
|
153
components/script/dom/webxr/xrreferencespace.rs
Normal file
153
components/script/dom/webxr/xrreferencespace.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{Point2D, RigidTransform3D};
|
||||
use webxr_api::{self, Floor, Frame, Space};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::{
|
||||
XRReferenceSpaceMethods, XRReferenceSpaceType,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::{cast_transform, ApiPose, BaseTransform, XRSession};
|
||||
use crate::dom::xrspace::XRSpace;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRReferenceSpace {
|
||||
xrspace: XRSpace,
|
||||
offset: Dom<XRRigidTransform>,
|
||||
ty: XRReferenceSpaceType,
|
||||
}
|
||||
|
||||
impl XRReferenceSpace {
|
||||
pub fn new_inherited(
|
||||
session: &XRSession,
|
||||
offset: &XRRigidTransform,
|
||||
ty: XRReferenceSpaceType,
|
||||
) -> XRReferenceSpace {
|
||||
XRReferenceSpace {
|
||||
xrspace: XRSpace::new_inherited(session),
|
||||
offset: Dom::from_ref(offset),
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
ty: XRReferenceSpaceType,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRReferenceSpace> {
|
||||
let offset = XRRigidTransform::identity(global, can_gc);
|
||||
Self::new_offset(global, session, ty, &offset)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new_offset(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
ty: XRReferenceSpaceType,
|
||||
offset: &XRRigidTransform,
|
||||
) -> DomRoot<XRReferenceSpace> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRReferenceSpace::new_inherited(session, offset, ty)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn space(&self) -> Space {
|
||||
let base = match self.ty {
|
||||
XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local,
|
||||
XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer,
|
||||
XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor,
|
||||
XRReferenceSpaceType::Bounded_floor => webxr_api::BaseSpace::BoundedFloor,
|
||||
_ => panic!("unsupported reference space found"),
|
||||
};
|
||||
let offset = self.offset.transform();
|
||||
Space { base, offset }
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> XRReferenceSpaceType {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl XRReferenceSpaceMethods<crate::DomTypeHolder> for XRReferenceSpace {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrreferencespace-getoffsetreferencespace>
|
||||
fn GetOffsetReferenceSpace(&self, new: &XRRigidTransform, can_gc: CanGc) -> DomRoot<Self> {
|
||||
let offset = new.transform().then(&self.offset.transform());
|
||||
let offset = XRRigidTransform::new(&self.global(), offset, can_gc);
|
||||
Self::new_offset(
|
||||
&self.global(),
|
||||
self.upcast::<XRSpace>().session(),
|
||||
self.ty,
|
||||
&offset,
|
||||
)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webxr/#dom-xrreferencespace-onreset
|
||||
event_handler!(reset, GetOnreset, SetOnreset);
|
||||
}
|
||||
|
||||
impl XRReferenceSpace {
|
||||
/// Get a transform that can be used to locate the base space
|
||||
///
|
||||
/// This is equivalent to `get_pose(self).inverse()` (in column vector notation),
|
||||
/// but with better types
|
||||
pub fn get_base_transform(&self, base_pose: &Frame) -> Option<BaseTransform> {
|
||||
let pose = self.get_pose(base_pose)?;
|
||||
Some(pose.inverse().cast_unit())
|
||||
}
|
||||
|
||||
/// Gets pose represented by this space
|
||||
///
|
||||
/// The reference origin used is common between all
|
||||
/// get_pose calls for spaces from the same device, so this can be used to compare
|
||||
/// with other spaces
|
||||
pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
|
||||
let pose = self.get_unoffset_pose(base_pose)?;
|
||||
let offset = self.offset.transform();
|
||||
// pose is a transform from the unoffset space to native space,
|
||||
// offset is a transform from offset space to unoffset space,
|
||||
// we want a transform from unoffset space to native space,
|
||||
// which is pose * offset in column vector notation
|
||||
Some(offset.then(&pose))
|
||||
}
|
||||
|
||||
/// Gets pose represented by this space
|
||||
///
|
||||
/// Does not apply originOffset, use get_viewer_pose instead if you need it
|
||||
pub fn get_unoffset_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
|
||||
match self.ty {
|
||||
XRReferenceSpaceType::Local => {
|
||||
// The eye-level pose is basically whatever the headset pose was at t=0, which
|
||||
// for most devices is (0, 0, 0)
|
||||
Some(RigidTransform3D::identity())
|
||||
},
|
||||
XRReferenceSpaceType::Local_floor | XRReferenceSpaceType::Bounded_floor => {
|
||||
let native_to_floor = self
|
||||
.upcast::<XRSpace>()
|
||||
.session()
|
||||
.with_session(|s| s.floor_transform())?;
|
||||
Some(cast_transform(native_to_floor.inverse()))
|
||||
},
|
||||
XRReferenceSpaceType::Viewer => {
|
||||
Some(cast_transform(base_pose.pose.as_ref()?.transform))
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
|
||||
self.upcast::<XRSpace>()
|
||||
.session()
|
||||
.with_session(|s| s.reference_space_bounds())
|
||||
}
|
||||
}
|
121
components/script/dom/webxr/xrreferencespaceevent.rs
Normal file
121
components/script/dom/webxr/xrreferencespaceevent.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceEventBinding::{
|
||||
XRReferenceSpaceEventInit, XRReferenceSpaceEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrreferencespace::XRReferenceSpace;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRReferenceSpaceEvent {
|
||||
event: Event,
|
||||
space: Dom<XRReferenceSpace>,
|
||||
transform: Option<Dom<XRRigidTransform>>,
|
||||
}
|
||||
|
||||
impl XRReferenceSpaceEvent {
|
||||
#[allow(crown::unrooted_must_root)]
|
||||
fn new_inherited(
|
||||
space: &XRReferenceSpace,
|
||||
transform: Option<&XRRigidTransform>,
|
||||
) -> XRReferenceSpaceEvent {
|
||||
XRReferenceSpaceEvent {
|
||||
event: Event::new_inherited(),
|
||||
space: Dom::from_ref(space),
|
||||
transform: transform.map(Dom::from_ref),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
space: &XRReferenceSpace,
|
||||
transform: Option<&XRRigidTransform>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRReferenceSpaceEvent> {
|
||||
Self::new_with_proto(
|
||||
global, None, type_, bubbles, cancelable, space, transform, can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
space: &XRReferenceSpace,
|
||||
transform: Option<&XRRigidTransform>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRReferenceSpaceEvent> {
|
||||
let trackevent = reflect_dom_object_with_proto(
|
||||
Box::new(XRReferenceSpaceEvent::new_inherited(space, transform)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
);
|
||||
{
|
||||
let event = trackevent.upcast::<Event>();
|
||||
event.init_event(type_, bubbles, cancelable);
|
||||
}
|
||||
trackevent
|
||||
}
|
||||
}
|
||||
|
||||
impl XRReferenceSpaceEventMethods<crate::DomTypeHolder> for XRReferenceSpaceEvent {
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceevent-xrreferencespaceevent>
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &XRReferenceSpaceEventInit,
|
||||
) -> Fallible<DomRoot<XRReferenceSpaceEvent>> {
|
||||
Ok(XRReferenceSpaceEvent::new_with_proto(
|
||||
&window.global(),
|
||||
proto,
|
||||
Atom::from(type_),
|
||||
init.parent.bubbles,
|
||||
init.parent.cancelable,
|
||||
&init.referenceSpace,
|
||||
init.transform.as_deref(),
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceeventinit-session>
|
||||
fn ReferenceSpace(&self) -> DomRoot<XRReferenceSpace> {
|
||||
DomRoot::from_ref(&*self.space)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceevent-transform>
|
||||
fn GetTransform(&self) -> Option<DomRoot<XRRigidTransform>> {
|
||||
self.transform
|
||||
.as_ref()
|
||||
.map(|transform| DomRoot::from_ref(&**transform))
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-istrusted>
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
155
components/script/dom/webxr/xrrenderstate.rs
Normal file
155
components/script/dom/webxr/xrrenderstate.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::MutableHandleValue;
|
||||
use webxr_api::SubImages;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::utils::to_frozen_array;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrlayer::XRLayer;
|
||||
use crate::dom::xrwebgllayer::XRWebGLLayer;
|
||||
use crate::script_runtime::JSContext;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRRenderState {
|
||||
reflector_: Reflector,
|
||||
depth_near: Cell<f64>,
|
||||
depth_far: Cell<f64>,
|
||||
inline_vertical_fov: Cell<Option<f64>>,
|
||||
base_layer: MutNullableDom<XRWebGLLayer>,
|
||||
layers: DomRefCell<Vec<Dom<XRLayer>>>,
|
||||
}
|
||||
|
||||
impl XRRenderState {
|
||||
pub fn new_inherited(
|
||||
depth_near: f64,
|
||||
depth_far: f64,
|
||||
inline_vertical_fov: Option<f64>,
|
||||
layer: Option<&XRWebGLLayer>,
|
||||
layers: Vec<&XRLayer>,
|
||||
) -> XRRenderState {
|
||||
debug_assert!(layer.is_none() || layers.is_empty());
|
||||
XRRenderState {
|
||||
reflector_: Reflector::new(),
|
||||
depth_near: Cell::new(depth_near),
|
||||
depth_far: Cell::new(depth_far),
|
||||
inline_vertical_fov: Cell::new(inline_vertical_fov),
|
||||
base_layer: MutNullableDom::new(layer),
|
||||
layers: DomRefCell::new(layers.into_iter().map(Dom::from_ref).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
depth_near: f64,
|
||||
depth_far: f64,
|
||||
inline_vertical_fov: Option<f64>,
|
||||
layer: Option<&XRWebGLLayer>,
|
||||
layers: Vec<&XRLayer>,
|
||||
) -> DomRoot<XRRenderState> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRRenderState::new_inherited(
|
||||
depth_near,
|
||||
depth_far,
|
||||
inline_vertical_fov,
|
||||
layer,
|
||||
layers,
|
||||
)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn clone_object(&self) -> DomRoot<Self> {
|
||||
XRRenderState::new(
|
||||
&self.global(),
|
||||
self.depth_near.get(),
|
||||
self.depth_far.get(),
|
||||
self.inline_vertical_fov.get(),
|
||||
self.base_layer.get().as_deref(),
|
||||
self.layers.borrow().iter().map(|x| &**x).collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_depth_near(&self, depth: f64) {
|
||||
self.depth_near.set(depth)
|
||||
}
|
||||
pub fn set_depth_far(&self, depth: f64) {
|
||||
self.depth_far.set(depth)
|
||||
}
|
||||
pub fn set_inline_vertical_fov(&self, fov: f64) {
|
||||
debug_assert!(self.inline_vertical_fov.get().is_some());
|
||||
self.inline_vertical_fov.set(Some(fov))
|
||||
}
|
||||
pub fn set_base_layer(&self, layer: Option<&XRWebGLLayer>) {
|
||||
self.base_layer.set(layer)
|
||||
}
|
||||
pub fn set_layers(&self, layers: Vec<&XRLayer>) {
|
||||
*self.layers.borrow_mut() = layers.into_iter().map(Dom::from_ref).collect();
|
||||
}
|
||||
pub fn with_layers<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&[Dom<XRLayer>]) -> R,
|
||||
{
|
||||
let layers = self.layers.borrow();
|
||||
f(&layers)
|
||||
}
|
||||
pub fn has_sub_images(&self, sub_images: &[SubImages]) -> bool {
|
||||
if let Some(base_layer) = self.base_layer.get() {
|
||||
match sub_images.len() {
|
||||
// For inline sessions, there may be a base layer, but it won't have a framebuffer
|
||||
0 => base_layer.layer_id().is_none(),
|
||||
// For immersive sessions, the base layer will have a framebuffer,
|
||||
// so we make sure the layer id's match up
|
||||
1 => base_layer.layer_id() == Some(sub_images[0].layer_id),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
// The layers API is only for immersive sessions
|
||||
let layers = self.layers.borrow();
|
||||
sub_images.len() == layers.len() &&
|
||||
sub_images
|
||||
.iter()
|
||||
.zip(layers.iter())
|
||||
.all(|(sub_image, layer)| Some(sub_image.layer_id) == layer.layer_id())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XRRenderStateMethods<crate::DomTypeHolder> for XRRenderState {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthnear>
|
||||
fn DepthNear(&self) -> Finite<f64> {
|
||||
Finite::wrap(self.depth_near.get())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthfar>
|
||||
fn DepthFar(&self) -> Finite<f64> {
|
||||
Finite::wrap(self.depth_far.get())
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-inlineverticalfieldofview>
|
||||
fn GetInlineVerticalFieldOfView(&self) -> Option<Finite<f64>> {
|
||||
self.inline_vertical_fov.get().map(Finite::wrap)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-baselayer>
|
||||
fn GetBaseLayer(&self) -> Option<DomRoot<XRWebGLLayer>> {
|
||||
self.base_layer.get()
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrrenderstate-layers>
|
||||
fn Layers(&self, cx: JSContext, retval: MutableHandleValue) {
|
||||
// TODO: cache this array?
|
||||
let layers = self.layers.borrow();
|
||||
let layers: Vec<&XRLayer> = layers.iter().map(|x| &**x).collect();
|
||||
to_frozen_array(&layers[..], cx, retval)
|
||||
}
|
||||
}
|
188
components/script/dom/webxr/xrrigidtransform.rs
Normal file
188
components/script/dom/webxr/xrrigidtransform.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{RigidTransform3D, Rotation3D, Vector3D};
|
||||
use js::rust::HandleObject;
|
||||
use js::typedarray::{Float32, Float32Array};
|
||||
|
||||
use crate::dom::bindings::buffer_source::HeapBufferSource;
|
||||
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
|
||||
use crate::dom::bindings::codegen::Bindings::XRRigidTransformBinding::XRRigidTransformMethods;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::dompointreadonly::DOMPointReadOnly;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrsession::ApiRigidTransform;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRRigidTransform {
|
||||
reflector_: Reflector,
|
||||
position: MutNullableDom<DOMPointReadOnly>,
|
||||
orientation: MutNullableDom<DOMPointReadOnly>,
|
||||
#[ignore_malloc_size_of = "defined in euclid"]
|
||||
#[no_trace]
|
||||
transform: ApiRigidTransform,
|
||||
inverse: MutNullableDom<XRRigidTransform>,
|
||||
#[ignore_malloc_size_of = "defined in mozjs"]
|
||||
matrix: HeapBufferSource<Float32>,
|
||||
}
|
||||
|
||||
impl XRRigidTransform {
|
||||
fn new_inherited(transform: ApiRigidTransform) -> XRRigidTransform {
|
||||
XRRigidTransform {
|
||||
reflector_: Reflector::new(),
|
||||
position: MutNullableDom::default(),
|
||||
orientation: MutNullableDom::default(),
|
||||
transform,
|
||||
inverse: MutNullableDom::default(),
|
||||
matrix: HeapBufferSource::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
transform: ApiRigidTransform,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRRigidTransform> {
|
||||
Self::new_with_proto(global, None, transform, can_gc)
|
||||
}
|
||||
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
transform: ApiRigidTransform,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRRigidTransform> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(XRRigidTransform::new_inherited(transform)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn identity(window: &GlobalScope, can_gc: CanGc) -> DomRoot<XRRigidTransform> {
|
||||
let transform = RigidTransform3D::identity();
|
||||
XRRigidTransform::new(window, transform, can_gc)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRRigidTransformMethods<crate::DomTypeHolder> for XRRigidTransform {
|
||||
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-xrrigidtransform
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
position: &DOMPointInit,
|
||||
orientation: &DOMPointInit,
|
||||
) -> Fallible<DomRoot<Self>> {
|
||||
if position.w != 1.0 {
|
||||
return Err(Error::Type(format!(
|
||||
"XRRigidTransform must be constructed with a position that has a w value of of 1.0, not {}",
|
||||
position.w
|
||||
)));
|
||||
}
|
||||
|
||||
if !position.x.is_finite() ||
|
||||
!position.y.is_finite() ||
|
||||
!position.z.is_finite() ||
|
||||
!position.w.is_finite()
|
||||
{
|
||||
return Err(Error::Type(
|
||||
"Position must not contain non-finite values".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if !orientation.x.is_finite() ||
|
||||
!orientation.y.is_finite() ||
|
||||
!orientation.z.is_finite() ||
|
||||
!orientation.w.is_finite()
|
||||
{
|
||||
return Err(Error::Type(
|
||||
"Orientation must not contain non-finite values".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let translate = Vector3D::new(position.x as f32, position.y as f32, position.z as f32);
|
||||
let rotate = Rotation3D::unit_quaternion(
|
||||
orientation.x as f32,
|
||||
orientation.y as f32,
|
||||
orientation.z as f32,
|
||||
orientation.w as f32,
|
||||
);
|
||||
|
||||
if !rotate.i.is_finite() {
|
||||
// if quaternion has zero norm, we'll get an infinite or NaN
|
||||
// value for each element. This is preferable to checking for zero.
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
let transform = RigidTransform3D::new(rotate, translate);
|
||||
Ok(XRRigidTransform::new_with_proto(
|
||||
&window.global(),
|
||||
proto,
|
||||
transform,
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-position
|
||||
fn Position(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
|
||||
self.position.or_init(|| {
|
||||
let t = &self.transform.translation;
|
||||
DOMPointReadOnly::new(
|
||||
&self.global(),
|
||||
t.x.into(),
|
||||
t.y.into(),
|
||||
t.z.into(),
|
||||
1.0,
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
}
|
||||
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-orientation
|
||||
fn Orientation(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
|
||||
self.orientation.or_init(|| {
|
||||
let r = &self.transform.rotation;
|
||||
DOMPointReadOnly::new(
|
||||
&self.global(),
|
||||
r.i.into(),
|
||||
r.j.into(),
|
||||
r.k.into(),
|
||||
r.r.into(),
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
}
|
||||
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-inverse
|
||||
fn Inverse(&self, can_gc: CanGc) -> DomRoot<XRRigidTransform> {
|
||||
self.inverse.or_init(|| {
|
||||
let transform = XRRigidTransform::new(&self.global(), self.transform.inverse(), can_gc);
|
||||
transform.inverse.set(Some(self));
|
||||
transform
|
||||
})
|
||||
}
|
||||
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-matrix
|
||||
fn Matrix(&self, _cx: JSContext) -> Float32Array {
|
||||
if !self.matrix.is_initialized() {
|
||||
self.matrix
|
||||
.set_data(_cx, &self.transform.to_transform().to_array())
|
||||
.expect("Failed to set on data on transform's internal matrix.")
|
||||
}
|
||||
|
||||
self.matrix
|
||||
.get_buffer()
|
||||
.expect("Failed to get transform's internal matrix.")
|
||||
}
|
||||
}
|
||||
|
||||
impl XRRigidTransform {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrpose-transform>
|
||||
pub fn transform(&self) -> ApiRigidTransform {
|
||||
self.transform
|
||||
}
|
||||
}
|
1111
components/script/dom/webxr/xrsession.rs
Normal file
1111
components/script/dom/webxr/xrsession.rs
Normal file
File diff suppressed because it is too large
Load diff
100
components/script/dom/webxr/xrsessionevent.rs
Normal file
100
components/script/dom/webxr/xrsessionevent.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRSessionEventBinding::{self, XRSessionEventMethods};
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::Event;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRSessionEvent {
|
||||
event: Event,
|
||||
session: Dom<XRSession>,
|
||||
}
|
||||
|
||||
impl XRSessionEvent {
|
||||
#[allow(crown::unrooted_must_root)]
|
||||
fn new_inherited(session: &XRSession) -> XRSessionEvent {
|
||||
XRSessionEvent {
|
||||
event: Event::new_inherited(),
|
||||
session: Dom::from_ref(session),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
session: &XRSession,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRSessionEvent> {
|
||||
Self::new_with_proto(global, None, type_, bubbles, cancelable, session, can_gc)
|
||||
}
|
||||
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
bubbles: bool,
|
||||
cancelable: bool,
|
||||
session: &XRSession,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRSessionEvent> {
|
||||
let trackevent = reflect_dom_object_with_proto(
|
||||
Box::new(XRSessionEvent::new_inherited(session)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
);
|
||||
{
|
||||
let event = trackevent.upcast::<Event>();
|
||||
event.init_event(type_, bubbles, cancelable);
|
||||
}
|
||||
trackevent
|
||||
}
|
||||
}
|
||||
|
||||
impl XRSessionEventMethods<crate::DomTypeHolder> for XRSessionEvent {
|
||||
// https://immersive-web.github.io/webxr/#dom-xrsessionevent-xrsessionevent
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &XRSessionEventBinding::XRSessionEventInit,
|
||||
) -> Fallible<DomRoot<XRSessionEvent>> {
|
||||
Ok(XRSessionEvent::new_with_proto(
|
||||
&window.global(),
|
||||
proto,
|
||||
Atom::from(type_),
|
||||
init.parent.bubbles,
|
||||
init.parent.cancelable,
|
||||
&init.session,
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
// https://immersive-web.github.io/webxr/#dom-xrsessioneventinit-session
|
||||
fn Session(&self) -> DomRoot<XRSession> {
|
||||
DomRoot::from_ref(&*self.session)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
120
components/script/dom/webxr/xrspace.rs
Normal file
120
components/script/dom/webxr/xrspace.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::RigidTransform3D;
|
||||
use webxr_api::{BaseSpace, Frame, Space};
|
||||
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrinputsource::XRInputSource;
|
||||
use crate::dom::xrjointspace::XRJointSpace;
|
||||
use crate::dom::xrreferencespace::XRReferenceSpace;
|
||||
use crate::dom::xrsession::{cast_transform, ApiPose, XRSession};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRSpace {
|
||||
eventtarget: EventTarget,
|
||||
session: Dom<XRSession>,
|
||||
input_source: MutNullableDom<XRInputSource>,
|
||||
/// If we're an input space, are we an aim space or a grip space?
|
||||
is_grip_space: bool,
|
||||
}
|
||||
|
||||
impl XRSpace {
|
||||
pub fn new_inherited(session: &XRSession) -> XRSpace {
|
||||
XRSpace {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
session: Dom::from_ref(session),
|
||||
input_source: Default::default(),
|
||||
is_grip_space: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_inputspace_inner(
|
||||
session: &XRSession,
|
||||
input: &XRInputSource,
|
||||
is_grip_space: bool,
|
||||
) -> XRSpace {
|
||||
XRSpace {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
session: Dom::from_ref(session),
|
||||
input_source: MutNullableDom::new(Some(input)),
|
||||
is_grip_space,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_inputspace(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
input: &XRInputSource,
|
||||
is_grip_space: bool,
|
||||
) -> DomRoot<XRSpace> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRSpace::new_inputspace_inner(session, input, is_grip_space)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn space(&self) -> Space {
|
||||
if let Some(rs) = self.downcast::<XRReferenceSpace>() {
|
||||
rs.space()
|
||||
} else if let Some(j) = self.downcast::<XRJointSpace>() {
|
||||
j.space()
|
||||
} else if let Some(source) = self.input_source.get() {
|
||||
let base = if self.is_grip_space {
|
||||
BaseSpace::Grip(source.id())
|
||||
} else {
|
||||
BaseSpace::TargetRay(source.id())
|
||||
};
|
||||
Space {
|
||||
base,
|
||||
offset: RigidTransform3D::identity(),
|
||||
}
|
||||
} else {
|
||||
panic!("invalid space found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XRSpace {
|
||||
/// Gets pose represented by this space
|
||||
///
|
||||
/// The reference origin used is common between all
|
||||
/// get_pose calls for spaces from the same device, so this can be used to compare
|
||||
/// with other spaces
|
||||
pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
|
||||
if let Some(reference) = self.downcast::<XRReferenceSpace>() {
|
||||
reference.get_pose(base_pose)
|
||||
} else if let Some(joint) = self.downcast::<XRJointSpace>() {
|
||||
joint.get_pose(base_pose)
|
||||
} else if let Some(source) = self.input_source.get() {
|
||||
// XXXManishearth we should be able to request frame information
|
||||
// for inputs when necessary instead of always loading it
|
||||
//
|
||||
// Also, the below code is quadratic, so this API may need an overhaul anyway
|
||||
let id = source.id();
|
||||
// XXXManishearth once we have dynamic inputs we'll need to handle this better
|
||||
let frame = base_pose
|
||||
.inputs
|
||||
.iter()
|
||||
.find(|i| i.id == id)
|
||||
.expect("no input found");
|
||||
if self.is_grip_space {
|
||||
frame.grip_origin.map(cast_transform)
|
||||
} else {
|
||||
frame.target_ray_origin.map(cast_transform)
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session(&self) -> &XRSession {
|
||||
&self.session
|
||||
}
|
||||
}
|
23
components/script/dom/webxr/xrsubimage.rs
Normal file
23
components/script/dom/webxr/xrsubimage.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRSubImageBinding::XRSubImage_Binding::XRSubImageMethods;
|
||||
use crate::dom::bindings::reflector::Reflector;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::xrviewport::XRViewport;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRSubImage {
|
||||
reflector: Reflector,
|
||||
viewport: Dom<XRViewport>,
|
||||
}
|
||||
|
||||
impl XRSubImageMethods<crate::DomTypeHolder> for XRSubImage {
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrsubimage-viewport>
|
||||
fn Viewport(&self) -> DomRoot<XRViewport> {
|
||||
DomRoot::from_ref(&self.viewport)
|
||||
}
|
||||
}
|
334
components/script/dom/webxr/xrsystem.rs
Normal file
334
components/script/dom/webxr/xrsystem.rs
Normal file
|
@ -0,0 +1,334 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use base::id::PipelineId;
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::{self as ipc_crate, IpcReceiver};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use profile_traits::ipc;
|
||||
use servo_config::pref;
|
||||
use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
|
||||
XRSessionInit, XRSessionMode, XRSystemMethods,
|
||||
};
|
||||
use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible};
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::trace::RootedTraceableBox;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::gamepad::Gamepad;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::dom::xrtest::XRTest;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::CanGc;
|
||||
use crate::script_thread::ScriptThread;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRSystem {
|
||||
eventtarget: EventTarget,
|
||||
gamepads: DomRefCell<Vec<Dom<Gamepad>>>,
|
||||
pending_immersive_session: Cell<bool>,
|
||||
active_immersive_session: MutNullableDom<XRSession>,
|
||||
active_inline_sessions: DomRefCell<Vec<Dom<XRSession>>>,
|
||||
test: MutNullableDom<XRTest>,
|
||||
#[no_trace]
|
||||
pipeline: PipelineId,
|
||||
}
|
||||
|
||||
impl XRSystem {
|
||||
fn new_inherited(pipeline: PipelineId) -> XRSystem {
|
||||
XRSystem {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
gamepads: DomRefCell::new(Vec::new()),
|
||||
pending_immersive_session: Cell::new(false),
|
||||
active_immersive_session: Default::default(),
|
||||
active_inline_sessions: DomRefCell::new(Vec::new()),
|
||||
test: Default::default(),
|
||||
pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(window: &Window) -> DomRoot<XRSystem> {
|
||||
reflect_dom_object(
|
||||
Box::new(XRSystem::new_inherited(window.pipeline_id())),
|
||||
window,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pending_or_active_session(&self) -> bool {
|
||||
self.pending_immersive_session.get() || self.active_immersive_session.get().is_some()
|
||||
}
|
||||
|
||||
pub fn set_pending(&self) {
|
||||
self.pending_immersive_session.set(true)
|
||||
}
|
||||
|
||||
pub fn set_active_immersive_session(&self, session: &XRSession) {
|
||||
// XXXManishearth when we support non-immersive (inline) sessions we should
|
||||
// ensure they never reach these codepaths
|
||||
self.pending_immersive_session.set(false);
|
||||
self.active_immersive_session.set(Some(session))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#ref-for-eventdef-xrsession-end>
|
||||
pub fn end_session(&self, session: &XRSession) {
|
||||
// Step 3
|
||||
if let Some(active) = self.active_immersive_session.get() {
|
||||
if Dom::from_ref(&*active) == Dom::from_ref(session) {
|
||||
self.active_immersive_session.set(None);
|
||||
// Dirty the canvas, since it has been skipping this step whilst in immersive
|
||||
// mode
|
||||
session.dirty_layers();
|
||||
}
|
||||
}
|
||||
self.active_inline_sessions
|
||||
.borrow_mut()
|
||||
.retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<XRSessionMode> for SessionMode {
|
||||
fn from(mode: XRSessionMode) -> SessionMode {
|
||||
match mode {
|
||||
XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR,
|
||||
XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR,
|
||||
XRSessionMode::Inline => SessionMode::Inline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xr-issessionsupported>
|
||||
fn IsSessionSupported(&self, mode: XRSessionMode, can_gc: CanGc) -> Rc<Promise> {
|
||||
// XXXManishearth this should select an XR device first
|
||||
let promise = Promise::new(&self.global(), can_gc);
|
||||
let mut trusted = Some(TrustedPromise::new(promise.clone()));
|
||||
let global = self.global();
|
||||
let window = global.as_window();
|
||||
let (task_source, canceller) = window
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source_with_canceller();
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |message| {
|
||||
// router doesn't know this is only called once
|
||||
let trusted = if let Some(trusted) = trusted.take() {
|
||||
trusted
|
||||
} else {
|
||||
error!("supportsSession callback called twice!");
|
||||
return;
|
||||
};
|
||||
let message: Result<(), webxr_api::Error> = if let Ok(message) = message {
|
||||
message
|
||||
} else {
|
||||
error!("supportsSession callback given incorrect payload");
|
||||
return;
|
||||
};
|
||||
if let Ok(()) = message {
|
||||
let _ =
|
||||
task_source.queue_with_canceller(trusted.resolve_task(true), &canceller);
|
||||
} else {
|
||||
let _ =
|
||||
task_source.queue_with_canceller(trusted.resolve_task(false), &canceller);
|
||||
};
|
||||
}),
|
||||
);
|
||||
if let Some(mut r) = window.webxr_registry() {
|
||||
r.supports_session(mode.into(), sender);
|
||||
}
|
||||
|
||||
promise
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xr-requestsession>
|
||||
#[allow(unsafe_code)]
|
||||
fn RequestSession(
|
||||
&self,
|
||||
mode: XRSessionMode,
|
||||
init: RootedTraceableBox<XRSessionInit>,
|
||||
comp: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
let global = self.global();
|
||||
let window = global.as_window();
|
||||
let promise = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
if mode != XRSessionMode::Inline {
|
||||
if !ScriptThread::is_user_interacting() {
|
||||
if pref!(dom.webxr.unsafe_assume_user_intent) {
|
||||
warn!("The dom.webxr.unsafe-assume-user-intent preference assumes user intent to enter WebXR.");
|
||||
} else {
|
||||
promise.reject_error(Error::Security);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
if self.pending_or_active_session() {
|
||||
promise.reject_error(Error::InvalidState);
|
||||
return promise;
|
||||
}
|
||||
|
||||
self.set_pending();
|
||||
}
|
||||
|
||||
let mut required_features = vec![];
|
||||
let mut optional_features = vec![];
|
||||
let cx = GlobalScope::get_cx();
|
||||
|
||||
if let Some(ref r) = init.requiredFeatures {
|
||||
for feature in r {
|
||||
unsafe {
|
||||
if let Ok(ConversionResult::Success(s)) =
|
||||
String::from_jsval(*cx, feature.handle(), ())
|
||||
{
|
||||
required_features.push(s)
|
||||
} else {
|
||||
warn!("Unable to convert required feature to string");
|
||||
if mode != XRSessionMode::Inline {
|
||||
self.pending_immersive_session.set(false);
|
||||
}
|
||||
promise.reject_error(Error::NotSupported);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref o) = init.optionalFeatures {
|
||||
for feature in o {
|
||||
unsafe {
|
||||
if let Ok(ConversionResult::Success(s)) =
|
||||
String::from_jsval(*cx, feature.handle(), ())
|
||||
{
|
||||
optional_features.push(s)
|
||||
} else {
|
||||
warn!("Unable to convert optional feature to string");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !required_features.contains(&"viewer".to_string()) {
|
||||
required_features.push("viewer".to_string());
|
||||
}
|
||||
|
||||
if !required_features.contains(&"local".to_string()) && mode != XRSessionMode::Inline {
|
||||
required_features.push("local".to_string());
|
||||
}
|
||||
|
||||
let init = SessionInit {
|
||||
required_features,
|
||||
optional_features,
|
||||
first_person_observer_view: pref!(dom.webxr.first_person_observer_view),
|
||||
};
|
||||
|
||||
let mut trusted = Some(TrustedPromise::new(promise.clone()));
|
||||
let this = Trusted::new(self);
|
||||
let (task_source, canceller) = window
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source_with_canceller();
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap();
|
||||
let mut frame_receiver = Some(frame_receiver);
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |message| {
|
||||
// router doesn't know this is only called once
|
||||
let trusted = trusted.take().unwrap();
|
||||
let this = this.clone();
|
||||
let frame_receiver = frame_receiver.take().unwrap();
|
||||
let message: Result<Session, webxr_api::Error> = if let Ok(message) = message {
|
||||
message
|
||||
} else {
|
||||
error!("requestSession callback given incorrect payload");
|
||||
return;
|
||||
};
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(request_session: move || {
|
||||
this.root().session_obtained(message, trusted.root(), mode, frame_receiver);
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
}),
|
||||
);
|
||||
if let Some(mut r) = window.webxr_registry() {
|
||||
r.request_session(mode.into(), init, sender, frame_sender);
|
||||
}
|
||||
promise
|
||||
}
|
||||
|
||||
// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md
|
||||
fn Test(&self) -> DomRoot<XRTest> {
|
||||
self.test.or_init(|| XRTest::new(&self.global()))
|
||||
}
|
||||
}
|
||||
|
||||
impl XRSystem {
|
||||
fn session_obtained(
|
||||
&self,
|
||||
response: Result<Session, XRError>,
|
||||
promise: Rc<Promise>,
|
||||
mode: XRSessionMode,
|
||||
frame_receiver: IpcReceiver<Frame>,
|
||||
) {
|
||||
let session = match response {
|
||||
Ok(session) => session,
|
||||
Err(e) => {
|
||||
warn!("Error requesting XR session: {:?}", e);
|
||||
if mode != XRSessionMode::Inline {
|
||||
self.pending_immersive_session.set(false);
|
||||
}
|
||||
promise.reject_error(Error::NotSupported);
|
||||
return;
|
||||
},
|
||||
};
|
||||
let session = XRSession::new(&self.global(), session, mode, frame_receiver);
|
||||
if mode == XRSessionMode::Inline {
|
||||
self.active_inline_sessions
|
||||
.borrow_mut()
|
||||
.push(Dom::from_ref(&*session));
|
||||
} else {
|
||||
self.set_active_immersive_session(&session);
|
||||
}
|
||||
promise.resolve_native(&session);
|
||||
// https://github.com/immersive-web/webxr/issues/961
|
||||
// This must be called _after_ the promise is resolved
|
||||
session.setup_initial_inputs();
|
||||
}
|
||||
|
||||
// https://github.com/immersive-web/navigation/issues/10
|
||||
pub fn dispatch_sessionavailable(&self) {
|
||||
let xr = Trusted::new(self);
|
||||
let global = self.global();
|
||||
let window = global.as_window();
|
||||
window
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source()
|
||||
.queue(
|
||||
task!(fire_sessionavailable_event: move || {
|
||||
// The sessionavailable event indicates user intent to enter an XR session
|
||||
let xr = xr.root();
|
||||
let interacting = ScriptThread::is_user_interacting();
|
||||
ScriptThread::set_user_interacting(true);
|
||||
xr.upcast::<EventTarget>().fire_bubbling_event(atom!("sessionavailable"), CanGc::note());
|
||||
ScriptThread::set_user_interacting(interacting);
|
||||
}),
|
||||
window.upcast(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
235
components/script/dom/webxr/xrtest.rs
Normal file
235
components/script/dom/webxr/xrtest.rs
Normal file
|
@ -0,0 +1,235 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsval::JSVal;
|
||||
use profile_traits::ipc;
|
||||
use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg};
|
||||
|
||||
use crate::dom::bindings::callback::ExceptionHandling;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
||||
use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
|
||||
use crate::dom::bindings::codegen::Bindings::XRTestBinding::{FakeXRDeviceInit, XRTestMethods};
|
||||
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::fakexrdevice::{get_origin, get_views, get_world, FakeXRDevice};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::CanGc;
|
||||
use crate::script_thread::ScriptThread;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRTest {
|
||||
reflector: Reflector,
|
||||
devices_connected: DomRefCell<Vec<Dom<FakeXRDevice>>>,
|
||||
}
|
||||
|
||||
impl XRTest {
|
||||
pub fn new_inherited() -> XRTest {
|
||||
XRTest {
|
||||
reflector: Reflector::new(),
|
||||
devices_connected: DomRefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<XRTest> {
|
||||
reflect_dom_object(Box::new(XRTest::new_inherited()), global)
|
||||
}
|
||||
|
||||
fn device_obtained(
|
||||
&self,
|
||||
response: Result<IpcSender<MockDeviceMsg>, XRError>,
|
||||
trusted: TrustedPromise,
|
||||
) {
|
||||
let promise = trusted.root();
|
||||
if let Ok(sender) = response {
|
||||
let device = FakeXRDevice::new(&self.global(), sender);
|
||||
self.devices_connected
|
||||
.borrow_mut()
|
||||
.push(Dom::from_ref(&device));
|
||||
promise.resolve_native(&device);
|
||||
} else {
|
||||
promise.reject_native(&());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XRTestMethods<crate::DomTypeHolder> for XRTest {
|
||||
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
|
||||
#[allow(unsafe_code)]
|
||||
fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit, can_gc: CanGc) -> Rc<Promise> {
|
||||
let global = self.global();
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
let origin = if let Some(ref o) = init.viewerOrigin {
|
||||
match get_origin(o) {
|
||||
Ok(origin) => Some(origin),
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return p;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let floor_origin = if let Some(ref o) = init.floorOrigin {
|
||||
match get_origin(o) {
|
||||
Ok(origin) => Some(origin),
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return p;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let views = match get_views(&init.views) {
|
||||
Ok(views) => views,
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return p;
|
||||
},
|
||||
};
|
||||
|
||||
let supported_features = if let Some(ref s) = init.supportedFeatures {
|
||||
s.iter().cloned().map(String::from).collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let world = if let Some(ref w) = init.world {
|
||||
let w = match get_world(w) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return p;
|
||||
},
|
||||
};
|
||||
Some(w)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (mut supports_inline, mut supports_vr, mut supports_ar) = (false, false, false);
|
||||
|
||||
if let Some(ref modes) = init.supportedModes {
|
||||
for mode in modes {
|
||||
match mode {
|
||||
XRSessionMode::Immersive_vr => supports_vr = true,
|
||||
XRSessionMode::Immersive_ar => supports_ar = true,
|
||||
XRSessionMode::Inline => supports_inline = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let init = MockDeviceInit {
|
||||
viewer_origin: origin,
|
||||
views,
|
||||
supports_inline,
|
||||
supports_vr,
|
||||
supports_ar,
|
||||
floor_origin,
|
||||
supported_features,
|
||||
world,
|
||||
};
|
||||
|
||||
let global = self.global();
|
||||
let window = global.as_window();
|
||||
let this = Trusted::new(self);
|
||||
let mut trusted = Some(TrustedPromise::new(p.clone()));
|
||||
|
||||
let (task_source, canceller) = window
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source_with_canceller();
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |message| {
|
||||
let trusted = trusted
|
||||
.take()
|
||||
.expect("SimulateDeviceConnection callback called twice");
|
||||
let this = this.clone();
|
||||
let message =
|
||||
message.expect("SimulateDeviceConnection callback given incorrect payload");
|
||||
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(request_session: move || {
|
||||
this.root().device_obtained(message, trusted);
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
}),
|
||||
);
|
||||
if let Some(mut r) = window.webxr_registry() {
|
||||
r.simulate_device_connection(init, sender);
|
||||
}
|
||||
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
|
||||
fn SimulateUserActivation(&self, f: Rc<Function>) {
|
||||
ScriptThread::set_user_interacting(true);
|
||||
rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
|
||||
let _ = f.Call__(vec![], value.handle_mut(), ExceptionHandling::Rethrow);
|
||||
ScriptThread::set_user_interacting(false);
|
||||
}
|
||||
|
||||
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
|
||||
fn DisconnectAllDevices(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||
// XXXManishearth implement device disconnection and session ending
|
||||
let global = self.global();
|
||||
let p = Promise::new(&global, can_gc);
|
||||
let mut devices = self.devices_connected.borrow_mut();
|
||||
if devices.is_empty() {
|
||||
p.resolve_native(&());
|
||||
} else {
|
||||
let mut len = devices.len();
|
||||
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
let mut rooted_devices: Vec<_> =
|
||||
devices.iter().map(|x| DomRoot::from_ref(&**x)).collect();
|
||||
devices.clear();
|
||||
|
||||
let mut trusted = Some(TrustedPromise::new(p.clone()));
|
||||
let (task_source, canceller) = global
|
||||
.as_window()
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source_with_canceller();
|
||||
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |_| {
|
||||
len -= 1;
|
||||
if len == 0 {
|
||||
let trusted = trusted
|
||||
.take()
|
||||
.expect("DisconnectAllDevices disconnected more devices than expected");
|
||||
let _ =
|
||||
task_source.queue_with_canceller(trusted.resolve_task(()), &canceller);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for device in rooted_devices.drain(..) {
|
||||
device.disconnect(sender.clone());
|
||||
}
|
||||
};
|
||||
p
|
||||
}
|
||||
}
|
137
components/script/dom/webxr/xrview.rs
Normal file
137
components/script/dom/webxr/xrview.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::RigidTransform3D;
|
||||
use js::typedarray::{Float32, Float32Array};
|
||||
use webxr_api::{ApiSpace, View};
|
||||
|
||||
use crate::dom::bindings::buffer_source::HeapBufferSource;
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewBinding::{XREye, XRViewMethods};
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession};
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRView {
|
||||
reflector_: Reflector,
|
||||
session: Dom<XRSession>,
|
||||
eye: XREye,
|
||||
viewport_index: usize,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
proj: HeapBufferSource<Float32>,
|
||||
#[ignore_malloc_size_of = "defined in rust-webxr"]
|
||||
#[no_trace]
|
||||
view: View<ApiSpace>,
|
||||
transform: Dom<XRRigidTransform>,
|
||||
requested_viewport_scale: Cell<f64>,
|
||||
}
|
||||
|
||||
impl XRView {
|
||||
fn new_inherited(
|
||||
session: &XRSession,
|
||||
transform: &XRRigidTransform,
|
||||
eye: XREye,
|
||||
viewport_index: usize,
|
||||
view: View<ApiSpace>,
|
||||
) -> XRView {
|
||||
XRView {
|
||||
reflector_: Reflector::new(),
|
||||
session: Dom::from_ref(session),
|
||||
eye,
|
||||
viewport_index,
|
||||
proj: HeapBufferSource::default(),
|
||||
view,
|
||||
transform: Dom::from_ref(transform),
|
||||
requested_viewport_scale: Cell::new(1.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<V: Copy>(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
view: &View<V>,
|
||||
eye: XREye,
|
||||
viewport_index: usize,
|
||||
to_base: &BaseTransform,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRView> {
|
||||
let transform: RigidTransform3D<f32, V, BaseSpace> = view.transform.then(to_base);
|
||||
let transform = XRRigidTransform::new(global, cast_transform(transform), can_gc);
|
||||
|
||||
reflect_dom_object(
|
||||
Box::new(XRView::new_inherited(
|
||||
session,
|
||||
&transform,
|
||||
eye,
|
||||
viewport_index,
|
||||
view.cast_unit(),
|
||||
)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn session(&self) -> &XRSession {
|
||||
&self.session
|
||||
}
|
||||
|
||||
pub fn viewport_index(&self) -> usize {
|
||||
self.viewport_index
|
||||
}
|
||||
}
|
||||
|
||||
impl XRViewMethods<crate::DomTypeHolder> for XRView {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrview-eye>
|
||||
fn Eye(&self) -> XREye {
|
||||
self.eye
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrview-projectionmatrix>
|
||||
fn ProjectionMatrix(&self, _cx: JSContext) -> Float32Array {
|
||||
if !self.proj.is_initialized() {
|
||||
let cx = GlobalScope::get_cx();
|
||||
// row_major since euclid uses row vectors
|
||||
let proj = self.view.projection.to_array();
|
||||
self.proj
|
||||
.set_data(cx, &proj)
|
||||
.expect("Failed to set projection matrix.")
|
||||
}
|
||||
self.proj
|
||||
.get_buffer()
|
||||
.expect("Failed to get projection matrix.")
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrview-transform>
|
||||
fn Transform(&self) -> DomRoot<XRRigidTransform> {
|
||||
DomRoot::from_ref(&self.transform)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrview-recommendedviewportscale>
|
||||
fn GetRecommendedViewportScale(&self) -> Option<Finite<f64>> {
|
||||
// Just return 1.0 since we currently will always use full-sized viewports
|
||||
Finite::new(1.0)
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrview-requestviewportscale>
|
||||
fn RequestViewportScale(&self, scale: Option<Finite<f64>>) {
|
||||
if let Some(scale) = scale {
|
||||
if *scale > 0.0 {
|
||||
let clamped_scale = scale.clamp(0.0, 1.0);
|
||||
self.requested_viewport_scale.set(clamped_scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr-ar-module-1/#dom-xrview-isfirstpersonobserver>
|
||||
fn IsFirstPersonObserver(&self) -> bool {
|
||||
// Servo is not currently supported anywhere that supports this, so return false
|
||||
false
|
||||
}
|
||||
}
|
196
components/script/dom/webxr/xrviewerpose.rs
Normal file
196
components/script/dom/webxr/xrviewerpose.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::RigidTransform3D;
|
||||
use js::conversions::ToJSValConvertible;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsval::{JSVal, UndefinedValue};
|
||||
use js::rust::MutableHandleValue;
|
||||
use webxr_api::{Viewer, ViewerPose, Views};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewerPoseBinding::XRViewerPoseMethods;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::xrpose::XRPose;
|
||||
use crate::dom::xrrigidtransform::XRRigidTransform;
|
||||
use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession};
|
||||
use crate::dom::xrview::XRView;
|
||||
use crate::realms::enter_realm;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRViewerPose {
|
||||
pose: XRPose,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
views: Heap<JSVal>,
|
||||
}
|
||||
|
||||
impl XRViewerPose {
|
||||
fn new_inherited(transform: &XRRigidTransform) -> XRViewerPose {
|
||||
XRViewerPose {
|
||||
pose: XRPose::new_inherited(transform),
|
||||
views: Heap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
session: &XRSession,
|
||||
to_base: BaseTransform,
|
||||
viewer_pose: &ViewerPose,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRViewerPose> {
|
||||
let _ac = enter_realm(global);
|
||||
rooted_vec!(let mut views);
|
||||
match &viewer_pose.views {
|
||||
Views::Inline => views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
&session.inline_view(),
|
||||
XREye::None,
|
||||
0,
|
||||
&to_base,
|
||||
can_gc,
|
||||
)),
|
||||
Views::Mono(view) => views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
view,
|
||||
XREye::None,
|
||||
0,
|
||||
&to_base,
|
||||
can_gc,
|
||||
)),
|
||||
Views::Stereo(left, right) => {
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
left,
|
||||
XREye::Left,
|
||||
0,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
right,
|
||||
XREye::Right,
|
||||
1,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
},
|
||||
Views::StereoCapture(left, right, third_eye) => {
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
left,
|
||||
XREye::Left,
|
||||
0,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
right,
|
||||
XREye::Right,
|
||||
1,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
third_eye,
|
||||
XREye::None,
|
||||
2,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
},
|
||||
Views::Cubemap(front, left, right, top, bottom, back) => {
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
front,
|
||||
XREye::None,
|
||||
0,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
left,
|
||||
XREye::None,
|
||||
1,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
right,
|
||||
XREye::None,
|
||||
2,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
top,
|
||||
XREye::None,
|
||||
3,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
bottom,
|
||||
XREye::None,
|
||||
4,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
views.push(XRView::new(
|
||||
global,
|
||||
session,
|
||||
back,
|
||||
XREye::None,
|
||||
5,
|
||||
&to_base,
|
||||
can_gc,
|
||||
));
|
||||
},
|
||||
};
|
||||
let transform: RigidTransform3D<f32, Viewer, BaseSpace> =
|
||||
viewer_pose.transform.then(&to_base);
|
||||
let transform = XRRigidTransform::new(global, cast_transform(transform), can_gc);
|
||||
let pose = reflect_dom_object(Box::new(XRViewerPose::new_inherited(&transform)), global);
|
||||
|
||||
let cx = GlobalScope::get_cx();
|
||||
unsafe {
|
||||
rooted!(in(*cx) let mut jsval = UndefinedValue());
|
||||
views.to_jsval(*cx, jsval.handle_mut());
|
||||
pose.views.set(jsval.get());
|
||||
}
|
||||
|
||||
pose
|
||||
}
|
||||
}
|
||||
|
||||
impl XRViewerPoseMethods<crate::DomTypeHolder> for XRViewerPose {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrviewerpose-views>
|
||||
fn Views(&self, _cx: JSContext, mut retval: MutableHandleValue) {
|
||||
retval.set(self.views.get())
|
||||
}
|
||||
}
|
54
components/script/dom/webxr/xrviewport.rs
Normal file
54
components/script/dom/webxr/xrviewport.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::Rect;
|
||||
use webxr_api::Viewport;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewportBinding::XRViewportMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRViewport {
|
||||
reflector_: Reflector,
|
||||
#[no_trace]
|
||||
viewport: Rect<i32, Viewport>,
|
||||
}
|
||||
|
||||
impl XRViewport {
|
||||
fn new_inherited(viewport: Rect<i32, Viewport>) -> XRViewport {
|
||||
XRViewport {
|
||||
reflector_: Reflector::new(),
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, viewport: Rect<i32, Viewport>) -> DomRoot<XRViewport> {
|
||||
reflect_dom_object(Box::new(XRViewport::new_inherited(viewport)), global)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRViewportMethods<crate::DomTypeHolder> for XRViewport {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-x>
|
||||
fn X(&self) -> i32 {
|
||||
self.viewport.origin.x
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-y>
|
||||
fn Y(&self) -> i32 {
|
||||
self.viewport.origin.y
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-width>
|
||||
fn Width(&self) -> i32 {
|
||||
self.viewport.size.width
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-height>
|
||||
fn Height(&self) -> i32 {
|
||||
self.viewport.size.height
|
||||
}
|
||||
}
|
168
components/script/dom/webxr/xrwebglbinding.rs
Normal file
168
components/script/dom/webxr/xrwebglbinding.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
|
||||
use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRWebGLBinding_Binding::XRWebGLBindingMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContext_Binding::WebGLRenderingContextMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::{
|
||||
XRCubeLayerInit, XRCylinderLayerInit, XREquirectLayerInit, XRProjectionLayerInit,
|
||||
XRQuadLayerInit, XRTextureType,
|
||||
};
|
||||
use crate::dom::bindings::codegen::UnionTypes::WebGLRenderingContextOrWebGL2RenderingContext;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrcompositionlayer::XRCompositionLayer;
|
||||
use crate::dom::xrcubelayer::XRCubeLayer;
|
||||
use crate::dom::xrcylinderlayer::XRCylinderLayer;
|
||||
use crate::dom::xrequirectlayer::XREquirectLayer;
|
||||
use crate::dom::xrframe::XRFrame;
|
||||
use crate::dom::xrprojectionlayer::XRProjectionLayer;
|
||||
use crate::dom::xrquadlayer::XRQuadLayer;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::dom::xrview::XRView;
|
||||
use crate::dom::xrwebglsubimage::XRWebGLSubImage;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRWebGLBinding {
|
||||
reflector: Reflector,
|
||||
session: Dom<XRSession>,
|
||||
context: Dom<WebGLRenderingContext>,
|
||||
}
|
||||
|
||||
impl XRWebGLBinding {
|
||||
pub fn new_inherited(session: &XRSession, context: &WebGLRenderingContext) -> XRWebGLBinding {
|
||||
XRWebGLBinding {
|
||||
reflector: Reflector::new(),
|
||||
session: Dom::from_ref(session),
|
||||
context: Dom::from_ref(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
global: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
session: &XRSession,
|
||||
context: &WebGLRenderingContext,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRWebGLBinding> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(XRWebGLBinding::new_inherited(session, context)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XRWebGLBindingMethods<crate::DomTypeHolder> for XRWebGLBinding {
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-xrwebglbinding>
|
||||
fn Constructor(
|
||||
global: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
session: &XRSession,
|
||||
context: WebGLRenderingContextOrWebGL2RenderingContext,
|
||||
) -> Fallible<DomRoot<XRWebGLBinding>> {
|
||||
let context = match context {
|
||||
WebGLRenderingContextOrWebGL2RenderingContext::WebGLRenderingContext(ctx) => ctx,
|
||||
WebGLRenderingContextOrWebGL2RenderingContext::WebGL2RenderingContext(ctx) => {
|
||||
ctx.base_context()
|
||||
},
|
||||
};
|
||||
// Step 2
|
||||
if session.is_ended() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
// step 3
|
||||
if context.IsContextLost() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
// Step 4
|
||||
if !session.is_immersive() {
|
||||
return Err(Error::InvalidState);
|
||||
};
|
||||
|
||||
// Step 5 throw an InvalidStateError If context’s XR compatible boolean is false.
|
||||
|
||||
Ok(XRWebGLBinding::new(
|
||||
global, proto, session, &context, can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createprojectionlayer>
|
||||
fn CreateProjectionLayer(
|
||||
&self,
|
||||
_: XRTextureType,
|
||||
_: &XRProjectionLayerInit,
|
||||
) -> Fallible<DomRoot<XRProjectionLayer>> {
|
||||
// https://github.com/servo/servo/issues/27468
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createquadlayer>
|
||||
fn CreateQuadLayer(
|
||||
&self,
|
||||
_: XRTextureType,
|
||||
_: &Option<XRQuadLayerInit>,
|
||||
) -> Fallible<DomRoot<XRQuadLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcylinderlayer>
|
||||
fn CreateCylinderLayer(
|
||||
&self,
|
||||
_: XRTextureType,
|
||||
_: &Option<XRCylinderLayerInit>,
|
||||
) -> Fallible<DomRoot<XRCylinderLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createequirectlayer>
|
||||
fn CreateEquirectLayer(
|
||||
&self,
|
||||
_: XRTextureType,
|
||||
_: &Option<XREquirectLayerInit>,
|
||||
) -> Fallible<DomRoot<XREquirectLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcubelayer>
|
||||
fn CreateCubeLayer(&self, _: &Option<XRCubeLayerInit>) -> Fallible<DomRoot<XRCubeLayer>> {
|
||||
// https://github.com/servo/servo/issues/27493
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-getsubimage>
|
||||
fn GetSubImage(
|
||||
&self,
|
||||
_: &XRCompositionLayer,
|
||||
_: &XRFrame,
|
||||
_: XREye,
|
||||
) -> Fallible<DomRoot<XRWebGLSubImage>> {
|
||||
// https://github.com/servo/servo/issues/27468
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-getviewsubimage>
|
||||
fn GetViewSubImage(
|
||||
&self,
|
||||
_: &XRProjectionLayer,
|
||||
_: &XRView,
|
||||
) -> Fallible<DomRoot<XRWebGLSubImage>> {
|
||||
// https://github.com/servo/servo/issues/27468
|
||||
Err(Error::NotSupported)
|
||||
}
|
||||
}
|
367
components/script/dom/webxr/xrwebgllayer.rs
Normal file
367
components/script/dom/webxr/xrwebgllayer.rs
Normal file
|
@ -0,0 +1,367 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use canvas_traits::webgl::{WebGLCommand, WebGLContextId, WebGLTextureId};
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{Rect, Size2D};
|
||||
use js::rust::HandleObject;
|
||||
use webxr_api::{ContextId as WebXRContextId, LayerId, LayerInit, Viewport};
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
|
||||
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::{
|
||||
XRWebGLLayerInit, XRWebGLLayerMethods, XRWebGLRenderingContext,
|
||||
};
|
||||
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::webglframebuffer::WebGLFramebuffer;
|
||||
use crate::dom::webglobject::WebGLObject;
|
||||
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
|
||||
use crate::dom::webgltexture::WebGLTexture;
|
||||
use crate::dom::window::Window;
|
||||
use crate::dom::xrframe::XRFrame;
|
||||
use crate::dom::xrlayer::XRLayer;
|
||||
use crate::dom::xrsession::XRSession;
|
||||
use crate::dom::xrview::XRView;
|
||||
use crate::dom::xrviewport::XRViewport;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
impl<'a> From<&'a XRWebGLLayerInit> for LayerInit {
|
||||
fn from(init: &'a XRWebGLLayerInit) -> LayerInit {
|
||||
LayerInit::WebGLLayer {
|
||||
alpha: init.alpha,
|
||||
antialias: init.antialias,
|
||||
depth: init.depth,
|
||||
stencil: init.stencil,
|
||||
framebuffer_scale_factor: *init.framebufferScaleFactor as f32,
|
||||
ignore_depth_values: init.ignoreDepthValues,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRWebGLLayer {
|
||||
xr_layer: XRLayer,
|
||||
antialias: bool,
|
||||
depth: bool,
|
||||
stencil: bool,
|
||||
alpha: bool,
|
||||
ignore_depth_values: bool,
|
||||
/// If none, this is an inline session (the composition disabled flag is true)
|
||||
framebuffer: Option<Dom<WebGLFramebuffer>>,
|
||||
}
|
||||
|
||||
impl XRWebGLLayer {
|
||||
pub fn new_inherited(
|
||||
session: &XRSession,
|
||||
context: &WebGLRenderingContext,
|
||||
init: &XRWebGLLayerInit,
|
||||
framebuffer: Option<&WebGLFramebuffer>,
|
||||
layer_id: Option<LayerId>,
|
||||
) -> XRWebGLLayer {
|
||||
XRWebGLLayer {
|
||||
xr_layer: XRLayer::new_inherited(session, context, layer_id),
|
||||
antialias: init.antialias,
|
||||
depth: init.depth,
|
||||
stencil: init.stencil,
|
||||
alpha: init.alpha,
|
||||
ignore_depth_values: init.ignoreDepthValues,
|
||||
framebuffer: framebuffer.map(Dom::from_ref),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
session: &XRSession,
|
||||
context: &WebGLRenderingContext,
|
||||
init: &XRWebGLLayerInit,
|
||||
framebuffer: Option<&WebGLFramebuffer>,
|
||||
layer_id: Option<LayerId>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<XRWebGLLayer> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(XRWebGLLayer::new_inherited(
|
||||
session,
|
||||
context,
|
||||
init,
|
||||
framebuffer,
|
||||
layer_id,
|
||||
)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn layer_id(&self) -> Option<LayerId> {
|
||||
self.xr_layer.layer_id()
|
||||
}
|
||||
|
||||
pub fn context_id(&self) -> WebGLContextId {
|
||||
self.xr_layer.context_id()
|
||||
}
|
||||
|
||||
pub fn session(&self) -> &XRSession {
|
||||
self.xr_layer.session()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size2D<u32, Viewport> {
|
||||
if let Some(framebuffer) = self.framebuffer.as_ref() {
|
||||
let size = framebuffer.size().unwrap_or((0, 0));
|
||||
Size2D::new(
|
||||
size.0.try_into().unwrap_or(0),
|
||||
size.1.try_into().unwrap_or(0),
|
||||
)
|
||||
} else {
|
||||
let size = match self.context().Canvas() {
|
||||
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => canvas.get_size(),
|
||||
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
|
||||
let size = canvas.get_size();
|
||||
Size2D::new(
|
||||
size.width.try_into().unwrap_or(0),
|
||||
size.height.try_into().unwrap_or(0),
|
||||
)
|
||||
},
|
||||
};
|
||||
Size2D::from_untyped(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_target(&self) -> u32 {
|
||||
if cfg!(target_os = "macos") {
|
||||
glow::TEXTURE_RECTANGLE
|
||||
} else {
|
||||
glow::TEXTURE_2D
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> {
|
||||
debug!("XRWebGLLayer begin frame");
|
||||
let framebuffer = self.framebuffer.as_ref()?;
|
||||
let context = framebuffer.upcast::<WebGLObject>().context();
|
||||
let sub_images = frame.get_sub_images(self.layer_id()?)?;
|
||||
let session = self.session();
|
||||
// TODO: Cache this texture
|
||||
let color_texture_id =
|
||||
WebGLTextureId::maybe_new(sub_images.sub_image.as_ref()?.color_texture)?;
|
||||
let color_texture = WebGLTexture::new_webxr(context, color_texture_id, session);
|
||||
let target = self.texture_target();
|
||||
|
||||
// Save the current bindings
|
||||
let saved_framebuffer = context.get_draw_framebuffer_slot().get();
|
||||
let saved_framebuffer_target = framebuffer.target();
|
||||
let saved_texture_id = context
|
||||
.textures()
|
||||
.active_texture_slot(target, context.webgl_version())
|
||||
.ok()
|
||||
.and_then(|slot| slot.get().map(|texture| texture.id()));
|
||||
|
||||
// We have to pick a framebuffer target.
|
||||
// If there is a draw framebuffer, we use its target,
|
||||
// otherwise we just use DRAW_FRAMEBUFFER.
|
||||
let framebuffer_target = saved_framebuffer
|
||||
.as_ref()
|
||||
.and_then(|fb| fb.target())
|
||||
.unwrap_or(constants::DRAW_FRAMEBUFFER);
|
||||
|
||||
// Update the attachments
|
||||
context.send_command(WebGLCommand::BindTexture(target, Some(color_texture_id)));
|
||||
framebuffer.bind(framebuffer_target);
|
||||
framebuffer
|
||||
.texture2d_even_if_opaque(
|
||||
constants::COLOR_ATTACHMENT0,
|
||||
self.texture_target(),
|
||||
Some(&color_texture),
|
||||
0,
|
||||
)
|
||||
.ok()?;
|
||||
if let Some(id) = sub_images.sub_image.as_ref()?.depth_stencil_texture {
|
||||
// TODO: Cache this texture
|
||||
let depth_stencil_texture_id = WebGLTextureId::maybe_new(id)?;
|
||||
let depth_stencil_texture =
|
||||
WebGLTexture::new_webxr(context, depth_stencil_texture_id, session);
|
||||
framebuffer
|
||||
.texture2d_even_if_opaque(
|
||||
constants::DEPTH_STENCIL_ATTACHMENT,
|
||||
constants::TEXTURE_2D,
|
||||
Some(&depth_stencil_texture),
|
||||
0,
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
|
||||
// Restore the old bindings
|
||||
context.send_command(WebGLCommand::BindTexture(target, saved_texture_id));
|
||||
if let Some(framebuffer_target) = saved_framebuffer_target {
|
||||
framebuffer.bind(framebuffer_target);
|
||||
}
|
||||
if let Some(framebuffer) = saved_framebuffer {
|
||||
framebuffer.bind(framebuffer_target);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn end_frame(&self, _frame: &XRFrame) -> Option<()> {
|
||||
debug!("XRWebGLLayer end frame");
|
||||
// TODO: invalidate the old texture
|
||||
let framebuffer = self.framebuffer.as_ref()?;
|
||||
// TODO: rebind the current bindings
|
||||
framebuffer.bind(constants::FRAMEBUFFER);
|
||||
framebuffer
|
||||
.texture2d_even_if_opaque(constants::COLOR_ATTACHMENT0, self.texture_target(), None, 0)
|
||||
.ok()?;
|
||||
framebuffer
|
||||
.texture2d_even_if_opaque(
|
||||
constants::DEPTH_STENCIL_ATTACHMENT,
|
||||
constants::DEPTH_STENCIL_ATTACHMENT,
|
||||
None,
|
||||
0,
|
||||
)
|
||||
.ok()?;
|
||||
framebuffer.upcast::<WebGLObject>().context().Flush();
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(crate) fn context(&self) -> &WebGLRenderingContext {
|
||||
self.xr_layer.context()
|
||||
}
|
||||
}
|
||||
|
||||
impl XRWebGLLayerMethods<crate::DomTypeHolder> for XRWebGLLayer {
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-xrwebgllayer>
|
||||
fn Constructor(
|
||||
global: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
session: &XRSession,
|
||||
context: XRWebGLRenderingContext,
|
||||
init: &XRWebGLLayerInit,
|
||||
) -> Fallible<DomRoot<Self>> {
|
||||
let context = match context {
|
||||
XRWebGLRenderingContext::WebGLRenderingContext(ctx) => ctx,
|
||||
XRWebGLRenderingContext::WebGL2RenderingContext(ctx) => ctx.base_context(),
|
||||
};
|
||||
|
||||
// Step 2
|
||||
if session.is_ended() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
// XXXManishearth step 3: throw error if context is lost
|
||||
// XXXManishearth step 4: check XR compat flag for immersive sessions
|
||||
|
||||
let (framebuffer, layer_id) = if session.is_immersive() {
|
||||
// Step 9.2. "Initialize layer’s framebuffer to a new opaque framebuffer created with context."
|
||||
let size = session
|
||||
.with_session(|session| session.recommended_framebuffer_resolution())
|
||||
.ok_or(Error::Operation)?;
|
||||
let framebuffer = WebGLFramebuffer::maybe_new_webxr(session, &context, size)
|
||||
.ok_or(Error::Operation)?;
|
||||
|
||||
// Step 9.3. "Allocate and initialize resources compatible with session’s XR device,
|
||||
// including GPU accessible memory buffers, as required to support the compositing of layer."
|
||||
let context_id = WebXRContextId::from(context.context_id());
|
||||
let layer_init = LayerInit::from(init);
|
||||
let layer_id = session
|
||||
.with_session(|session| session.create_layer(context_id, layer_init))
|
||||
.map_err(|_| Error::Operation)?;
|
||||
|
||||
// Step 9.4: "If layer’s resources were unable to be created for any reason,
|
||||
// throw an OperationError and abort these steps."
|
||||
(Some(framebuffer), Some(layer_id))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// Ensure that we finish setting up this layer before continuing.
|
||||
context.Finish();
|
||||
|
||||
// Step 10. "Return layer."
|
||||
Ok(XRWebGLLayer::new(
|
||||
&global.global(),
|
||||
proto,
|
||||
session,
|
||||
&context,
|
||||
init,
|
||||
framebuffer.as_deref(),
|
||||
layer_id,
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-getnativeframebufferscalefactor>
|
||||
fn GetNativeFramebufferScaleFactor(_window: &Window, session: &XRSession) -> Finite<f64> {
|
||||
let value: f64 = if session.is_ended() { 0.0 } else { 1.0 };
|
||||
Finite::wrap(value)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-antialias>
|
||||
fn Antialias(&self) -> bool {
|
||||
self.antialias
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-ignoredepthvalues>
|
||||
fn IgnoreDepthValues(&self) -> bool {
|
||||
self.ignore_depth_values
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-fixedfoveation>
|
||||
fn GetFixedFoveation(&self) -> Option<Finite<f32>> {
|
||||
// Fixed foveation is only available on Quest/Pico headset runtimes
|
||||
None
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-fixedfoveation>
|
||||
fn SetFixedFoveation(&self, _value: Option<Finite<f32>>) {
|
||||
// no-op until fixed foveation is supported
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebuffer>
|
||||
fn GetFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> {
|
||||
self.framebuffer.as_ref().map(|x| DomRoot::from_ref(&**x))
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferwidth>
|
||||
fn FramebufferWidth(&self) -> u32 {
|
||||
self.size().width
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferheight>
|
||||
fn FramebufferHeight(&self) -> u32 {
|
||||
self.size().height
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport>
|
||||
fn GetViewport(&self, view: &XRView) -> Option<DomRoot<XRViewport>> {
|
||||
if self.session() != view.session() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let index = view.viewport_index();
|
||||
|
||||
let viewport = self.session().with_session(|s| {
|
||||
// Inline sessions
|
||||
if s.viewports().is_empty() {
|
||||
Rect::from_size(self.size().to_i32())
|
||||
} else {
|
||||
s.viewports()[index]
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: According to spec, viewport sizes should be recalculated here if the
|
||||
// requested viewport scale has changed. However, existing browser implementations
|
||||
// don't seem to do this for stereoscopic immersive sessions.
|
||||
// Revisit if Servo gets support for handheld AR/VR via ARCore/ARKit
|
||||
|
||||
Some(XRViewport::new(&self.global(), viewport))
|
||||
}
|
||||
}
|
49
components/script/dom/webxr/xrwebglsubimage.rs
Normal file
49
components/script/dom/webxr/xrwebglsubimage.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::Size2D;
|
||||
use webxr_api::Viewport;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::XRWebGLSubImageBinding::XRWebGLSubImage_Binding::XRWebGLSubImageMethods;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::webgltexture::WebGLTexture;
|
||||
use crate::dom::xrsubimage::XRSubImage;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct XRWebGLSubImage {
|
||||
xr_sub_image: XRSubImage,
|
||||
color_texture: Dom<WebGLTexture>,
|
||||
depth_stencil_texture: Option<Dom<WebGLTexture>>,
|
||||
image_index: Option<u32>,
|
||||
#[no_trace]
|
||||
size: Size2D<u32, Viewport>,
|
||||
}
|
||||
|
||||
impl XRWebGLSubImageMethods<crate::DomTypeHolder> for XRWebGLSubImage {
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-colortexture>
|
||||
fn ColorTexture(&self) -> DomRoot<WebGLTexture> {
|
||||
DomRoot::from_ref(&self.color_texture)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-depthstenciltexture>
|
||||
fn GetDepthStencilTexture(&self) -> Option<DomRoot<WebGLTexture>> {
|
||||
self.depth_stencil_texture.as_deref().map(DomRoot::from_ref)
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-imageindex>
|
||||
fn GetImageIndex(&self) -> Option<u32> {
|
||||
self.image_index
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-texturewidth>
|
||||
fn TextureWidth(&self) -> u32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-textureheight>
|
||||
fn TextureHeight(&self) -> u32 {
|
||||
self.size.height
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue