Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WebXR Locomotion (player movement) to tesseract_viewer_python #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions tesseract_viewer_python/examples/shapes_viewer_ssl_webxr_headset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# SSL may be necessary for some systems that use WebXR. By default WebXR is not allowed to run on insecure origins.
# The user will need to accept the security warning for a self signed certificate.
# Also allow all incoming addresses to connect to the server by binding to 0.0.0.0 instead of localhost.
# Firewalls may also need to be configured to allow incoming connections.
# Point your VR headset to the https address of the computer running the server. For example
# https://192.168.1.10:8000 if the server is running on port 8000 on the computer with IP address
# 192.168.1.10.
#
# To generate a self-signed certificate, run the following command (openssl must be installed):
#
# openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem
#
# When viewed on a VR headset web browser, click the "Enter VR" button to enter VR mode.

from tesseract_robotics.tesseract_environment import Environment
from tesseract_robotics.tesseract_common import ResourceLocator, SimpleLocatedResource
import os
import re
import traceback
from tesseract_robotics_viewer import TesseractViewer
import numpy as np
import time
import sys
import ssl

shapes_urdf="""
<robot name="multipleshapes">

<link name="world"/>
<link name="cylinder_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="red">
<color rgba="0.8 0 0 1"/>
</material>
</visual>
</link>

<joint name="clyinder_joint" type="revolute">
<parent link="world"/>
<child link="cylinder_link"/>
<axis xyz="0 1 0"/>
<limit effort="0" lower="-2.0944" upper="2.0944" velocity="6.2832"/>
</joint>

<link name="box_link">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<material name="green">
<color rgba="0 0.8 0 1"/>
</material>
</visual>
</link>

<joint name="box_joint" type="revolute">
<parent link="world"/>
<child link="box_link"/>
<origin xyz="1 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="0" lower="-2.0944" upper="2.0944" velocity="6.2832"/>
</joint>

<link name="sphere_link">
<visual>
<geometry>
<sphere radius="0.5"/>
</geometry>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
</visual>
</link>

<joint name="sphere_joint" type="revolute">
<parent link="world"/>
<child link="sphere_link"/>
<origin xyz="2 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="0" lower="-2.0944" upper="2.0944" velocity="6.2832"/>
</joint>

</robot>
"""

TESSERACT_SUPPORT_DIR = os.environ["TESSERACT_SUPPORT_DIR"]

class TesseractSupportResourceLocator(ResourceLocator):
def __init__(self):
super().__init__()

def locateResource(self, url):
try:
try:
if os.path.exists(url):
return SimpleLocatedResource(url, url, self)
except:
pass
url_match = re.match(r"^package:\/\/tesseract_support\/(.*)$",url)
if (url_match is None):
print("url_match failed")
return None
if not "TESSERACT_SUPPORT_DIR" in os.environ:
return None
tesseract_support = os.environ["TESSERACT_SUPPORT_DIR"]
filename = os.path.join(tesseract_support, os.path.normpath(url_match.group(1)))
ret = SimpleLocatedResource(url, filename, self)
return ret
except:
traceback.print_exc()


t_env = Environment()

# locator must be kept alive by maintaining a reference
locator=TesseractSupportResourceLocator()
t_env.init(shapes_urdf, locator)

ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(certfile='cert.pem', keyfile='key.pem')
viewer = TesseractViewer(("0.0.0.0", 8000), ssl_context)

viewer.update_environment(t_env, [0,0,0])

viewer.start_serve_background()

if sys.version_info[0] < 3:
raw_input("press enter")
else:
input("press enter")

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VRButton } from 'https://unpkg.com/[email protected]/examples/jsm/webxr/VRB
import { LineMaterial } from 'https://unpkg.com/[email protected]/examples/jsm/lines/LineMaterial.js'
import { Line2 } from 'https://unpkg.com/[email protected]/examples/jsm/lines/Line2.js'
import { LineGeometry } from 'https://unpkg.com/[email protected]/examples/jsm/lines/LineGeometry.js'
import { XRControllerModelFactory } from 'https://unpkg.com/[email protected]/examples/jsm/webxr/XRControllerModelFactory.js';
import 'https://cdn.jsdelivr.net/npm/[email protected]/robust-websocket.min.js';

