OnClick on model in AFrame-AR.js scene - onclick

I am working on an Augmented reality scene using Aframe and ARJS. I am currently rendering obj models when the marker is detected. My requirement is to be able to click on individual models upon rendering and manipulate them. For some reason onclick doesnt seem to be working on aframe model entities but it works fine on other primitive entities like box . This is my approach -
AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('click', function (evt) {
console.log('I was clicked at: ', evt.detail.intersection.point);
});
}
});
</script>
</head>
<body>
<a-scene embedded arjs='trackingMethod: best; debugUIEnabled: false;' foo>
<a-assets>
<a-asset-item id="crate-obj" src="model.obj"></a-asset-item>
<a-asset-item id="crate-mtl" src="model.mtl"></a-asset-item>
<img id="texture" src="brick.jpg">
</a-assets>
<a-marker preset='hiro'>
<a-entity ><a-obj-model class="collidable" cursor-listener id="animated-marker" src="#crate-obj" position="0 -1.6 0" mtl="#crate-mtl" rotation="-90 0 0" scale="0.004 0.004 0.004" material="" obj-model=""></a-obj-model></a-entity>
//onclick doesn't work
<a-entity material=" src: url(box.png) " class="collidable" cursor-
listener position="0 -1 0"></a-entity> //onclick works here
</a-marker>
<a-camera-static/>
</a-scene>
</body>
</html>
Is there anything I might be overlooking ? Or is there any other way to achieve this requirement.Thanks.

You need to use the cursor component, since the click event is based on raytracing in 3D.
<a-marker preset='hiro' cursor='rayOrigin: mouse'></a-marker>

Related

A-frame click event doesn't work for object intersecting cursor when entering VR

I have created an A-frame scene, and I am using a large invisible sphere to catch any mouse click to change an image. This works perfectly before entering VR mode, however, on entering VR mode, the click events are not caught until you look away from the object and look back.
From reading the documentation my guess is that this isn't counting the cursor as intersecting the object if it starts the scene in that position?
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('clickhandler', {
init: function () {
this.el.addEventListener('mousedown', function (evt) {
document.querySelector("#sky").setAttribute("src", "photo1.jpg");
});
}
});
</script>
<a-scene embedded background="color: black">
<a-entity camera look-controls position="0 0 0">
<a-cursor visible="false"></a-cursor>
</a-entity>
<a-sky id="sky" src="photo-initial.jpg"></a-sky>
<a-entity id="circle" cursor-listener geometry="primitive: circle; radius: 25" visible="false" material="color: blue" clickhandler position="-2.5 0.25 -1.5" rotation="0 15 0"></a-entity>
</a-scene>
So when this scene starts, the image is in front of the view, a large invisible sphere fills the whole view - from the browser, I can immediately click to trigger the click event. But if I reload the page and go straight into VR and click, nothing happens until I click to look 180 degrees away, and then look back. Then if I click, the event triggers.

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.

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>

A-Frame .obj model displaying but broken

Total beginner to a-frame here, have been through the tutorial scenes and am now setting up my first using .obj models.
Using a remote server, feel like that's important information.
I've seen questions about models not showing up but mine is displaying broken and I'm not sure where to start fixing it.
This is how it looks in windows 3D builder:
And this is how it looks in my project (backed on pink plane for contrast):
Here's the html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pokemon Stadium</title>
<link href="css/main.css" rel="stylesheet">
<script src="https://aframe.io/releases/0.4.0/aframe.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<!-- Scene -->
<a-scene onLoad="">
<!------------------------------------------------ Assets --------------------------------------------------------->
<a-assets>
<a-asset-item id="stadium-obj" src="assets/models/stadium/Pokemon+Stadium.obj"></a-asset-item>
<a-asset-item id="stadium-mtl" src="assets/models/stadium/Pokemon+Stadium.mtl"></a-asset-item>
<a-asset-item id="ivy-obj" src="assets/models/ivysaur/Pokemon.obj"></a-asset-item>
<a-asset-item id="ivy-mtl" src="assets/models/ivysaur/Pokemon.mtl"></a-asset-item>
</a-assets>
<!------------------------------------------------- Scene --------------------------------------------------------->
<!-- Skybox -->
<a-sky color="#279DF4"></a-sky>
<!-- Abyss -->
<a-plane scale="1000 1000" position="0 -10 0" color="#212121" rotation="-90 0 0" material="shader: flat"></a-plane>
<!-- Stadium -->
<a-entity obj-model="obj: #stadium-obj; mtl: #stadium-mtl" position="0 0 -30" scale="0.05 0.05 0.05" material="side: double"></a-entity>
<!-- Bulbasaur -->
<a-entity obj-model="obj: #ivy-obj; mtl: #ivy-mtl" position="15 10 0" scale="1 1 1" rotation="0 90 0"></a-entity>
<!-- Camera + cursor -->
<a-entity camera look-controls position="10 12 0" rotation="-23 -90 0">
<a-cursor id="cursor"
animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
animation__fusing="property: fusing; startEvents: fusing; from: 1 1 1; to: 0.1 0.1 0.1; dur: 1500"
event-set__1="_event: mouseenter; color: springgreen"
event-set__2="_event: mouseleave; color: black"
fuse="true"
raycaster="objects: .link"></a-cursor>
</a-entity>
</a-scene>
</body>
</html>
You probably have to set the type of side of the material to THREE.DoubleSide. With A-Frame you should be able to change the type using the following DOM attribute on an AEntity: material="side: double".
If this is not the case, you should update your post with a snippet of your scene elements.
EDIT:
As shown in the image, parts of my model are rendered incorrectly. The modelloader in THREEjs reads all meshes in a model and binds them to a grouped object. To fix this, you have to set the side of the material of the meshes to DoubleSided. Luckily in A-Frame, the obj-model component emits an event when the model has loaded successfully, we add a listener for the emitted event model-loaded on the DOM element and append a callback which returns a customevent. The customevent sends a reference to the model group. Query for the AEntity, I've appended an id modelEl to mine.
<script>
(function(modelEl) {
if (!window['AFRAME'] && !modelEl) {
return;
}
modelEl.addEventListener('model-loaded', function(evt) {
var model = evt.detail.model;
traverse(model);
});
})(document.getElementById('stadium'));
function traverse(node) {
node.children.forEach(function(child) {
if (child.children) {
traverse(child);
}
updateMaterial(child['material'], THREE.DoubleSide);
});
}
function updateMaterialSide(material, side) {
if (!material) {
return;
}
if (material instanceof THREE.Material) {
material.side = side;
material.needsUpdate = true
} else if (material instanceof THREE.MultiMaterial) {
material.materials.forEach(function(childMaterial) {
updateMaterial(childMaterial, side);
});
}
}
</script>
After running the above script, my model, and some of the texture loading, has been fixed.
Something to consider would be digging into creating custom components and modify or extend the obj-model component to prevent having to query for every model that may have the same issue.
If none of this worked, I believe something might be wrong with your wavefront obj export settings.
EDIT2:
Regarding my comment, here is a result of the fixed obj file in A-Frame:
For convenience sake you can find the MTL and OBJ file here:
obj file
mtl file

