AFRAME mouse auto following - aframe

Is it possible to make AFRAME cursor to automatically follow the movement?
I readed trough documentation but still didn't find any ideas.
I found this package but this is out-of-date: https://www.npmjs.com/package/aframe-no-click-look-controls
Expected result should be same: https://alexrkass.github.io/aframe-thetarestricted-example/

To "lock" the mouse and move like in an FPS game, you can use the look-controls pointerLockEnabled property:
look-controls="pointerLockEnabled: true"
Check it out in this glitch
If you want to customize it a bit (like that website with angle limiters), I'd try creating a custom component like this one:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
// register component
AFRAME.registerComponent("foo", {
init: function() {
const max_y_angle = 45; // max left right angle
const max_x_angle = 15; // max up down angle
document.body.addEventListener("mousemove", evt => {
// get the mouse position normalized to <-1,1>
const x = -(evt.clientY / window.innerHeight) * 2 + 1;
const y = -(evt.clientX / window.innerWidth) * 2 + 1;
// set the camera entity rotation values
this.el.camera.el.setAttribute("rotation", {
x: max_x_angle * x,
y: max_y_angle * y,
z: 0
})
})
}
})
</script>
<a-scene foo>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-entity camera position="0 1.6 0"></a-entity>
</a-scene>
Here's a glitch with a react example.

I got also stuck with react. Can't get it working with react.
The error im getting is: The component foo has been already registered. Check that you are not loading two versions of the same component or two different components of the same name.
import React from "react";
import { graphql, Link } from "gatsby";
import 'swiper/css';
import Layout from "../components/layout";
import "../assets/css/main.css";
import Img from "gatsby-image";
import { Scene } from "aframe-react";
import "aframe";
const IndexPage = ({data}) => {
var AFRAME = require('aframe');
AFRAME.registerComponent('foo', {
init: function() {
const max_y_angle = 45; // max left right angle
const max_x_angle = 15; // max up down angle
document.body.addEventListener("mousemove", evt => {
// get the mouse position normalized to <-1,1>
const x = -(evt.clientY / window.innerHeight) * 2 + 1;
const y = -(evt.clientX / window.innerWidth) * 2 + 1;
this.el.camera.el.setAttribute("rotation", {
x: max_x_angle * x,
y: max_y_angle * y,
z: 0
})
})
}
});
return (
<Layout seo={data.strapiHomepage.seo}>
<div className="full-width loader-view">
<div className="content-before-start">
<Img className="logo-image" fluid={data.logo.childImageSharp.fluid} />
<h1>{data.strapiHomepage.loading_title_homepage}</h1>
<p>{data.strapiHomepage.loading_description_homepage}</p>
<div className="button"><Link to="/main">{data.strapiHomepage.start_vr_homepage}</Link></div>
</div>
<div className="three-six-zero-image">
<Scene foo>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-entity camera position="0 1.6 0" ></a-entity>
</Scene>
</div>
</div>
<div>
</div>
</Layout>
);
};

Related

In Aframe, how to point directional light to a certain position instead to an entity?

I want my Directional Light to point to [10,5,5]. I don't have any object there. However, in Aframe documentation, target attributed points only to a certain object.
<a-light type="directional" position="0 0 0" rotation="-90 0 0" target="#directionaltarget">
<a-entity id="directionaltarget" position="0 0 -1"></a-entity>
</a-light>
The target doesn't have to be a "physical" object with a mesh. In this case, it's just an empty dummy object which entire purpose is to provide an anchor point where the light should be directed at.
If you want it to be at 10 5 5, then just change the dummys position:
<a-light type="directional" position="0 0 0" rotation="-90 0 0" target="#directionaltarget">
<a-entity id="directionaltarget" position="10 5 5"></a-entity>
</a-light>
Keep in mind, that here 10 5 5 is in regard to the parents position, because the target is nested.
If you don't want to create an entity, you can create the light in threejs - but still it requires a dummy object:
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("left-light", {
init: function() {
const directionalLight = new THREE.DirectionalLight(0xff0000, 1.5); // red light
const target = new THREE.Object3D(); // dummy target
directionalLight.target = target; // direct the light onto the target
directionalLight.position.set(0, 0, 0) // move light to the center
target.position.set(1, 0, 0); // move the target to the right
// add the target and the light
this.el.sceneEl.object3D.add(target);
this.el.sceneEl.object3D.add(directionalLight);
}
})
</script>
<a-scene left-light>
<a-light></a-light>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#FFFFFF"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#FFFFFF"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFFFFF"></a-cylinder>
</a-scene>

