A-frame Want click on entity shows other entity - aframe

I have an entity and I want when I click or point with the a-frame cursor that is changes the attribute visible of another entity to true.
<a-entity id="pug" gltf-model="#pug" position="-1.75 0.0035 3"
scale="0.01 0.01 0.01" rotation="0 -11 0" >shadow
event-set__down="_event: mousedown; scale: 1.2 1.2 1.2"
event-set__up="_event: mouseup; scale: 1 1 1"
event-set__leave="_event: mouseleave; scale: 1 1 1">
</a-entity>
If I have that the interaction with the cursor works but I don't how (or even if) I can make it affect the attribute of another entity.
I want that :
<script>
$(document).ready(function(){
$("#pug").mouseenter(function(){
$('#bubble').attr('visible', 'true');
});
});
</script>
I do have cursor in my camera but I don't know... I'm new to A-Frame and I must be missing something :/ Thanks!

Keep in mind, that changing CSS properties won't affect the rendered entities. The best way to change properties is by using entity.setAttribute("attribute", "value")
I'm not sure if you can do a switch (visible / invisible) using the event-set component, but you can make an entity visible, or invisible by setting the visible attribute:
<a-entity event-set__click="_target: a-cylinder; visible: false;"></a-entity>
check it out here.
But i would recommend creating an a-frame component. You can check them out in the docs, in this case it looks like this:
AFRAME.registerComponent("foo", {
init: function() {
this.el.addEventListener("click", (e) => {
let cylinder = document.querySelector("a-cylinder")
cylinder.setAttribute("visible", !cylinder.getAttribute("visible"))
})
}
})
HTML:
<a-entity foo>
quite simple:
1) The AFRAME.registerComponent("foo" bit "declares" the component.
2) The init: function() function is executed when the component is initialized
3) Inside the click listener, I've made a simple toggle, which sets the visibility to the opposite of the actual value. If its visible, switch it to !visible = false.
Check it out here.

Related

How to create a UI link in A-frame

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!");
};
});
});
};

How can I auto walk in A-Frame?

