I have elements that need to be precisely positioned relative to an image target.
Is there any way to do this via some kind of GUI?
Currently it’s a lot of trial and error tweaking position
values ad nauseum.
Hi @Ze_ev_Gilad at this time the best options is to either use the jellyfish photos for reference and or bring your image target into a 3D modeling software such as Blender and position your objects according to the image target. I’ve attached a photo of how I set up my image targe in blender to better align objects. Then second photo showing some animations.
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.
Thank you! Very useful info.
Discovered the other week that the event xrimagescanning
is sent out whenever image targets are set, and the detail will contain the following properties for all image targets:
So, it’s not necessary to wait for the metadata from the image or video to set the scale, unless you create example-name-image-targets
dynamically (like I do). You can instead calculate the scale directly in the example-named-image-target
component, using originalHeight
and originalWidth
together with Math.min(geometry.scaledWidth, geometry.scaledHeight)
.
Hi Rickard ,
Did you find a way to disable the " xr image scanning " function …
I am seeing a lot of positional jitter , else floating point errors when the marker position updates. Then we have a tall 3d object so its made worse at the extents. Ideally once the " xr image scanning " is done , we should be able to turn it off …
Thanks !