|
| 1 | +import { Container, ContainerProperties, ContainerRef, isInteractionPanel } from '@react-three/uikit' |
| 2 | +import { forwardRef, useRef } from 'react' |
| 3 | +import { computed } from '@preact/signals-core' |
| 4 | +import { Euler, Matrix4, Quaternion, Vector3 } from 'three' |
| 5 | + |
| 6 | +const matrixHelper = new Matrix4() |
| 7 | +const quaternionHelper = new Quaternion() |
| 8 | + |
| 9 | +export const Highlighter = forwardRef<ContainerRef, ContainerProperties>( |
| 10 | + ({ onPointerOver, onPointerLeave, children, ...props }, ref) => { |
| 11 | + const highlightRef = useRef<ContainerRef | null>(null) |
| 12 | + return ( |
| 13 | + <Container |
| 14 | + ref={ref} |
| 15 | + {...props} |
| 16 | + onPointerOver={(e) => { |
| 17 | + if (!isInteractionPanel(e.object) || highlightRef.current == null) { |
| 18 | + return |
| 19 | + } |
| 20 | + const { |
| 21 | + internals: { globalMatrix, root, size }, |
| 22 | + } = e.object |
| 23 | + const transformation = computed(() => { |
| 24 | + const { value } = globalMatrix |
| 25 | + if (value == null) { |
| 26 | + return { translation: new Vector3(), scale: new Vector3(), rotation: new Euler() } |
| 27 | + } |
| 28 | + matrixHelper.copy(value) |
| 29 | + const translation = new Vector3() |
| 30 | + const scale = new Vector3() |
| 31 | + matrixHelper.decompose(translation, quaternionHelper, scale) |
| 32 | + const rotation = new Euler().setFromQuaternion(quaternionHelper) |
| 33 | + return { translation, scale, rotation } |
| 34 | + }) |
| 35 | + const width = computed(() => transformation.value.scale.x * (size.value?.[0] ?? 0)) |
| 36 | + const height = computed(() => transformation.value.scale.y * (size.value?.[1] ?? 0) * 1) |
| 37 | + highlightRef.current.setStyle({ |
| 38 | + visibility: 'visible', |
| 39 | + transformTranslateX: computed( |
| 40 | + () => transformation.value.translation.x / root.pixelSize.value - 0.5 * width.value, |
| 41 | + ), |
| 42 | + transformTranslateY: computed( |
| 43 | + () => -transformation.value.translation.y / root.pixelSize.value - 0.5 * height.value, |
| 44 | + ), |
| 45 | + transformTranslateZ: computed(() => transformation.value.translation.z / root.pixelSize.value), |
| 46 | + |
| 47 | + transformScaleZ: computed(() => transformation.value.scale.z), |
| 48 | + transformRotateX: computed(() => transformation.value.rotation.x), |
| 49 | + transformRotateZ: computed(() => transformation.value.rotation.y), |
| 50 | + transformRotateY: computed(() => transformation.value.rotation.z), |
| 51 | + width, |
| 52 | + height, |
| 53 | + }) |
| 54 | + onPointerOver?.(e) |
| 55 | + }} |
| 56 | + onPointerLeave={(e) => { |
| 57 | + highlightRef.current?.setStyle({ visibility: 'hidden' }) |
| 58 | + |
| 59 | + onPointerLeave?.(e) |
| 60 | + }} |
| 61 | + > |
| 62 | + {children} |
| 63 | + <Container |
| 64 | + ref={highlightRef} |
| 65 | + pointerEvents="none" |
| 66 | + positionType="absolute" |
| 67 | + positionLeft="50%" |
| 68 | + positionTop="50%" |
| 69 | + zIndexOffset={Infinity} |
| 70 | + borderColor="red" |
| 71 | + borderWidth={1} |
| 72 | + /> |
| 73 | + </Container> |
| 74 | + ) |
| 75 | + }, |
| 76 | +) |
0 commit comments