Difficulty creating basic Line (@react-three/fiber and Typescript)

Can anyone help me get this line to show up? Im using @react-three/fiber and Typescript

My Faulty Component:


import * as THREE from 'three'

const RoadLine = ({start, end}: {start: THREE.Vector3, end: THREE.Vector3}) => {

    return (
        <line>
            <bufferGeometry setFromPoints={() => new THREE.BufferGeometry().setFromPoints([start, end])}/>
            <lineBasicMaterial color={'green'}/>
        </line>
    )
}

export default RoadLine


------------------------------------------------------------------------------------------

The Props Im sending in: 

{roads?.map(road => {
                        return (
                            <RoadLine
                            key={road.id}
                            start={new THREE.Vector3(1,0,3)} //numbers just for test atm
                            end={new THREE.Vector3(11,0,33)} //numbers just for test atm
                            />
                        )
                    })}

Im trying to make a basic line from point a to b essentially, not sure if I'm going about it correctly

Upvotes: 4

Views: 9530

Answers (3)

episodeyang
episodeyang

Reputation: 782

Both answers contain known anti-patterns that should be avoided.

  1. @william's puts Vector3 instantiation in a useFrame. This is bad and should be avoided as much as possible. See implementation examples in both drei and three.js code base.
  2. @sean's answer condition on start and end. These two values are lists. React does shallow comparison, so this often leads to re-render when these two variables are re-constructed, even though they contain the same value.
  3. Both answers use map to instantiate the points. This is not a good style unless you are trying to accommodate more than two points

Instead of implementing your own, I recommend using the Line component from drei. they use instancing to improve performance, and accommodate multiple segments. See below:

import {
  Line,
} from "@react-three/drei";

export Example = ({cx, cy, distance}) => (
     <Line
        points={[                // Array of points, Array<Vector3
          [-cx, -cy, -distance], //   | Vector2 | [number, number, number] 
          [cx, -cy, -distance],  //   | [number, number] | number>
          [cx, cy, -distance],
          [-cx, cy, -distance],
          [-cx, -cy, -distance],
        ]}                      
        color={"#23aaff"}       // Default
        lineWidth={1}           // In pixels (default)
        // segments             // If true, renders a THREE.LineSegments2. Otherwise, renders a THREE.Line2
      />
)

Upvotes: 5

William Pilger
William Pilger

Reputation: 19

our friend's answer almost works, but for the typescript some tweaking is needed:

import * as THREE from 'three'
import React, { useRef } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'

type props = {
    start: number[],
    end: number[]
}

function Line (props: props) {
    const ref = useRef<THREE.Line>()

    useFrame(() => {
        if(ref.current){
            ref.current.geometry.setFromPoints([props.start, props.end].map((point) => new THREE.Vector3(...point)));
        }
    })
    return (
        <line ref={ref}>
            <bufferGeometry />
            <lineBasicMaterial color="hotpink"/>
        </line>
    )
}


export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
      <pointLight position={[-10, -10, -10]} />
      <Line start={[0,0,0]} end={[1,1,0]} />
      <Line start={[1,1,0]} end={[2,0,0]} />
    </Canvas>
  )
}

Upvotes: 1

Sean M
Sean M

Reputation: 79

I was struggling with this until I came across this response from Paul Henschel.

It becomes easier if you don't think of [line] as a wrapper or an abstraction. the jsx you write is threejs. if you write a line in threejs you can make it in jsx. but you're mixing up props and functions. THREE.BufferGeometry.setPoints is a function, and you overwrite it. in react/jsx does not call onClick, it sets it. you can't call a function declaratively, you do that as a side effect in either useEffect or useLayoutEffect. you use the latter if you want to call your function before the thing renders on screen.

He linked this codesandbox, I will post the code here as well. https://codesandbox.io/s/basic-demo-forked-e46t9?file=/src/App.js

import * as THREE from 'three'
import React, { useLayoutEffect, useRef } from 'react'
import { Canvas } from '@react-three/fiber'

function Line({ start, end }) {
  const ref = useRef()
  useLayoutEffect(() => {
    ref.current.geometry.setFromPoints([start, end].map((point) => new THREE.Vector3(...point)))
  }, [start, end])
  return (
    <line ref={ref}>
      <bufferGeometry />
      <lineBasicMaterial color="hotpink" />
    </line>
  )
}

export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
      <pointLight position={[-10, -10, -10]} />
      <Line start={[0, 0, 0]} end={[1, 0, 0]} />
      <Line start={[1, 0, 0]} end={[1, 1, 0]} />
    </Canvas>
  )
}

Upvotes: 7

Related Questions