首页 > 解决方案 > 从 API 获取数据后如何更新 BufferGeometry 中的点?

问题描述

我希望通过首先创建随机 50000 个点的 BufferGeometry 来创建点云,然后在获取数据后使用新数据更新这些点。这是我想出的组件

import React from "react";
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber'
import { useRef, useEffect, useState, useMemo, useCallback } from "react";
import axios from 'axios';

  function Particles({points}) {

    

    const [positions, colors] = useMemo(() => {
      let positions = [],
        colors = []

      if(points.length == 0){
        console.log(positions.length)
        console.log("Creating random points")
        for (let i = 0; i < 50000; i++) {
          positions.push(5 - Math.random() * 10)
          positions.push(5 - Math.random() * 10)
          positions.push(5 - Math.random() * 10)
          colors.push(1)
          colors.push(0.5)
          colors.push(0.5)
        }
      }
      else{
        console.log("Filling in values")
        console.log(positions.length)
        for (let i = 0; i < points.length; i++) {
          positions.push(points[i][0])
          positions.push(points[i][1])
          positions.push(points[i][2])
          colors.push(1)
          colors.push(0.5)
          colors.push(0.5)
        }
      }
      return [new Float32Array(positions), new Float32Array(colors)]
    }, [points])
  
    return (
      <points>
        <bufferGeometry attach="geometry">
          <bufferAttribute attachObject={["attributes", "position"]} count={positions.length / 3} array={positions} itemSize={3} />
          <bufferAttribute attachObject={["attributes", "color"]} count={colors.length / 3} array={colors} itemSize={3} />
        </bufferGeometry>
        <pointsMaterial attach="material" vertexColors size={10} sizeAttenuation={false} />
      </points>
    )
  }




  

const PointCloudPanel = () => {

    const [points, setPoints] = useState([])
      
    axios.post('http://127.0.0.1:8090/get_point_cloud')
    .then(res => {
        setPoints(res.data)
        console.log(points)
    })

    return (
        <div>
            <Canvas style={{width: `80%`, height: `1000px`}} margin-left='auto' margin-right='auto' orthographic camera={{ zoom: 60 }} raycaster={{ params: { Points: { threshold: 0.2 } } }}>
              <Particles points={points} />
            </Canvas>
        </div>
    )
}

export default PointCloudPanel

此代码没有达到预期的效果,而是继续循环调用 API。获取的数据只是具有 x,y,z 值的列表列表,例如 [[1,2,3],[4,3,6],[3,6,8]].

标签: reactjsthree.jsreact-three-fiber

解决方案


points不应该这样更新。更改道具会导致重新渲染。

尝试改变顶点位置并使用内部 API( .needsUpdate)更新它们

// state.js

const state = {
  // this can be used as prop in initial render
  // but eventually it should be updated manually via ref.
  // and most importantly, length of array should not be changed.
  points: new Float32Array([/* ... */]),
  requireUpdate: false;
};
export default state;

// your component
import useFrame from '@react-three/fiber';
import state from 'state';
const YourComponent = () => {
  const refPoints = useRef();
  useFrame(() => {
    if (refPoints.current && state.requireUpdate) {
      // manually inject numbers into property. so that it won't trigger re-render.
      const pos = refPoints.current.geometry.getAttribute('position');
      for (let i = 0; i <= state.points.length; i ++) {
        pos.array[i] = state.points[i]
      }
      refPoints.current.geometry.setAttribute('position', pos);
      refPoints.current.geometry.attributes.position.needsUpdate = true;
      state.requireUpdate = false;
    }
  });
  return (<points ref={refPoints}>
           {/* ... bufferGeometry and bufferAttributes ... */}
         </points>);
};

// inside UI component
import state from 'state';
const UIComponent = () => {
  const updatePoints = async () => {
    const result = await makeYourOwnRequest();
    state.points = new Float32Array([...result]);
    state.requireUpdate = true;
  }
  return <button onClick={updatePoints} />
}

与典型的反应代码不同,更新 r3f 中的状态不应触发重新渲染。在这种情况下,值突变非常方便。要构建更复杂的东西,请尝试使用zustand。它支持浅状态更新,不会触发与值更新相关的事件。

阅读这个three.js 官方文档会有所帮助。


推荐阅读