Hi, i have a problem finding raycaster intersection point to display coordinates.
For some reason, intersection point is shifted verticaly from mouse point.
I have look a lot of quiestions about this without any luck, can you help me?
My Project is a web app built on React with NextJS
This is the code of the viewer component
import React, { useEffect, useRef, useMemo, useCallback, useState } from "react"
import { Canvas, ThreeEvent, useThree } from "@react-three/fiber"
import { Environment, OrbitControls, PerspectiveCamera, PointerLockControls } from "@react-three/drei"
import * as THREE from "three"
import { PM3DData } from "@/lib/pm3dDataGenerator"
interface Point {
x: number
y: number
z: number
}
interface CurvePlot3DProps {
PM3DData: PM3DData,
PM3DDataPmax: PM3DData,
PmaxEdgeData: PM3DData,
color?: string
plotStyle: "point" | "line" | "mesh"
pointSize?: number
setHoverData?: (data: { position: string; point: Point } | null) => void
}
interface PlaneToBufferGeometry {
(plane: THREE.Plane, size?: number): THREE.BufferGeometry;
}
const scaleData = (data: Point[]): [Point[], number] => {
let minX = Number.POSITIVE_INFINITY,
maxX = Number.NEGATIVE_INFINITY
let minY = Number.POSITIVE_INFINITY,
maxY = Number.NEGATIVE_INFINITY
let minZ = Number.POSITIVE_INFINITY,
maxZ = Number.NEGATIVE_INFINITY
data.forEach((point) => {
minX = Math.min(minX, point.x)
maxX = Math.max(maxX, point.x)
minY = Math.min(minY, point.y)
maxY = Math.max(maxY, point.y)
minZ = Math.min(minZ, point.z)
maxZ = Math.max(maxZ, point.z)
})
const rangeX = maxX - minX
const rangeY = maxY - minY
const rangeZ = maxZ - minZ
const minRange = Math.min(rangeX, rangeY)
const scaleFactor = minRange / rangeZ
const scaledData = data.map((point) => ({
x: point.x,
y: point.y,
z: (point.z) * scaleFactor,
}))
return [scaledData, scaleFactor]
}
const PointPlot: React.FC<CurvePlot3DProps> = ({ PM3DDataPmax, color = "#ff0000", pointSize = 20 }) => {
const [scaledData] = useMemo(() => scaleData(PM3DDataPmax.points), [PM3DDataPmax.points])
const points = useMemo(() => {
return scaledData.map((point) => new THREE.Vector3(point.x, point.y, point.z))
}, [scaledData])
const geometry = useMemo(() => {
return new THREE.BufferGeometry().setFromPoints(points)
}, [points])
return (
<points>
<bufferGeometry attach="geometry" {...geometry} />
<pointsMaterial attach="material" color={color} size={pointSize} sizeAttenuation={true} />
</points>
)
}
const LinePlot: React.FC<CurvePlot3DProps> = ({ PM3DDataPmax, color = "#FF0000FF" }) => {
const [scaledData] = useMemo(() => scaleData(PM3DDataPmax.points), [PM3DDataPmax.points])
const points = useMemo(() => {
return scaledData.map((point) => new THREE.Vector3(point.x, point.y, point.z))
}, [scaledData])
const lineGeometry = useMemo(() => {
const geometry = new THREE.BufferGeometry().setFromPoints(points)
return geometry
}, [points])
return (
<line>
<bufferGeometry attach="geometry" {...lineGeometry} />
<lineBasicMaterial attach="material" color={color} linewidth={2} />
</line>
)
}
// const MeshPlot2: React.FC<CurvePlot3DProps> = ({ PM3DData, color = "#ff0000" }) => {
// const [scaledData] = useMemo(() => scaleData(PM3DData.points), [PM3DData.points])
// const mesh = useMemo(() => {
// const geometry = new THREE.BufferGeometry()
// const vertices = scaledData.flatMap((point) => [point.x, point.y, point.z])
// geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3))
// geometry.setIndex(PM3DData.indices)
// geometry.computeVertexNormals()
// return geometry
// }, [PM3DData])
// const edges = useMemo(() => {
// return new THREE.EdgesGeometry(mesh)
// }, [mesh])
// return (
// <group>
// <mesh geometry={mesh} castShadow receiveShadow>
// {/* <meshPhongMaterial color={color} side={THREE.DoubleSide} /> */}
// <meshStandardMaterial color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} />
// </mesh>
// <lineSegments geometry={edges}>
// {/* <lineBasicMaterial color={new THREE.Color().setRGB( 0.1, 0, 0 )} /> */}
// <lineBasicMaterial opacity={0.5} color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} />
// {/* rgb 25,50,50 */}
// </lineSegments>
// </group>
// )
// }
const MeshPlot: React.FC<CurvePlot3DProps> = ({ PM3DData, PmaxEdgeData, color = "#ff0000", setHoverData }) => {
const meshRef = useRef<THREE.Mesh>(null)
const intersectionRef = useRef<THREE.Mesh>(null);
const [scaledData, scaleFactor] = useMemo(() => scaleData(PM3DData.points), [PM3DData])
const geometry = useMemo(() => {
const geo = new THREE.BufferGeometry()
const vertices = scaledData.flatMap((point) => [point.x, point.y, point.z]) // Swap y and z
geo.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3))
// Assuming a grid structure in the data, create faces
geo.setIndex(PM3DData.indices)
geo.computeVertexNormals()
return geo
}, [scaledData])
const geometryEdge = useMemo(() => {
debugger
const geo = new THREE.BufferGeometry()
const vertices = PmaxEdgeData.points.flatMap((point) => [point.x, point.y, point.z * scaleFactor]) // Swap y and z
geo.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3))
// Assuming a grid structure in the data, create faces
geo.setIndex(PmaxEdgeData.indices)
geo.computeVertexNormals()
return geo
}, [PmaxEdgeData.points])
// debugger
// const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), PM3DData.phiPmax*scaleFactor); // Horizontal plane
// const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 5); // Horizontal plane
// const planeHelper = new THREE.PlaneHelper(plane, 100, 0xff0000);
const plane = useMemo(() => {
return new THREE.Plane(new THREE.Vector3(0, 0, 1), PM3DData.phiPmax * scaleFactor);
}, []);
const edges = useMemo(() => {
return new THREE.EdgesGeometry(geometry)
}, [geometry])
const edges2 = useMemo(() => {
return new THREE.EdgesGeometry(geometryEdge)
}, [geometryEdge])
const [mousePoint, setMousePoint] = useState<Point | null>(null)
const { camera, raycaster, scene, gl } = useThree()
const handleInteraction = useCallback(
(event: ThreeEvent<PointerEvent>) => {
const { clientX, clientY } = event
const canvas = document.querySelector('canvas') as HTMLCanvasElement
const rect = canvas.getBoundingClientRect();
debugger
const mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// const mouse = new THREE.Vector2((clientX / window.innerWidth) * 2 - 1, -(clientY / window.innerHeight) * 2 + 1)
// const mouse = new THREE.Vector2((clientX / canvas.clientWidth) * 2 - 1, -(clientY / canvas.clientHeight) * 2 + 1)
// const mouse = new THREE.Vector2(((clientX + canvas.clientLeft) / canvas.width) * 2 - 1, -((clientY - canvas.clientTop) / canvas.height) * 2 + 1)
// const mouse = new THREE.Vector2(((clientX + window.screenLeft) / window.innerWidth) * 2 - 1, -((clientY - window.screenTop) / window.innerHeight) * 2 + 1)
// const mouse = new THREE.Vector2(((event.clientX - rect.left) / (rect.right - rect.left)) * 2 - 1
// ,-((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1)
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(scene.children, true)
if (intersects.length > 0) {
const intersect = intersects[0]
const { point } = intersect
setHoverData && setHoverData({
position: `x: ${point.x}, y: ${point.y}, z: ${point.z}`,
point: point,
})
// setMousePoint( new THREE.Vector3(point.x, point.y, -point.z))
setMousePoint(point)
// alert(`x: ${closestPoint.x}, y: ${closestPoint.y}, z: ${closestPoint.z}`)
} else {
setHoverData && setHoverData(null)
}
},
[camera, raycaster, scene, scaledData],
)
const handlePointerLeave = useCallback(() => {
setHoverData && setHoverData(null)
}, [])
const mousePointGeometry = useMemo(() => {
var point = mousePoint ? [new THREE.Vector3(mousePoint.x, mousePoint.y, mousePoint.z)] : []
return new THREE.BufferGeometry().setFromPoints(point)
}, [mousePoint])
return (
<group onPointerMove={handleInteraction} onPointerLeave={handlePointerLeave}>
{/* <mesh ref={meshRef} geometry={geometry} castShadow receiveShadow>
<meshStandardMaterial color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} clippingPlanes={[planeGeometry]} clipIntersection={true} />
</mesh>
<lineSegments geometry={edges}>
<lineBasicMaterial opacity={0.5} color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} clippingPlanes={[planeGeometry]} clipIntersection={true}/>
</lineSegments> */}
<mesh ref={meshRef} geometry={geometry}>
<meshStandardMaterial attach="material" color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} clippingPlanes={[plane]} />
</mesh>
{/* <lineSegments geometry={edges}>
<lineBasicMaterial color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} clippingPlanes={[plane]} />
</lineSegments> */}
<mesh ref={meshRef} geometry={geometryEdge}>
<meshStandardMaterial attach="material" color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} />
</mesh>
{/* <lineSegments geometry={edges2}>
<lineBasicMaterial color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} />
</lineSegments> */}
{/* <points>
<bufferGeometry attach="geometry" {...mousePointGeometry} />
<pointsMaterial attach="material" color={"red"} size={5} />
</points> */}
{mousePoint && (
<mesh position={new THREE.Vector3(mousePoint.x, mousePoint.y, mousePoint.z)}>
<sphereGeometry args={[0.2, 16, 16]} />
<meshBasicMaterial color="red" />
</mesh>)}
</group>
)
}
const CameraController: React.FC<{ pm3dData: Point[] }> = ({ pm3dData }) => {
const { camera } = useThree()
const controlsRef = useRef<any>()
const [scaledData] = useMemo(() => scaleData(pm3dData), [pm3dData])
useEffect(() => {
if (controlsRef.current) {
const box = new THREE.Box3()
// Calculate bounding box
scaledData.forEach((point) => {
box.expandByPoint(new THREE.Vector3(point.x, point.z, point.y))
})
const center = box.getCenter(new THREE.Vector3())
const size = box.getSize(new THREE.Vector3())
// Set camera position
const maxDim = Math.max(size.x, size.y, size.z)
const fov = (camera as THREE.PerspectiveCamera).fov * (Math.PI / 180)
let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2))
cameraZ *= 1.5 // Zoom out a little so objects don't fill the screen
camera.position.set(center.x, cameraZ, center.y)
// camera.position.set(center.x, center.z, center.y)
camera.lookAt(center)
camera.far = cameraZ * 10
camera.near = cameraZ / 1000
camera.near =1
// Update controls
controlsRef.current.target.set(center.x, center.y, center.z)
controlsRef.current.update()
}
}, [pm3dData, camera])
return (
<OrbitControls
ref={controlsRef}
rotateSpeed={5}
zoomSpeed={5}
panspeed={7}
target={new THREE.Vector3(0, 0, 0)}
minAzimutAngle={Math.PI / 2}
miaxAzimutAngle={Math.PI / 2}
/>
)
}
const Scene: React.FC<CurvePlot3DProps> = (props) => {
return (
<>
<PerspectiveCamera
makeDefault
up={[0, 0, -1]}
/>
<CameraController pm3dData={props.PM3DData.points} />
<ambientLight intensity={0.2} />
<directionalLight
position={[5, 5, 5]}
intensity={1}
castShadow
shadow-mapSize-width={1024}
shadow-mapSize-height={1024}
/>
<pointLight position={[-5, -5, -5]} intensity={0.5} />
{/* <mesh receiveShadow position={[0, -4, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[20, 20]} />
<shadowMaterial opacity={0.3} />
</mesh> */}
{props.plotStyle === "point" && <PointPlot {...props} />}
{props.plotStyle === "line" && <LinePlot {...props} />}
{props.plotStyle === "mesh" && <MeshPlot {...props} />}
<axesHelper args={[5]} scale={10} />
<Environment preset="sunset" />
()
</>
)
}
const CurvePlot3D: React.FC<CurvePlot3DProps> = (props) => {
const [hoverData, setHoverData] = useState<{ position: string; point: Point } | null>(null)
return (
<div className="relative w-full h-[40rem]">
<Canvas shadows gl={{ localClippingEnabled: true }}>
<Scene {...props} setHoverData={setHoverData} />
</Canvas>
<div className="absolute top-0 right-0 p-2 w-40">
<div>x: {hoverData?.point.x.toFixed(3)}</div>
<div>y: {hoverData?.point.y.toFixed(3)}</div>
<div>z: {hoverData?.point.z.toFixed(3)}</div>
{/* <div>Point: {hoverData?.point}</div> */}
</div>
</div>
)
}
export default CurvePlot3D