How can I reset the camera position when the user clicks the enter VR button in an A-Frame scene?
I set a nice camera position for desktop wasd-controls users, but want to reset the position and rotation when a Vive user enters VR.
I followed the pattern of this answer to setup the camera initially:
<!-- Player -->
<a-entity id='cameraWrapper' position='1 1.8034 1' rotation='-35 40 0'>
<a-entity camera look-controls wasd-controls="fly: true"></a-entity>
</a-entity>
and here I attempt to reset the position and rotation with the window.onvrdisplaypresentchange event from the WebVR API:
//
// if a VR display (like the Vive headset begins presenting
// (if the scene goes into VR mode)
// reset the camera position and rotation
//
window.onvrdisplaypresentchange = function() {
d3.select('#cameraWrapper')
.attr('position', '0 0 0')
.attr('rotation', '0 0 0');
}
here's a [code sandbox demo] that reproduces the problem state (http://blockbuilder.org/micahstubbs/94e06c2849b6c7c60105d0243bc1936a#mode=sidebyside)
I'm developing this scene with A-Frame Master and the Aug 29 experimental build of Chromium with the --enable-webvr and --enable-gamepad-extensions flags enabled.
edit: what I really want to do is also reset the camera's position x,z and rotation x,y,z to the Vive hmd's values, in addition to resetting position y (which is what camera="userHeight: 1.6" does for us)
Use camera.userHeight to set a non-VR height.
<a-entity camera="userHeight: 1.6">
The camera in non-VR will be 1.6m high. In VR, this height will be removed. When going back out of VR, the 1.6m height will be re-applied.
https://aframe.io/docs/0.4.0/components/camera.html#vr-behavior
The <a-camera> and default cameras have this by default.
Related
I am looking to add a piece of text to the camera on an A-frame scene which will act as a link to transfer to a non-VR page.
<a-entity camera look-controls position="0 0 0">
<a-cursor visible="false">
</a-cursor>
<a-entity text="value: Galleries; color: black; width: 2;" position="-0.25 0.7 -1" onClickLink>
</a-entity>
</a-entity>
The onClickLink function is registered and uses window.location.href = "https://www.google.com"; to change page as suggested in the docs.
I have created a gallery, and want to give users a simple UI link at the top to take them back to a list of other galleries - however - in the example above the onClickLink function just gets applied to the whole scene rather than only the piece of text. I guess it has maybe been applied to its parent - the camera - so any click counts as firing the event? Or do I need to add something to the event to determine what object was clicked? I'm not sure how raycasting would work on something in the top left corner of the camera?
I do not want to use a 3D object in the scene, and I similarly don't want to use the Link portal entity that will be placed in the scene (I did try going down this route similar to the above, and it again just applied the link to the entire scene rather than on clicking the link itself).
Is there a way to achieve what I'm after?
You can use standard html elements and a-frame element to gather and use Css (position: fixed; z-index: 999) to bring it forward to the top but to create an external html link I share part of my code hope you find the idea, you can use jquery(or pure javascript) to add click event to your a-frame objest, but first add below code to <a-camera tag:
raycaster="far: 10000; objects: .clickable"
then add "clickable" class name to any A-frame object you want to be clicked by user
<a-image visible=true id="web" class="clickable external" src="#imgweb" alpha-test="0.5" position="-0.14 -0.9 0" height="0.18" width="0.18"></a-image>
and then like any html dom object you can access to aframe objects by javascript:
window.onload = function() {
$(".external").each(function(index) {
$(this).on("click", function() {
console.log(this.id + ' Pushed');
switch (this.id.toLowerCase()) {
case 'web':
window.open("http://www.farsix.com/");
break;
case 'instagram':
window.open("https://www.instagram.com/");
break;
default:
console.log("Unknown Link!");
};
});
});
};
I pasted the code from A-Frame's hello world sample scene, directly from their GitHub homepage, into this CodePen.
The scene looks fine when I view it on desktop, and even when I view it on my phone. But when I view it on my phone AND click the VR headset button (at lower right), the entire scene renders behind me! (As if the z-axis were suddenly flipped!)
Here is a CodePen with an even simpler example:
<a-scene>
<a-sphere position="0 1.25 -5" radius="1.25" color="green"></a-sphere>
<a-sphere position="0 1.25 5" radius="1.25" color="red"></a-sphere>
</a-scene>
That is, a green sphere 5 units in front of the camera, and a red sphere 5 units behind the camera. When viewed from my desktop or my phone (a Samsung Galaxy S7 Edge, which is VR capable) in regular mode I see the green sphere, but when I view the scene from my phone in VR mode, I see the red sphere (and have to turn around, looking behind me) to see the green sphere.
What's going on? Do you see the same thing? And how do I solve this problem?!?
The root problem (as #PiotrAdamMilewski suggested) is that my phone was taking on the gyroscope's orientation upon entering VR mode. The solution is to aim the camera at the scene when the user puts on their VR headset - this ensures that the user will be looking exactly where you want when their VR experience begins.
HTML (Angular 8):
<a-scene #scene>
<!-- ... -->
<a-entity #cameraParent>
<a-entity camera look-controls pointerLockEnabled #camera>
</a-entity>
</a-entity>
</a-scene>
JavaScript (TypeScript):
#ViewChild('scene', { static : true }) scene;
#ViewChild('cameraParent', { static : true }) cameraParent;
#ViewChild('camera', { static : true }) camera;
// ...
this.scene.nativeElement.addEventListener('enter-vr', ()=> {
const camera = this.camera.nativeElement;
const cameraParent = this.cameraParent.nativeElement;
cameraParent.object3D.rotation.x = -camera.object3D.rotation.x;
cameraParent.object3D.rotation.y = -camera.object3D.rotation.y;
cameraParent.object3D.rotation.z = -camera.object3D.rotation.z;
});
Notice the camera is wrapped in a container, and that we rotate the camera's container (by the reverse of the camera angle) rather than the camera itself. Props to #PiotrAdamMilewski for helping me identify the root cause, and to #RomanMahotskyi for suggesting this implementation. Teamwork makes the dream work!
I've just added the laser pointer to the right hand in my A-Frame scene and it's pointing in the wrong direction.
A-frame Laser pointing 45deg to the left
In this image I am pointing at the blue box above the tower using an Oculus Touch controller. As you can see the hand is pointing correctly and feels like it matches my real life hand position quite well but the laser is going about 45deg to the left.
Here is my code:
<a-entity id="cameraRig" position="-25 -10 138" rotation="0 -25 0">
<a-entity id="head" camera="near: 0.1; far: 10000" wasd-controls look-controls></a-entity>
<a-entity id="teleHand" hand-controls="left" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; type: parabolic; button: trigger; collisionEntities: #terrain, #bridgeFull"></a-entity>
<a-entity id="pointerHand" hand-controls="right" laser-controls></a-entity>
Is there a way I can reposition the laser? Ideally to come out of the pointing fingure but if not then just pointing in the right direction will do?
Update: I just tried raycaster="showLine: true" instead and this does point in the correct direction so it appears to be a bug with the laser component. I could use the raycaster and write my own collision logic but would much prefer to use the mouse events that you get with the laser.
Second Update: Just discovered if I add both the raycaster and cursor component I get the exact same functionality as adding the laser component but with the raycast pointing in the correct direction.
This has solved the problem I have but will leave this here as I'm sure others will run into this problem until the laser component is fixed.
I've filed an issue here https://github.com/aframevr/aframe/issues/3396
For clarity I was using v0.7.1 of A-frame
Okay, so to post the answer here. hand-controls and laser-controls aren't meant to mix. Hand controls is more for 6DoF and laser-controls is more if you want to support both 3DoF and 6DoF. Laser-controls will set its own raycaster properties on controllerconnected event.
To set your own raycaster properties:
AFRAME.registerComponent('force-raycaster', {
schema: {
direction: {type: 'vec3'},
origin: {type: 'vec3'}
},
init: function () {
this.el.addEventListener('controllerconnected', {
// Wait for laser-controls to set its raycaster properties.
setTimeout(() {
this.el.setAttribute('raycaster', this.data);
}, 50);
});
}
});
<a-entity laser-controls hand-controls force-raycaster="direction: 0 0 -1; origin: 0 -0.1 0">
I have the following code for the laser controls which are perfectly positioned when the camera looks straight ahead after entering VR mode.
<a-entity position="0.25 1.25 -0.2" class="laser-controls">
<a-entity laser-controls="hand: right" line="color: red"></a-entity></a-entity>
The issue is: when I rotate my head (camera), I would like to let the controls follow my head rotation smoothly (I have some code which looks if the rotation is greater than 110 degrees). I don't want the controllers be part of the camera since they should keep their own independent rotation. What I like is the behaviour of the controller model in Oculus Home (Gear VR).
How can I achieve this is my custom component, let's say in my tick function, which is called every two seconds (that code works already).
Thanks!
How about using getAttribute() to check the rotation of the camera component and the laser control's entity? Then you could check if the difference exceeds 110 degrees or not:
let angle = laser.getAttribute('rotation');
if (camera.getAttribute('rotation').y - laser.getAttribute('rotation').y>110){
angle.y++;
laser.setAttribute('rotation',angle);
} else if(camera.getAttribute('rotation').y - laser.getAttribute('rotation').y<-110){
angle.y--;
laser.setAttribute('rotation',angle);
}
UPDATE
If You want to position Your controller near Your head You can:
1.Instead of angle.y++/-- change it to Your camera's rotation. You can also change its x/y position close to the camera ( like camera.position.x + 0.5 )
2.But the above is instant, if You want to make it smooth, You could use the animation component, when the delta degree is >110 deg, set the animation attributes to move to the camera component location/rotation, emit a beginning event, disable the rotation check, listen for the animation end event, and enable the check. a bit like this:
init: function(){
this.check = true;
let check = this.check;
animationel.addEventListener('animationend',function(){
check = true;
});
},tick(){
if(this.check){
if(rotationCheck()){
this.check = false;
}
}
}
Using Aframe how I can change the camera rotation manually so it works on mobile and still use look-controls?
I've tried via the html attribute like so:
document
.querySelector('[camera]')
.setAttribute('rotation', { x: 0, y: deg, z: 0 })
This works fine on desktop however on mobile the look-controls component seems to reset the camera rotation to previous value.
I've setup a demo the issue here, which changes rotation & disables look-controls. Then I set a timeout which re-ables look-controls after 1 sec. https://embed.plnkr.co/WA1rKucPk0cGffrbfrTh/
Pressing any of the html buttons rotates you to a an object and disables the look-controls. 1 sec later the control are re-abled. If you try this on mobile, you'll notice the camera rotates, then 1 sec its reverts when the look-controls are enabled.
Is there some offset I need to reset on mobile too, the lookcontrols.yawObject perhaps?
Put the camera inside an entity:
<a-entity rotation="0 0 0">
<a-camera></a-camera>
</a-entity>
Then rotate the entity instead of the camera.
you need to change the look-controls.js a little bit to make it works,because when on mobile it will not get rotation attribute from the camera entity
function updateOrientation in look-controls.js:
if (sceneEl.isMobile) {
// On mobile, do camera rotation with touch events and sensors.
rotation.x = radToDeg(hmdEuler.x) + radToDeg(pitchObject.rotation.x);
rotation.y = radToDeg(hmdEuler.y) + radToDeg(yawObject.rotation.y);
rotation.z = radToDeg(hmdEuler.z);
}