adding object dynamically in live a-frame camera view - aframe

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

Related

How to get real position in space of the entity in 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>

aframe - How to get what element collided with another element

I'm developing a simple VR game using A-Frame, and I'm struggling with collisions.
Specifically, I'm using aframe-physics-extras.min.js for the collision-filter, and aframe-extras.min.js for "hit" (and "hitend", in case) event handling.
In my game there are many bullets and many targets. I can get a "hit" event when a target is hit, but I can't find a way to get what bullet hit that target.
When a target is hit and I use the "hit" event, I can then refer to that specific target using "this.el", so that for example I can remove it from the scene with this.el.sceneEl.removeChild(this.el).
Is there a way to get the element that collided with the target? For example something like this.el.collidingEntity ?
This is the relevant part of the code:
// collision-filter : Requires aframe-physics-extras
// hit (and hitend, if used) : Requires aframe-extras
AFRAME.registerComponent('hit_target', {
init: function() {
this.el.addEventListener('hit', (e) => {
this.el.sceneEl.removeChild(this.el); // This is the Target
// this.el.collidingEntity.sceneEl.removeChild(this.el.collidingEntity); // THIS is what I'd need, to know what hit the Target
})
}
});
// Bullet
var elbullet = document.createElement('a-sphere');
elbullet.setAttribute('class', 'bullet');
elbullet.setAttribute('scale', '0.05 0.05 0.05');
elbullet.setAttribute('opacity', '1');
elbullet.setAttribute('color', '#ff3333');
elbullet.setAttribute('position', point);
elbullet.setAttribute('collision-filter', 'group: bullet; collidesWith: target');
elbullet.setAttribute('dynamic-body', 'shape: sphere; sphereRadius:0.05;');
elbullet.setAttribute('sphere-collider','');
document.querySelector('a-scene').appendChild(elbullet);
// Target
var eltarget = document.createElement('a-gltf-model');
eltarget.setAttribute('src', '#target');
eltarget.setAttribute('class', 'target');
eltarget.setAttribute('scale', '1 1 1');
eltarget.setAttribute('opacity', '1');
eltarget.setAttribute('position', (rnd(-8,8,0))+' '+(rnd(-8,8,0))+' '+(rnd(-20,-6,0)));
eltarget.setAttribute('collision-filter', 'group: target; collidesWith: bullet');
eltarget.setAttribute('hit_target','');
document.querySelector('a-scene').appendChild(eltarget);
A-Frame's aabb-collider component can help you print out the following:
bullet collided with ["plate_big", "plate_small"]
When the bullet hits the target, hitstart event will fire.
You can use event.target.id to get the bullet id.
You can use event.target.components["aabb-collider"]["intersectedEls"] to get the targets.
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll("a-entity").forEach(function(entity) {
entity.addEventListener("hitstart", function(event) {
console.log(
event.target.id,
"collided with",
event.target.components["aabb-collider"]["intersectedEls"].map(x => x.id)
);
});
});
});
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-aabb-collider-component#3.2.0/dist/aframe-aabb-collider-component.min.js"></script>
<a-scene>
<a-entity id="bullet" geometry="primitive: cone; radiusBottom:0.5; height:2;" material="color: red" position="0 1 -5" aabb-collider="objects: a-entity"></a-entity>
<a-entity id="plate_big" geometry="primitive: cylinder; height:0.2;" material="color: blue" position="0 0.8 -5" aabb-collider="objects: a-entity"></a-entity>
<a-entity id="plate_small" geometry="primitive: cylinder; height:0.2; radius:0.6;" material="color: blue" position="0 1.4 -5" aabb-collider="objects: a-entity"></a-entity>
</a-scene>

Add link to GLTF object in aframe

