A-Frame : How to rotate a distant component with the raycaster? - aframe

In Aframe, is there a way to use the controller to rotate a distant component with the raycaster ?

Yes. cursor entity, with raycaster, and class='clickable' like this
<a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects:
.clickable"></a-entity>
then add a custom component on the object you want to spin, that listens for the mouse event, and then spins the object, until the mouse Leave event occurs. like this
AFRAME.registerComponent('over-listener', {
schema:{
mouseover:{type: 'boolean', default: false}
},
init: function () {
var el = this.el; // reference to the entity that contains this component
var data = this.data; // reference to the properties of this component.
// Listen for mouseenter event
this.el.addEventListener('mouseenter', function (evt) {
// You can't change the property directly. You must use setAttribute.
el.setAttribute('over-listener','mouseover', true);
// Change the color of the button to indicate rollover state is on.
el.setAttribute('material','color','#55ee00');;
});
// Listen for mouseleave event
this.el.addEventListener('mouseleave', function (evt) {
el.setAttribute('over-listener','mouseover', false);
el.setAttribute('material','color','orange');
});
},
tick: function(){ // called every frame
if(this.data.mouseover){ // Check the mouseover state
let elOcta = document.querySelector('#octahedron');
let rot = elOcta.getAttribute('rotation');
elOcta.setAttribute('rotation',{x: rot.x, y: rot.y , z: rot.z + 1});
}
}
});
here is a glitch
https://glitch.com/~rollover-rotate

Related

meteor emit event then used by aframe eventlistener

