How to position items relative to image target?

Whenever the events xrimagefound or xrimageupdated are fired, the detail will contain scaledWidth and scaledHeight, which are scaling information so you know if it’s a landscape or a portrait image. The bigger of these two will always be 1 (meter).

However, the smaller dimension tells you how many meters it takes to fill up the fill width/height of the image target. So if you got a scaledHeight of 0.74 (meters), that tells you that if you set the height to 0.74, your video or image element will take up the height of the entire image target.

But the problem is that 8th Wall always crops the image targets while scaling down the larger dimension (width, in the example) to 1, which will scale down the entire visible element.

So the solution is to calculate the ratio of your image or video element after the metadata has loaded, multiply that with the minimum dimension (e.g. scaledHeight 0.74), and use the minimum dimension and the modified ratio to set the scale on the child element of your image target element.

NOTE: It’s really important that the image target is the exact same size as your element and that you don’t adjust the cropping at all. Simply upload your image and follow all the required steps for adding a new image target.

Example code

I’m writing this blindly so … take it for what it is.

<!-- HTML -->
 <a-entity example-named-image-target="name: video-target">
    <a-entity 
      example-play-video="video: #jelly-video; thumb: #jelly-thumb;"
      geometry="primitive: plane"
    ></a-entity>
 </a-entity>

Original source: Image Targets Flyer example (copied and modified)

/** Javascript: registering components in index.js **/

import { exampleNamedImageTarget } from './example-named-image-target.js';
import { examplePlayVideo } from './example-play-video.js';

AFRAME.registerComponent('example-named-image-target', exampleNamedImageTarget);
AFRAME.registerComponent('example-play-video', examplePlayVideo);
/** Javascript component: example-named-image-target.js **/

const exampleNamedImageTarget = {
  schema: {
    name: {type: 'string'},
  },
  init() {
    const {object3D} = this.el
    const {name} = this.data

    const setupScalingFactor = ({detail}) => {
      if (name === detail.name) {
        // HERE IS WHERE THE MAGIC HAPPENS
        this.el.firstElementChild.components.minDimension = Math.min(detail.scaledHeight, detail.scaledWidth)
      }
    }

    // Basically the same as the original code
    const updateImage = ({detail}) => {
      if (name === detail.name) {
        object3D.position.copy(detail.position)
        object3D.quaternion.copy(detail.rotation)
        object3D.scale.set(detail.scale, detail.scale, detail.scale)
        object3D.visible = true
      }
    }

    const hideImage = ({detail}) => {
      if (name === detail.name) {
        object3D.visible = false
      }
    }

    this.el.sceneEl.addEventListener('xrimagefound', setupScalingFactor)
    this.el.sceneEl.addEventListener('xrimageupdated', updateImage)
    this.el.sceneEl.addEventListener('xrimagelost', hideImage)
  },
}

export { exampleNamedImageTarget }

Original source: xrextras-named-image-target (copied, simplified, and modified)

/** Javascript component: example-play-video.js **/

const examplePlayVideo = {
  schema: {
    video: {type: 'string'},
    thumb: {type: 'string'},
  },
  init() {
    const v = document.querySelector(this.data.video)
    const p = this.data.thumb && document.querySelector(this.data.thumb)
    const {el} = this

    el.setAttribute('material', 'src', p || v)
    el.setAttribute('class', 'cantap')

    /**
    * MORE MAGIC: SETTING CORRECT SCALE
    * 'scaledWidth' and 'scaledHeight' are in meters
    */
    const setScale(scaledWidth, scaledHeight) {
      el.object3D.scale.set(
        this.el.components.minDimension * scaledWidth,
        this.el.components.minDimension * scaledHeight
      )
    }

    // THIS ASSUMES YOU PRELOADED THE ELEMENT INSIDE A-ASSET
    // Parameters are either 1 or bigger than 1.
    setScale(
      (v.videoWidth > v.videoHeight) ? v.videoWidth / v.videoHeight : 1,
      (v.videoHeight > v.videoWidth) ? v.videoHeight / v.videoWidth : 1
    )

    el.addEventListener('click', () => {
      if (!playing) {
        el.setAttribute('material', 'src', v)
        v.play()
      } else {
        el.setAttribute('material', 'src', p || v)
        v.pause()
      }
    })
  },
}

export { examplePlayVideo }

Original source: xrextras-play-video (copied, modified, and extended)

Note that if the thumbnail doesn’t have the same dimensions as the element itself, then you need to take that into consideration by setting the scale based on the image dimensions and then setting the scale again based on the video dimensions when the video is about to be played.

2 Likes