how to curve a plan entity to match a sphere surface? - aframe

i'm kinda new to aframe
i have a question : with the aframe-position-spherical-component, i managed to position my element in 360deg
but how can i "curve" my element? (to match the sphering display) (btw my element are
<html>
<head>
<script src="/lib/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-position-spherical-component/index.js"></script>
</head>
<body>
<a-scene>
<a-image src="/static/cat.jpeg" width="3" height="1.5" rotation="0 180 0" position-spherical="10 90 180"></a-image>
</a-scene>
</body>
</html>
first attempt to solve this :
for now i managed to get something near what i want, but the value were found manually and aren't perfect
<a-curvedimage src="/static/cat.jpeg" width="3" height="1.5" theta-length="64" radius="3" theta-start="-32" rotation="0 180 0" position-spherical="10 90 180"></a-curvedimage>
second attempt (with the a-sphere solution) :
kinda work, but image are mirrored, and adding a click event like to show an image bigger is difficult to achieve
<a-assets>
<img id="cat" src="/static/cat.jpeg" />
</a-assets>
<a-box scale="0.1 0.1 0.1" color="red"></a-box>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 210 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 240 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 270 0" material="side: back; shader: flat; src: #cat"></a-sphere>
<a-sphere radius="10" geometry="phiLength: 20; thetaLength: 14.12; thetaStart: 65" rotation="0 300 0" material="side: back; shader: flat; src: #cat"></a-sphere>

<a-curvedimage> is based on a cylinder (source) so it may not fit well.
So how about actually using the sphere geometry ?
tldr fiddle here
Geometry
You could make it look like a <a-curvedimage> using theta and psi properties of a sphere:
<a-sphere geometry='thetaStart: 45; thetaLength: 45; psiLength: 45'></a-sphere>
This should result in a <a-curvedimage>ish plane, but also curved in the vertical axis. Play around with the psi and theta to see more triangle, or diamond shaped geometries.
Fitting into a sphere
This seems like a job for a custom component ! If you didn't use them before, check out the link, otherwise the component below simply copies the sphere radius and position and uses their values for the image.
AFRAME.registerComponent('foo', {
schema: {
// we'll use it to provide the sphere
target: {type: selector}
},
init: function() {
let sphere = this.data.target
// make sure the sphere radius and transform is identical:
this.el.setAttribute('radius', sphere.getAttribute('radius'))
this.el.setAttribute('position', sphere.getAttribute('position'))
}
})
And simply use it like this:
<!-- the background with some position and radius -->
<a-sphere id='background'></a-sphere>
<!-- the inner sphere -->
<a-sphere foo='target: #background'></a-sphere>
Z-Fighting
You should notice that the image is not visible, or it's distorted. By now we have two spheres with identical size and transforms so the renderer won't know which one is in front of the another.
You could deal with this easily - by changing the radius for the inner sphere:
this.el.setAttribute('radius', sphere.getAttribute('radius') * 0.95)
Or you could move the inner sphere a bit towards the center - like in the provided fiddle:
// grab the inner sphere's mesh
let mesh = this.el.getObject3D('mesh')
// we need an axis - I'd substract the mesh's center from the spheres center
let axis = sphere.getAttribute('position').clone()
axis.add(mesh.geometry.boundingSphere.center.clone().multiplyScalar(-1))
axis.normalize();
// move the inner sphere a bit along the axis
this.el.object3D.translateOnAxis(axis, 0.05)
Enlarging the image on click
Usually we'd use the scale attribute, but here we can manipulate the phi and theta values to make the image bigger. Also you should bring the image to front when enlarged, to prevent z-fighting between images:
this.el.addEventListener('click', e => {
this.clicked = !this.clicked
// preset values depending if the image is clicked or not
let thetaLength = this.clicked ? 65 : 45
let thetaStart = this.clicked ? 35 : 45
let psiLength = this.clicked ? 65 : 45
let psiStart = this.clicked ? -10 : 0
let scale = this.clicked ? 0.95 : 1
// apply the new geometry
this.el.setAttribute('geometry', {
'thetaLength': thetaLength,
'thetaStart': thetaStart,
'phiLength': psiLength,
'phiStart' : psiStart
})
this.el.setAttribute('radius', sphere.getAttribute('radius') * scale)
})
fiddle here. It would be better to keep these values in variables (base value, and a delta), but i hope this way it's easier to get the idea.

Related

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 do I overlay text on top of a <a-plane> with AR.js and A-Frame?

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".

How to detect camera and sphere's distance in A-Frame

I'm trying to display some text when the camera move closer to the sphere. The idea is when user see the sphere moves much closer, the text then shows up, let's say, "Hello". But right now I only know how to add text with fixed position by using a-entity, I have no idea how to detect the distance between camera and sphere, and display text when user can see the sphere comes closer.
Here's my code now:
<html>
<head>
<script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-sphere position="0 0 0" perspective="true" radius="1.5" color="#aaa" id="sphere1"></a-sphere>
<a-entity position="4.5 2 0" text="width: 10; color: white; value: Hello"></a-entity>
<a-sky color="#000"></a-sky>
<a-entity class="camwrap" position="0 0 0">
<a-camera look-controls wasd-controls="fly:true acceleration:1" near="1" position="0 0 20" user-height="0" fov="60">
</a-camera>
</a-entity>
</a-scene>
<script>
const cam = document.querySelector("a-camera");
setTimeout(function() {
cam.components["wasd-controls"].keys["KeyW"] = true;
}, 1000);
</script>
</body>
Any thoughts?
If You know the camera position, and the sphere position - you can calculate the distance:
1) sqrt((x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2),
2) or by using the THREE.js a.distanceTo(b) method.
Throw your calculations into a custom component, and check the distance in a tick function:
init: function() {
this.cam = document.querySelector("[camera]")
this.sphere = document.querySelector("a-sphere")
},
tick: function() {
let camPos = this.cam.object3D.position
let spherePos = this.sphere.object3D.position
let distance = camPos.distanceTo(spherePos)
if (distance < 5) {
// camera closer than 5m, do something
}
}
Check it out in this fiddle.
You could alternately make an invisible sphere around the main sphere,
and use collision detection to make the text appear.
Collision detection is built into the aframe.extras.js

