I created a class that helps me to show a 3D model using A-Frame. In this class, there are some spheres created at runtime ad inserted into the scene. I'm trying to add an event listener (I have to show a message when those spheres are clicked)
Here is the code:
export class AFrameObjViewNavMarkProvider implements Provider {
// Class Variables...
// Constructor...
// ----- Method ----- \\
public setPointerService(pointersService: PointersService) {
// method code...
}
public setPointerTrigger(value: boolean) {
// method code...
}
// ----- Handlers ----- \\
// click Handler
clickHandler(event, model: Model){
// code for saving into backend...
// save pointer into the back-end
this.pointersService.loadPointer(newPointer).subscribe((pointer) => {
this.showPointer(pointer);
});
}
showPointer(pointer: Pointer){
// create a string containing the position
let pointString = pointer.position[0].toFixed(3) + " "
+ pointer.position[1].toFixed(3) + " "
+ pointer.position[2].toFixed(3);
// compute the box that contains the model
let modelRef = <any>document.getElementById("model");
const box = new THREE.Box3().setFromObject(modelRef.object3D);
const boxSizes = box.getSize(new THREE.Vector3());
// compute the min size of the box (x, y, z)
// it will be used to set pointer radius
let minBoxSize = Math.min(boxSizes.x, boxSizes.y, boxSizes.z);
let radius = minBoxSize / 30;
let scene = document.getElementById("scene");
let marker = document.createElement("a-sphere");
scene.appendChild(marker);
marker.setAttribute("class", "pointer");
marker.setAttribute("radius", `${radius}`);
marker.setAttribute("color", "#CC0000");
marker.setAttribute("position", pointString);
}
// ----- Visual Methods ----- \\
renderModel(model: Model) {
// position-setter is used to set the model position according to its size
AFrameUtils.registerPositionSetter();
// reference to the provider itself
let caller: any = this;
function clickHandler(event) {
caller.clickHandler(event, model);
}
// sets the behaviour in response to a click event
AFRAME.registerComponent('click-handler', {
// init also calls update
init: function () {
let mouseDownTime: number = null;
let mouseDownPoint: any = null;
this.el.addEventListener('mousedown', event => {
mouseDownTime = new Date().getTime();
mouseDownPoint = event.detail.intersection.point;
});
this.el.addEventListener('mouseup', event => {
if(!event.detail.intersection) return;
let mouseUpTime = new Date().getTime();
let mouseUpPoint = event.detail.intersection.point;
// compute the differences (time and position) between press and release
let timeDiff = mouseUpTime - mouseDownTime;
// if press and release occur within 185 ms
// we consider the event as a click
if (timeDiff <= 185 && JSON.stringify(mouseDownPoint) === JSON.stringify(mouseUpPoint)) {
clickHandler(event);
}
});
}
});
let renderingArea = document.getElementById('rendering-area');
renderingArea.innerHTML = `
<a-scene embedded id="scene" cursor="rayOrigin: mouse" raycaster="objects: .clickable">
<!-- Assets definition -->
<a-assets>
<a-asset-item id="object-ref" src="${model.sources[0]}"></a-asset-item>
<a-asset-item id="material-ref" src="${model.sources[1]}"></a-asset-item>
</a-assets>
<!-- Using the asset management system. -->
<a-obj-model id="model" class="clickable" src="#object-ref" mtl="#material-ref" position-setter click-handler>
</a-obj-model>
<!-- Camera -->
<a-camera id="camera" wasd-controls="fly:true"></a-camera>
<!-- Environment elements-->
<a-sky id="sky" color="#000000"></a-sky>
</a-scene>
`;
setTimeout(() => {
this.pointersService.getPointersByModelId(model.id).subscribe(pointers => {
for(let pointer of pointers){
this.showPointer(pointer);
}
let markers = Array.from(document.getElementsByClassName('pointer'));
for(let marker of markers){
marker.addEventListener('click', () => {
console.log('click on pointer');
})
}
});
}, 125);
}
}
and a screenshot:
First Try: I tried to register another component, like this:
AFRAME.registerComponent('pointer-handler', {
init: function(){
this.el.addEventListener('click', () => {
console.log('click on pointer');
});
}
});
and use setAttribute("pointer-handler", "")
into the showPointer
method
Second Try: I tried to directly add the event listener using marker.addEventListener
into the showPointer
method.
Third Try: the third attempt can be found on the code, into the callback of setTimeout
.
Every attempt doesn't work; if I try to click on one sphere then nothing happens. However, if I open the A-Frame inspector, navigate to one of the spheres and click on it, the message gets logged. I suspect that the handler is added, but something causes the click to be undetected.
Every suggestion will be appreciated.
Thanks!
EDIT: You can find the repo here
Using the setAttribute("pointer-handler", "")
approach is absolutely valid and a correct way of doing what you want to achieve.
I think it may be the click
event that's causing problems. I suggest you replace it with mouseup
and mousedown
events.
Also make sure you can indeed fire the events - you need a cursor attached to your camera
run in full screen (top right corner after running snippet to see the whole scene - close is also in the top right).
You can add new elements by pressing the button in the top left. See how click sometimes is fired and sometimes not at all.
let boxCounter = 0;
function createNewElement() {
let box = document.createElement("a-box");
boxCounter++;
box.setAttribute("position", "" + boxCounter + " 0 -2");
box.setAttribute("scale", "0.5");
box.setAttribute("color", "red");
box.setAttribute("click-handler", "");
document.getElementById("scene").appendChild(box);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('click-handler', {
init: function() {
this.el.addEventListener('mousedown', (event) => {
console.log("I was mousedowned!");
this.el.setAttribute('material', 'color', 'green');
});
this.el.addEventListener('mouseup', (event) => {
console.log("I was mouseuped!");
this.el.setAttribute('material', 'color', 'red');
});
this.el.addEventListener('click', (event) => {
console.log("I was clicked!");
this.el.setAttribute('scale', '0.5 0.5 0.5');
});
}
});
</script>
<a-scene id="scene">
<a-entity camera 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>
<a-sphere color="red" position="0 0 -2" scale="0.5 0.5 0.5" click-handler></a-sphere>
</a-scene>
<button onclick="createNewElement()" style="width: 100px; height: 100px; position: absolute; top: 0; left: 0;" value="toggle"></button>
</body>
</body>
</html>