Hi, I’ve gone through this example, but doesn’t matter which position I apply to the annotation, it doesn’t render above the mesh, rather the annotations are being rendered above the model instead of being on top of the mesh. I can’t figure out what i’m missing.
import { Html } from '@react-three/drei'
import { useState } from 'react'
export interface Annotation {
id: number
title: string
description: string
targetNodeName: string;
position: [number, number, number]
camPos: {
x: number;
y: number;
z: number;
}
lookAt: {
x: number;
y: number;
z: number;
}
}
export const annotations: Annotation[] = [
{
id: 1,
title: 'Left auricle',
targetNodeName: 'left-auricle',
description: 'Ear-like flap overlying the left atrium',
position: [-0.5, 1.2, 0.3],
camPos: { x: 1, y: 100, z: 1 },
lookAt: { x: -0.5, y: 1.2, z: 0.3 }
},
{
id: 2,
title: 'Right auricle',
targetNodeName: 'right-auricle',
description: 'Ear-like flap overlying the right atrium',
position: [0.6, 1.1, 0.4],
camPos: { x: 1, y: 1, z: 1 },
lookAt: { x: 0.6, y: 1.1, z: 0.4 }
}
];
interface AnnotationsProps {
onSelectAnnotation: (annotation: Annotation) => void;
}
export const Annotations = ({ onSelectAnnotation }: AnnotationsProps) => {
const [selected, setSelected] = useState<Annotation | null>(null)
const handleClick = (annotation: Annotation) => {
setSelected(annotation)
onSelectAnnotation(annotation)
}
return (
<>
{annotations.map((annotation, index) => {
return (
<Html key={index} position={[annotation.lookAt.x, annotation.lookAt.y, annotation.lookAt.z]}>
<svg onClick={() => handleClick(annotation)} height="34" width="34" transform="translate(-16 -16)" style={{ cursor: 'pointer' }}>
<circle
cx="17"
cy="17"
r="16"
stroke="white"
strokeWidth="2"
fill="rgba(0,0,0,.66)"
/>
<text x="12" y="22" fill="white" fontSize={17} fontFamily="monospace" style={{ pointerEvents: 'none' }}>
{index + 1}
</text>
</svg>
{annotation.description && annotation.id === selected?.id && (
<div className='relative'>
<div id={'desc_' + index} className="annotationDescription" dangerouslySetInnerHTML={{ __html: annotation.description }} />
<button onClick={() => setSelected(null)} className="absolute top-1 right-2 text-white text-md">close</button>
</div>
)}
</Html>
)
})}
</>
)
}
import { Text, useGLTF } from "@react-three/drei";
import { useLayoutEffect, useRef } from "react";
import { JSX } from "react/jsx-runtime";
import * as THREE from 'three';
import { GLTFParser } from "three/examples/jsm/Addons.js";
export interface GLTF {
animations: THREE.AnimationClip[]
scene: THREE.Group
scenes: THREE.Group[]
cameras: THREE.Camera[]
asset: {
copyright?: string
generator?: string
version?: string
minVersion?: string
extensions?: any
extras?: any
}
parser: GLTFParser
userData: any
}
export type ObjectMap = {
nodes: {
[name: string]: THREE.Object3D
}
materials: {
[name: string]: THREE.Material
}
}
export function Heart(props: JSX.IntrinsicElements['group']) {
const groupRef = useRef<THREE.Group>(null);
const { nodes, materials }: ObjectMap = useGLTF('/heart_superficial_anatomy.glb')
useLayoutEffect(() => {
if (groupRef.current) {
const box = new THREE.Box3().setFromObject(groupRef.current)
const center = box.getCenter(new THREE.Vector3())
groupRef.current.rotation.x = Math.PI
groupRef.current.position.sub(center) // shift entire group so it's centered
}
}, [])
const getMeshName = (object: THREE.Object3D): string | null => {
const mesh = object as THREE.Mesh;
if (mesh.material) {
const material = mesh.material as THREE.MeshBasicMaterial;
return material.name;
}
return null;
};
return (
<group ref={groupRef} {...props} dispose={null} scale={0.01}>
<group position={[0, 0, 5]} rotation={[0, 0, 0]}>
{/* {Object.entries(nodes).map(([key, value]) => {
const mesh = value as THREE.Mesh;
const material = mesh.material as THREE.MeshBasicMaterial;
console.log("🚀 ~ {Object.entries ~ material:", material)
return (
mesh.isMesh && (
<mesh
key={key}
name={material.name}
geometry={mesh.geometry}
material={material}
castShadow
receiveShadow
onPointerOver={(e) => {
e.stopPropagation();
}}
onPointerOut={(e) => {
if (e.intersections.length === 0) {
console.log('e.intersections.length', e.intersections.length)
}
}}
onClick={(e) => {
e.stopPropagation();
const name = getMeshName(e.object);
if (name) {
console.log('e.object.uuid', e.object.id)
}
}}
/>
)
);
})} */}
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_2?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_3?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_4?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_5?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_6?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_7?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_8?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_9?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_10?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_11?.geometry}
material={materials?.texture}
/>
<mesh
castShadow
receiveShadow
geometry={nodes?.Object_12?.geometry}
material={materials?.texture}
/>
</group>
</group>
)
}
useGLTF.preload('/heart_superficial_anatomy.glb');