A-frame: How to setup wall colliders

Can anyone tell me how to setup wall colliders? I have setup a room using OBJ files for the walls. Many thanks.
Take a look at the source code for Don McCurdy's "Walls" example:
https://sandbox.donmccurdy.com/vr/walls/
Note the addition of the physics component in the a-scene element. This is what gets the magic going.
You need to include the aframe-extras script along with aframe.
For anyone looking for a good solution nowadays, the best I found so far is to use a nav-mesh
The nav-mesh is a simple 3d object that represents the walkable area in your project.
Here is what you'll need:
To generate the nav-mesh, use the plugin https://github.com/donmccurdy/aframe-inspector-plugin-recast
To move the camera you will not use wasd-controls, but aframe-extras's movement-controls
How to
Once the plugin is added to the page, follow these steps:
I found it was better to generate without the walls, so I hide the walls first, and make sure the floor ends where the walls would be. Also, keep all objects that user should not be able to pass through in their final place.
In the browser, use ctrl + alt + i to open the inspector
In the bottom part of the inspector, you can change cellSize and cellHeight to 0.1
Export and save it
in the HTML add a new asset:
<a-asset-item id="my-nav-mesh" src="assets/navmesh.gltf"></a-asset-item>
Add a new entity that points to the nav mesh:
<a-entity gltf-model="#my-nav-mesh" nav-mesh position="0 -0.1 0" visible="false"></a-entity>
Add the movement-controls to your camera Rig, with the constrainToNavMesh: true;. Here is how mine ended up:
<a-entity id="rig" movement-controls="constrainToNavMesh: true; speed: 0.8;" position="0 0 26">
<a-entity id="camera"
camera position="0 2.6 0"
look-controls="pointerLockEnabled: true">
<a-cursor></a-cursor>
</a-entity>
Expected Result:
So, by adding the nav-mesh and using the movement-controls instead of WASD, for example, you will make your camera moveable only on the created mesh.
You can also make the mesh invisible (adding visible="false to the nav-mesh entity), and toggle its position on Z so it doesnt feel like a higher plane.
Source
I actually got this information structured from this amazing free youtube video, from Danilo Pasquariello.
https://www.youtube.com/watch?v=Y52czIft9OU
How my project is looking after doing the steps above (I just made the mesh visible again for this screenshot
kinematic-body.js is deprecated.
Don McCurdy encourages the use of teleportation
See this post too: Move camera in aframe with physics engine
aframe inspector plugin didn't work on my project.
I did that temporary
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
<script
src="https://unpkg.com/aframe-aabb-collider-component#^2.2.1/dist/aframe-aabb-collider-component.min.js"></script>
<script src="https://unpkg.com/aframe-event-set-component#3.0.3/dist/aframe-event-set-component.min.js"></script>
<script>
let isStop = false
AFRAME.registerComponent("cam", {
init: function () {
window.addEventListener('keypress', e => {
if (isStop) {
const camera = document.getElementById('camera')
if (e.key === 's' || e.key === 'a' || e.key === 'd') {
camera.setAttribute('wasd-controls-enabled', 'true')
isStop = false
}
}
})
this.el.addEventListener("hitclosest", (e) => {
console.log('ok');
isStop = true
this.el.setAttribute('wasd-controls-enabled', 'false')
})
this.el.addEventListener("hitstart", (e) => {
isStop = true
this.el.setAttribute('wasd-controls-enabled', 'false')
})
}
})
</script>
<a-scene>
<a-entity id="rig" position="0 .5 -1">
<a-camera wasd-controls-enabled="true" cam id="camera" aabb-collider="objects: .collide"
geometry="primitive: box" aabb-collider="objects: a-box">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<a-box color="blue" class="collide" width='1' height='1' position="0 1.6 -5">
</a-box>
<a-box color="red" class="collide" width='1' height='1' position="2 1.6 -5">
</a-box>
<a-box color="pink" class="collide" width='10' height='1' position="10 1.6 -5">
</a-box>
</a-scene>
Here's ammo, which is a library for aframes.
https://github.com/n5ro/aframe-physics-system/blob/master/AmmoDriver.md#ammo-shape
You could read Collision Filtering for the detailed solution.

Resources