class TesseractViewer {
Expand All @@ -31,6 +32,17 @@ class TesseractViewer {
this._update_trajectory_timer = null;
this._update_markers_timer = null;
this._update_scene_timer = null;
this._xr_dolly = null;
this._xr_controller1_grip = null;
this._xr_controller2_grip = null;
this._xr_controller1_model = null;
this._xr_controller2_model = null;
this._xr_gamepad1 = null;
this._xr_gamepad2 = null;
this._controls = null;
this._xr_drag_controller_start = null;
this._xr_drag_controller_orientation = null;
this._xr_drag_dolly_start = null;

}

Expand All @@ -39,9 +51,7 @@ class TesseractViewer {
this._clock = new THREE.Clock();

const camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.x = 3;
camera.position.y = 3;
camera.position.z = -1.5;
camera.position.set(3, 3, -1.5)
this._camera = camera;

const renderer = new THREE.WebGLRenderer( { antialias: true } );
Expand Down Expand Up @@ -71,14 +81,20 @@ class TesseractViewer {

document.body.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
this._controls = new OrbitControls( camera, renderer.domElement );

let _this = this;

// Only add VR button if it is supported
if ( 'xr' in navigator )
{
if (await navigator.xr.isSessionSupported( 'immersive-vr' ))
{
document.body.appendChild( VRButton.createButton( renderer ) );

renderer.xr.addEventListener('sessionstart', function() {
_this.enterXR();
});
}
}

Expand All @@ -101,7 +117,6 @@ class TesseractViewer {

await this.updateScene();

let _this = this;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
let do_update = true;
Expand All @@ -119,7 +134,19 @@ class TesseractViewer {

createWebSocket() {
// Create a new WebSocket instance
const socket = new RobustWebSocket('ws://localhost:8000/websocket', null, {

// Get host and port from current URL
const host = window.location.hostname;
const port = window.location.port;
let ws_protocol = "ws";

if (window.location.protocol === "https:") {
ws_protocol = "wss";
}

const ws_url = ws_protocol + "://" + host + ":" + port + "/websocket";

const socket = new RobustWebSocket(ws_url, null, {
shouldReconnect: (event,ws) => { return 1000; }
});

Expand Down Expand Up @@ -157,7 +184,9 @@ class TesseractViewer {

var delta = this._clock.getDelta();
if ( this._animation_mixer ) this._animation_mixer.update( delta );
};

this.xrLocomotion();
}

async fetchIfModified(url, etag) {
let fetch_res;
Expand Down Expand Up @@ -704,8 +733,147 @@ class TesseractViewer {
tf.parent.remove(tf);
});
}

initXRControllers(dolly) {
const controller1 = this._renderer.xr.getController(0);
const controller2 = this._renderer.xr.getController(1);

dolly.add(controller1);
dolly.add(controller2);

const controllerModelFactory = new XRControllerModelFactory();

const controllerGrip1 = this._renderer.xr.getControllerGrip(0);
this._xr_controller1_grip = controllerGrip1;
this._xr_controller1_model = controllerModelFactory.createControllerModel(controllerGrip1);
controllerGrip1.add(this._xr_controller1_model);
dolly.add(controllerGrip1);

const controllerGrip2 = this._renderer.xr.getControllerGrip(1);
this._xr_controller2_grip = controllerGrip2;
this._xr_controller2_model = controllerModelFactory.createControllerModel(controllerGrip2);
controllerGrip2.add(this._xr_controller2_model);
dolly.add(controllerGrip2);

let _this = this;

controllerGrip1.addEventListener("connected", (e)=> {
_this._xr_gamepad1 = e.data.gamepad;
})

controllerGrip2.addEventListener("connected", (e)=> {
_this._xr_gamepad2 = e.data.gamepad;
})
}

enterXR() {
this._controls.saveState();
this._xr_dolly = new THREE.Object3D();
this.initXRControllers(this._xr_dolly)
this._xr_dolly.add(this._camera);
this._camera.position.set(0, 0, 0);
this._camera.rotation.set(0, 0, 0);
this._scene.add(this._xr_dolly);
this._xr_dolly.position.set(2.5,0,0);
this._xr_dolly.rotateY(Math.PI / 2.0);

this._renderer.xr.getSession().addEventListener('end', () => {
this._scene.add(this._camera);
this._scene.remove(this._xr_dolly);
this._xr_dolly.remove(this._camera);
this._xr_dolly = null;
this._xr_controller1_grip.remove(this._xr_controller1_model);
this._xr_controller2_grip.remove(this._xr_controller2_model);
this._xr_controller1_grip = null;
this._xr_controller2_grip = null;
this._xr_controller1_model = null;
this._xr_controller2_model = null;
this._controls.reset();
this._controls.update();
});


}

xrLocomotion()
{
if (this._xr_dolly && this._xr_gamepad2) {
try
{

if (this._xr_gamepad2.buttons[5].pressed)
{
if (!this._xr_drag_controller_start)
{
this._xr_drag_controller_start = this._xr_controller2_grip.position.clone();
let world_quat = new THREE.Quaternion();
this._xr_drag_controller_orientation = this._camera.getWorldQuaternion(world_quat);

this._xr_drag_dolly_start = this._xr_dolly.position.clone();
}

let controller_diff = this._xr_controller2_grip.position.clone().sub(this._xr_drag_controller_start);
controller_diff.applyQuaternion(this._xr_drag_controller_orientation.clone());
let y_diff = controller_diff.y;
y_diff = Math.floor(y_diff / 0.1);
if (y_diff < 0) {
y_diff = y_diff + 1;
}
controller_diff.y = y_diff * 0.1;
let dolly_pos = this._xr_drag_dolly_start.clone().sub(controller_diff);
this._xr_dolly.position.copy(dolly_pos);
}
else
{
if (this._xr_drag_controller_start)
{
this._xr_drag_controller_start = null;
this._xr_drag_dolly_start = null;
}
}

}
catch (e)
{
console.log(e);
}

if (this._xr_gamepad2.axes.length == 4) {
let axis_2 = this._xr_gamepad2.axes[2];
if (axis_2 > 0.2)
{
let scale = -(axis_2 - 0.2)/0.8;
this._xr_dolly.rotateY(0.01 * scale);
}
if (axis_2 < -0.2)
{
let scale = -(axis_2 + 0.2)/0.8;
this._xr_dolly.rotateY(0.01 * scale);
}

let axis_3 = this._xr_gamepad2.axes[3];
let dolly_world_quat = new THREE.Quaternion();
this._xr_dolly.getWorldQuaternion(dolly_world_quat);
let dolly_forward = new THREE.Vector3(0,0,-1);
dolly_forward.applyQuaternion(dolly_world_quat);

if (axis_3 > 0.2)
{
let scale = -(axis_3 - 0.2)/0.8;
this._xr_dolly.position.add(dolly_forward.clone().multiplyScalar(0.01 * scale));
}
if (axis_3 < -0.2)
{
let scale = -(axis_3 + 0.2)/0.8;
this._xr_dolly.position.add(dolly_forward.clone().multiplyScalar(0.01 * scale));
}
}
}
};
}



window.addEventListener("DOMContentLoaded", async function () {
let viewer = new TesseractViewer();
window.tesseract_viewer = viewer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ class TesseractViewer():

:param server_address: The address to bind the websocket server to. Defaults to ('127.0.0.1',8000)
:type server_address: Tuple[str,int], optional
:param ssl_context: The SSL context to use for the server. Default is None.
:type ssl_context: ssl.SSLContext, optional
"""
def __init__(self, server_address = ('127.0.0.1',8000)):
def __init__(self, server_address = ('127.0.0.1',8000), ssl_context = None):

self.server_address = server_address
self.aio_viewer = tesseract_viewer_aio.TesseractViewerAIO(self.server_address)
self.aio_viewer = tesseract_viewer_aio.TesseractViewerAIO(self.server_address, ssl_context)

self.scene_json = None
self.scene_glb = None
Expand Down
Loading
Loading