I need some help. For a project on whihc I am working on we use aframe. At this moment there is no need for VR, but we are working on 3d spaces.
I need two ' buttons' on the side of my scene. The user needs to see them the whole time.
Because of that in added the 'buttons as entities as child of the camera. Works like a charm. But when I change the viewport. Smaller screen ,mobile etc. you can't see them anymore the position changes.
Is there anyway to prevent this?
This is the code.
<a-entity camera look-controls>
<a-entity position="3 1 -2" >
<a-entity id="fullScreen" geometry="primitive: circle; radius:
0.1;" position=" 0 0.5 0" material="src:#fullIco; transparent: true; opacity: 0.5; " fullscreen> </a-entity>
<a-entity id="shareScene" geometry="primitive: circle; radius: 0.1;" position=" 0 0.25 0" material="src:#shareIco; transparent: true; opacity: 0.5; "sharescene> </a-entity>
</a-entity>
example
If You're not using VR, how about using normal buttons positioned over the a-frame canvas ?
Create a <div>, or <button> with a fixed position, anywhere you want it to be, and make it execute your code onclick. Like this:
<button onclick="changesphere()">
You can access the a-frame entities with queryselectors, and use them like any other DOM elements.
So just define your function like this:
var changesphere = function() {
if (document.querySelector("a-sphere").getAttribute("color") !== "blue") {
document.querySelector("a-sphere").setAttribute("color", "blue");
} else {
document.querySelector("a-sphere").setAttribute("color", "green");
}
}
Check out my example here.
Related
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"
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);
I am trying to detect collision of two boxes in A-FRAME v. 0.5.0. I use raycaster example:
https://aframe.io/docs/0.5.0/components/raycaster.html#whitelisting-entities-to-test-for-intersection
but for me it only works with cursor intersecting one of the meshes.
As it is written, raycaster detects when line created from a starting point to a certain direction intersects desired mesh (here marked with a collidable class). It appears that start of this collision-detecting line is somehow set on the camera or on a cursor but not on one of the boxes. How to reassign this starting point?
Before initializing scene I added component:
AFRAME.registerComponent('collider-check', {
dependencies: ['raycaster'],
init: function () {
console.log("we have component");
this.el.addEventListener('raycaster-intersected', function () {
console.log('Player hit something!');
});
},
});
and then A-FRAME entities
<a-entity id="player" collider-check >
<a-entity id="rc"
raycaster="objects: .collidable"
geometry="primitive: box; width: 0.5; height: 4; depth: 0.5"
material="shader: flat; color:gray"
position="0 -0.9 0"
rotation="90 0 0" ></a-entity>
</a-entity>
<a-entity id="inmotion" class="collidable"
geometry="primitive: box; width: 0.5; height: 4; depth: 0.5"
position="1 0 0"
material="shader: flat; color: #00CCDD">
<a-animation id="canim"
attribute="position"
dur="2000"
from ="-2 -1 0"
to="2 0 0.5"
fill="forwards"
direction="alternate"
repeat="indefinite">
</a-animation>
</a-entity>
Here is jsfiddle with the example;
https://jsfiddle.net/Suiseki/9ggs6x4m/2/
Using the raycaster is one way to check for collisions in 3D space, but it's best if one of those shapes is a ray/line. If you have two 3D objects, it's easier to use bounding box or bounding sphere collisions, without a raycaster. Here are example implementations of each:
aabb-collider
sphere-collider
Both will file hit events on the elements when they collide. Example usage, for a slightly different case.
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.
I have an app where the user has to answer a question by clicking the correct area on a collada model - there are 2 options and the user knows that an area is selectable when the cursor turns blue (mouse enters the cylinder, which has visible=false).
The mouse turns blue fine, however where the mouse leaves the cylinder, the cursor color should return to black, but instead it stays blue.
Thanks in advance for your help.
<a-cursor id="myCursor" color="black"></a-cursor>
<a-entity id='questionA' geometry="primitive: cylinder; height: .01; radius: 2.5" material="color:green; opacity: 1" correctAnswer > </a-entity>
var cursorVar = document.getElementById('myCursor');
var questionVar = document.getElementById('questionA');
questionVar.addEventListener('mouseenter', function() {
cursorVar.setAttribute('color', 'blue');
});
questionVar.addEventListener('mouseleave', function() {
cursorVar.setAttribute('color', 'black');
});
The cursor side also emits the event. Try listening to the event from the cursor as a workaround:
cursorEl.addEventListener('mouseleave');
0.3.0 which should be releasing tomorrow or in two days has improvements on the cursor, raycaster, and raycasting against COLLADA models. This includes the ability to limit what the raycaster tests against which would be ideal in the workaround above.
Could be several factors in your bug, I think once 0.3.0 is out, I'd be able to say with more clarity what's happening.
Does this CodePen perform as you expect?
I can't see the problem looking at your code (maybe it's something to do with the cursor primitive). Try replacing the cursor primitive with a manually defined one used in the CodePen link above:
<!-- Camera with gaze cursor -->
<a-entity position="0 1.8 5">
<a-entity camera look-controls wasd-controls>
<a-entity cursor
geometry="primitive: ring; radiusOuter: 0.025;
radiusInner: 0.015; segmentsTheta: 32"
material="color: #283644; shader: flat"
raycaster="far: 30"
position="0 0 -0.75"
id='cursor'>
</a-entity>
</a-entity>
</a-entity>
That said, the raycasting does seem a bit inaccurate (with 0.2.0 at least).