For my A-Frame project I want the player to move forward without depending on any controllers or keyboards. The player should always be moving forward, and should be able to decide the direction by looking around (without changing the z value). Using look-controls on the camera and movement-controls (from aframe-extras) on the rig gives this behavior, apart from the fact that it still depends on a controller. How can I implement auto walk in A-Frame?
<a-entity id="rig" movement-controls>
<a-entity id="camera" position="0 1.7 0" camera look-controls>
</a-entity>
If you are using the movement-control component, you can add your own "controls". As the doc say, all you have to do is to name it with a "-controls" suffix (see https://github.com/n5ro/aframe-extras/tree/master/src/controls#customizing-movement-controls ). This is the code for a simple "autowalk" custom controls component for "movement-controls":
AFRAME.registerComponent('autowalk-controls', {
isVelocityActive: function () {
return true;
},
getVelocityDelta: function () {
return new THREE.Vector3(0, 0, -1);
}
});
And you can simply use it by adding it to the controls proprety of "movement-controls" like that:
movement-controls="controls: autowalk"

animating a custom attribute in a custom component

I am attempting to animate a material property that is buried inside of a gltf-model (that has many objects). I can find the parameter and animate it with a tick event, and it looks like this
https://glitch.com/~uv-reveal-wave
It almost works, but animating with formulas is a nightmare. Instead I want to control it with the animation component. Unfortunately, exposing the parameter directly seems impossible, as I have to use getObject3D.traverse() just to locate the object, and then get the parameter.
Instead I have made a custom attribute (in schema) and and animating that attribute. And in the update function, I'm using that attribute to drive the buried parameter. It should work, but I can't seem to get a response in the update function. Iow, my custom attribute is not animating.
AFRAME.registerComponent('matclipplane', { // attached to gltf-model tree
schema:{
clipHeightA:{type: 'number', default: 0}
},
init: function () {
let el = this.el;
let comp = this;
comp.treeModels = [];
el.addEventListener('model-loaded', function(ev){
let mesh = el.getObject3D('mesh');
mesh.traverse(function(node){
if (node.isMesh){
comp.treeModels.push(node);
}
comp.modelLoaded = true;
});
...
update: function(){
console.log("update"); // returns nothing. no update from
animating attributes clipHeightA
let comp = this;
if (comp.modelLoaded){
comp.treeModels[1].material.map.offset.y =
this.data.clipHeightA;
}
}
...
AFRAME.registerComponent("click-listener", { // attached to box button
schema:{
dir:{type:"boolean", default: false}
},
init: function(){
let el=this.el;
let data = this.data;
el.addEventListener("click", function(evt){
let tree = document.querySelector('#tree');
data.dir = !data.dir;
if(data.dir){
el.setAttribute("material",'color', 'orange');
tree.emit("grow");
} else {
el.setAttribute('material','color','#332211');
tree.emit('shrink');
}
});
}
});
<a-entity id="tree" gltf-model="#tree" scale="5 5 5"
animation__grow="property: clipHeightA; from: 0; to: 1;
startEvents: grow; dur: 500"
matclipplane></a-entity>
<a-entity id="button" geometry="primitive: box" material="color:orange;
metalness: 0.5" scale="0.2 0.2 0.2" class="clickable" click-listener></a-
entity>
<a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects:
.clickable"></a-entity>
here is the glitch in progress
https://glitch.com/~uv-reveal
Clicking on the orange cube should launch the uv reveal animation. I expect that calling a custom component attribute should trigger an update event, but the update does not log in the console.
Not sure how to animate a parameter inside a gltf.
Do I have to implement the animejs component directly inside my custom component?
Or can this be done with the animate component?
According to the docs you can animate properties through
object3D like animation="property: object3D.x"
components like animation="property: components.myComp.myProp"
I don't see it mentioned (although it's used in the docs) but it also works when you provide the property as the components attribute:
animation__grow="property: matclipplane.clipHeightA;"
glitch here. You can see update is being called.
Also tree was an id of both the asset and the component, therefore grow was emitted on the asset item.

How can I make my entity spin when the user hovers over it?

I am using the event-set-component to cause my obj model under to increase scale when the cursor hovers over it.
This is working correctly.
But how would I make it spin as well as increase size?
I found the following code on AFrame docs but I do not know how to implement it so it triggers when the mouse is over the entity.
<a-animation attribute="material.opacity" begin="fade" to="0"></a-animation>
As you have asked for a different method in your comment I suggest to use a multi use component like the one I have written:
AFRAME.registerComponent('event-animate', {
schema: {
target: {type: 'selector'},
aevent: {default: 'animation1'},
triggeraction: {default: 'click'}
},
init: function () {
var data = this.data;
this.el.addEventListener(data.triggeraction, function () {
data.target.emit(data.aevent);
});
}
});
So in HTML it would look something like this:
<a-entity id="object1"
event-animate="target:object1;
triggeraction:mouseenter;
aevent:eventstart">
<a-animation attribute="scale"
dur="5000"
begin="eventstart"
from="1"
to ="5"
direction="alternate">
</a-animation>
<a-animation attribute="rotation"
dur="5000"
begin="eventstart"
from="0 0 0"
to="0 360 0"
direction="alternate">
</a-animation>
</a-entity>
The direction="alternate" should bring it back to its original position.
The quoted animation will work, if You set the begin event properly:
<a-animation attribute="rotation"
dur="2000"
begin="mouseenter"
to="0 360 0"
repeat="1"><a-animation>
On mouseenter, the animation triggers, and rotates the entity once.
To gain more control over what You do, You would need to get deep into making components.
1. The Easiest way i can think of, is using both the animation component, and Your own. You would need to set up a component listening for the mouseenter/mousexit, and trigger the animation:
AFRAME.registerComponent('mouseenterhandler', {
init: function () {
let el = this.el; //Your element reference
el.addEventListener('mouseenter, function () {
// get the rotation, by getAttribute, or by accessing the
//object3D.rotation
let rotation = el.getAttribute('rotation');
//lets rotate it to the same position
rotation.y += 360;
//set the animation component accordingly:
el.children[0].setAttribute('to',rotation);
//emit the 'begin' event:
el.emit('startrotating');
});
}
});
Quick Improvement if necessary: disable the listener, when the animation is triggered. Made with a boolean switched on the mouseenter event, and the animationend event.
2. You can choose not to use the animation component, and check on tick() if the cursor is over. If so, rotate the element by the actualRotation.y+0.1 ( or any other desired rotation ).
As noted before, You can access the rotation by getAttribute() or el.object3D.rotation.
As for the scale, You if You need to rotate + rescale the object on the mouseenter event, just add another animation, and use it like i did with the rotation.I'm not sure how it's usually done, in my experience animations are good, when there are not that many interactions, because they sometimes do unexpected things, which You have to predict/find out, and prevent.
On the other hand, making any animation manually ( changing properties on tick ) may seem laggy if the rotation delta is too big. You need to play with it, and find out which suits You best.

How do I "preserve" an entity when changing its parent in the DOM?

I have a "variable assignment" component that looks like the following (the blue diamond with the yellow sphere attached):
http://i.imgur.com/nJotPgW.gif (unfortunately I don't have enough reputation to inline the image)
Here the yellow sphere that things can snap into is created in the initialization of the variable assignment component like so
AFRAME.registerComponent('variable-assignment', {
schema: {
grabbable: {default: true}
},
init: function () {
this.el.innerHTML = `
<a-sphere
snap-site="controller:#right-hand"
radius=".1"
color="yellow"
material="transparent:true; opacity:.5;"
position=".22 0 0">
</a-sphere>
`;
this.label = 'x';
this.el.setAttribute('geometry', {
primitive: 'octahedron',
radius: .1,
color: 'blue'
});
...
The snap-site component has code that detects a collision with the red sphere and then makes it a child element. So the DOM looks something like this before the collision.
<a-sphere color=red></a-sphere>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere>
</a-entity>
and after
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere color=red></a-sphere>
<a-sphere>
</a-entity>
The problem is when I want to move the entity with variable-assignment inside another DOM element using appendChild the initialization function for the variable-assignment called again the innerHTML is reset. So for example if we have
<a-entity container></a-entity>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere color=red></a-sphere>
<a-sphere>
</a-entity>
And we want to move variable-assignment into container using something like containerElement.appendChild(variableAssignmentEntity) the inner red sphere gets removed
<a-entity container>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere>
</a-entity>
</a-entity>
As a work around/hack I was thinking about using a flag of some sort to see if initialize had already been called before for the element/entity the component was a property of and then not run the initialization code, something like
init: function () {
if (this.el.getAttribute('initialized')) {
return;
}
this.el.setAttribute('initialized', true);
...
but this seems like a bad way to do it and also looking into the A-Frame source it seems appendChild causes all components to be removed then added again so it doesn't actually work either or at least causes other things to break.
Is there a good way to do this or is there a different way to define the variable-assignment component so the yellow sphere snap-site component isn't a child set in the initialization?
You're correct that appendChild causes components to be re-initialized, and might not work as expected. Here are a couple alternatives:
After removing the element, call el = el.cloneNode() to make a clean copy and append that, instead.
Don't actually make the sphere a child of your variable-assignment component, but instead just have the 'parent' component update the position/rotation of the child in the tick() function.
Use the underlying three.js API to pass around the THREE.Object3D, without actually changing the parent of any elements.
An example of (3):
var mesh = el.getObject3D('mesh)';
el.removeObject3D('mesh');
otherEl.setObject3D('mesh', mesh);

Resources