How to get real position in space of the entity in aframe? - aframe

I have tried multiple ways to get the position of the element immediately after animation but every time I am receiving a hard-coded position of the entity that I have entered. I have used tick function to track the object all the time. These are the following ways I have tried to keep a track of current location of the entity but of them are returning a position that I have assigned to the entity. Is it a bug in aframe 1.2.0 because in previous versions its working fine.
Object.values(el.getAttribute('position'))
el.object3D.animations
Object.values(document.querySelector('#duck1').object3D.getWorldPosition(new THREE.Vector3()))
Object.values(el.object3D.getWorldPosition(new THREE.Vector3()))
For the reference I have atttached the live code as well. Right after mouseenters into cube the animation will trigger. After the end of animation location of the entity will be shown in console.log entry
The Code:
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('eventhandling', {
tick: function () {
var el = this.el;
var entity = document.querySelector('#duck1');
el.addEventListener('animationcomplete', function(){
console.log("getAttribute: "+Object.values(el.getAttribute('position')));
console.log("object3D.position: "+(el.object3D.animations));
console.log("document.querySelector: "+ Object.values(document.querySelector('#duck1').object3D.getWorldPosition(new THREE.Vector3())));
console.log("object3D.getWorldPosition(new THREE.Vector3()): "+Object.values(el.object3D.getWorldPosition(new THREE.Vector3())));
entity.emit('starteventforAnimation');
});
}
});
</script>
</head>
<body>
<a-scene>
<a-entity class="rota" id="duck1" color="#FF0000" scale="0.1 0.1 .1" position="2 0 -7" animation="property: rotation;from: ; to:0 -360 0; loop:true; easing:linear; dur:30000; pauseEvents: mouseenter; resumeEvents: starteventforAnimation " animation__mouseenter="property: rotation;from: ; to:0 360 0; easing:linear; dur:4000; startEvents: mouseenter ;pauseEvents: starteventforAnimation; resumeEvents: mouseenter" eventhandling>
<a-box class="rota" color="#FF0000" gltf-model="spaceship.glb" position="20 0 -10" scale="2 3 3" collison-check="el: #otherduck; radius: 0.15; other-radius: 0.15;"> </a-box>
</a-entity>
<a-camera position="0 1.2 1.3"><a-cursor objects=".rota" ></a-cursor></a-camera> <
</a-scene>
</body>
</html>

You're looking for something called "world position".
The sphere is always at the same position regarding the box. The rotation of the box causes the sphere to move because the entire frame of reference is rotating. Its local position stays the same, but its world position is changing.
One way of getting the world position would be:
// local position
this.el.getAttribute("position");
// grab the reference to the underlaying object
const mesh = this.el.getObject3D("mesh");
// or const mesh = this.el.object3D;
// create a vector where the position will be copied to
const worldpos = new THREE.Vector3();
// get the world position - it's in the worldpos vector
mesh.getWorldPosition(worldpos);
Like i did here:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("foo", {
init: function() {
// i'll keep the local position here
this.localpos = new THREE.Vector3();
// I'll keep the world position here
this.worldpos = new THREE.Vector3();
// this is the reference to the <p> element
this.textEl = document.querySelector("p")
},
// utility function
posToString: function(pos) {
return pos.x.toFixed(2) + " " + pos.y.toFixed(2) + " " + pos.z.toFixed(2);
},
// called on each frame
tick: function() {
// getAttribute("position") has the local position
// this.el.object3D.position has the local position
// this.el.getObject3D("mesh").position has the local position
this.localpos.copy(this.el.getAttribute("position"))
this.el.getObject3D("mesh").getWorldPosition(this.worldpos)
// compose the displayed message
let msg = "";
msg += "Sphere local position:" + this.posToString(this.localpos)
msg += "<br>"
msg += "Sphere world position:" + this.posToString(this.worldpos)
this.textEl.innerHTML = msg
}
})
</script>
<p style="position: fixed; z-index: 999;"></p>
<a-scene>
<a-box position="0 1 -4" color="blue"
animation="property: rotation;from: ; to:0 -360 0; loop:true; easing:linear; dur:3000">
<a-sphere position="2 0 0" color="green" radius="0.25" foo></a-sphere>
</a-box>
</a-scene>

Related

How to sync the position of entity with respect to two animations present inside same entity tag in aframe?

