Using an Object's postion for an Event in AFrame - javascript

I'm trying to make a component that checks the current position of a sphere in an AFrame scene and when it hits a specific coordinate and when it does it fires an event (In example below it resets it to its default position):
AFRAME.registerComponent("trackball", {
update: function() {
let pos = this.el.getAttribute("position");
if (pos == {x:-21.821,y: 1,z: 0})
{
this.el.setAttribute("position", "-21.821 5 0");
}
}
});
I'm not sure what format is returned when .getAttribute("position") is called so that may be why it's not working. I am running AFrame 1.1.0.

First of all update is called when attributes are changed via setAttribute(). If you want a function that is called on each render frame, then use tick().
Secondly, try using a range, instead of a fixed point, it's very likely that the object will move past the point between two ticks.
Something like this:
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/n5ro/aframe-physics-system#v4.0.1/dist/aframe-physics-system.min.js"></script>
<script>
AFRAME.registerComponent("trackball", {
tick: function() {
let pos = this.el.getAttribute("position");
if (pos.y < 0.5) {
// reset position
this.el.setAttribute("position", "0 3 -4")
// sync
this.el.components["dynamic-body"].syncToPhysics();
}
}
});
</script>
<a-scene physics cursor="rayOrigin: mouse">
<a-sphere position="0 1.25 -5" radius="0.25" color="#EF2D5E" dynamic-body trackball></a-sphere>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
Also try using the object3D properties instead setAttribute() and getAttribute() when dealing with frequently called functions (which certainly applies to tick()):
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/n5ro/aframe-physics-system#v4.0.1/dist/aframe-physics-system.min.js"></script>
<script>
AFRAME.registerComponent("trackball", {
// iife to initialize the "origin point" once
tick: (function() {
const origin = new THREE.Vector3(0, 3, -4);
const y_range = 0.5;
return function() {
// check if the ball is out of range
const pos = this.el.object3D.position
if (pos.y < y_range) {
// reset position
pos.copy(origin);
// sync
this.el.components["dynamic-body"].syncToPhysics();
}
}
})()
});
</script>
<a-scene physics cursor="rayOrigin: mouse">
<a-sphere position="0 1.25 -4" radius="0.25" color="#EF2D5E" dynamic-body trackball></a-sphere>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
Keep in mind, updating the position in such manner is more performant, but will cause getAttribute("position") to return the last position set via setAttribute("position", new_position)

Related

aframe examples model-viewer and adding animation handler

