如何更新点在BufferGeometry从API获取数据后?



我希望通过首先创建随机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]].

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

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