I have two animations present in my project: 'animation' and 'animation__mouseenter'. I am trying to control the animation by using properties like: startEvents, resumeEvents and pauseEvents. Everything is working fine until entity switches from 'animation__mouseenter' to 'animation'. After the completion of animation__mouseenter'. The entity starts from the last position where 'animation' left the entity, whereas what I am looking for is that it should continue from new location assigned by 'animation__mouseenter'.
I have also tried to use .setAttribute from JavaScript and tried to adjust the from property for 'animation' with the help of tick function which will continuously update the new location of the entity.
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('eventhandling', {
tick: function () {
var el = this.el;
var entity = document.querySelector('#duck1');
el.addEventListener('animationcomplete', function(){
entity.emit('starteventforAnimation');
});
}
});
</script>
</head>
<body>
<a-scene>
<a-entity class="rota" id="duck1" color="#FF0000" scale="0.1 0.1 .1" position="2 0 -7" animation="property: rotation;from: ; to:0 -360 0; loop:true; easing:linear; dur:30000; pauseEvents: mouseenter; resumeEvents: starteventforAnimation " animation__mouseenter="property: rotation;from: ; to:0 360 0; easing:linear; dur:4000; startEvents: mouseenter ;pauseEvents: starteventforAnimation; resumeEvents: mouseenter" eventhandling>
<a-box class="rota" color="#FF0000" gltf-model="spaceship.glb" position="20 0 -10" scale="2 3 3" collison-check="el: #otherduck; radius: 0.15; other-radius: 0.15;"> </a-box>
</a-entity>
<a-camera position="0 1.2 1.3"><a-cursor objects=".rota" ></a-cursor></a-camera> <
</a-scene>
</body>
</html>
From what I understand you want to change the direction of the rotation at any point (and slow it down, etc).
You're moving the object around by rotating the parent. Which means position doesn't play a major role here - rotation does.
Id use only one animation, and a component which would:
grab the current rotation, add or subtract 360
change the duration
update the existing animation with new values.
Here's an example with a couple of neat tricks:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("animation-manager", {
init: function() {
// bind updateAnimation() so that we have new functions
// doing exactly what we need
this.rotateClockwise = this.updateAnimation.bind(this, 1);
this.rotateCounterClockwise = this.updateAnimation.bind(this, -1);
// bind the above functions to mouseenter/mouseleave
this.el.addEventListener("mouseenter", this.rotateClockwise)
this.el.addEventListener("mouseleave", this.rotateCounterClockwise)
},
updateAnimation: (function() {
// this is an IIFE - there two vectors are created only once
// even though the returned function is called multiple times
const fromRotation = new THREE.Vector3();
const toRotation = new THREE.Vector3();
return function(dir) {
// get current rotation, and move the target rotation by 360 or -360
// depending on the direction
fromRotation.copy(this.el.getAttribute("rotation"));
toRotation.copy(this.el.getAttribute("rotation"));
toRotation.y += dir * 360
// update the animation
this.el.setAttribute("animation", {
"from": AFRAME.utils.coordinates.stringify(fromRotation),
"to": AFRAME.utils.coordinates.stringify(toRotation),
"dur": dir == -1 ? "8000" : "1000"
})
}
})()
})
</script>
<a-scene cursor="rayOrigin: mouse" raycaster="objects: a-box">
<a-box position="0 1 -4" color="blue" animation-manager
animation="property: rotation; to:0 -360 0; loop:true; easing:linear; dur:8000">
<a-sphere position="2 0 0" color="green" radius="0.25" foo></a-sphere>
</a-box>
</a-scene>
The neat tricks being function binding, IIFEs, and AFRAME.Utils

How to control camera rig orientation with optically tracked pinch in A-frame?

