How can I auto walk in A-Frame? - aframe

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"

Related

How to create a UI link in A-frame

I am looking to add a piece of text to the camera on an A-frame scene which will act as a link to transfer to a non-VR page.
<a-entity camera look-controls position="0 0 0">
<a-cursor visible="false">
</a-cursor>
<a-entity text="value: Galleries; color: black; width: 2;" position="-0.25 0.7 -1" onClickLink>
</a-entity>
</a-entity>
The onClickLink function is registered and uses window.location.href = "https://www.google.com"; to change page as suggested in the docs.
I have created a gallery, and want to give users a simple UI link at the top to take them back to a list of other galleries - however - in the example above the onClickLink function just gets applied to the whole scene rather than only the piece of text. I guess it has maybe been applied to its parent - the camera - so any click counts as firing the event? Or do I need to add something to the event to determine what object was clicked? I'm not sure how raycasting would work on something in the top left corner of the camera?
I do not want to use a 3D object in the scene, and I similarly don't want to use the Link portal entity that will be placed in the scene (I did try going down this route similar to the above, and it again just applied the link to the entire scene rather than on clicking the link itself).
Is there a way to achieve what I'm after?
You can use standard html elements and a-frame element to gather and use Css (position: fixed; z-index: 999) to bring it forward to the top but to create an external html link I share part of my code hope you find the idea, you can use jquery(or pure javascript) to add click event to your a-frame objest, but first add below code to <a-camera tag:
raycaster="far: 10000; objects: .clickable"
then add "clickable" class name to any A-frame object you want to be clicked by user
<a-image visible=true id="web" class="clickable external" src="#imgweb" alpha-test="0.5" position="-0.14 -0.9 0" height="0.18" width="0.18"></a-image>
and then like any html dom object you can access to aframe objects by javascript:
window.onload = function() {
$(".external").each(function(index) {
$(this).on("click", function() {
console.log(this.id + ' Pushed');
switch (this.id.toLowerCase()) {
case 'web':
window.open("http://www.farsix.com/");
break;
case 'instagram':
window.open("https://www.instagram.com/");
break;
default:
console.log("Unknown Link!");
};
});
});
};

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 access the interior of gltf-model object3D.children.material in AFrame

