Search code examples
aframecurvespherical-coordinate

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


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>

Solution

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