Preventing the camera entering entities in A-Frame - aframe

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>

Related

How can I auto walk in A-Frame?

For my A-Frame project I want the player to move forward without depending on any controllers or keyboards. The player should always be moving forward, and should be able to decide the direction by looking around (without changing the z value). Using look-controls on the camera and movement-controls (from aframe-extras) on the rig gives this behavior, apart from the fact that it still depends on a controller. How can I implement auto walk in A-Frame?
<a-entity id="rig" movement-controls>
<a-entity id="camera" position="0 1.7 0" camera look-controls>
</a-entity>
If you are using the movement-control component, you can add your own "controls". As the doc say, all you have to do is to name it with a "-controls" suffix (see https://github.com/n5ro/aframe-extras/tree/master/src/controls#customizing-movement-controls ). This is the code for a simple "autowalk" custom controls component for "movement-controls":
AFRAME.registerComponent('autowalk-controls', {
isVelocityActive: function () {
return true;
},
getVelocityDelta: function () {
return new THREE.Vector3(0, 0, -1);
}
});
And you can simply use it by adding it to the controls proprety of "movement-controls" like that:
movement-controls="controls: autowalk"

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

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

A-Frame using teleport-controls with shake.js events

I want to use fernandojsg's teleport controls on my A-Frame project, but the way I want to use them is with shake.js, one shake to make the teleport line appear and another one to actually teleport where you selected.
I've seen the documentations and came across the startEvents and endEvent properties, and I want to map them into the shake event... for me it sounds like I have to create a custom component to do this, but I wanted to seek help first, to see if this is possible without doing it.
So far I've made this (glitch.com/ link) but it doesn't work so far (please note that I've got some other libraries there that make use of shake, mousedown, and similar events... my plan is to activate or deactivate them depending on how the user wants to move)
<a-entity id="player" wasd-controls tap-to-walk>
<a-camera id="eyes" position="-.5 1.5 0" camera="" look-controls="" mouse-cursor="">
<a-entity id="cursor" cursor="fuse: false;"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.015; radiusOuter: 0.019"
material="color: white; shader: flat"
raycaster="far: 5; interval: 1000; objects: .clickable">
</a-entity>
<a-entity id="texto" text="value:Hola;align:center" position="0 -.3 -0.5"></a-entity>
<a-plane position="0 .7 -1" material="transparent: true; opacity: 0.5; color: #ffec04; shader:flat;" scale="1 0.2 1"></a-plane>
</a-camera>
<a-entity
teleport-controls="cameraRig: #player; teleport-origins: #eyes; startEvents:['shake','mousedown']"> </a-entity>
<a-entity id="step" sound="src: #step1"></a-entity>
</a-entity>
Thanks...
Yes you will need a some JS or a custom component to get shake.js and teleport controls to work together for two reasons:
shake.js emits its events outside the A-Frame scene on window where teleport-controls is not listening
shake.js only emits one event type, and you need to differentiate for startEvents and endEvents
But it doesn't need to be very complicated:
AFRAME.registerComponent('shake-listener', {
init: function () {
var targetEl = this.el
var count = 0
// you could also initialize the shake instance here
window.addEventListener('shake', function () {
if (++count % 2) {
targetEl.emit('shakestart')
} else {
targetEl.emit('shakeend')
}
})
}
})
Then add shake-listener to the same entity as teleport-controls="startEvents: shakestart; endEvents: shakeend"

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