How to detect camera and sphere's distance in A-Frame - aframe

I'm trying to display some text when the camera move closer to the sphere. The idea is when user see the sphere moves much closer, the text then shows up, let's say, "Hello". But right now I only know how to add text with fixed position by using a-entity, I have no idea how to detect the distance between camera and sphere, and display text when user can see the sphere comes closer.
Here's my code now:
<html>
<head>
<script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-sphere position="0 0 0" perspective="true" radius="1.5" color="#aaa" id="sphere1"></a-sphere>
<a-entity position="4.5 2 0" text="width: 10; color: white; value: Hello"></a-entity>
<a-sky color="#000"></a-sky>
<a-entity class="camwrap" position="0 0 0">
<a-camera look-controls wasd-controls="fly:true acceleration:1" near="1" position="0 0 20" user-height="0" fov="60">
</a-camera>
</a-entity>
</a-scene>
<script>
const cam = document.querySelector("a-camera");
setTimeout(function() {
cam.components["wasd-controls"].keys["KeyW"] = true;
}, 1000);
</script>
</body>
Any thoughts?

If You know the camera position, and the sphere position - you can calculate the distance:
1) sqrt((x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2),
2) or by using the THREE.js a.distanceTo(b) method.
Throw your calculations into a custom component, and check the distance in a tick function:
init: function() {
this.cam = document.querySelector("[camera]")
this.sphere = document.querySelector("a-sphere")
},
tick: function() {
let camPos = this.cam.object3D.position
let spherePos = this.sphere.object3D.position
let distance = camPos.distanceTo(spherePos)
if (distance < 5) {
// camera closer than 5m, do something
}
}
Check it out in this fiddle.

You could alternately make an invisible sphere around the main sphere,
and use collision detection to make the text appear.
Collision detection is built into the aframe.extras.js

Related

Preventing the camera entering entities in A-Frame