I'm loading a gltf-model into aframe and some materials on some objects need adjusting. I am attempting to isolate and manipulate them by directly accessing the object3D property of the entity that contains the gltf component. The part of the object3D tree that I need to access is the .children Array. When I log that part to the console, it is an empty array, but I can twirl it down in the console and see the object properties I need. How do I access this in my script? the .children property is returning an empty array.
You can see my project here:
http://www.sensorium.love/experiments/yamashiro/walkthroughlit2/bonsaiLightsTest.html
The small black rectangle with the flare texture on it is one of many planes from the gltf. The large flare in the background is a primitive I made in aframe with the material as I would like it to be. I attempted to assign this to another plane object in my gltf, and it did not render. It should be applied to the child.
If you inspect the console, you can see where I've logged this children array. It is an empty array, and yet if you twirl down the arrow next to it, you can see the underlying data I'm trying to access. But I can't understand how to access that in my script.
AFRAME.registerComponent('flareplanes',{
init:function(){
let l1 = document.querySelector('#lta1');
let lm3D1 = l1.object3D;
console.log(lm3D1);
let lmc = lm3D1.children;
console.log(lmc);
for(let propName in lmc){
console.log(lmc[propName]);
}
}
});
<a-scene>
<a-assets>
<a-asset-item id="bonsailights" src="BonsaiLights.glb" ></a-asset-item>
<img id="flare" src="assets/ledFlare.png"></a-asset-item>
</a-assets>
<a-entity id="lta1" gltf-part="src: #bonsailights;
part:BonsaiBendDLeafLiteL_01"></a-entity>
<a-entity id="lta2" gltf-part="src: #bonsailights; part:BonsaiBendDLeafLiteL_02" material="src: #flare; shader: flat; opacity: 0.99; blending: additive"></a-entity>
<a-entity id="plane" geometry="primitive: plane" position="1.0 1.6 2" rotation="0 180 0" material="src: #flare; shader: flat; opacity: 0.99; blending: additive" flareplanes></a-entity>
</a-scene>
let lmc = lm3D1.children;
console.log(lmc); // Array empty
console.log(lmc[0]); //undefined
// yet, in the console, twirling the arrow reveals the object I need to //access. It appears that this object is entry 0 in the array, but accessing //directly fails. How do I access this object in my script?
Try iterating through the mesh children, not the object3Ds:
var mesh = el.getObject3D('mesh');
mesh.traverse(node => {
if (node.isMesh) {
console.log(node.material)
}
});
Here's a glitch in which I access child materials to manipulate the opacity.
If the el.getObject3D('mesh') is null, try waiting for the model-loaded event:
handleModel: function() {
let mesh = this.el.getObject3D('mesh')
if (!mesh) {
this.el.addEventListener('model-loaded', this.handleModel.bind(this)
return
}
// the model should be loaded by this point
}

How do I "preserve" an entity when changing its parent in the DOM?

I have a "variable assignment" component that looks like the following (the blue diamond with the yellow sphere attached):
http://i.imgur.com/nJotPgW.gif (unfortunately I don't have enough reputation to inline the image)
Here the yellow sphere that things can snap into is created in the initialization of the variable assignment component like so
AFRAME.registerComponent('variable-assignment', {
schema: {
grabbable: {default: true}
},
init: function () {
this.el.innerHTML = `
<a-sphere
snap-site="controller:#right-hand"
radius=".1"
color="yellow"
material="transparent:true; opacity:.5;"
position=".22 0 0">
</a-sphere>
`;
this.label = 'x';
this.el.setAttribute('geometry', {
primitive: 'octahedron',
radius: .1,
color: 'blue'
});
...
The snap-site component has code that detects a collision with the red sphere and then makes it a child element. So the DOM looks something like this before the collision.
<a-sphere color=red></a-sphere>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere>
</a-entity>
and after
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere color=red></a-sphere>
<a-sphere>
</a-entity>
The problem is when I want to move the entity with variable-assignment inside another DOM element using appendChild the initialization function for the variable-assignment called again the innerHTML is reset. So for example if we have
<a-entity container></a-entity>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere color=red></a-sphere>
<a-sphere>
</a-entity>
And we want to move variable-assignment into container using something like containerElement.appendChild(variableAssignmentEntity) the inner red sphere gets removed
<a-entity container>
<a-entity variable-assignment>
<a-sphere snap-site>
<a-sphere>
</a-entity>
</a-entity>
As a work around/hack I was thinking about using a flag of some sort to see if initialize had already been called before for the element/entity the component was a property of and then not run the initialization code, something like
init: function () {
if (this.el.getAttribute('initialized')) {
return;
}
this.el.setAttribute('initialized', true);
...
but this seems like a bad way to do it and also looking into the A-Frame source it seems appendChild causes all components to be removed then added again so it doesn't actually work either or at least causes other things to break.
Is there a good way to do this or is there a different way to define the variable-assignment component so the yellow sphere snap-site component isn't a child set in the initialization?
You're correct that appendChild causes components to be re-initialized, and might not work as expected. Here are a couple alternatives:
After removing the element, call el = el.cloneNode() to make a clean copy and append that, instead.
Don't actually make the sphere a child of your variable-assignment component, but instead just have the 'parent' component update the position/rotation of the child in the tick() function.
Use the underlying three.js API to pass around the THREE.Object3D, without actually changing the parent of any elements.
An example of (3):
var mesh = el.getObject3D('mesh)';
el.removeObject3D('mesh');
otherEl.setObject3D('mesh', mesh);

What's wrong with my camera collision in A-Frame

I'm copying straight from the A-Frame documentation on RayCaster but I'm still not getting it right. I want the collider to occur when the camera connects with the cube.
Here's the github link for context.
<a-entity geometry="primitive: box" class="collidable"
position="1 2.5 0"></a-entity>
AFRAME.registerComponent('collider-check', {
dependencies: ['raycaster'],
init: function () {
this.el.addEventListener('raycaster-intersected', function () {
console.log('Player hit something!');
});
}
});
raycaster-intersected event is a ray from camera to cursor position, if the ray hit something, it will trigger raycaster-intersected event. collider event is more like checking distance between camera and something collidable, you can look at this component sphere-collider.js then, register your own collider-check component.

Resources