打标签
本章你可以学习到
- 在三维空间中拾取点并映射到屏幕,进行 DOM 标签渲染。
- 通过
project2d
在相机移动时刷新标签位置。
React 示例:屏幕标签
- JavaScript
- TypeScript
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) // 触发重渲染以刷新 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 }}>标签</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? '关闭拾取':'开启拾取'}</button>
</div>
</div>
)
}
import { useEffect, useRef, useState } from 'react'
import { Five, parseWork } from '@realsee/five'
import type { Vector2, Vector3 } from 'three'
const workURL = 'https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json'
type Tag = { id: number; position: Vector3 }
export default function FiveTagging() {
const containerRef = useRef<HTMLDivElement | null>(null)
const fiveRef = useRef<Five | null>(null)
const [enabled, setEnabled] = useState(false)
const [tags, setTags] = useState<Tag[]>([])
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 onWantsTap = (raycaster: any) => {
if (!enabled) return true
const hit = five.model.intersectRaycaster(raycaster)
if (hit) setTags((prev)=>[...prev, { id: Date.now(), position: hit.point }])
return false
}
five.on('wantsTapGesture', onWantsTap)
return () => {
window.removeEventListener('resize', onResize, false)
five.off('wantsTapGesture', onWantsTap)
five.dispose()
}
}, [enabled])
const project = (pos: Vector3): Vector2 | null => {
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 }}>标签</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? '关闭拾取':'开启拾取'}</button>
</div>
</div>
)
}
要点:
- 使用
wantsTapGesture
阻止默认点位移动; - 通过
five.model.intersectRaycaster
拾取三维点; project2d
将三维点映射为屏幕坐标,渲染为浮动 DOM。