mtx
mtx

Reputation: 1234

Animating bufferAttribute with react-three-fiber and react-spring

I have simple Points mesh with custom shader and buffer geometry.

The geometry has position, size and color attributes. On pointer hover, the hovered vertex turns into red color.

So far so good.

Now I would like to animate the change of color of the hovered vertex.

Here is the code snippet for the Points mesh:

const Points = (
  props = { hoveredIndex: null, initialCameraZ: 0, array: [], sizes: [] }
) => {
  const [_, setHover] = useState(false);

  const vertices = new Float32Array(props.array);
  const sizes = new Float32Array(props.sizes);
  const _colors = genColors(props.sizes.length); // returns [] of numbers.

  const uniforms = useMemo(() => {
    return {
      time: { type: "f", value: 0.0 },
      cameraZ: { type: "f", value: props.initialCameraZ }
    };
  }, [props.initialCameraZ]);

  // trying to use react-spring here
  const [animProps, setAnimProps] = useSpring(() => ({
    colors: _colors
  }));

  const geometry = useUpdate(
    (geo) => {
      if (props.hoveredIndex !== null) {
        const i = props.hoveredIndex * 3;
        const cols = [..._colors];
        cols[i] = 1.0;
        cols[i + 1] = 0.0;
        cols[i + 2] = 0.0;

        geo.setAttribute(
          "color",
          new THREE.BufferAttribute(new Float32Array(cols), 3)
        );

        setAnimProps({ colors: cols });
      } else {
        geo.setAttribute(
          "color",
          new THREE.BufferAttribute(new Float32Array(_colors), 3)
        );

        setAnimProps({ colors: _colors });
      }
    },
    [props.hoveredIndex]
  );

  return (
    <a.points
      onPointerOver={(e) => setHover(true)}
      onPointerOut={(e) => setHover(false)}
    >
      <a.bufferGeometry attach="geometry" ref={geometry}>
        <bufferAttribute
          attachObject={["attributes", "position"]}
          count={vertices.length / 3}
          array={vertices}
          itemSize={3}
        />
        <bufferAttribute
          attachObject={["attributes", "size"]}
          count={sizes.length}
          array={sizes}
          itemSize={1}
        />
        <a.bufferAttribute
          attachObject={["attributes", "color"]}
          count={_colors.length}
          array={new Float32Array(_colors)}
          // array={animProps.colors} // this does not work
          itemSize={3}
        />
      </a.bufferGeometry>
      <shaderMaterial
        attach="material"
        uniforms={uniforms}
        vertexShader={PointsShader.vertexShader}
        fragmentShader={PointsShader.fragmentShader}
        vertexColors={true}
      />
    </a.points>
  );
};

Full code and example is available on codesandbox

When I try to use animProps.colors for color in bufferAttribute it fails to change the color.

What am i doing wrong? How to make it right?

I know I could create start and target color attributes, pass them to the shader and interpolate there but that would beat the purpose of using react-three-fiber.

Is there a way animating buffer attributes in react-three-fiber?

Upvotes: 6

Views: 2275

Answers (1)

chantey
chantey

Reputation: 5827

This likely isn't working as expected because changes made to buffer attributes need to be explicitly flagged for sending to the GPU. I suppose that react-spring does not do this out of the box.

Here's a vanilla example, note the usage of needsUpdate:

import { useFrame } from '@react-three/fiber'
import React, { useRef } from 'react'
import { BufferAttribute, DoubleSide } from 'three'

const positions = new Float32Array([
    1, 0, 0,
    0, 1, 0,
    -1, 0, 0,
    0, -1, 0
])

const indices = new Uint16Array([
    0, 1, 3,
    2, 3, 1,
])

const Comp = () => {
    
    const positionsRef = useRef<BufferAttribute>(null)

    useFrame(() => {
        const x = 1 + Math.sin(performance.now() * 0.01) * 0.5
        positionsRef.current.array[0] = x
        positionsRef.current.needsUpdate = true
    })

    return <mesh>
        <bufferGeometry>
            <bufferAttribute
                ref={positionsRef}
                attach='attributes-position'
                array={positions}
                count={positions.length / 3}
                itemSize={3}
            />
            <bufferAttribute
                attach="index"
                array={indices}
                count={indices.length}
                itemSize={1}
            />
        </bufferGeometry>
        <meshBasicMaterial 
            color={[0, 1, 1]} 
            side={DoubleSide}
        />
    </mesh>
}

For further reading check out this tutorial.

Upvotes: 1

Related Questions