Compare commits

...

12 Commits
3.7 ... vr

Author SHA1 Message Date
Matthieu Baumann
90305de2de test: unbind the layer shaders 2023-10-31 10:58:10 +01:00
ftheurel
1a2451f4d3 Add context to renderer 2023-10-30 15:23:38 +00:00
Matthieu Baumann
f43c4273a1 bugfix: remove direct call of redraw inside fixLayoutDimensions 2023-10-30 15:44:55 +01:00
ftheurel
b201fb69fc Lower level animation implementation
The setAnimationLoop is replaced by call to requestAnimationFrame on the xrSession.
2023-10-30 14:31:25 +00:00
ftheurel
c2047278e4 Test commit 2023-10-23 09:15:25 +00:00
ftheurel
a03fc7a947 Fix Animation
Add bind for the renderer
2023-10-20 12:31:25 +00:00
ftheurel
db74147021 Update al-vr.html 2023-10-19 10:23:15 +00:00
ftheurel
4e0c2e69c5 Add setup VR animation loop 2023-10-19 10:03:50 +00:00
ftheurel
ed135d1306 Replace parameters
Replace the renderer and the animation parameters by the aladin view
2023-10-19 06:50:41 +00:00
ftheurel
b7642b4a81 Add import map and basic scene 2023-10-19 06:48:00 +00:00
ftheurel
6beca88785 Add VRButton to the view
The button is displayed but still can't launch the VR session
2023-10-18 14:15:04 +00:00
Matthieu Baumann
3bd1d4c1f0 first commit on vr branch 2023-10-18 14:50:24 +02:00
10 changed files with 456 additions and 19 deletions

113
examples/al-vr.html Normal file
View File

@@ -0,0 +1,113 @@
<!doctype html>
<html>
<head>
</head>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.157.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
}
}
</script>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
import * as THREE from 'three';
let aladin;
A.init.then(() => {
aladin = A.aladin(
'#aladin-lite-div',
{
survey: 'P/DSS2/color', // set a survey
projection: 'TAN', // set a projection
fov: 70, // initial field of view in degrees
target: '338.98958 33.96', // initial target
cooFrame: 'equatorial', // set galactic frame
showCooGrid: true, // set the grid
fullScreen: true,
vr: {animation: animate.bind(renderer)},
}
);
//aladin.setOverlayImageLayer("https://alasky.cds.unistra.fr/JWST/CDS_P_JWST_Stephans-Quintet_NIRCam+MIRI")
initScene(aladin.view.imageCanvas);
aladin.setRenderer(renderer);
});
let renderer = null;
let scene = null;
let camera = null;
let cubeMesh = null;
// let controls = null;
/**
* Initializes a 3D scene, camera, and renderer for virtual reality (VR).
*
* @param {HTMLCanvasElement} canvas - The HTML canvas element to render the
* 3D scene
*/
function initScene(canvas) {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
scene.add(camera);
renderer = new THREE.WebGLRenderer({canvas: canvas, context: canvas.getContext('webgl2', {xrCompatible: true})}); // NOTE Une différence ici
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.xr.enabled = true;
// renderer.xr.setReferenceSpaceType('local');
renderer.autoClear = false;
const light = new THREE.PointLight(0xffffff, 10);
light.position.set(0, 2, 1);
scene.add(light);
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff });
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.set(0, -1, 0);
planeMesh.rotation.x = -Math.PI / 2;
scene.add(planeMesh);
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
cubeMesh.position.set(0, 0, -2);
scene.add(cubeMesh);
}
/**
* Function to animate the 3D scene and rendering it.
*/
function animate() {
cubeMesh.rotation.x += 0.001;
cubeMesh.rotation.y += 0.001;
renderer.render( scene, camera );
}
// /**
// * Initializes a WebGL2 context and handles potential errors.
// */
// function initWebGL2() {
// // canvas = aladin.view.imageCanvas;
// canvas = document.getElementById(aladin.view.imageCanvas);
// // gl = canvas.getContext("webgl2", { alpha: true });
// gl = canvas.getContext('webgl2');
// if (!gl) { // If the gl didn't create properly
// alert('This browser doesn\'t support WebGL2');
// return;
// }
// }
</script>
</body>
</html>

