基于我偶然发现的这个练习,我一直在研究一种用于 n 体模拟的强力方法: https://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/nbody.html 该代码有些“有效”,可以在我的 github 上找到: https://github.com/jeromejanicot/thirdjs-fun
我在 useEffect 中使用 setInterval 是否正确? 我应该在哪里进行计算? 如何在 useEffect 和 useFrame 挂钩之间同步计算? 我做事的方式有什么特别不好的地方吗?
我定义了用于计算两个物体之间的成对力以及物体的方向加速度的函数:
src/引力/nBodySim.ts
export type Coordinate = [x: number, y: number, z: number];
export interface Particle {
name: string;
position: Coordinate;
color: string;
size: number;
mass: number;
velocity: Coordinate;
netForces: Coordinate;
acceleration: Coordinate;
}
export function pairwiseForce(
G: number,
particle1: Particle,
particle2: Particle
) {
const distanceSquared =
((particle2.position[0] - particle1.position[0]) ** 2 +
(particle2.position[1] - particle1.position[1]) ** 2 +
(particle2.position[2] - particle1.position[2]) ** 2) **
2;
const x =
((G * (particle1.mass * particle2.mass)) / distanceSquared) *
(particle2.position[0] - particle1.position[0] / distanceSquared);
const y =
((G * (particle1.mass * particle2.mass)) / distanceSquared) *
(particle2.position[1] - particle1.position[1] / distanceSquared);
const z =
((G * (particle1.mass * particle2.mass)) / distanceSquared) *
(particle2.position[2] - particle1.position[2] / distanceSquared);
return {
x: x,
y: y,
z: z,
};
}
export function acceleration(netForces: Coordinate, m: number) {
const ax = netForces[0] / m;
const ay = netForces[1] / m;
const az = netForces[2] / m;
return {
ax: ax,
ay: ay,
az: az,
};
}
然后我使用这些公式来计算 useEffect 内对象的位置。 物体的形状:
const particle1: Particle = {
name: "particle1",
position: [0, 0, 12],
color: "blue",
mass: 100,
size: 1,
velocity: [1, 1, 1],
netForces: [0, 0, 0],
acceleration: [0, 0, 0],
};
具有 setInterval 的 useEffect。我确信我打破了很多最佳实践,并且非常感谢有关我使用 React api 的反馈。 另外,我不太确定我的数值积分是否正确,但我可以在其他地方查找。
src/App.tsx
useEffect(() => {
const positionTracker = setInterval(() => {
for (let i = 0; i < particles.length; i++) {
const netForces: Coordinate = [0, 0, 0];
for (let j = 0; j < particles.length; j++) {
if (i == j) {
continue;
}
const { x, y, z } = pairwiseForce(
gravitationalConstant,
particles[i],
particles[j]
);
netForces[0] += x;
netForces[1] += y;
netForces[2] += z;
}
// 1 - Calculate velocity at t+delta/2
// acceleration t
const { ax, ay, az } = acceleration(netForces, particles[i].mass);
// velocity at t - delta/2 (for initial velocity of 1 then 0.5?) initial velocity * 0.25
const vMinusDeltaX = particles[i].velocity[0] / 2;
const vMinusDeltaY = particles[i].velocity[1] / 2;
const vMinusDeltaZ = particles[i].velocity[2] / 2;
// vfd/2 = vpd/2 * at
const vPlusDeltaX = vMinusDeltaX + ax;
const vPlusDeltaY = vMinusDeltaY + ay;
const vPlusDeltaZ = vMinusDeltaZ + az;
// 2 - Calculate Position of Particule based on vfd/2
setParticles(
produce((draft) => {
draft[i].netForces = [netForces[0], netForces[1], netForces[2]];
draft[i].position = [
particles[i].position[0] + vPlusDeltaX,
particles[i].position[1] + vPlusDeltaY,
particles[i].position[2] + vPlusDeltaZ,
];
draft[i].velocity = [
particles[i].velocity[0] + vPlusDeltaX,
particles[i].velocity[1] + vPlusDeltaY,
particles[i].velocity[2] + vPlusDeltaZ,
];
})
);
}
console.log("useEffect");
}, 1000);
return () => clearInterval(positionTracker);
});
我将新计算的对象位置传递给
react-three-fiber
组件。
return (
<>
...
<Canvas shadows camera={{ position: [0, 20, 0], far: 10000 }}>
<ambientLight />
<directionalLight position={[0, 0, 5]} color={"white"} />
<Physics gravity={[0, 50, 0]}>
{particles.map((particle) => (
<Node key={particle.name} id={particle.name} particle={particle} />
))}
</Physics>
<OrbitControls makeDefault />
</Canvas>
</>
);
然后使用
useFrame
库中 react-three-cannon
挂钩内的数据。
src/三/组件/node.tsx
interface NodeProps {
id: string;
particle: Particle;
}
export default function Node(props: NodeProps) {
const { id, particle } = props;
const [ref, api] = useSphere(() => ({
mass: 1,
args: [particle.size],
position: particle.position,
}));
useFrame(() => {
api.position.set(
particle.position[0],
particle.position[1],
particle.position[2]
);
});
return (
<mesh ref={ref as React.RefObject<Mesh<BufferGeometry>>} key={id}>
<sphereGeometry />
<meshStandardMaterial color={particle.color} />
</mesh>
);
}
我知道在 for 循环内改变状态不是一个好主意,但我没有找到另一种方法。
我尝试使用
instancedMesh
来处理对象,但我不知道如何更好地控制每个对象的特定属性(颜色、质量、速度等)。
您正在使用沉浸器
produce()
来生成新状态。您可以通过“突变”旧状态 (produce()
) 来在 particles
回调中进行所有突变,并获得一个新状态,然后进行设置。
我们可以提取整个更新函数(
updateParticles
),因为它不依赖于外部数据。由于您正在“柯里化生产者”,因此它会返回一个将使用基本状态调用的函数。您可以执行该函数内的所有循环。您现在可以将该函数用作 useState
的 updater 函数。const updateParticles = produce((particles: Particle[]) => {
for (let i = 0; i < particles.length; i++) {
const netForces: Coordinate = [0, 0, 0];
for (let j = 0; j < particles.length; j++) {
if (i == j) continue;
const { x, y, z } = pairwiseForce(
gravitationalConstant,
particles[i],
particles[j]
);
netForces[0] += x;
netForces[1] += y;
netForces[2] += z;
}
// 1 - Calculate velocity at t+delta/2
// acceleration t
const { ax, ay, az } = acceleration(netForces, particles[i].mass);
// velocity at t - delta/2 (for initial velocity of 1 then 0.5?) initial velocity * 0.25
const vMinusDeltaX = particles[i].velocity[0] / 2;
const vMinusDeltaY = particles[i].velocity[1] / 2;
const vMinusDeltaZ = particles[i].velocity[2] / 2;
// vfd/2 = vpd/2 * at
const vPlusDeltaX = vMinusDeltaX + ax;
const vPlusDeltaY = vMinusDeltaY + ay;
const vPlusDeltaZ = vMinusDeltaZ + az;
// 2 - Calculate Position of Particule based on vfd/2
particles[i].netForces = [netForces[0], netForces[1], netForces[2]];
particles[i].position = [
particles[i].position[0] + vPlusDeltaX,
particles[i].position[1] + vPlusDeltaY,
particles[i].position[2] + vPlusDeltaZ,
];
particles[i].velocity = [
particles[i].velocity[0] + vPlusDeltaX,
particles[i].velocity[1] + vPlusDeltaY,
particles[i].velocity[2] + vPlusDeltaZ,
];
}
});
您的
useEffect
当前没有依赖项数组,这意味着它会在每次渲染时执行(当
particles
更新时)。这意味着调用清理函数,并创建一个新的间隔。您实际上将 setInterval
用作 setState
。由于状态更新不依赖于particles
状态,因为它是从
updatedPrticles
传递到setParticles
的,所以可以给[]
设置一个空的依赖数组(useEffect
),间隔只会组件卸载时被清除:export function App() {
const [particles, setParticles] = useState<Particle[]>([]);
useEffect(() => {
const positionTracker = setInterval(() => {
setParticles(updateParticles); // use the updater function
}, 1000);
return () => clearInterval(positionTracker);
}, []);
return (
...
);
}
我对 React Three Fiber 不够熟悉,所以我无法帮助你。