Mesh not showing on mobile

Im working on a web app using THREE to display 3D geometry

I have everythimg working on desktop, but for some reason, mesh do not show on mobile.

Desktop:

Mobile:

This is the code of my Plot 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
    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])

  // 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-three') 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;

      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;

      // 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)
      // camera.updateMatrix();
      // camera.updateMatrixWorld();
      // scene.updateMatrixWorld(true);
      raycaster.setFromCamera(mouse, camera)

      debugger
      // const intersects = raycaster.intersectObjects(scene.children.filter(ch => ch.name === "meshes").flatMap(ch => (ch as THREE.Group).children), true)
      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 intersect = intersects[0]
            // const { point } = intersect
            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( new THREE.Vector3(point.x, point.y, -point.z))
            setMousePoint(point)

            break;
          }
        }

        // 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 (
    <>
      {/* <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> */}

      <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>


      {/* <lineSegments geometry={edges2}>
        <lineBasicMaterial color={new THREE.Color().setRGB(0 / 255, 204 / 255, 0 / 255)} />
      </lineSegments> */}
      {/* {mousePoint && <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>)}

    </>
  )
}

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 *= 0.5 // Zoom out a little so objects don't fill the screen

      camera.position.set(-1*cameraZ, 1*cameraZ, -1*cameraZ)
      // camera.position.set(center.x, center.z, center.y)
      camera.lookAt(center)
      camera.far = cameraZ * 10
      camera.near = cameraZ / 1000
      camera.near = 0.1

      // Update controls
      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} />
      {/* <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 }} 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>Point: {hoverData?.point}</div> */}
      </div>
    </div>
  )
}

export default CurvePlot3D


Regards

Sorry, I managed to fix it. I believe the issue was caused by an extremely skewed mesh triangle, which somehow prevented the entire mesh from rendering.

Once I fixed the logic, everything worked as expected.

White strip maybe: shader variables precision, normal issue, uv etc.