I have <a-scene> using A-Frame that includes many randomly placed spheres (<a-sphere>) and a camera rig. The spheres are centred at random (x,y,z) coordinates at initialisation (and are at different locations on each access to the web page). The user can move the camera with a controller or wasd keys through the scene. I want to prevent the camera from entering any of the spheres.
It seems that I should be able to do that either using a nav mesh or a physics engine. For the former, I need guidance on whether it is possible to construct a nav mesh that excludes many spheres (and just those spheres) and if so, how. With the physics engine, it seems a heavyweight way of dealing with the problem, since the spheres are static. Any advice or examples?
Finally, when the camera hitting the sphere is detected, what is the best way of actually preventing it from entering? Do I reposition the camera using javascript?
The best way is to use nav-mesh. Basically nav-mesh is a 3d plane where it has slots positioned in place of where camera movement is restricted.
If you want some simplified code for nav-mesh use this
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<!-- import the deprecated Geometry part -->
<script src="https://threejs.org/examples/js/deprecated/Geometry.js"></script>
<!-- before this tries to use its API -->
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v6.1.1/dist/aframe-extras.js"></script>
<a-scene>
<a-entity id="rig" movement-controls="constrainToNavMesh: true;" foo>
<a-entity id="camera" camera position="0 1.6 0" look-controls></a-entity>
</a-entity>
<a-plane nav-mesh rotation="-90 0 0" width="5" height="5" color="yellow"></a-plane>
<a-plane rotation="-90 0 0" width="25" height="25" color="blue" position="0 -0.01 0"></a-plane>
</a-scene>
Here instead of aframe primitive as navmesh you can use navemesh created by 3D modelling
To create a basic nav-mesh use blender software. In that create a mesh(plane) and cut slots using knife tool there. To get positioning of spheres download the 3d model in aframe as gltf import in blender and check it
navmesh example code.This example by AdaRoseCannon has endrosed all use cases in using nav-mesh
Here in AdaRoseCannon's example they have excluded holes in nav-mesh with a class name you can use this approach to cut dynamic holes .The a-plane entity here can be dynamically set too
<a-entity id="head"
camera="near:0.01;"
look-controls="pointerLockEnabled: false"
position="0 1.65 0"
wasd-controls="acceleration:20;"
simple-navmesh-constraint="navmesh:.navmesh;fall:0.5;height:1.65;exclude:.navmesh-hole;"
></a-entity>
<a-plane rotation="-90 0 0" width="1.2" height="0.6" class="navmesh-hole" visible="false"></a-plane>
Synn kindly wrote a component that did what I needed:
/*
* prevent the camera from entering a 3d entity
* coded by Synn ( https://github.com/chabloz )
*/
AFRAME.registerComponent('sphere-collider-constraint', {
schema: {
selector: {
default: '',
},
distance: {
default: 0.5,
},
},
init: function () {
this.lastPosition = new THREE.Vector3()
this.el.object3D.getWorldPosition(this.lastPosition)
this.myPos = new THREE.Vector3()
this.el.object3D.getWorldPosition(this.myPos)
this.targetPos = new THREE.Vector3()
},
tick: function () {
// if haven't moved since last tick, do nothing
this.el.object3D.getWorldPosition(this.myPos)
if (this.myPos.distanceTo(this.lastPosition) === 0) return
let didHit = false
for (const obj of document.querySelectorAll(this.data.selector)) {
obj.object3D.getWorldPosition(this.targetPos)
const distanceTo = this.myPos.distanceTo(this.targetPos)
if (distanceTo <= obj.components.geometry.data.radius + this.data.distance) {
didHit = true
break
}
}
if (didHit) {
this.el.object3D.position.copy(this.lastPosition)
this.el.object3D.parent.worldToLocal(this.el.object3D.position)
} else {
this.el.object3D.getWorldPosition(this.lastPosition)
}
},
})
Then use it in something like this:
<a-entity id="cameraRig" movement-controls="fly: true; speed: 0.7" sphere-collider-constraint="selector: .node">
<a-camera id="camera" look-controls="pointerLockEnabled: true">
</a-camera>
</a-entity>

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.

how to curve a plan entity to match a sphere surface?