I wanna replicate what this guy does.
Basically, you go back and forth from one corner of your room to another and rotate the scene when you reach the guardian fence.
https://repl.it/#tetegithub/aframe#index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.1.0/aframe.min.js">
</script>
</head>
<body>
<a-scene>
<a-box id="box" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
<a-entity id="rig" rotation="0 0 0">
<a-entity id="camera" camera look-controls></a-entity>
<!-- *** -->
<a-entity id="leftHand"
hand-tracking-controls="hand: left;modelColor:#E9967A;"
onpinchstarted="rig.object3D.rotation.x +=Math.Pi;"></a-entity>
<!-- *** -->
<a-entity id="rightHand" hand-tracking-controls="hand: right;modelColor:#E9967A"></a-entity>
</a-entity>
</a-scene>
</body>
</html>
I've added "onpinchstarted" event in the left hand's tag in the hope that when I pinch with my left hand the camera rig will rotate. And it doesn't.I think I have to somehow work with the event listeners and handlers but all the docs I read look like they are written for the robots. Any advice appreciated.
Does the rotation work if you trigger it outside of the event listener?
I see you're referring to "rig" in onpinchstarted, does "rig" exist as a variable in that scope?
One solution would be to start with a helper function that does the rotation, then run it in the console to confirm it works. Then, attach it to the scene via a javascript instead of html (doesn't have to be a component, but it might be easier to reuse).
The docs are unclear if onpinchstarted would work vs pinchstarted https://aframe.io/docs/1.1.0/components/hand-tracking-controls.html#events_pinchended
Well, I came up with a sort of a solution by dissecting the example project.
rig.js
AFRAME.registerComponent('rig', {
init: function () {
this.bindMethod();
this.el.sceneEl.addEventListener('pinchstarted', this.onPinchStarted);
},
//I still don't get what this thing does.
bindMethod: function () {
this.onPinchStarted = this.onPinchStarted.bind(this);
},
onPinchStarted: function () {
this.el.setAttribute('rotation', {y: this.el.getAttribute('rotation').y + 30});
},
});
index.html
<script src="rig.js"></script>
<a-entity rig>
<a-entity camera look-controls position="0 1 0"></a-entity>
<a-entity hand-tracking-controls="hand: left; modelColor:#E9967A;"></a-entity>
<a-entity hand-tracking-controls="hand: right; modelColor:#E9967A;"></a-entity>
</a-entity>
Now I want the rig to yaw the same amount the camera has rotated while I was holding the pinch with my left hand.
And I made it work after a while thanks to this person.
index.html
<script src="rotator.js"></script>
<a-entity id="rig">
<a-camera></a-camera>
<a-entity
oculus-touch-controls="hand: left;"
hand-tracking-controls="hand: left; modelColor:#E9967A;"
rotator="rig: #rig"
></a-entity>
<a-entity
oculus-touch-controls="hand: right;"
hand-tracking-controls="hand: right; modelColor:#E9967A;"
rotator="rig: #rig"
></a-entity>
</a-entity>
As web XR hand tracking in the Oculus Browser where I test it is still experimental and unstable I added touch controllers' grip buttons.
rotator.js
/* global AFRAME, THREE */
AFRAME.registerComponent("rotator", {
schema: {
rig: { type: "selector" },
},
init: function() {
this.bindMethods();
this.el.addEventListener("pinchstarted", this.onPinchStarted);
this.el.addEventListener("pinchended", this.onPinchEnded);
this.el.addEventListener("gripdown", this.onPinchStarted);
this.el.addEventListener("gripup", this.onPinchEnded);
this.rig = this.data.rig;
this.box = this.data.box;
this.box2 = this.data.box2;
this.camera = this.el.sceneEl.camera.el;
this.axisY = new THREE.Vector3(0, 1, 0);
},
bindMethods: function() {
this.onPinchStarted = this.onPinchStarted.bind(this);
this.onPinchEnded = this.onPinchEnded.bind(this);
},
onPinchStarted: function() {
this.trigger = 1;
this.oldCameraAngle = this.camera.getAttribute("rotation").y;
},
tick: function() {
if (this.trigger == 1) {
var angleDifference = THREE.Math.degToRad(
this.oldCameraAngle - this.camera.getAttribute("rotation").y );
this.oldCameraAngle = this.camera.getAttribute("rotation").y;
var cameraPosition = new THREE.Vector3();
cameraPosition.setFromMatrixPosition(this.camera.object3D.matrixWorld);
this.rig.object3D.position.add( cameraPosition.negate() );
this.rig.object3D.position.applyAxisAngle( this.axisY, angleDifference );
this.rig.object3D.position.add( cameraPosition.negate() );
this.rig.object3D.rotateOnAxis( this.axisY, angleDifference );
}
},
onPinchEnded: function() {
this.trigger = 0;
}
});
GitHub link and the version published on my website.

Dynamically created A-FRAME content set to embedded will not show A-FRAME inspector

I'm creating A-FRAME scenes dynamically with a function that takes a image path as an argument and returns a built A-FRAME a-scene.
The returned value is working and appearing correctly in the DOM though attempting to initialize the A-FRAME inspector with ctrl + alt + i does not work, mainly due to the dynamically appended content.
Is there a flag or setting within A-FRAME to allow for the inspector to work with dynamically appended content or other such workaround?
function buildCelestialBody(a) {
let aframe= `
<!-- AFRAME CONTENT -->
<!-- MAIN SCENE -->
<a-scene embedded
loading-screen="dotsColor: #ffffff; backgroundColor: black;"
vr-mode-ui="enabled: false fuseTimeout: 0">
<!-- ASSETS -->
<a-assets>
<img id="celestialbody" src="${a}"></img>
</a-assets>
<!-- CAMERA -->
<a-entity id='cameraWrapper' position="0 0 0" rotation="0 0 0">
<a-camera id="camera1" camera="active: true; zoom: 3.5;
spectator: false;"
look-controls="enabled: false"></a-camera>
</a-entity>
<!-- CELESTIAL BODY -->
<a-sphere src="#celestialbody"
geometry="primitive: sphere;
segmentsHeight: 100;
segmentsWidth: 100"
scale="1 1 1"
position="0 0 -8.5;"
animation="property: rotation;
dur: 200000;
from: 400 360 0;
to: 400 -360 0;
dir: reverse;
loop: true;
easing: linear"
id="celestialbody" celestialbody>
</a-sphere>
<!-- LIGHT -->
<a-entity id="light"
light="type:directional; castShadow:true;"
position="2 1 1"
animation="property: position;
dur: 2000;
from: 2 1 1;
to: -2 1 1;
dir: reverse;
loop: true;
autoplay: false;
easing: easeInQuad;">
</a-entity>
</a-entity>
<!-- LIGHT SOURCES -->
<a-light id="light_1" type="ambient" color="#9ecdf9" intensity="0.460" position="2 4 4"></a-light>
</a-scene>
<!-- AFRAME CONTENT END -->`
return aframe;
}
You need to place your creation code within a component, not simply a globally scoped function. Placing it within a component insures that everything( inlcuding the inspector) is loaded first, then your code runs, and the objects will be registered by the inspector.
AFRAME.registerComponent('treeman', {
init: function(){
let el = this.el;
let sceneEl = document.querySelector('a-scene');
let trees = sceneEl.querySelector('#trees');
el.addEventListener("model-loaded", e =>{
//trees.emit('modelisloaded');
let tree3D = el.getObject3D('mesh');
if (!tree3D){return;}
tree3D.traverse(function(node){
if (node.isMesh){
node.renderOrder = 2;
console.log(node.name, node.renderOrder, node.material);
node.material.color=new THREE.Color(0xaa5522);
node.material.map = null;
}
});
let countX = 10;
let cubes = [];
let size = 0.125, spacing = 0.05, x;
for (let i=0; i<countX; i++){
cubes[i] = document.createElement('a-entity'); // create the element
// create components, id, geometry, position
cubes[i].setAttribute('id', 'tree_'+i.toString());
cubes[i].setAttribute('gltf-model', '#tree');
x = (size + spacing) * countX * (-0.5) + i * (size + spacing) ;
y = Math.random() * 0.25;
z = Math.random() * 1;
cubes[i].setAttribute('position', x.toString()+ ' '+y.toString()+' '+z.toString() );
// you can add event listeners here for interaction, such as mouse events.
sceneEl.appendChild(cubes[i]);// Append the element to the scene, so it becomes part of the DOM.
}
});
}
});
<a-entity id="treemodel" visible="false" gltf-model="#tree" scale="5 5 5" treeman></a-entity>
I just ran this an confirmed that the dynamically created objects do appear within the inspector.
glitch here

adding object dynamically in live a-frame camera view

I am trying to place an image in a-frame camera view. please share an example.
A quick way to do this is to add an invisible "marker" as a child of the camera, and use its position as the spawn point when adding an object.
HTML
<a-scene>
<a-camera>
<a-entity id="marker" position="0 0 -5"></a-entity>
</a-camera>
<a-cylinder id="floor" height="0.1" radius="10" color="green"></a-cylinder>
</a-scene>
JS
var sceneEl = document.querySelector('a-scene');
var markerEl = document.querySelector('#marker');
// Add boxe when spacebar is pressed.
document.addEventListener('keyup', function (e) {
if (e.keyCode !== 32) return;
var newEl = document.createElement('a-box');
newEl.setAttribute('color', 'red');
sceneEl.appendChild(newEl);
var position = markerEl.object3D.getWorldPosition();
position.y = 0.5;
newEl.setAttribute('position', position);
});
Codepen: https://codepen.io/donmccurdy/pen/QOOXbK?editors=1010

a-frame: Unable to change camera position and rotation angle

I am using the a-frame components for a WebVR application. The camera has look-controls with a cursor to let the user pick an object. And when the event is fired I wish to change the camera position. This is what I have done. A camera is defined in the scene.
<a-entity id="cam" camera="userHeight: 1.6" look-controls>
<a-entity cursor="fuse: true; fuseTimeout: 500" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03" material="color: black; shader: flat">
</a-entity>
</a-entity>
I have a box with a listener as follows
<a-box box-listener color="tomato" position="1 0 3" depth="1" height="1" width="1"></a-box>
When the user moves the cursor to the box and holds for half a second, the event gets fired. The event script is as follows:
AFRAME.registerComponent('box-listener', {
init: function() {
this.el.addEventListener('click', function(evt) {
var cam = document.getElementById("cam");
var campos = cam.getAttribute("position");
campos.x = 1; // some random change.
var camrot = cam.getAttribute("rotation");
camrot.x = 0;
camrot.y = 0;
camrot.z = 0; // setting it back to looking straight ahead
var look = cam.getAttribute("look-controls");
look.enabled = false;
cam.setAttribute('look-controls',look);
cam.setAttribute("position", campos);
cam.setAttribute("rotation", camrot);
look.enabled = true;
cam.setAttribute('look-controls',look);
});
}
});
By putting logs I find that the values are getting updated in the camera element however nothing changes on the screen.
where am I going wrong? Does the camera element need to be refreshed?
Thanks,
Raj

Resources