I am trying to put a text next to an object then rotate it by 90 degrees. Below is the portion of the code I used.
<a-sphere
color="green"
look-at="[gps-camera]"
scale="0.49205916205718183 0.49205916205718183 0.49205916205718183"
gps-entity-place="latitude: 3.xxxxx; longitude: 101.xxxxx;">
<a-text
look-at="[gps-camera]"
value="Point 1"
color="yellow"
rotation="0 90 0"
scale="7, 7"
position="-2.5 4 0"
z-offset="3"
faceUser: true;>
</a-text></a-sphere>
<a-sphere
The result is this:
What I want to achieve is this (please ignore the background):
I tried to change all the values in this expression but nothing happens:
rotation="0 90 0"
To rotate only the text element (like on the image), you need to change the roll (rotation="0 0 90"):
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<a-scene>
<a-text color="black" value="some Text" position="-0.5 0.5 -3" rotation="0 0 90"></a-text>
</a-scene>
To rotate the element around the sphere, you should rotate the sphere, not the text (provided the text is the child element). Click the sphere to see the result:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("foo", {
init: function() {
this.el.addEventListener("click", () => {
// rotate by 90 degrees when clicked
let rot = this.el.getAttribute("rotation")
rot.z += 90
this.el.setAttribute("rotation", rot)
})
}
})
</script>
<a-scene cursor="rayOrigin: mouse" raycaster="objects: a-sphere">
<a-sphere position="0 1.5 -3" radius="0.25" color="#EF2D5E" foo>
<a-text color="black" value="some Text" position="-0.5 0.5 0"></a-text>
</a-sphere>
</a-scene>
I recommend debugging the scene in pure a-frame.
Related
I want some components to respond to the user's position and orientation in the scene. I have little experience with interactive a-frame scenes and haven't written a component myself.
Generally, I'd want components to be able to provide callbacks for other components to call, or if that's not possible then some kind of inter-component data handoff. The "receiving" component would change its contents (children), appearance and/or behavior.
If we were to take a really simple example, let's say that I want the scene to include either a box if the user is at x>0, or a sphere if they're at x<=0.
Breaking this down, I'll be happy to understand how to...:
Read user position and make it available for others. I found how to read the position; I guess I could just take the <a-scene> element and set some attribute, such as user-position="1 2 3".
Write some code, somewhere, that runs a function when this position changes (I'll debounce it, I imagine) and makes changes to a scene. I think that if I wrote my own component to include the whole scene, I'd need to...:
Set the user position as an attribute on that element;
Define an update method;
In the update method, compare current vs previous user location.
...but I'm wondering if maybe this is overkill and I can just hook somehow into a-scene, or something else entirely.
If I take the approach I mentioned above, I guess the missing piece is how to "declare" what to render? For example, using ReactJS I'd just do return x > 0 ? <a-box/> : <a-sphere/>;. Is there an equivalent, or would I need to reach into the DOM and manually add/remove <a-box> child and such?
Thank you!
EDIT: I sort of got my box/sphere working (glitch), but it feels quite strange, would love to improve this.
How to make A-Frame components talk to each other?
0. setAttribute
You can change any property in any component with
element.setAttribute("component_name", "value");
but I assume you want more than reacting to update calls. Something more flexible than the component schema and a bit more performant when used 60 times per second/
1. events
component 1 emits an event
components 2 - x listen for an event, and react accordingly.
Not dependant on hard-coded component names, you can easily have multiple recipients, and a possibly stable API:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-reader", {
tick: function() {
// read the position and broadcast it around
const pos = this.el.object3D.position;
const positionString = "x: " + pos.x.toFixed(2) +
", z: " + pos.z.toFixed(2)
this.el.emit("position-update", {text: positionString})
}
})
AFRAME.registerComponent("position-renderer", {
init: function() {
const textEl = document.querySelector("a-text");
this.el.addEventListener("position-update", (evt) => {
textEl.setAttribute("value", evt.detail.text);
})
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-camera position-renderer position-reader>
<a-text position="-0.5 0 -0.75" color="black" value="test"></a-text>
</a-camera>
</a-scene>
2. Directly
Taking this literally, you can grab the component "object" reference with
entity.components["componentName"]
and call its functions:
entity.components["componentName"].function();
For example - one component grabs the current position, and tells the other one to print it:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-reader", {
init: function() {
// wait until the entity is loaded and grab the other component reference
this.el.addEventListener("loaded", evt => {
this.rendererComp = this.el.components["position-renderer"];
})
},
tick: function() {
if (!this.rendererComp) return;
// read the position and call 'updateText' in the 'position-renderer'
const pos = this.el.object3D.position;
const positionString = "x: " + pos.x.toFixed(2) +
", z: " + pos.z.toFixed(2)
this.rendererComp.updateText(positionString)
}
})
AFRAME.registerComponent("position-renderer", {
init: function() {
this.textEl = document.querySelector("a-text");
},
updateText: function(string) {
this.textEl.setAttribute("value", string);
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-camera position-renderer position-reader>
<a-text position="-0.5 0 -0.75" color="black" value="test"></a-text>
</a-camera>
</a-scene>
In Your case I'd check the position, and manage the elements in one component. Or use one to determine if the position.x > 0 || < 0, and the other one for visibility changes.
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("position-check", {
schema: {
z: {default: 0}
},
tick: function() {
const pos = this.el.object3D.position;
// check if we're 'inside', or outside
if (pos.z >= this.data.z) {
// emit an event only once per occurence
if (!this.inside) this.el.emit("got-inside");
this.inside = true
} else {
// emit an event only once per occurence
if (this.inside) this.el.emit("got-outside");
this.inside = false
}
}
})
AFRAME.registerComponent("manager", {
init: function() {
const box = this.el.querySelector("a-box");
const sphere = this.el.querySelector("a-sphere")
//react to the changes
this.el.sceneEl.camera.el.addEventListener("got-inside", e => {
box.setAttribute("visible", true);
sphere.setAttribute("visible", false);
})
this.el.sceneEl.camera.el.addEventListener("got-outside", e => {
box.setAttribute("visible", false);
sphere.setAttribute("visible", true);
})
}
})
</script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-entity manager>
<a-box position="0 1 -3" visible="false"></a-box>
<a-sphere position="0 1 -3" visible="false"></a-sphere>
</a-entity>
<a-camera position-check="z: 0"></a-camera>
</a-scene>
I am new to A-Frame and still trying to figure everything out! I'm currently constructing a 3D space and would like to create a guided experience for visitors by providing dots on the floor for them to click and be transported to that position. I found this code online which is perfect but I can't get it to work.
Here is the link to my project on Glitch: https://glitch.com/~museum-exhibit-demo
This is the code for my camera:
<a-entity position="1.8 -1.1 3" rotation="0 90 0" id="pov">
<a-camera universal-controls="movementControls: checkpoint" checkpoint-controls="mode: animate">
<a-entity cursor position="0 0 -1" geometry="primitive: ring; radiusInner: 0.01; radiusOuter: 0.015;" material="color: #CCC; shader: flat;"> </a-entity>
</a-camera>
</a-entity>
And this is the code for the cylinder:
<a-cylinder checkpoint radius="0.1.5" height="0.01" position="-0.164 0.111 2.363" color="#39BB82"></a-cylinder>
Can anyone spot where I'm going wrong?
UPDATE:
I just read the current source of aframe-extra and it seems that nothing is broken! In fact there was a backward-incompatible change in the new versions. Rather than the old syntax of:
universal-controls="movementControls: checkpoint;"
Now this new syntax should be used:
movement-controls="controls: checkpoint;"
But keep in mind that since version 3.2.7, the movement offset is calculated on all 3 XYZ axis and therefore the camera will move to the center of the checkpoint. If you want to preserve the height (y) then simply add the code below above line 83:
targetPosition.y = position.y;
Here is a complete working example:
<html>
<head>
<meta charset="utf-8">
<title>Checkpoint Control with AFrame 1.2.0</title>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v6.1.1/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene stats>
<!-- CAMERA -->
<a-entity position="0 0 0" id="pov">
<a-camera camera="active: true; spectator: false;" look-controls="pointerLockEnabled:true" movement-controls="controls: checkpoint;" checkpoint-controls="mode: animate; animateSpeed: 10" wasd-controls="enabled: false;" position="0 1.6 22">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<!-- CHECKPOINTS -->
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 20" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 16" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 12" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 8" color="#FF0000"></a-cylinder>
</a-scene>
</body>
</html>
Information below this line is not valid anymore.
As Piotr already mentioned, the new releases of Aframe-Extra are somehow broken!
Using an older version everything will work again.
Below is a working example with Aframe-extra version 3.2.7.
Once the page is fully loaded, click on the screen to lock the cursor, then point the cursor (tiny ring) at a red circle and click.
I also noted few additional options just in case:
spectator: false (switch between 1st and 3rd person view)
pointerLockEnabled:true (hide the mouse)
mode: animate (the other option is teleport)
animateSpeed: 10 (well... adjusts the animation speed)
wasd-controls="enabled: false;" (otherwise user can move around via WASD/arrow keys)
Code:
<html>
<head>
<meta charset="utf-8">
<title>Checkpoint Control with AFrame 1.2.0</title>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/donmccurdy/aframe-extras#v3.2.7/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene stats>
<!-- CAMERA -->
<a-entity position="0 0 0" id="pov">
<a-camera camera="active: true; spectator: false;" look-controls="pointerLockEnabled:true" universal-controls="movementControls: checkpoint;" checkpoint-controls="mode: animate; animateSpeed: 10" wasd-controls="enabled: false;" position="0 1.6 22">
<a-cursor></a-cursor>
</a-camera>
</a-entity>
<!-- CHECKPOINTS -->
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 20" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 16" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 12" color="#FF0000"></a-cylinder>
<a-cylinder checkpoint radius="0.5" height="0.01" position="0 0 8" color="#FF0000"></a-cylinder>
</a-scene>
</body>
</html>
This won't answer the question, but should solve your problem.
You can substitute the checkpoint-controls with a simple animation system:
you click on a cylinder
you animate the camera from the current position to the cylinder
Which could be implemented like this:
// use a system to keep a global track if we are already moving
AFRAME.registerSystem('goto', {
init: function() {
this.isMoving = false
}
})
// this component will have the actual logic
AFRAME.registerComponent('goto', {
init: function() {
let camRig = document.querySelector('#rig')
// upon click - move the camera
this.el.addEventListener('click', e => {
// check if we are already moving
if (this.system.isMoving) return;
// lock other attempts to move
this.system.isMoving = true
// grab the positions
let targetPos = this.el.getAttribute("position")
let rigPos = camRig.getAttribute("position")
// set the animation attributes.
camRig.setAttribute("animation", {
"from": rigPos,
"to": AFRAME.utils.coordinates.stringify({x: targetPos.x, y: rigPos.y, z: targetPos.z}),
"dur": targetPos.distanceTo(rigPos) * 750
})
camRig.emit('go')
})
// when the animation is finished - update the "shared" variable
camRig.addEventListener('animationcomplete', e=> {
this.system.isMoving = false
})
}
})
with a setup like this:
<!-- Camera with locked movement --/>
<a-entity id="rig" animation="property: position; startEvents: go">
<a-camera look-controls wasd-controls-enabled="false"></a-camera>
<a-entity>
<!-- Cylinder node --/>
<a-cylinder goto></a-cylinder>
You can see it working in this glitch.
I am in a webAR project with AR.js and A-Frame and I am trying to overlay text on a plane or box, at first I thought the text was behind the plane but after I give an opacity of 0.5 to plane, I realized that the text is not really coexisting with the plane where the two intersect. What am I doing wrong?
Appreciate any help!
Preview
My code:
<script src="js/aframe-0.9.0.min.js"></script>
<script src="js/aframe-ar-1.7.1.min.js"></script>
<a-scene vr-mode-ui="enabled: false" embedded arjs='debugUIEnabled:false; sourceType:webcam; detectionMode: mono_and_matrix; matrixCodeType: 3x3; cameraParametersUrl: camera_para.dat; maxDetectionRate: 10;' renderer="logarithmicDepthBuffer: true; precision: high;">
<a-assets>
<a-mixin id="text"
text="align: center; width: 3;
font: https://cdn.aframe.io/fonts/Aileron-Semibold.fnt;
value: Some text.">
</a-mixin>
</a-assets>
<a-marker type="barcode" value="51" smooth="true" smoothCount="10" smoothTolerance="0.005" smoothThreshold="1">
<a-plane rotation="-90 0 0" position="0 0 0" material="opacity: 0.7" color="red"></a-plane>
<a-text mixin="text" position="0 1 0" wrap-count="15" rotation="-90 0 0" color="blue"></a-text>
</a-marker>
<a-light type="ambient" color="#fff"></a-light>
<a-light type="directional" color="#fff" intensity="0.3" position="-0.5 1 1"></a-light>
<a-entity camera></a-entity>
Both the plane and the text have the same z position (0).
Change one of them to have a different(more or less than 0) z property.
You want to find out the location of the camera (in z). You can do this using the inspector (control alt I).
If the camera is positive z, make the text position a positive number.
If the camera is negative z, make the text pos a negative number.
Then they will not be on the same plane and will not be "z-fighting".
I'm currently trying to animate a camera's rotation in a scene. The animation should occur after looking at a circle for 1 second.
The problem is that the bottom of the scene don't seem reachable after the animation, althrough that it is before the animation occured.
I tried first to animate the camera, then a container of the camera. The second option produced an other problem, it seems that all the sky is displaced and I don't understand how to "correct" that.
Video of the problem when the animation is on the camera
Here are two codepens :
Animation on the camera
<script src="https://aframe.io/releases/0.6.1/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-animation-component/dist/aframe-animation-component.min.js"></script>
<a-scene>
<a-assets>
<img id="city" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/city.jpg" data-position="0 0 0" alt="Table de conférence">
</a-assets>
<a-sky src="#city" rotation="0 0 0"></a-sky>
<a-circle position="-8 -5 2"
rotation="90 0 0"
geometry="radius: 1;"
material="side: double;"
>
</a-circle>
<a-entity id="camera-container" rotation="0 100 0">
<a-entity id="camera" look-controls camera="fov: 80; userHeight: 0" wasd-controls="enabled: false;">
<a-animation attribute="rotation"
dur="1000"
begin="animation__fuse-complete"
from="-31.2 4 0"
to="2 5.5 0"
></a-animation>
<a-entity id="cursor" cursor="fuse: true; fuseTimeout: 100"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.013; radiusOuter: 0.02"
material="color: #000; shader: flat"
animation__fuse="startEvents: fusing;
property: scale;
dur: 1000;
to: 0.4 0.4 0.4"
animation__fuseed="startEvents: animation__fuse-complete;
property: scale;
dur: 1;
to: 1 1 1"
>
</a-entity>
</a-entity>
</a-entity>
Animation on a container
<script src="https://aframe.io/releases/0.6.1/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-animation-component/dist/aframe-animation-component.min.js"></script>
<a-scene>
<a-assets>
<img id="city" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/city.jpg" data-position="0 0 0" alt="Table de conférence">
</a-assets>
<a-sky src="#city" rotation="0 0 0"></a-sky>
<a-circle position="-8 -5 2"
rotation="90 0 0"
geometry="radius: 1;"
material="side: double;"
>
</a-circle>
<a-entity id="camera-container" rotation="0 100 0">
<a-animation attribute="rotation"
dur="1000"
begin="animation__fuse-complete"
from="0 100 0"
to="30 100 0"></a-animation>
<a-entity id="camera" look-controls camera="fov: 80; userHeight: 0" wasd-controls="enabled: false;">
<a-entity id="cursor" cursor="fuse: true; fuseTimeout: 100"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.013; radiusOuter: 0.02"
material="color: #000; shader: flat"
animation__fuse="startEvents: fusing;
property: scale;
dur: 1000;
to: 0.4 0.4 0.4"
animation__fuseed="startEvents: animation__fuse-complete;
property: scale;
dur: 1;
to: 1 1 1">
</a-entity>
</a-entity>
</a-entity>
</a-scene>
How to correctly animate the rotation of the camera ?
I'm on Windows 10, with Chrome 59, A-Frame 0.6.1 and aframe-animation-component
Thank you in advance and have a nice day !
I came across a similar problem with being unable to reach the bottom rotation position after executing setAttribute on the camera rotation. Here is what I think is happening:
The problem arises because the rotation on the X-axis changes via code, moving from 0 to 30 degrees without there being a corresponding head motion. As a result, 30 degrees are lost off of the lower limit of -90 degrees.
I think that there is a strong intention in the design of A-Frame that visual angles that arise via head position remain consistent. If you are looking straight ahead and suddenly the angle is 30 degrees higher, yet you haven't moved your head, everything will be askew. You'd have to look downward 30 degrees to see "straight ahead", etc.
It is easy to try and code these discontinuities when A-Frame is viewed through a desktop monitor rather than a visor. I think the "solution" is going to have to be to design some way to have the camera return to its starting position when you run your animation so that it doesn't lose its correspondence to the player's head.
I'm trying a similar example like Stereo-Sky A-Frame Component from this link. The demo link does not work so I have uploaded the example here after fixing the source for aframe-master.min.js.
After viewing it in VR (in my samsung s7 edge with card board), I have found that the image for both eye does not align. Especially when I try putting the pointer on the way points, I have found they are off by small amount.
I'm confused what exactly I have done wrong; is the source for A-Frame is wrong? Or the images need to be "modified" for left and right eye? Is there a settings on the device that may be messing the image render? For ease of reference I am putting the source code : -
<!DOCTYPE html>
<html>
<head>
<title>A-Frame walkthrough (final)</title>
<script src="https://rawgit.com/aframevr/aframe/3620e8e/dist/aframe-master.min.js"></script>
<script src="stereocube.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<img id="circle" src="circle.png"/>
</a-assets>
<a-camera stereocam position="-47 0 -35" ><a-cursor fuse="true" color="#2E3A87" timeout="700">
<a-animation begin="cursor-click" easing="ease-in" attribute="scale" dur="800"
fill="backwards" from="1.0 1.0 1.0" to="100 100 100">
</a-animation>
</a-camera>
<a-mixin id="waypt" rotation="0 0 25" scale="2 2 2"></a-mixin>
<a-mixin id="spin" attribute="rotation" dur="10000" fill="forwards" to="0 360 25" repeat="indefinite"></a-mixin>
<a-mixin id="enter" begin="cursor-mouseenter" easing="ease-in" attribute="scale" dur="200" to="4 4 4"></a-mixin>
<a-mixin id="leave" begin="cursor-mouseleave" easing="ease-out" attribute="scale" dur="200" to="2 2 2"></a-mixin>
<a-box id="wp1" color="orange" href="brooke0" position="-40 -10 10" mixin="waypt" src="#circle">
<a-animation mixin="enter"></a-animation>
<a-animation mixin="leave"></a-animation>
<a-animation mixin="spin"></a-animation>
</a-box>
<a-box id="wp2" color="green" href="brooke1" position=" 0 -14 0" mixin="waypt" src="#circle" ><a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-box id="wp3" color="lightblue" href="brooke3" position=" 15 -9 35" mixin="waypt" src="#circle"> <a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-box id="wp4" color="red" href="brooke2" position="-47 -9 -35" mixin="waypt" src="#circle"> <a-animation mixin="spin"></a-animation><a-animation mixin="enter"></a-animation><a-animation mixin="leave"></a-animation></a-box>
<a-entity id="skyL" position="-47 0 -35" skycube="folder:brooke2L; eye:left" scale="1 1 -1"></a-entity>
<a-entity id="skyR" position="-47 0 -35" skycube="folder:brooke2R; eye:right" scale="1 1 -1"></a-entity>
</a-scene>
</body>
<script>
var app = {
init: function() {
// Add Click handler's to our waypoints in javascript
[].forEach.call(document.querySelectorAll('a-box'), function(box) {
box.addEventListener('click', function () {
window.setTimeout(function(){
app.setSkybox ( box.getAttribute('href') );
app.moveCamera ( box.getAttribute('position'), document.querySelectorAll('a-camera')[0] );
app.hideWaypoints(box.id);
},600)
})
});
app.hideWaypoints("wp4");
},
setSkybox: function(selectedFolder) {
document.querySelector('#skyL').setAttribute('skycube',{folder: selectedFolder+"L", eye:"left"});
document.querySelector('#skyR').setAttribute('skycube',{folder: selectedFolder+"R", eye:"right"});
},
hideWaypoints: function(boxid) {
document.querySelector('#wp4').setAttribute('visible',boxid === "wp1");
document.querySelector('#wp2').setAttribute('visible',boxid != "wp4");
document.querySelector('#wp3').setAttribute('visible',boxid != "wp4");
},
moveCamera: function(newPosition, camera) {
var pos = newPosition.x +" 0 "+ newPosition.z;
camera.setAttribute('position', pos );
// move the cubemap to the same position as the camera, to avoid distortion
document.querySelector('#skyL').setAttribute('position', pos );
document.querySelector('#skyR').setAttribute('position', pos );
},
}
app.init();
</script>
</html>