I have an aframe object in main.html:
<a-gltf-model id='player1' foobox playmyclip src="#myMixBun" ></a-gltf-model>
I want to have it perform a gltf clip animation when the eventListener ‘driveplay’ is emitted.
In foofile.js:
AFRAME.registerComponent('playmyclip', {
init: function () {
var el = this.el;
el.addEventListener('driveplay', function () {
el.setAttribute('animation-mixer', {clip: 'Drive', loop: 'once'});
});
}
});
Currently I have it so when the j key is hit ‘driveplay’ is emitted:
AFRAME.registerComponent('foobox', {
init: function() {
var el = this.el
var swingno = 0;
self = this;
document.addEventListener('keydown', (event) => {
const keyName = event.key;
if (keyName === 'j') {
el.emit('driveplay',{},true);
// code to store record of event in Mongo for second player
var playerid = self.el.getAttribute('id');
var playerMid = Games.findOne({name: playerid})._id;
Games.update({_id: playerMid},{$set:{swings : swingno}});
swingno = swingno + 1;
}
})
}
});
But I also need to have the animation to happen at the same time on player2‘s screen. So in the above code I increment a variable in Mongo every time the animation happens. Then in main.js meteor automatically emits an event whenever “swingno” changes in Mongo.
main.js:
import {EventEmitter} from 'meteor/raix:eventemitter';
Event = new EventEmitter();
var swingcnt1 = 0;
Template.hello.helpers({
counter() {
if (Games.findOne()) //mongo is ready to access
{
var plyr1Swing = Games.findOne({name: "player1"}).swings;
if (plyr1Swing !== swingcnt1) {
Event.emit('driveplay',{},true);
swingcnt1 = plyr1Swing;
console.log(“this shows on player2’s console automatically”, swingcnt1);
}
}
else {null}
return { ........};
},
When player1 hits the “j” key the animation happens correctly on his screen, plus the event is recorded in Mongo, and player2 receives the updated Mongo value (since it show in the console.log).
The problem is the
Event.emit('driveplay',{},true);
statement doesn’t trigger the animation in player2‘s screen. This is a little tricky, since I need meteor’s “raix:eventemitter” package to create an event that the aframe event listener can see. It’s possible I’m not actually emitting an event at all, since I don’t know how to test if it’s working. Or possibly aframe can’t see the emitted event.
Possibly there’s an easier way of doing this. Thanks for any help.
SOLVED The solution was to use the listener code from meteor’s raix:eventemitter package inside Aframe’s component.
https://atmospherejs.com/raix/eventemitter
AFRAME.registerComponent('playmyclip', {
init: function ()
var el = this.el;
listener = function() {
el.setAttribute('animation-mixer', {clip: 'Drive', loop: 'once'});
};
Event.on('driveplay', listener);
}
});
Plus the foobox component no longer needs:
if (keyName === 'j') {
el.emit('driveplay',{},true);
Both player1 and player2 get the animation event from
Event.emit('driveplay',{},true);
in main.js when the meteor helper notices a change in the mongodb.

How to change dynamically the text value of an <a-text>?

I want to make a dynamic digital clock in A-Frame. I'm using a text element but I can't change its text by setting the property in JS. I can still change others attributes like the color.
html:
...
<a-text id="clock" clock-text value="00:00" position="2.45 0 0.01" color="#FFFFFF" align="right"></a-text>
...
js:
...
AFRAME.registerComponent('clock-text', {
init: function() {
var el = this.el;
el.setAttribute('value', '20:30');
el.setAttribute('color', 'black');
},
update: function() {
el.setAttribute('value', '20:30');
}
});
You can get the full code in the jsfiddle I'm using.
So I think this is a race condition issue, since there's actually two updates going on in a-text, when it initializes and when it receives the new value (causing a component update to be called).
the text component does emit one event called 'textfontset' when updateFont gets called from its init.
Using that event, you can start your clock after that event is emitted
AFRAME.registerComponent('clock-text', {
init: function() {
var el = this.el;
this.ready = false;
el.addEventListener('textfontset', function() {
this.ready = true;
}.bind(this));
},
tick: function() {
var el = this.el;
if (!this.ready) {
return;
}
el.setAttribute('value', '20:30');
}
});
https://jsfiddle.net/xcofjjm9/1/

How to access the default camera from a component?

A component I'm making needs the default camera, but this.el.sceneEl.camera returns undefined.
Simplified example:
AFRAME.registerComponent('test', {
init: function () {
console.log(this.el.sceneEl.camera)
}
});
How can I retrieve the camera?
Might have to wait for the camera to be set? Need to document this, but there's an event:
this.el.sceneEl.addEventListener('camera-set-active', function (evt) {
console.log(evt.detail.cameraEl);
});

How to listen to changes in the rotation of the camera in A-Frame?

Is there a way to add a listener on the current angle of view?
In other words, I'd like to trigger a function every time the user looks behind him.
The quickest way seems to be having a listener that checks the current head rotation and trust triggers the function if is within a certain range of degrees
Edit
The componentchange event is throttled. And it is more performant to not go through the event system for frequent updates. The camera rotation always changes every frame in VR so there is no need to think whether the camera has changed. So we read rotation every frame with component tick.
AFRAME.registerComponent('rotation-reader', {
tick: function () {
var rotation = this.el.getAttribute('rotation');
if (rotation.y < 180) {
// ...
}
}
});
// <a-camera rotation-reader>
Original
https://aframe.io/docs/0.2.0/core/entity.html#listening-for-component-changes
You can use the componentchanged event to listen to changes in the rotation:
document.querySelector('[camera]').addEventListener('componentchanged', function (evt) {
if (evt.name !== 'rotation') { return; }
if (evt.newData.y < 180) { // ... }
});
Or better as a component (this will trigger an event when rotation is certain amount):
AFRAME.registerComponent('trigger-on-look-behind', {
schema: {type: 'string'},
init: function () {
var eventName = this.data;
this.el.addEventListener('componentchanged', function (evt) {
if (evt.name !== 'rotation') { return; }
if (evt.newData.y < 180) {
this.emit(eventName);
}
});
}
});
And then:
<a-camera trigger-on-look-behind="looked-behind"></a-camera>

Extend polyline and handle mouse event

I write JS app where I draw a lot of polylines using array of points, but in avery point I have some additional properties in this point (GPS data, speed etc).
I want to show these additional props onmouseover or onmouseclick event.
I have two ways:
use the standard polylines and event handler. But in this case I can't to determine additional properties for start point of this polyline cause I can't to save these props in polyline properties. There is one solution - save in array additional properties and try to find them by LatLng of first point of the polyline, but it's too slow I guess..
extend polyline and save additional properties in new Object, but I can't to extend mouse events :(
To extend polyline I use this code:
function myPolyline(prop, opts){
this.prop = prop;
this.Polyline = new google.maps.Polyline(opts);
}
myPolyline.prototype.setMap = function(map) {
return this.Polyline.setMap(map);
}
myPolyline.prototype.getPath = function() {
return this.Polyline.getPath();
}
myPolyline.prototype.addListener= function(prop) {
return this.Polyline.addListener();
}
myPolyline.prototype.getProp= function() {
return this.prop;
}
myPolyline.prototype.setProp= function(prop) {
return this.prop = prop;
}
and create new object in for loop (i - index of current point in array of points) like that:
var polyline_opts = {
path: line_points,
strokeColor: color,
geodesic: true,
strokeOpacity: 0.5,
strokeWeight: 4,
icons: [
{
icon: lineSymbol,
offset: '25px',
repeat: '50px'
}
],
map: map
};
var add_prop = {
id: i,
device_id: device_id
};
...
devices_properties[device_id].tracks[(i-1)] = new myPolyline(add_prop, polyline_opts);
Where:
line_points - array of points (just two points),
i - current point index
devices_properties[device_id].tracks - array of extended polylines (with add properties) by my device_id index
After that I set event handler like that:
var tmp = devices_properties[device_id].tracks[(i-1)];
google.maps.event.addListener(tmp.Polyline, 'click', function(e) {
...
console.log(tmp.prop.id);
...
}
But in this case I always get the same id in console..
When I use
google.maps.event.addListener(devices_properties[device_id].tracks[(i-1)].Polyline, 'click', function(e) {
...
console.log(???); // How to get parent of polyline fired the event?
...
}
I don't know how to get parent of polyline fired the event?
I answer my own question - It's done, I've just have some troubles with using "for" instead "$.each" :)
Before I use:
for ( i = 1; i < devices_properties[device_id].history_points.length; i++ ) {
...
create myPolyline
...
}
and it's doesn't work - created one event handle.
After:
$.each(devices_properties[device_id].history_points, function(i, tmp){
...
create myPolyline ()
...
}
and it works - create a lot of event handlers.
To handle event I use this:
google.maps.event.addListener(c_polyline.Polyline, 'mouseover', function(e) {
var prop = c_polyline.getProp();
...
console.log(prop.id, prop.device_id);
}

Resources