In Aframe, how to point directional light to a certain position instead to an entity? - aframe

I want my Directional Light to point to [10,5,5]. I don't have any object there. However, in Aframe documentation, target attributed points only to a certain object.
<a-light type="directional" position="0 0 0" rotation="-90 0 0" target="#directionaltarget">
<a-entity id="directionaltarget" position="0 0 -1"></a-entity>
</a-light>

The target doesn't have to be a "physical" object with a mesh. In this case, it's just an empty dummy object which entire purpose is to provide an anchor point where the light should be directed at.
If you want it to be at 10 5 5, then just change the dummys position:
<a-light type="directional" position="0 0 0" rotation="-90 0 0" target="#directionaltarget">
<a-entity id="directionaltarget" position="10 5 5"></a-entity>
</a-light>
Keep in mind, that here 10 5 5 is in regard to the parents position, because the target is nested.
If you don't want to create an entity, you can create the light in threejs - but still it requires a dummy object:
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("left-light", {
init: function() {
const directionalLight = new THREE.DirectionalLight(0xff0000, 1.5); // red light
const target = new THREE.Object3D(); // dummy target
directionalLight.target = target; // direct the light onto the target
directionalLight.position.set(0, 0, 0) // move light to the center
target.position.set(1, 0, 0); // move the target to the right
// add the target and the light
this.el.sceneEl.object3D.add(target);
this.el.sceneEl.object3D.add(directionalLight);
}
})
</script>
<a-scene left-light>
<a-light></a-light>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#FFFFFF"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#FFFFFF"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFFFFF"></a-cylinder>
</a-scene>

Related

How to make A-Frame components talk to each other?

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>

Aframe - How to render HUD on top of everything else

The A-Frame camera docs say to create a HUD like this:
<a-entity camera look-controls>
<a-entity geometry="primitive: plane; height: 0.2; width: 0.2" position="0 0 -1"
material="color: gray; opacity: 0.5"></a-entity>
</a-entity>
However, this causes the HUD element to be clipped by in-game objects. Normally HUDs are rendered on a separate layer above all the other game objects. Is there a simple way to fix this?
You have to:
Set renderer.sortObjects = true;
Set a the render order of the entity this.el.object3D.renderOrder = 100;
Disable the material depth test material.depthTest = false;
I created a simple overlay component that easily applies the above to any entity.
AFRAME.registerComponent("overlay", {
dependencies: ['material'],
init: function () {
this.el.sceneEl.renderer.sortObjects = true;
this.el.object3D.renderOrder = 100;
this.el.components.material.material.depthTest = false;
}
});
<a-entity camera look-controls wasd-controls position="0 1.6 0">
<a-plane id="hud" overlay rotation="0 0 0" position="0 0 -2" width="1" height="1" color="purple" shadow></a-plane>
</a-entity>
Example on glitch

How do I use checkpoint controls in A-Frame?

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.

How to stop A-frame from rendering?

I read the documentation about the scene entity that's responsible for rendering the scene, but I didn't find anything regarding stopping the rendering to happen.
Question: Is there a way to halt the rendering?
I'm not sure you can completely stop the rendering, however you can pause specific entities or the scene:
https://aframe.io/docs/0.7.0/core/entity.html#pause
Here's a demo, it waits for the scene to load, and then calls the pause function on the scene, which pauses all components and entities in the scene.
https://glitch.com/edit/#!/a-frame-pause-scene-on-load
<a-scene>
<a-node id="waitOnMe"></a-node>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow>
<a-animation attribute="rotation"
dur="10000"
fill="forwards"
to="0 360 0"
repeat="indefinite"></a-animation>
</a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
Note: I've added an a-node element to guarantee it catches the load event at the end of the page.
Then the js to pause:
<script>
var scene = document.querySelector('a-scene')
scene.addEventListener('loaded', function () {
scene.pause();
});
document.getElementById('waitOnMe').emit('loaded');
</script>
Related info about waiting for load can be found here:
https://stackoverflow.com/a/47363072/8005106
I did not find any satisfactory answer to this, so here's how I managed to stop it (and also start it when needed):
let sceneEl = document.querySelector('a-scene');
let renderer = sceneEl.renderer;
// Stop rendering
renderer.setAnimationLoop(null);
// Start rendering, personally i set this inside a button click
renderer.setAnimationLoop(sceneEl.render);

Changing animation's 'to' in js doesn't work when emitting an event to trigger the effect

I'm trying to play a short rotation animation for this parent camera entity. I have the following a-animation:
<a-animation attribute="rotation" dur="1000" to="0 163.5761975835419 0" fill="forwards" begin="rotateCam"></a-animation>
Then, I try to set the 'to' attribute before emitting rotateCam (with animation.setAttribute(...)), but it only plays the default rotation I set in the HTML. Am I missing something?
See this fiddle for example. v0.5.0.
Thank you for your time.
After some research and talking to dirkk0 on A-Frame Slack it became clear they're going for a component-based approach. I have changed my code to apply ngokevin's animation component and it solved my problem! :)
Relevant test (AFrame v0.5.0)
HTML:
<a-scene>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-box position="-1 0.5 -3" width="1" height="1" depth="1" color="#4CC3D9" animation="property: rotation; dur: 1000; startEvents: rotateBox"></a-box>
<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-sky color="#ECECEC"></a-sky>
</a-scene>
JS:
setTimeout(() => {
let box = document.querySelector('a-box');
box.setAttribute('animation', 'to', '0 20 0');
box.emit('rotateBox');
}, 2000);

Resources