I'm working on a project in which I want to repurpuse this example
https://aframe.io/aframe/examples/showcase/model-viewer/
all I want to add is a trigger that starts the animation on click event.
I have managed to implement this to run on my server
https://github.com/aframevr/aframe/tree/master/examples/showcase/model-viewer
but now struggling to code the event handler.
in model-viewer.js I can see a line that triggers animation at the start
modelEl.setAttribute('animation-mixer', '');
I cant seem to figure out how to play it on click.
I have done this implementation before in a simpler setup (https://codepen.io/wspluta/pen/rNwReNB)
<script>
AFRAME.registerComponent('animationhandler', {
init: function() {
let playing = false;
this.el.addEventListener('click', () => {
if (!playing) {
this.el.setAttribute('animation-mixer', 'clip:*; loop: once; clampWhenFinished: true; duration : 6');
playing=true;
} else {
this.el.removeAttribute('animation-mixer');
playing=false;
}
});
}
})
</script>
<body>
<a-scene>
<a-assets>
<a-asset id="sky" src="https://raw.githubusercontent.com/WSPluta/webxr102/main/tatooine.jpg"> </a-asset>
<a-asset-item id="tie" src="https://raw.githubusercontent.com/WSPluta/webxr102/main/newTie.gltf"></a-asset-item>
</a-assets> <a-entity id="tie" gltf-model="#tie" position="0 0.5 -5" scale="0.25 0.25 0.25" animationhandler></a-entity>
<a-plane id="background" position="0 5 -15" height="9" width="16" rotation="0 0 0" opacity="0.9"></a-plane>
<a-sky src="#sky"></a-sky>
<a-camera>
<a-cursor color="yellow"></a-cursor>
</a-camera>
</a-scene>
</body>
but I'm unable to figure out how to modify example/showcase document in order to implement it. I really want to reuse the camera movement and all the good stuff that comes from the example/showcase file.
I wanted to play an animation and used this code with react/nextjs to achieve this functionality.
const handleAnimate = useCallback(
(e) => {
const { keyCode } = e
const rot = document.getElementById("camOrbit").getAttribute("rotation");
// when w is pressed
if (keyCode === 87) {
document
.getElementById("modAnim")
.setAttribute("animation-mixer", `clip: Walk; loop: repeat`);
document.getElementById("modAnim").setAttribute("rotation", `0 ${rot.y} 0`);
//
//
//
//
// document.getElementById("modAnim").setAttribute("position", `${pos.x} 0 ${pos.z}`);
document.addEventListener("keyup", (event) => {
document
.getElementById("modAnim")
.setAttribute("animation-mixer", `clip: Idle; loop: repeat`);
});
}
},
[setAttribute]
);

Making a Button to move an object

I am making an A-Frame site and I need to create a button to move a ball with the id:"test" to it's starting position: 0 8 0. I've tried with the setAttribute script and it doesn't work. This is the javascript code I'm currently working with:
AFRAME.registerComponent('ballreset', {
events: {
click: function(evt)
{
document.getElementById('test').setAttribute('position', {x:0, y:8, z:0});
}
}
});
Edit: I found a typo in the code. But didn't solve problem
Without a full example its hard to tell what exactly is the problem but,
make sure you do have a raycaster - based cursor component. Mouse clicks won't work with webGL renders like they do with HTML elements.
make sure the click listener is working, ie. by logging each click
make sure the element with the given id exists at the time of the click
Below is a working version of something similar to what you want to achieve (click any object):
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('foo', {
events: {
click: function(evt) {
// grab the current position
let pos = this.el.getAttribute("position");
// move upwards
this.el.setAttribute('position', { x: pos.x, y: pos.y + 0.25, z: pos.z });
}
}
});
</script>
<!-- attach a cursor component -->
<a-scene cursor="rayOrigin: mouse">
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" foo></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" foo></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" foo></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" foo></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>

AFrame: reparenting an element keeping its world position, rotation

I'm trying to reparent an element (entity), keeping its position, rotation (and if possible, size, eg, scale) in the scene. Ideally, I would like to have a component (say "reparent") that when set on an entity, "moves" it to the specified parent, keeping the aspect of the entity in the scene. For example, for the next HTML code, the green entity would become a child of the red one, but would keep its position and rotation in the scene:
<a-scene id="scene" background="color: grey">
<a-entity id="red"
position="-1 3.5 -4" rotation="30 0 0" material="color: red"
geometry="primitive: cylinder"></a-entity>
<a-entity id="green" reparent="parent: #red"
position="-3 2 -4" rotation="0 45 0" material="color: green"
geometry="primitive: box"></a-entity>
</a-scene>
Apparently, this can be done at the three level using attach, but when I try to write a simple component using it, it doesn't work, apparently due to what the reparenting at the HTML does (I assume, because of AFrame).
I've written his test component to show the problem:
AFRAME.registerComponent('reparent', {
schema: {
parent: {type: 'selector', default: null},
},
init: function () {
const el = this.el;
const parent = this.data.parent;
move = function(evt) {
parent.object3D.attach(el.object3D);
console.log(el.object3D.parent.el.id, el.parentElement.id);
// parent.appendChild(el);
// console.log(el.object3D.parent.el.id, el.parentElement.id);
};
el.sceneEl.addEventListener('loaded', move);
}
});
When run, the result is red scene: the parent of object3D changed, but at the HTML level the parent of the element remains the scene. The green box appears as intended (in "-3 2 -4", world coordinates, and with the right rotation).
If I uncomment the two commented lines, I get a second line red red, but the green box disappears. In other words, now at the HTML the element got reparented, as intended, but somehow its object3D is not working anymore.
Any idea about why this fails, or some other idea for such a component?
All of this with AFrame 1.1.0.
After getting the advice that reusing the same element (entity) for reparenting is not a good idea (see Change parent of component and keep position), I explored the idea of copying the entity to be reparented in a new element under the new parent, and then remove the "old" one (instead of directly reparenting the entity). Granted, it is not the most clean hack in the world, but I hope it helps in my use case. So, I wrote a component:
AFRAME.registerComponent('reparent', {
schema: {
parent: {type: 'selector', default: null},
},
update: function () {
const el = this.el;
const parent = this.data.parent;
if (el.parentElement == parent) {
// We're already a child of the intended parent, do nothing
return;
};
// Reparent, once object3D is ready
reparent = function() {
// Attach the object3D to the new parent, to get position, rotation, scale
parent.object3D.attach(el.object3D);
let position = el.object3D.position;
let rotation = el.object3D.rotation;
let scale = el.object3D.scale;
// Create new element, copy the current one on it
let newEl = document.createElement(el.tagName);
if (el.hasAttributes()) {
let attrs = el.attributes;
for(var i = attrs.length - 1; i >= 0; i--) {
let attrName = attrs[i].name;
let attrVal = el.getAttribute(attrName);
newEl.setAttribute(attrName, attrVal);
};
};
// Listener for location, rotation,... when the new el is laded
relocate = function() {
newEl.object3D.location = location;
newEl.object3D.rotation = rotation;
newEl.object3D.scale = scale;
};
newEl.addEventListener('loaded', relocate, {'once': true});
// Attach the new element, and remove this one
parent.appendChild(newEl);
el.parentElement.removeChild(el);
};
if (el.getObject3D('mesh')) {
reparent();
} else {
el.sceneEl.addEventListener('object3dset', reparent, {'once': true});
};
}
});
You can check it works with the following HTML file:
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="reparent.js"></script>
</head>
<body>
<a-scene id="scene" background="color: grey">
<a-entity id="red"
position="1 2 -4" rotation="30 0 0" material="color: red"
geometry="primitive: cylinder"></a-entity>
<a-entity id="green" reparent="parent: #red"
position="-1 0 -4" rotation="0 45 0" material="color: green"
geometry="primitive: box"></a-entity>
<a-entity id="wf"
position="-1 0 -4" rotation="0 45 0" material="wireframe: true"
geometry="primitive: box"></a-entity>
</a-scene>
</body>
</html>
The wireframe entity is just to show how the green box stays exactly in the same position, although it was "reparented" under the red entity (explore the DOM to check that). As explained above, the green box is really a new one, cloned from the original one, which was then deleted by the component.
The component should work when its parent property is changed, allowing for dynamic reparenting when needed.

Can I change A-Frame component's attribute (loop from false to true) in the console?

I am trying to build a simple scene with A-Frame in JS to excercise the basics(codepen). So far so good I got the geometry like I want it, not perfect but it's ok.
<a-cylinder position="-4.8 1 0" radius="0.2" height="2" color="slategray">
<a-cylinder position="0 1.5 0" radius="0.15" height="1.5" color="slategray">
<a-box position="-1 0.5 0" width="2" height="0.5" depth="0.3" color="slategray">
<a-circle cursor-listener id="green" position="-0.5 -0.03 0.17" color="#196f3d " radius="0.2"></a-circle></a-circle>
<a-circle cursor-listener id="yellow" position="0 -0.03 0.17" color="#7d6608 " radius="0.2"></a-circle>
<a-circle cursor-listener id="red" position="+0.5 -0.03 0.17" color="#641e16" radius="0.2"></a-circle>
</a-box>
</a-cylinder>
</a-cylinder>
<a-ring position="0 0 0" rotation="-90 0 0 " color="gray" radius-inner="5" radius-outer="7">
<a-sphere id="car" position="-0 -1 0.4" radius="0.01" animation="property:rotation;to:0 0 -360; easing:linear; dur:5000;loop:false;autoplay:false">
<a-box position="-5.5 0 0" width="0.4" height="0.4" depth="0.4" ></a-box>
</a-sphere>
</a-ring>
I can make the rectangle orbit around the center of the scene, but what i want to do is make the animation a click event based, so I though of switching the loop attribute from false to true.
i went ahead and tried it in the console but nothing changed.
var s=document.getElementById("car")
s.components.animation.animation.loop=true
Note: I've also changed the autoplay to false and tried switching it back from the console by executing
var s=document.getElementById("car")
s.components.animation.animation.autoplay=true
but again nothing changed.
I think I misunderstood some in the documentation, I'm not sure exactly.
Can somebody please help me.
---------------------------------EDIT------------------------
I've added these events setting to the animation attribute of the the parent of component of my box
startEvents: rotation-start; pauseEvents: rotation-pause; resumeEvents: rotation-resume;
e.g :
<a-sphere id="car" position="-0 -1 0.4" radius="0.01" animation="property:rotation;startEvents: rotation-start; pauseEvents: rotation-pause; resumeEvents: rotation-resume;to:0 0 -360; easing:linear; dur:5000;loop:true;">
<a-box position="-5.5 0 0" width="0.4" height="0.4" depth="0.4" ></a-box>
</a-sphere>
so now I can play/pause the animation in the browser console using :
var e=document.getElementById("car")
e.components.animation.animation.play()
e.components.animation.animation.pause()
but trying to do it inside script tag so that i can utilize the cursor-listener event doesn't work :
<script>
AFRAME.registerComponent('cursor-listener', {
update: function () {
this.el.addEventListener('click', function (evt) {
var car=document.getElementById("car");
if(this.id=="red"){
car.components.animation.animation.pause()
}
if(this.id=="green"){
console.log(this.data);
this.components.animation.animation.play()
}
});
}
});
</script>
throws error:
TypeError: this.components.animation is undefinedtraffic_light.html:23:21
---------------------------------------------------EDIT N°2-------------------------
I fixed it , here is the new js :
<script>
AFRAME.registerComponent('cursor-listener', {
update: function () {
this.el.addEventListener('click', function (evt) {
var car=document.getElementById("car_origin");
if(this.id=="red"){
car.components.animation.animation.pause()
}
if(this.id=="green"){
car.components.animation.animation.play()
}
});
}
});
</script>

Registered Component click event in Aframe Version 0.8.2 and master isn't working

I was using this exact code in the previous version of Aframe 0.8.0 And the clicking was working normally.
However, when I tried using the 0.8.2 or the master version it showed no signs of clicking and without showing any errors
This is my registered component which I would like to detect clicks on the entities it is attached to
AFRAME.registerComponent('change-color-on-hover', {
init: function () {
var data = this.data;
var el = this.el; // <a-box>
var defaultColor = el.getAttribute('material').color;
/**
* Attach 'click' event
*/
el.addEventListener('click', function () {
alert('clicked');
});
}});
The Element where I want to detect the click (I am setting its position later and they it's appearing where they should be)
<a-image position="" src="#blue_target_rendered" height="30" width="40" depth="1" shadow event-set__click="_event: click; color:black" change-color-on-hover look-at="[camera]" id="hotspot-{{$hotspot->id}}" data-link-to="{{$hotspot->link_to}}" data-link-from="{{$hotspot->link_from}}">
With a setup like this:
<a-entity id="cameraParent" position="0 0 0" >
<a-entity id="cam" camera="zoom:1;" look-controls collider-check position="0 0 0" >
<a-entity cursor=" rayOrigin: mouse" geometry="primitive: ring; radiusInner: 0; radiusOuter: 0" material="color: black; shader: flat"></a-entity>
<a-entity raycaster="showLine: true; far: 1000"></a-entity>
</a-entity>
</a-entity>
The line component created by the raycaster is interfering with the rays emitted by the cursor = rayOrigin: mouse. In other words, you're clicking on the element with the line on each click.
If you reposition the raycaster a bit:
<a-entity position="0 -0.1 0" raycaster="showLine:true; far: 1000"></a-entity>
The mouse cursor should be working fine.

Categories

Resources