Skip to main content
Documentation

Adding Tags

Place interactive tags in a Realsee 3D space with Five SDK in React.

What you will learn in this chapter

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

React example: screen tags

import { useEffect, useRef, useState } from 'react'
import { Five, parseWork } from '@realsee/five'

const workURL = 'https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json'

export default function FiveTagging() {
  const containerRef = useRef(null)
  const fiveRef = useRef(null)
  const [enabled, setEnabled] = useState(false)
  const [tags, setTags] = useState([]) // { id, position: THREE.Vector3 }
  const [tick, setTick] = useState(0) // trigger a re-render to refresh project2d

  useEffect(() => {
    const five = new Five()
    fiveRef.current = five
    if (containerRef.current) five.appendTo(containerRef.current)
    fetch(workURL).then(r=>r.json()).then(json=>five.load(parseWork(json)))

    const onResize = () => five.refresh()
    window.addEventListener('resize', onResize, false)

    const onCameraUpdate = () => setTick((t) => t + 1)
    five.on('cameraUpdate', onCameraUpdate)

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

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

  const project = (pos) => {
    const five = fiveRef.current
    if (!five) return null
    return five.project2d(pos, true)
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <div ref={containerRef} style={{ width: '100%', height: '100%', overflow: 'hidden', position: 'relative' }}>
        {tags.map((t) => {
          const v2 = project(t.position)
          if (!v2) return null
          return (
            <div key={t.id} style={{ position: 'absolute', transform: 'translate(-50%, 0)', left: v2.x, top: v2.y }}>
              <div style={{ background: '#333', color: '#fff', padding: '6px 10px', borderRadius: 4, fontSize: 12 }}>Tag</div>
            </div>
          )
        })}
      </div>
      <div style={{ position: 'fixed', top: 16, left: 16, display: 'flex', gap: 8 }}>
        <button className="btn btn-primary" onClick={()=>setEnabled(v=>!v)}>{enabled? 'Disable picking':'Enable picking'}</button>
      </div>
    </div>
  )
}

Key points:

  • Use wantsTapGesture to prevent the default waypoint navigation;
  • Pick the 3D point with five.model.intersectRaycaster;
  • project2d maps the 3D point to screen coordinates, rendered as a floating DOM element.