Is it possible to add a link to an GLTF 3d object (which is triggered with a marker?)
I've tried the usual 'a-link' method, the onClick method, even applying an id and using jQuery - all without luck - any help would be appreciated.
<a-scene embedded arjs>
<a-marker id="dragon" preset="custom" type="pattern" url="pattern-dragonfly.patt">
<a-entity animation-mixer="clip: *;" scale="1.5 1.5 1.5" gltf-model-next="src: url(dragon.gltf);"></a-entity>
</a-marker>
<a-entity camera></a-entity>
</a-scene>
To make this work, you need to create a cursor with a raycaster, and a custom component for the gltf.
<a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects: .clickable"></a-entity>
<a-entity id="tree" gltf-model="#gltftree" scale="5 5 5" treeman class="clickable" ></a-entity>
Inside the custom component, first you traverse the gltf and store references to the models that you want to be interactive, like this
init: function(){
let el = this.el;
let self = this;
self.trees = [];
el.addEventListener("model-loaded", e =>{
let tree3D = el.getObject3D('mesh');
if (!tree3D){return;}
console.log('tree3D', tree3D);
tree3D.traverse(function(node){
if (node.isMesh){
console.log(node);
self.trees.push(node);
node.material = new THREE.MeshStandardMaterial({color: 0x33aa00});
}
});
});
Then you make event listeners that detect intersection events, and save which object has been intersected, and highlight it, so users know it is live, like this
el.addEventListener('raycaster-intersected', e =>{
self.raycaster = e.detail.el;
let intersection = self.raycaster.components.raycaster.getIntersection(el);
console.log('click', intersection.object.name, self.mouseOverObject,
intersection.object.name != self.mouseOverObject );
if (self.mouseOverObject != intersection.object.name){
intersection.object.material.emissive = new THREE.Color(0xFFFF00);
intersection.object.material.emissiveIntensity = 0.5;
} else {
intersection.object.material.emissive = new THREE.Color(0x000000);
intersection.object.material.emissiveIntensity = 0.0;
}
self.mouseOverObject = intersection.object.name;
});
el.addEventListener('raycaster-intersected-cleared', e =>{
self.trees.forEach(function(tree){
tree.material.emissive = new THREE.Color(0x000000);
tree.material.emissiveIntensity = 0.0;
});
self.mouseOverObject = null;
});
Finally add a click listener that operate the hyperlink, like this
el.addEventListener('click', function(){
console.log(self.mouseOverObject);
if(self.mouseOverObject === "Trunk_A"){
console.log('link');
let url = 'https://supermedium.com/supercraft/';
let win = window.open(url, '_blank');
win.focus();
}
});
glitch here
Click the trunk to activate the hyperlink.

Event listener 'componentchanged' is only firing for 'rotation' and not 'position'

When trying to detect the movement of the camera's position using the componentchanged event listener, I'm only able to detect the rotation when outputting evt.detail.name to the console.
To move the camera around in the scene with the keyboard, I'm using the A-Frame Extras library.
Here's the code I'm using that outputs the rotation only...
Javascript:
document.addEventListener("DOMContentLoaded", function(event)
{
document.querySelector("a-entity[camera]").addEventListener("componentchanged", function (evt)
{
// The console message outputs 'rotation' and never outputs 'position'
console.log("Event name: " + evt.detail.name);
if(evt.detail.name === "position")
{
console.log("Camera has moved from " + evt.oldData + " to " + evt.newData);
}
else
{
console.log("position has not changed");
}
});
});
HTML:
<a-entity id="rig" movement-controls position="0 0 0">
<a-entity camera position="0 1.6 0" look-controls="pointerLockEnabled:false"></a-entity>
</a-entity>
Use a tick function to check for changes in position on the camera rig:
tick: function () {
var rigEl = this.el;
var currentPosition = rigEl.object3D.position;
// Compare to this.lastPosition (a vector3 you create)
this.lastPosition.copy(rigEl.object3D.position);
}
Have to agree-the tick answer isn't a great solution. Instead of calling function code once an event happens(in this case a change to the camera position) you're calling the tick function continuously(I'm guessing through setInterval) leading to a lot of unnecessary processing. It's not just the tick function call on the setInterval-but also any of the code the end developer places in that function(yes, you can shield this out by storing the position of the camera in the tick function and constantly comparing it to the new position so code only executes on position change-but why would you bother with all of this, when firing an event on an actual position change saves you the hassle?)
Keydown has its own drawbacks(the obvious one being AFrame on mobile doesn't use keydown-you'd have to lock onto touchstart or something), but it's still better than the alternative.
I was able to solve this issue. You can do the following...
document.addEventListener("DOMContentLoaded", function(event)
{
var cameraEl = document.querySelector("#camera");
var worldPos = new THREE.Vector3();
function getCameraPosition()
{
worldPos.setFromMatrixPosition(cameraEl.object3D.matrixWorld);
console.log("Position: " + worldPos.x + " " + worldPos.y + " " + worldPos.z);
}
document.addEventListener("keydown", function(evt)
{
// check if keys pressed are W, A, S, D and Up, Down, Left, Right
if (evt.keyCode == 87 || evt.keyCode == 65 || evt.keyCode == 83 || evt.keyCode == 63 || evt.keyCode == 38 || evt.keyCode == 37 || evt.keyCode == 39 || evt.keyCode == 40)
{
getCameraPosition();
}
});
});
You'll want to also add an ID to your camera...
<a-entity id="rig" movement-controls position="0 0 0">
<a-entity id="camera" camera position="0 1.6 0" look-controls="pointerLockEnabled:false"></a-entity>
</a-entity>

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