Skip to content


 Zabbix
 Grafana
 Prometheus
 React Three Fiber
 Threejs and TypeScript
 SocketIO and TypeScript
 Blender Topological Earth
 Sweet Home 3D
 Design Patterns Python
 Design Patterns TypeScript
   
 Course Coupon Codes
Three.js and TypeScript
Kindle Edition
$6.99 $9.99 Paperback 
$22.99 $29.99




Design Patterns in TypeScript
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Design Patterns in Python
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Lerp

Video Lecture

Section Video Links
Lerp : Part 1 Lerp Lerp
Lerp : Part 2 Lerp Lerp

Description

Animating object transforms and material properties using interpolation (Lerp).

Lerping is a very minimal and simple technique used to quickly transition a property from one state to another.

In this lesson, I use the lerp technique to modify the cameras position. I also modify many objects positions, rotations and colors depending on whether that object was clicked or the pointer is hovering over it.

I also quickly demonstrate the Drei Center helper, using Array.map to generate a series of objects in the JSX and introduce the React Three Fiber useThree hook.

./src/App.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Canvas, useThree, useFrame } from '@react-three/fiber'
import { Vector3 } from 'three'
import { Stats, Environment, Center } from '@react-three/drei'
import Button from './Button'

const vec = new Vector3()

function Rig() {
  return useFrame(({ camera, mouse }) => {
    vec.set(mouse.x * 2, mouse.y * 2, camera.position.z)
    camera.position.lerp(vec, 0.025)
    camera.lookAt(0, 0, 0)
  })
}

export default function App() {
  return (
    <Canvas camera={{ position: [0, 0, 5] }}>
      <Environment preset="forest" background />
      <Center>
        {[...Array(5).keys()].map((x) =>
          [...Array(3).keys()].map((y) => (
            <Button key={x + y * 5} position={[x * 2.5, y * 2.5, 0]} />
          ))
        )}
      </Center>
      <Rig />
      <Stats />
    </Canvas>
  )
}

./src/Button.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { useRef, useState, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import { MathUtils } from 'three'
import { Color } from 'three'

const black = new Color('black')
export default function Button(props) {
  const ref = useRef()
  const [hovered, setHovered] = useState(false)
  const [selected, setSelected] = useState(false)
  const colorTo = useMemo(
    () => new Color(Math.floor(Math.random() * 16777216)),
    []
  )

  useFrame(() => {
    ref.current.rotation.x = hovered
      ? MathUtils.lerp(ref.current.rotation.x, -Math.PI * 2, 0.025)
      : MathUtils.lerp(ref.current.rotation.x, 0, 0.025)

    ref.current.position.z = selected
      ? MathUtils.lerp(ref.current.position.z, 0, 0.025)
      : MathUtils.lerp(ref.current.position.z, -3, 0.025)

    ref.current.material.color.lerp(selected ? colorTo : black, 0.025)
  })

  return (
    <mesh
      {...props}
      ref={ref}
      onPointerDown={() => {
        setSelected(!selected)
      }}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
    >
      <icosahedronGeometry />
      <meshPhysicalMaterial
        roughness={0}
        metalness={0}
        thickness={3.12}
        ior={1.74}
        transmission={1.0}
      />
    </mesh>
  )
}

Lerp with Epsilon

One problem with Lerp, that may not ever be a problem for you, is that the destination/to of the lerp may never arrive, although it will be very close, and may appear to be correct. Lerping takes the current value, and the destination/to value, and moves it closer depending on the alpha value. The resulting difference will be a floating point number getting closer and closer to 0, but never actually reaching 0.

You can use this lerping function below, instead of MathUtils.lerp when you need your lerp to finish on the exact destination/to value. See the detail of the function, and you can see that it considers a minimum difference of .001. If the difference between the current value, and the destination/to value is less than .001, then it will return the desired destination/to value instead of the calculated value. The 0.001 is the Epsilon value that my function is using. You can replace it with a different minimum if you desire.

function lerp(x, y, a) {
  const r = (1 - a) * x + a * y
  return Math.abs(x - y) < 0.001 ? y : r
}

You then replace any usage of MathUtils.lerp

ref.current.position.z = selected
  ? MathUtils.lerp(ref.current.position.z, 0, 0.025)
  : MathUtils.lerp(ref.current.position.z, -3, 0.025)

with the custom lerp.

ref.current.position.z = selected
  ? lerp(ref.current.position.z, 0, 0.025)
  : lerp(ref.current.position.z, -3, 0.025)
Drei Center github.com/pmndrs/drei
Array.prototype.map MDN
useThree docs.pmnd.rs
MathUtils.lerp threejs.org
Vector3.lerp threejs.org sbcode.net
Color.lerp threejs.org
Maath/easing damp github.com/pmndrs/maath

Working Example

```