How to animate triangle vertices in a-frame

I am trying to animate an individual vertex of a triangle primitive in a-frame using the animation tag and am having some strange results
In one case, If I try to animate as I would expect to, changing the x,y,z values like so...
<a-triangle
vertex-a="-1 1 -5"
vertex-b="-1 1 -10"
vertex-c="4 1 -10"
color="blue"
material="side: double">
<a-animation
attribute="vertex-b"
from="-1 1 -10"
to="-1 5 -10"
dur="4000"
>
</a-animation>
</a-triangle>
Fiddle https://jsfiddle.net/k7fbmo9k/
...I get the following error
'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is
NaN. The "position" attribute is likely to have NaN values.'
However, If I only offer a single value like so..
<a-triangle
vertex-a="-1 1 -5"
vertex-b="-1 1 -10"
vertex-c="4 1 -10"
color="blue"
material="side: double">
<a-animation
attribute="vertex-b"
from="1"
to="5"
dur="4000"
>
</a-animation>
</a-triangle>
Fiddle https://jsfiddle.net/k7fbmo9k/1/
..it seems to 'work' as in no errors and there is animation but I have no control over it.
Am I doing something wrong?
Thanks for any advice as ever
I have a solution. Using the animation component https://www.npmjs.com/package/aframe-animation-component
the vertices will animate as expected. See example below
<a-triangle
vertex-a="-1 1 -5"
vertex-b="-1 1 -10"
vertex-c="4 1 -10"
color="blue"
material="side: double"
animation="property: vertex-b; to: -1 5 -10; dur: 1000">
</a-triangle>
Try attribute="geometry.vertexB". It might not work to animate the primitive attributes, so we animate the geometry component directly.
Otherwise, if you are comfortable enough, it is better to animate vertices at a more advanced level for performance. You can use a vertex shader.
Or you can manipulate the vertices manually:
var geometry = triangleEl.getObject3D('mesh').geometry;
geometry.attributes.position[0] += 0.1;
geometry.attributes.position[1] += 0.1;
geometry.attributes.position[2] += 0.1;
geometry.attributes.position.needsUpdate = true;
And to animate, do it in a tick handler:
AFRAME.registerComponent('animate-vertex', {
tick: function (t) {
// Animate vertex position until desired position using `t` to interpolate time.
}
});

How to fire A-FRAME collision event when two boxes intersect each other?

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.

Resources