Skip to main content
Documentation

Tagging

Place interactive tags in a Realsee 3D space with Five SDK in Vue without an additional wrapper.

In this chapter you will learn

  • How to pick a point in 3D space, map it to the screen, and render a DOM tag.
  • How to refresh tag positions with project2d as the camera moves.

Vue example: screen tags

<template>
  <div style="width: 100vw; height: 100vh">
    <div ref="container" style="width: 100%; height: 100%; overflow: hidden; position: relative" />
    <div v-for="t in validTags" :key="t.id" :style="t.style">
      <div style="background: #333; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 12px">Tag</div>
    </div>
    <div style="position: fixed; top: 16px; left: 16px"><button class="btn btn-primary" @click="enabled = !enabled">{{
      enabled ? 'Disable picking' : 'Enable picking' }}</button></div>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted, ref, computed } from 'vue'
import { Five, parseWork } from '@realsee/five'

const container = ref(null)
let five = null
const enabled = ref(false)
const tags = ref([]) // { id, position: THREE.Vector3 }
const tick = ref(0)
const workURL = 'https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json'

// Computed property: safely filter and project the tags
const validTags = computed(() => {
  // Depend on tick to trigger recomputation
  tick.value
  return tags.value
    .filter(tag => tag && tag.position) // Make sure both tag and position exist
    .map(tag => {
      const projected = project(tag.position)
      return projected ? { ...tag, projected, style: toStyle(projected) } : null
    })
    .filter(Boolean) // Drop invalid projection results
})

onMounted(() => {
  five = new Five()
  if (container.value) five.appendTo(container.value)
  fetch(workURL).then(r => r.json()).then(json => five.load(parseWork(json)))
  const onResize = () => five.refresh()
  window.addEventListener('resize', onResize, false)
  const onCameraUpdate = () => { tick.value++ }
  five.on('cameraUpdate', onCameraUpdate)

  const onWantsTap = (raycaster) => {
    if (!enabled.value) return true
    const hit = five.model.intersectRaycaster(raycaster)
    if (hit && hit[0] && hit[0].point) {
      tags.value.push({ id: Date.now(), position: hit[0].point })
    }
    return false
  }
  five.on('wantsTapGesture', onWantsTap)

  onUnmounted(() => {
    window.removeEventListener('resize', onResize, false)
    five.off('cameraUpdate', onCameraUpdate)
    five.off('wantsTapGesture', onWantsTap)
    five.dispose()
  })
})

function project(pos) {
  if (!five || !pos) return null
  return five.project2d(pos, true)
}
function toStyle(v2) {
  if (!v2 || typeof v2.x !== 'number' || typeof v2.y !== 'number') {
    return { display: 'none' }
  }
  return { position: 'absolute', transform: 'translate(-50%, 0)', left: `${v2.x}px`, top: `${v2.y}px` }
}
</script>

Key points:

  • Use wantsTapGesture to suppress the default move-to-point behavior.
  • Pick a 3D point with five.model.intersectRaycaster.
  • Use cameraUpdate to trigger re-rendering, and project2d to map the 3D point to screen coordinates.