in aframe, the values seem to be explicit strings:
< a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere
I want the values to be able to be set dynamically.
What is the "easiest" way to correctly implement the following pseudocode in a simple aframe web page:
(1) var clr;
(2) var pos;
(3) evaluate clr and pos in functions
(4) < a-sphere position=pos radius="1.25" color=clr shadow>
then, perhaps as in a hand-coded animation, continue to recompute clr and pos and show the altered sphere.
You cannot bind JS variables and DOM attributes with A-Frame out of the box. I recommend against that since it will make code harder to follow as complexity increases. The A-Frame way to do what you describe is defining your own component:
AFRAME.registerComponent(“animate-position”,{
tick: function () {
var position = this.el.getAttribute(“position”);
... animation position calculation logic ...
this.el.setAttribute(“position”, position);
}
});
You can then assign the component to the entity
<a-sphere animate-position><a-sphere>
Related
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"
Have a task: create 10 sphere objects,
put them in pool; on each click got each sphere from pool
and show to user at cursor intersection point.
Problem: can't figure how to properly create and after this, put it to pool. Please check code below.
Currently each sphere create dynamicly like this: (in a-scene on click event)
let {x, y, z} = event.detail.intersection.point
sceneEl.insertAdjacentHTML('beforeend',
`<a-sphere data-coords="[${x}, ${y}, ${z}]" data-clickable position="${x} ${y} ${z}" radius="32.0" color="#eee"></a-sphere>`)
need in further work to get each a-sphere object from pool.
Layout:
<a-scene id="scene" pool__sphere="mixin: sphere; size: 10" main-scene class="ascene" cursor="rayOrigin: mouse" raycaster="zobjects: a-sky">
....
<!-- Asset-s from them want to create each a-sphere -->
<a-assets>
<a-mixin id="gray" color="#eee" ></a-mixin>
<a-mixin id="radius" radius="32.0" ></a-mixin>
<a-mixin id="sphere" geometry="primitive: sphere"></a-mixin>
</a-assets>
Pool creation:
AFRAME.registerComponent('main-scene', {
init() {
//here maybe needed to create each a-sphere object
//end add each to pool,
//then on each scene click, needed to get one by one sphere from pool
//pool creation
let sceneEl = this.el
var el = sceneEl.components
sceneEl.play();
//pool logs 10 empty objects {} if console.log('pool with spheres', el.pool__sphere)
el.pool__sphere.returnEntity(el);
console.log('entity', el)
},
// console.log('el', el)
play (){
}
})
Maybe it's me, but not got how exactly do it
There no clear example for obj. creating in doc. only for object get from pool
please look: https://github.com/aframevr/aframe/blob/master/docs/components/pool.md
I'm not sure if the question is about <a-sphere> pools, or creating objects before "pooling" them, so:
1) You don't need to manually create the objects which are supposed to be pooled.
2) The "template" for the pooled entites is defined by the mixin attribute. Any component (geometry, material, models, custom ones) needs to be defined within the given mixin.
Source code here. So, with a simple declaration:
<a-scene pool__spheres='mixin: ball; size: 10'>
<a-assets>
<a-mixin id="ball" geometry='primitive: sphere' material="color: red">
</a-mixin>
the pool component will already create 10 <a-entity>ies, all using the #ball mixin.
You only need to grab the entities from the pool on click:
this.el.addEventListener('click', e => {
let pool = this.el.sceneEl.components["pool__spheres"]
let sphere = pool.requestEntity();
});
and return them to the pool at some point:
let pool = this.el.sceneEl.components["pool__spheres"]
pool.returnEntity(this.el)
Check it out in this fiddle.
I am using the updated super-hands component in a-frame. I have replicated the basic example from the docs (see below).
<head>
<title>Most Basic Super-Hands Example</title>
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.1.2/dist/aframe-extras.min.js"></script>
<script src="https://unpkg.com/super-hands#3.0.0/dist/super-hands.min.js"></script>
</head>
<body>
<a-scene>
<a-assets></a-assets>
<a-entity>
<a-camera></a-camera>
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="left"></a-entity>
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="right"></a-entity>
</a-entity>
<!-- hover & drag-drop won't have any obvious effect without some additional event handlers or components. See the examples page for more -->
<a-box hoverable grabbable stretchable draggable dropppable color="blue" position="0 0 -1"></a-box>
</a-scene>
</body>
Which works well out of the box when it comes to grabbing objects and being able to affect their position, but not their rotation. I wondered if it was possible, (without using physics and adding a static body to the controller) to affect the rotation of the targeted objects?
I have been able to get the rotation working by adding the relevant mixin and phase-shift component from this example but given it seems possible to grab objects affecting their position I wondered if I could modify the basic example to change the rotation instead of the position (ideally I would be able to choose whether to affect position, rotation or both). I guess I'm imagining perhaps creating another component like rotatable or maybe a build on the grabbable component like grabbable="property: rotation" that I could add to the target objects.
I would like to know if this is possible in general as I would like to understand the component better so as to have more control. To give some context for this particular question however, I have a situation where the super-hands controllers are children of a dynamic-body and once a static-body is added, it causes problems with the behaviour of the parent dynamic-body.
Any advice appreciated as ever, if you need any more info, please let me know.
If you want the rotation without physics, you'll need to implement your own version of grabbable that does the matrix math. It would go something like this:
tick: (function () {
const grabeeMatrix = new window.THREE.Matrix4()
const ignoreScale = new window.THREE.Vector3()
return function () {
if (this.grabber) {
grabeeMatrix.multiplyMatrices(
this.grabber.object3D.matrixWorld,
this.grabOffsetMatrix
)
grabeeMatrix.multiplyMatrices(this.parentOffsetMatrix, grabeeMatrix)
// using decomp over direct Object3D.matrix manipulation
// keeps in sync with other A-Frame components
grabeeMatrix.decompose(
this.el.object3D.position,
this.el.object3D.quaternion,
// let stretchable manage scale
ignoreScale
)
}
}
})(),
resetGrabber: function () {
if (!this.grabber) {
return false
}
this.grabber.object3D.updateMatrixWorld()
this.el.object3D.parent.updateMatrixWorld()
// save difference between grabber world matrix and grabee world matrix
this.grabOffsetMatrix
.getInverse(this.grabber.object3D.matrixWorld)
.multiply(this.el.object3D.matrixWorld)
// save difference between grabee world and local matrices
this.parentOffsetMatrix.getInverse(this.el.object3D.parent.matrixWorld)
return true
},
I am using the event-set-component to cause my obj model under to increase scale when the cursor hovers over it.
This is working correctly.
But how would I make it spin as well as increase size?
I found the following code on AFrame docs but I do not know how to implement it so it triggers when the mouse is over the entity.
<a-animation attribute="material.opacity" begin="fade" to="0"></a-animation>
As you have asked for a different method in your comment I suggest to use a multi use component like the one I have written:
AFRAME.registerComponent('event-animate', {
schema: {
target: {type: 'selector'},
aevent: {default: 'animation1'},
triggeraction: {default: 'click'}
},
init: function () {
var data = this.data;
this.el.addEventListener(data.triggeraction, function () {
data.target.emit(data.aevent);
});
}
});
So in HTML it would look something like this:
<a-entity id="object1"
event-animate="target:object1;
triggeraction:mouseenter;
aevent:eventstart">
<a-animation attribute="scale"
dur="5000"
begin="eventstart"
from="1"
to ="5"
direction="alternate">
</a-animation>
<a-animation attribute="rotation"
dur="5000"
begin="eventstart"
from="0 0 0"
to="0 360 0"
direction="alternate">
</a-animation>
</a-entity>
The direction="alternate" should bring it back to its original position.
The quoted animation will work, if You set the begin event properly:
<a-animation attribute="rotation"
dur="2000"
begin="mouseenter"
to="0 360 0"
repeat="1"><a-animation>
On mouseenter, the animation triggers, and rotates the entity once.
To gain more control over what You do, You would need to get deep into making components.
1. The Easiest way i can think of, is using both the animation component, and Your own. You would need to set up a component listening for the mouseenter/mousexit, and trigger the animation:
AFRAME.registerComponent('mouseenterhandler', {
init: function () {
let el = this.el; //Your element reference
el.addEventListener('mouseenter, function () {
// get the rotation, by getAttribute, or by accessing the
//object3D.rotation
let rotation = el.getAttribute('rotation');
//lets rotate it to the same position
rotation.y += 360;
//set the animation component accordingly:
el.children[0].setAttribute('to',rotation);
//emit the 'begin' event:
el.emit('startrotating');
});
}
});
Quick Improvement if necessary: disable the listener, when the animation is triggered. Made with a boolean switched on the mouseenter event, and the animationend event.
2. You can choose not to use the animation component, and check on tick() if the cursor is over. If so, rotate the element by the actualRotation.y+0.1 ( or any other desired rotation ).
As noted before, You can access the rotation by getAttribute() or el.object3D.rotation.
As for the scale, You if You need to rotate + rescale the object on the mouseenter event, just add another animation, and use it like i did with the rotation.I'm not sure how it's usually done, in my experience animations are good, when there are not that many interactions, because they sometimes do unexpected things, which You have to predict/find out, and prevent.
On the other hand, making any animation manually ( changing properties on tick ) may seem laggy if the rotation delta is too big. You need to play with it, and find out which suits You best.
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);