i'm kinda new to aframe
i have a question : with the aframe-position-spherical-component, i managed to position my element in 360deg
but how can i "curve" my element? (to match the sphering display) (btw my element are
<html>
<head>
<script src="/lib/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-position-spherical-component/index.js"></script>
</head>
<body>
<a-scene>
<a-image src="/static/cat.jpeg" width="3" height="1.5" rotation="0 180 0" position-spherical="10 90 180"></a-image>
</a-scene>
</body>
</html>
first attempt to solve this :
for now i managed to get something near what i want, but the value were found manually and aren't perfect
<a-curvedimage src="/static/cat.jpeg" width="3" height="1.5" theta-length="64" radius="3" theta-start="-32" rotation="0 180 0" position-spherical="10 90 180"></a-curvedimage>
second attempt (with the a-sphere solution) :
kinda work, but image are mirrored, and adding a click event like to show an image bigger is difficult to achieve
<a-assets>
<img id="cat" src="/static/cat.jpeg" />
</a-assets>
<a-box scale="0.1 0.1 0.1" color="red"></a-box>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 210 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 240 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 270 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 300 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-curvedimage> is based on a cylinder (source) so it may not fit well.
So how about actually using the sphere geometry ?
tldr fiddle here
Geometry
You could make it look like a <a-curvedimage> using theta and psi properties of a sphere:
<a-sphere geometry='thetaStart: 45; thetaLength: 45; psiLength: 45'></a-sphere>
This should result in a <a-curvedimage>ish plane, but also curved in the vertical axis. Play around with the psi and theta to see more triangle, or diamond shaped geometries.
Fitting into a sphere
This seems like a job for a custom component ! If you didn't use them before, check out the link, otherwise the component below simply copies the sphere radius and position and uses their values for the image.
AFRAME.registerComponent('foo', {
schema: {
// we'll use it to provide the sphere
target: {type: selector}
},
init: function() {
let sphere = this.data.target
// make sure the sphere radius and transform is identical:
this.el.setAttribute('radius', sphere.getAttribute('radius'))
this.el.setAttribute('position', sphere.getAttribute('position'))
}
})
And simply use it like this:
<!-- the background with some position and radius -->
<a-sphere id='background'></a-sphere>
<!-- the inner sphere -->
<a-sphere foo='target: #background'></a-sphere>
Z-Fighting
You should notice that the image is not visible, or it's distorted. By now we have two spheres with identical size and transforms so the renderer won't know which one is in front of the another.
You could deal with this easily - by changing the radius for the inner sphere:
this.el.setAttribute('radius', sphere.getAttribute('radius') * 0.95)
Or you could move the inner sphere a bit towards the center - like in the provided fiddle:
// grab the inner sphere's mesh
let mesh = this.el.getObject3D('mesh')
// we need an axis - I'd substract the mesh's center from the spheres center
let axis = sphere.getAttribute('position').clone()
axis.add(mesh.geometry.boundingSphere.center.clone().multiplyScalar(-1))
axis.normalize();
// move the inner sphere a bit along the axis
this.el.object3D.translateOnAxis(axis, 0.05)
Enlarging the image on click
Usually we'd use the scale attribute, but here we can manipulate the phi and theta values to make the image bigger. Also you should bring the image to front when enlarged, to prevent z-fighting between images:
this.el.addEventListener('click', e => {
this.clicked = !this.clicked
// preset values depending if the image is clicked or not
let thetaLength = this.clicked ? 65 : 45
let thetaStart = this.clicked ? 35 : 45
let psiLength = this.clicked ? 65 : 45
let psiStart = this.clicked ? -10 : 0
let scale = this.clicked ? 0.95 : 1
// apply the new geometry
this.el.setAttribute('geometry', {
'thetaLength': thetaLength,
'thetaStart': thetaStart,
'phiLength': psiLength,
'phiStart' : psiStart
})
this.el.setAttribute('radius', sphere.getAttribute('radius') * scale)
})
fiddle here. It would be better to keep these values in variables (base value, and a delta), but i hope this way it's easier to get the idea.

Raycaster camera with a-sky intersects with cursor

I just started developing with a-frame, please excuse if the answer is obvious.
In my project I would like to get the position of a-sky where the user is looking at. Therefor I implemented a raycaster within the camera, which works fine so far.
HTML
<a-camera listener>
<a-entity raycaster="far: 1000" position="0 -0.9 0" rotation="0 0 0"></a-entity>
</a-camera>
<a-sky follow-intersection
id="sky"
src="#skybox-image">
</a-sky>
TS
AFRAME.registerComponent("follow-intersection", {
init: function() {
this.el.addEventListener("raycaster-intersected", evt => {
this.intersectingRaycaster = evt.detail.el.components.raycaster;
});
this.el.addEventListener("raycaster-intersected-cleared", () => {
this.intersectingRaycaster = null;
});
},
tick: function(t) {
if (!this.intersectingRaycaster) {
return;
}
const intersection = this.intersectingRaycaster.getIntersection(this.el);
if (intersection) {
let point = intersection.uv;
console.log(point.x, point.y);
}
So far this works fine, the problem is that after I set the cursor in the scene (which is needed for the project)
<a-scene
cursor="rayOrigin: listener"
>
I always get the intersections with the cursor, which are not wanted.
How can I only get the intersections of the camera? Thank you!
I don't think the cursor in a-scene is needed. You could just do:
<a-camera listener>
<a-entity cursor raycaster="far: 1000" position="0 -0.9 0" rotation="0 0 0"></a-entity>
</a-camera>

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