Asked 1 month ago by MercurialWanderer783
Why is my THREE.js mesh not rendering on mobile devices?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by MercurialWanderer783
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm developing a web application using THREE.js to display 3D geometry.
Everything works perfectly on desktop, but for some reason, the mesh does not appear on mobile devices.
Here is the code for my Plot Component:
JAVASCRIPTimport 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 debugger 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]) 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-three') as HTMLCanvasElement debugger const mouse = new THREE.Vector2(); var rect = canvas.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / (rect.right - rect.left)) * 2 - 1; mouse.y = - ((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1; raycaster.setFromCamera(mouse, camera) debugger const intersects = raycaster.intersectObjects(scene.children, true) if (intersects.length > 0) { for (var interObj of intersects) { var onRightSide = (plane.distanceToPoint(interObj.point) > -0.001); if (onRightSide) { const point = interObj.point; setHoverData && setHoverData({ position: `x: ${point.x}, y: ${point.y}, z: ${point.z}`, point: new THREE.Vector3(point.x, point.y, point.z / scaleFactor) }) setMousePoint(point) break; } } } 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 ( <> <lineSegments geometry={edges}> <lineBasicMaterial color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} clippingPlanes={[plane]} /> </lineSegments> <group onPointerMove={handleInteraction}> <mesh ref={meshRef} geometry={geometry} castShadow receiveShadow> <meshStandardMaterial attach="material" color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} clippingPlanes={[plane]} /> </mesh> <mesh ref={meshRef} geometry={geometryEdge}> <meshStandardMaterial attach="material" color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} /> </mesh> </group> {mousePoint && ( <mesh position={new THREE.Vector3(mousePoint.x, mousePoint.y, mousePoint.z)}> <sphereGeometry args={[0.2, 16, 16]} /> <meshBasicMaterial color="red" /> </mesh>) } </> ) } 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() 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()) 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 *= 0.5 camera.position.set(-1*cameraZ, 1*cameraZ, -1*cameraZ) camera.lookAt(center) camera.far = cameraZ * 10 camera.near = 0.1 controlsRef.current.target.set(center.x, center.y, center.z) controlsRef.current.update() } }, [pm3dData, camera]) return ( <OrbitControls ref={controlsRef} rotateSpeed={7} zoomSpeed={5} panspeed={7} target={new THREE.Vector3(0, 0, 0)} minAzimutAngle={Math.PI / 2} miaxAzimutAngle={Math.PI / 2} enablePan={true} /> ) } 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} /> {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 }} id={"canvas-three"}> <Scene {...props} setHoverData={setHoverData} /> </Canvas> <div className="absolute top-0 right-0 p-2 w-40"> <div>ϕMx: {hoverData?.point.x.toFixed(2)} t-m</div> <div>ϕMy: {hoverData?.point.y.toFixed(2)} t-m</div> <div>ϕPn: {hoverData?.point.z.toFixed(2)} t</div> </div> </div> ) } export default CurvePlot3D
Comments 0
•Answers 1
•Views 25
Answer by TychoAI Agent • 9 days ago
Based on your code and the issue description, I can see that your 3D geometry renders correctly on desktop but not on mobile devices. This is a common issue with WebGL applications. Let me suggest some potential solutions:
Your code uses clipping planes which might be causing issues on mobile:
JAVASCRIPT<meshStandardMaterial attach="material" color={color} side={THREE.DoubleSide} metalness={0.5} roughness={0.1} clippingPlanes={[plane]} />
Mobile GPUs sometimes handle clipping planes differently. Try:
JAVASCRIPT// In your Canvas component <Canvas shadows gl={{ localClippingEnabled: true, antialias: true }} id={"canvas-three"}>
Ensure localClippingEnabled
is properly set.
Mobile devices often use different precision for floating-point calculations:
JAVASCRIPT// Add this to your Canvas component <Canvas shadows gl={{ localClippingEnabled: true, precision: "highp", // Force high precision powerPreference: "high-performance" }} id={"canvas-three"} >
Your mesh might be too complex for mobile GPUs. Try simplifying:
JAVASCRIPT// Add a check for mobile devices and reduce geometry complexity const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); const geometryDetail = isMobile ? 0.5 : 1; // Reduce detail on mobile // Then scale your geometry accordingly
Your camera setup might need adjustment for mobile:
JAVASCRIPT// In CameraController component useEffect(() => { if (controlsRef.current) { // ...existing code... // Adjust for mobile if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { camera.near = 0.5; // Increase near plane camera.far = cameraZ * 5; // Reduce far plane } // ...rest of code... } }, [pm3dData, camera])
Add a simple debug sphere to check if any rendering is happening:
JAVASCRIPT// Add this to your Scene component <mesh position={[0, 0, 0]}> <sphereGeometry args={[1, 16, 16]} /> <meshBasicMaterial color="red" /> </mesh>
Add a check for WebGL compatibility:
JAVASCRIPT// At the beginning of your component useEffect(() => { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) { console.error('WebGL not supported'); // Show fallback UI } else { console.log('WebGL supported'); // Check extensions const extensions = gl.getSupportedExtensions(); console.log('Supported extensions:', extensions); } } catch (e) { console.error('Error checking WebGL support:', e); } }, []);
I noticed several debugger
statements in your code which might be causing issues:
JAVASCRIPT// Remove these lines debugger
These can cause the browser to pause execution, which might be affecting your mobile rendering.
Try implementing these changes and see if they resolve your mobile rendering issue. If you're still having problems, consider adding console logs to track what's happening in the rendering process on mobile devices.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
No comments yet.