View File

@@ -9,8 +9,8 @@ pub mod vao {
use crate::object::element_array_buffer::ElementArrayBuffer;
use crate::webgl_ctx::WebGlContext;
use std::collections::HashMap;
use crate::Abort;
use std::collections::HashMap;
pub struct VertexArrayObject {
array_buffer: HashMap<&'static str, ArrayBuffer>,
@@ -88,7 +88,10 @@ pub mod vao {
}*/
pub fn num_elements(&self) -> usize {
self.element_array_buffer.as_ref().unwrap_abort().num_elements()
self.element_array_buffer
.as_ref()
.unwrap_abort()
.num_elements()
}
pub fn num_instances(&self) -> i32 {
@@ -155,6 +158,7 @@ pub mod vao {
pub fn unbind(&self) {
self.vao.gl.bind_vertex_array(None);
self._shader.unbind(&self.vao.gl);
}
}
@@ -170,8 +174,9 @@ pub mod vao {
}
impl<'a, 'b> ShaderVertexArrayObjectBoundRef<'a, 'b> {
pub fn draw_arrays(&self, mode: u32, byte_offset: i32, size: i32) {
pub fn draw_arrays(&self, mode: u32, byte_offset: i32, size: i32) -> &Self {
self.vao.gl.draw_arrays(mode, byte_offset, size);
self
}
pub fn draw_elements_with_i32(
@@ -180,11 +185,12 @@ pub mod vao {
num_elements: Option<i32>,
type_: u32,
byte_offset: i32,
) {
) -> &Self {
let num_elements = num_elements.unwrap_or(self.vao.num_elements() as i32);
self.vao
.gl
.draw_elements_with_i32(mode, num_elements, type_, byte_offset);
self
}
pub fn draw_elements_instanced_with_i32(
@@ -192,7 +198,7 @@ pub mod vao {
mode: u32,
offset_element_idx: i32,
num_instances: i32,
) {
) -> &Self {
self.vao.gl.draw_elements_instanced_with_i32(
mode,
self.vao.num_elements() as i32,
@@ -200,10 +206,12 @@ pub mod vao {
offset_element_idx,
num_instances,
);
self
}
pub fn unbind(&self) {
self.vao.gl.bind_vertex_array(None);
self._shader.unbind(&self.vao.gl);
}
}
@@ -444,7 +452,10 @@ pub mod vao {
}*/
pub fn num_elements(&self) -> usize {
self.element_array_buffer.as_ref().unwrap_abort().num_elements()
self.element_array_buffer
.as_ref()
.unwrap_abort()
.num_elements()
}
pub fn num_instances(&self) -> i32 {
@@ -511,7 +522,8 @@ pub mod vao {
}
pub fn unbind(&self) {
//self.vao.gl.bind_vertex_array(None);
self.vao.gl.bind_vertex_array(None);
self._shader.unbind(&self.vao.gl);
}
}
@@ -528,13 +540,15 @@ pub mod vao {
}
use crate::object::array_buffer::VertexBufferObject;
impl<'a, 'b> ShaderVertexArrayObjectBoundRef<'a, 'b> {
pub fn draw_arrays(&self, mode: u32, byte_offset: i32, size: i32) {
pub fn draw_arrays(&self, mode: u32, byte_offset: i32, size: i32) -> &Self {
for (attr, buf) in self.vao.array_buffer.iter() {
buf.bind();
buf.set_vertex_attrib_pointer_by_name::<f32>(self.shader, attr);
}
self.vao.gl.draw_arrays(mode, byte_offset, size);
self
}
pub fn draw_elements_with_i32(
@@ -543,7 +557,7 @@ pub mod vao {
num_elements: Option<i32>,
type_: u32,
byte_offset: i32,
) {
) -> &Self {
for (attr, buf) in self.vao.array_buffer.iter() {
buf.bind();
buf.set_vertex_attrib_pointer_by_name::<f32>(self.shader, attr);
@@ -555,6 +569,7 @@ pub mod vao {
self.vao
.gl
.draw_elements_with_i32(mode, num_elements, type_, byte_offset);
self
}
pub fn draw_elements_instanced_with_i32(
@@ -562,7 +577,7 @@ pub mod vao {
mode: u32,
offset_element_idx: i32,
num_instances: i32,
) {
) -> &Self {
for (attr, buf) in self.vao.array_buffer.iter() {
buf.bind();
buf.set_vertex_attrib_pointer_by_name::<f32>(self.shader, attr);
@@ -587,10 +602,12 @@ pub mod vao {
offset_element_idx,
num_instances,
);
self
}
pub fn unbind(&self) {
//self.vao.gl.bind_vertex_array(None);
self.vao.gl.bind_vertex_array(None);
self.shader.unbind(&self.vao.gl);
}
}
@@ -716,6 +733,9 @@ pub mod vao {
pub fn unbind(&self) {
//self.vao.gl.bind_vertex_array(None);
self.vao.gl.bind_vertex_array(None);
self.shader.unbind(&self.vao.gl);
}
}

View File

@@ -875,7 +875,7 @@ impl App {
&self.colormaps,
&self.projection,
)?;
/*
// Draw the catalog
//let fbo_view = &self.fbo_view;
//catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?;
@@ -903,7 +903,7 @@ impl App {
self.line_renderer.draw(&self.camera)?;
//let dpi = self.camera.get_dpi();
//ui.draw(&gl, dpi)?;
*/
// Reset the flags about the user action
self.camera.reset();

View File

@@ -1047,7 +1047,8 @@ impl HiPS {
Some(self.num_idx as i32),
WebGl2RenderingContext::UNSIGNED_SHORT,
0,
);
)
.unbind();
}
Ok(())

View File

@@ -225,7 +225,8 @@ impl RayTracer {
None,
WebGl2RenderingContext::UNSIGNED_SHORT,
0,
);
)
.unbind();
#[cfg(feature = "webgl2")]
shader
.attach_uniform("position_tex", &self.position_tex)
@@ -236,6 +237,7 @@ impl RayTracer {
WebGl2RenderingContext::UNSIGNED_SHORT,
0,
)
.unbind();
}
pub fn is_rendering(&self, camera: &CameraViewPort) -> bool {

View File

@@ -612,7 +612,8 @@ impl Image {
Some(num_indices),
WebGl2RenderingContext::UNSIGNED_SHORT,
((off_indices as usize) * std::mem::size_of::<u16>()) as i32,
);
)
.unbind();
off_indices += self.num_indices[idx];
}

View File

@@ -259,7 +259,8 @@ impl Layers {
None,
WebGl2RenderingContext::UNSIGNED_SHORT,
0,
);
)
.unbind();
}
// The first layer must be paint independently of its alpha channel

View File

@@ -50,6 +50,7 @@ import { ContextMenu } from "./gui/ContextMenu.js";
import { ALEvent } from "./events/ALEvent.js";
import { Color } from './Color.js';
import { ImageFITS } from "./ImageFITS.js";
import { VRButton } from "./VRButton.js";
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
import A from "./A.js";
@@ -458,7 +459,8 @@ export let Aladin = (function () {
//this.discoverytree = new DiscoveryTree(this);
//}
this.view.redraw();
// [ ] That might pose problems
//this.view.redraw();
// go to full screen ?
if (options.fullScreen) {
@@ -471,6 +473,11 @@ export let Aladin = (function () {
this.contextMenu = new ContextMenu(this);
this.contextMenu.attachTo(this.view.catalogCanvas, DefaultActionsForContextMenu.getDefaultActions(this));
}
// initialize the VR button
if (options.vr) {
this.aladinDiv.appendChild(VRButton.createButton(this.view));
}
};
/**** CONSTANTS ****/
@@ -671,6 +678,11 @@ export let Aladin = (function () {
});
};
// @API
Aladin.prototype.setRenderer = function(renderer) {
this.options.vr.renderer = renderer;
}
Aladin.prototype.setFrame = function (frameName) {
if (!frameName) {
return;

252
src/js/VRButton.js Normal file
View File

@@ -0,0 +1,252 @@
/**
* This is an adaptation of the original VRButton.
* Original at:
* https://github.com/mrdoob/three.js/blob/dev/examples/jsm/webxr/VRButton.js
*/
/**
* VRButton class that handles the creation of a VR session
*
* @class VRButton
*/
class VRButton {
/**
* Constructs a VRButton
*
* @static
* @param {View} view - The aladin view
* @return {HTMLButtonElement|HTMLAnchorElement} The VR mode button or an
* error message
*/
static createButton(view) {
const button = document.createElement('button');
/**
* Function for handling the process of entering VR mode.
*/
function showEnterVR(/* device*/) {
let currentSession = null;
/**
* Callback function to handle when the XR session is started
*
* @param {XRSession} session - The XR session that has been started
*/
async function onSessionStarted(session) {
session.addEventListener('end', onSessionEnded);
let gl = view.imageCanvas.getContext('webgl2');
await gl.makeXRCompatible();
session.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
await view.options.vr.renderer.xr.setSession(session);
button.textContent = 'EXIT VR';
// view.options.vr.renderer.setAnimationLoop(view.redrawVR.bind(view));
session.requestReferenceSpace('local-floor').then((refSpace) => {
const xrRefSpace = refSpace;
session.requestAnimationFrame((t, frame) => {view.redrawVR(t, frame, xrRefSpace)});
});
currentSession = session;
}
/**
* Function to render the whole scene
*/
// NOTE A supprimer
function onXRAnimationFrame(t, xrFrame) {
currentSession.requestAnimationFrame(onXRAnimationFrame);
view.redrawVR();
}
/**
* Callback function to handle when the XR session ends
*/
function onSessionEnded(/* event*/) {
currentSession.removeEventListener('end', onSessionEnded);
button.textContent = 'ENTER VR';
currentSession = null;
}
//
button.style.display = '';
button.style.cursor = 'pointer';
button.style.left = 'calc(50% - 50px)';
button.style.width = '100px';
button.textContent = 'ENTER VR';
button.onmouseenter = function() {
button.style.opacity = '1.0';
};
button.onmouseleave = function() {
button.style.opacity = '0.5';
};
button.onclick = function() {
if (currentSession === null) {
// WebXR's requestReferenceSpace only works if the corresponding
// feature was requested at session creation time. For simplicity,
// just ask for the interesting ones as optional features, but be
// aware that the requestReferenceSpace call will fail if it turns
// out to be unavailable.
// ('local' is always available for immersive sessions and doesn't
// need to be requested separately.)
const sessionInit = {optionalFeatures: ['local-floor']};
navigator.xr.requestSession(
'immersive-vr', sessionInit).then(onSessionStarted);
} else {
currentSession.end();
}
};
}
/**
* Function for disabling the VR mode button
*
* @param {HTMLButtonElement} button - The VR mode button element to
* be disabled
*/
function disableButton() {
button.style.display = '';
button.style.cursor = 'auto';
button.style.left = 'calc(50% - 75px)';
button.style.width = '150px';
button.onmouseenter = null;
button.onmouseleave = null;
button.onclick = null;
}
/**
* Function for handling the case where WebXR is not supported
*
* @description This function disables the VR mode button and displays a
* message indicating that VR is not supported
*
* @param {HTMLButtonElement} button - The VR mode button element to be
* disabled and updated with a message
*/
function showWebXRNotFound() {
disableButton();
button.textContent = 'VR NOT SUPPORTED';
}
/**
* Function for handling the case where VR is not allowed due to an
* exception
*
* @description This function disables the VR mode button, logs an
* exception to the console, and displays a message indicating that VR
* is not allowed
*
* @param {any} exception - The exception object or error that indicates
* why VR is not allowed
* @param {HTMLButtonElement} button - The VR mode button element to be
* disabled and updated with a message
*/
function showVRNotAllowed(exception) {
disableButton();
console.warn('Exception when trying to call xr.isSessionSupported',
exception);
button.textContent = 'VR NOT ALLOWED';
}
/**
* Function for styling an HTML element with specific CSS properties
*
* @param {HTMLElement} element - The HTML element to be styled
*/
function stylizeElement(element) {
element.style.position = 'absolute';
element.style.bottom = '20px';
element.style.padding = '12px 6px';
element.style.border = '1px solid #fff';
element.style.borderRadius = '4px';
element.style.background = 'rgba(0,0,0,0.1)';
element.style.color = '#fff';
element.style.font = 'normal 13px sans-serif';
element.style.textAlign = 'center';
element.style.opacity = '0.5';
element.style.outline = 'none';
element.style.zIndex = '999';
}
if ('xr' in navigator) {
button.id = 'VRButton';
button.style.display = 'none';
stylizeElement(button);
navigator.xr.isSessionSupported('immersive-vr').then(function(supported) {
supported ? showEnterVR() : showWebXRNotFound();
if (supported && VRButton.xrSessionIsGranted) {
button.click();
}
}).catch(showVRNotAllowed);
return button;
} else {
const message = document.createElement('a');
if (window.isSecureContext === false) {
message.href = document.location.href.replace(/^http:/, 'https:');
message.innerHTML = 'WEBXR NEEDS HTTPS';
} else {
message.href = 'https://immersiveweb.dev/';
message.innerHTML = 'WEBXR NOT AVAILABLE';
}
message.style.left = 'calc(50% - 90px)';
message.style.width = '180px';
message.style.textDecoration = 'none';
stylizeElement(message);
return message;
}
}
/**
* Registers a listener for the "sessiongranted" event to track the XR
* session being granted.
*
* @description This method checks if the WebXR API is available and
* registers a listener for the "sessiongranted" event to track when an
* XR session is granted. It sets the `VRButton.xrSessionIsGranted`
* property to `true` when the event is triggered.
*/
static registerSessionGrantedListener() {
if ('xr' in navigator) {
// WebXRViewer (based on Firefox) has a bug where addEventListener
// throws a silent exception and aborts execution entirely.
if (/WebXRViewer\//i.test(navigator.userAgent)) return;
navigator.xr.addEventListener('sessiongranted', () => {
VRButton.xrSessionIsGranted = true;
});
}
}
}
VRButton.xrSessionIsGranted = false;
VRButton.registerSessionGrantedListener();
export {VRButton};

View File

@@ -370,7 +370,7 @@ export let View = (function () {
}
this.computeNorder();
this.redraw();
//this.redraw();
};
var pixelateCanvasContext = function (ctx, pixelateFlag) {
@@ -1059,6 +1059,41 @@ export let View = (function () {
View.FPS_INTERVAL = 1000 / 140;
View.prototype.redrawVR = function (t, frame, xrRefSpace) {
const session = frame.session;
session.requestAnimationFrame((t, frame) => {this.redrawVR(t, frame, xrRefSpace)});
let pose = frame.getViewerPose(xrRefSpace);
if (!pose) return;
// Elapsed time since last loop
const now = Date.now();
const elapsedTime = now - this.then;
// If enough time has elapsed, draw the next frame
//if (elapsedTime >= View.FPS_INTERVAL) {
// Get ready for next frame by setting then=now, but also adjust for your
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
// Drawing code
try {
this.moving = this.wasm.update(elapsedTime);
} catch (e) {
console.warn(e)
}
////// 2. Draw catalogues////////
const isViewRendering = this.wasm.isRendering();
if (isViewRendering || this.needRedraw) {
this.drawAllOverlays();
}
this.needRedraw = false;
this.options.vr.animation();
}
/**
* redraw the whole view
*/