How to make A-Frame components talk to each other?

I want some components to respond to the user's position and orientation in the scene. I have little experience with interactive a-frame scenes and haven't written a component myself.
Generally, I'd want components to be able to provide callbacks for other components to call, or if that's not possible then some kind of inter-component data handoff. The "receiving" component would change its contents (children), appearance and/or behavior.
If we were to take a really simple example, let's say that I want the scene to include either a box if the user is at x>0, or a sphere if they're at x<=0.
Breaking this down, I'll be happy to understand how to...:
Read user position and make it available for others. I found how to read the position; I guess I could just take the <a-scene> element and set some attribute, such as user-position="1 2 3".
Write some code, somewhere, that runs a function when this position changes (I'll debounce it, I imagine) and makes changes to a scene. I think that if I wrote my own component to include the whole scene, I'd need to...:
Set the user position as an attribute on that element;
Define an update method;
In the update method, compare current vs previous user location.
...but I'm wondering if maybe this is overkill and I can just hook somehow into a-scene, or something else entirely.
If I take the approach I mentioned above, I guess the missing piece is how to "declare" what to render? For example, using ReactJS I'd just do return x > 0 ? <a-box/> : <a-sphere/>;. Is there an equivalent, or would I need to reach into the DOM and manually add/remove <a-box> child and such?
Thank you!
EDIT: I sort of got my box/sphere working (glitch), but it feels quite strange, would love to improve this.
How to make A-Frame components talk to each other?
0. setAttribute
You can change any property in any component with
element.setAttribute("component_name", "value");
but I assume you want more than reacting to update calls. Something more flexible than the component schema and a bit more performant when used 60 times per second/
1. events
component 1 emits an event
components 2 - x listen for an event, and react accordingly.
Not dependant on hard-coded component names, you can easily have multiple recipients, and a possibly stable API:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-reader", {
tick: function() {
// read the position and broadcast it around
const pos = this.el.object3D.position;
const positionString = "x: " + pos.x.toFixed(2) +
", z: " + pos.z.toFixed(2)
this.el.emit("position-update", {text: positionString})
}
})
AFRAME.registerComponent("position-renderer", {
init: function() {
const textEl = document.querySelector("a-text");
this.el.addEventListener("position-update", (evt) => {
textEl.setAttribute("value", evt.detail.text);
})
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-camera position-renderer position-reader>
<a-text position="-0.5 0 -0.75" color="black" value="test"></a-text>
</a-camera>
</a-scene>
2. Directly
Taking this literally, you can grab the component "object" reference with
entity.components["componentName"]
and call its functions:
entity.components["componentName"].function();
For example - one component grabs the current position, and tells the other one to print it:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-reader", {
init: function() {
// wait until the entity is loaded and grab the other component reference
this.el.addEventListener("loaded", evt => {
this.rendererComp = this.el.components["position-renderer"];
})
},
tick: function() {
if (!this.rendererComp) return;
// read the position and call 'updateText' in the 'position-renderer'
const pos = this.el.object3D.position;
const positionString = "x: " + pos.x.toFixed(2) +
", z: " + pos.z.toFixed(2)
this.rendererComp.updateText(positionString)
}
})
AFRAME.registerComponent("position-renderer", {
init: function() {
this.textEl = document.querySelector("a-text");
},
updateText: function(string) {
this.textEl.setAttribute("value", string);
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-camera position-renderer position-reader>
<a-text position="-0.5 0 -0.75" color="black" value="test"></a-text>
</a-camera>
</a-scene>
In Your case I'd check the position, and manage the elements in one component. Or use one to determine if the position.x > 0 || < 0, and the other one for visibility changes.
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-check", {
schema: {
z: {default: 0}
},
tick: function() {
const pos = this.el.object3D.position;
// check if we're 'inside', or outside
if (pos.z >= this.data.z) {
// emit an event only once per occurence
if (!this.inside) this.el.emit("got-inside");
this.inside = true
} else {
// emit an event only once per occurence
if (this.inside) this.el.emit("got-outside");
this.inside = false
}
}
})
AFRAME.registerComponent("manager", {
init: function() {
const box = this.el.querySelector("a-box");
const sphere = this.el.querySelector("a-sphere")
//react to the changes
this.el.sceneEl.camera.el.addEventListener("got-inside", e => {
box.setAttribute("visible", true);
sphere.setAttribute("visible", false);
})
this.el.sceneEl.camera.el.addEventListener("got-outside", e => {
box.setAttribute("visible", false);
sphere.setAttribute("visible", true);
})
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-entity manager>
<a-box position="0 1 -3" visible="false"></a-box>
<a-sphere position="0 1 -3" visible="false"></a-sphere>
</a-entity>
<a-camera position-check="z: 0"></a-camera>
</a-scene>

Aframe - How to render HUD on top of everything else

The A-Frame camera docs say to create a HUD like this:
<a-entity camera look-controls>
<a-entity geometry="primitive: plane; height: 0.2; width: 0.2" position="0 0 -1"
material="color: gray; opacity: 0.5"></a-entity>
</a-entity>
However, this causes the HUD element to be clipped by in-game objects. Normally HUDs are rendered on a separate layer above all the other game objects. Is there a simple way to fix this?
You have to:
Set renderer.sortObjects = true;
Set a the render order of the entity this.el.object3D.renderOrder = 100;
Disable the material depth test material.depthTest = false;
I created a simple overlay component that easily applies the above to any entity.
AFRAME.registerComponent("overlay", {
dependencies: ['material'],
init: function () {
this.el.sceneEl.renderer.sortObjects = true;
this.el.object3D.renderOrder = 100;
this.el.components.material.material.depthTest = false;
}
});
<a-entity camera look-controls wasd-controls position="0 1.6 0">
<a-plane id="hud" overlay rotation="0 0 0" position="0 0 -2" width="1" height="1" color="purple" shadow></a-plane>
</a-entity>
Example on glitch

Raycaster camera with a-sky intersects with cursor

I just started developing with a-frame, please excuse if the answer is obvious.
In my project I would like to get the position of a-sky where the user is looking at. Therefor I implemented a raycaster within the camera, which works fine so far.
HTML
<a-camera listener>
<a-entity raycaster="far: 1000" position="0 -0.9 0" rotation="0 0 0"></a-entity>
</a-camera>
<a-sky follow-intersection
id="sky"
src="#skybox-image">
</a-sky>
TS
AFRAME.registerComponent("follow-intersection", {
init: function() {
this.el.addEventListener("raycaster-intersected", evt => {
this.intersectingRaycaster = evt.detail.el.components.raycaster;
});
this.el.addEventListener("raycaster-intersected-cleared", () => {
this.intersectingRaycaster = null;
});
},
tick: function(t) {
if (!this.intersectingRaycaster) {
return;
}
const intersection = this.intersectingRaycaster.getIntersection(this.el);
if (intersection) {
let point = intersection.uv;
console.log(point.x, point.y);
}
So far this works fine, the problem is that after I set the cursor in the scene (which is needed for the project)
<a-scene
cursor="rayOrigin: listener"
>
I always get the intersections with the cursor, which are not wanted.
How can I only get the intersections of the camera? Thank you!
I don't think the cursor in a-scene is needed. You could just do:
<a-camera listener>
<a-entity cursor raycaster="far: 1000" position="0 -0.9 0" rotation="0 0 0"></a-entity>
</a-camera>

A-Frame image/cursor does not point to the same object

I'm trying a similar example like Stereo-Sky A-Frame Component from this link. The demo link does not work so I have uploaded the example here after fixing the source for aframe-master.min.js.
After viewing it in VR (in my samsung s7 edge with card board), I have found that the image for both eye does not align. Especially when I try putting the pointer on the way points, I have found they are off by small amount.
I'm confused what exactly I have done wrong; is the source for A-Frame is wrong? Or the images need to be "modified" for left and right eye? Is there a settings on the device that may be messing the image render? For ease of reference I am putting the source code : -
<!DOCTYPE html>
<html>
<head>
<title>A-Frame walkthrough (final)</title>
<script src="https://rawgit.com/aframevr/aframe/3620e8e/dist/aframe-master.min.js"></script>
<script src="stereocube.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<img id="circle" src="circle.png"/>
</a-assets>
<a-camera stereocam position="-47 0 -35" ><a-cursor fuse="true" color="#2E3A87" timeout="700">
<a-animation begin="cursor-click" easing="ease-in" attribute="scale" dur="800"
fill="backwards" from="1.0 1.0 1.0" to="100 100 100">
</a-animation>
</a-camera>
<a-mixin id="waypt" rotation="0 0 25" scale="2 2 2"></a-mixin>
<a-mixin id="spin" attribute="rotation" dur="10000" fill="forwards" to="0 360 25" repeat="indefinite"></a-mixin>
<a-mixin id="enter" begin="cursor-mouseenter" easing="ease-in" attribute="scale" dur="200" to="4 4 4"></a-mixin>
<a-mixin id="leave" begin="cursor-mouseleave" easing="ease-out" attribute="scale" dur="200" to="2 2 2"></a-mixin>
<a-box id="wp1" color="orange" href="brooke0" position="-40 -10 10" mixin="waypt" src="#circle">
<a-animation mixin="enter"></a-animation>
<a-animation mixin="leave"></a-animation>
<a-animation mixin="spin"></a-animation>
</a-box>
<a-box id="wp2" color="green" href="brooke1" position=" 0 -14 0" mixin="waypt" src="#circle" ><a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-box id="wp3" color="lightblue" href="brooke3" position=" 15 -9 35" mixin="waypt" src="#circle"> <a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-box id="wp4" color="red" href="brooke2" position="-47 -9 -35" mixin="waypt" src="#circle"> <a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-entity id="skyL" position="-47 0 -35" skycube="folder:brooke2L; eye:left" scale="1 1 -1"></a-entity>
<a-entity id="skyR" position="-47 0 -35" skycube="folder:brooke2R; eye:right" scale="1 1 -1"></a-entity>
</a-scene>
</body>
<script>
var app = {
init: function() {
// Add Click handler's to our waypoints in javascript
[].forEach.call(document.querySelectorAll('a-box'), function(box) {
box.addEventListener('click', function () {
window.setTimeout(function(){
app.setSkybox ( box.getAttribute('href') );
app.moveCamera ( box.getAttribute('position'), document.querySelectorAll('a-camera')[0] );
app.hideWaypoints(box.id);
},600)
})
});
app.hideWaypoints("wp4");
},
setSkybox: function(selectedFolder) {
document.querySelector('#skyL').setAttribute('skycube',{folder: selectedFolder+"L", eye:"left"});
document.querySelector('#skyR').setAttribute('skycube',{folder: selectedFolder+"R", eye:"right"});
},
hideWaypoints: function(boxid) {
document.querySelector('#wp4').setAttribute('visible',boxid === "wp1");
document.querySelector('#wp2').setAttribute('visible',boxid != "wp4");
document.querySelector('#wp3').setAttribute('visible',boxid != "wp4");
},
moveCamera: function(newPosition, camera) {
var pos = newPosition.x +" 0 "+ newPosition.z;
camera.setAttribute('position', pos );
// move the cubemap to the same position as the camera, to avoid distortion
document.querySelector('#skyL').setAttribute('position', pos );
document.querySelector('#skyR').setAttribute('position', pos );
},
}
app.init();
</script>
</html>

Resources