gpt4 book ai didi

How do I more properly utilize useEffect and useFrame to calculate this n-body simulation?(如何更正确地使用useEffect和useFrame来计算此n-Body模拟?)

转载 作者:bug小助手 更新时间:2023-10-25 11:03:46 33 4
gpt4 key购买 nike



I have been working on a brute force approach for a n-body simulation based on this exercise I stumbled upon:
https://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/nbody.html

The code somewhat ""works"" and is available on my github:
https://github.com/jeromejanicot/threejs-fun

我一直在研究一种基于我偶然发现的这个练习的n体模拟的蛮力方法:https://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/nbody.html代码有点“工作”,可以在我的github上找到:https://github.com/jeromejanicot/threejs-fun


My main questions


Is my use of setInterval inside the useEffect proper?
Where should I perform my calculations?
How do I synchronize my calculation between the useEffect and the useFrame hook?
Is there anything particularly bad with the way I am doing things?

我在useEffect中使用setInterval是否正确?我应该在哪里进行计算?如何在useEffect和useFrame挂钩之间同步计算?我做事的方式有什么特别糟糕的地方吗?


An overview of how things work:


I have defined the functions used to calculate the pairwise force between two objects, and the directional acceleration for an object:

我已经定义了用于计算两个对象之间的成对作用力和对象的方向加速度的函数:


src/gravitational/nBodySim.ts

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,
};
}

I then use these formulas to compute the position of the objects inside of a useEffect.
The shape of the Objects:

然后,我使用这些公式来计算对象在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],
};

The useEffect with a setInterval. I am sure I am breaking a lot of best practices and I would appreciate feedback on my use of the React api.
Also, I am not quite sure my numerical integration is proper, but that's something I can look up elsewhere.

使用setInterval的Use Effect。我确信我违反了许多最佳实践,我希望得到有关我使用Reaction API的反馈。此外,我不太确定我的数值积分是否正确,但这是我可以在其他地方查找的东西。


src/App.tsx

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);
});

I pass the newly computed position of the objects to react-three-fiber components.

我将新计算的对象位置传递给Reaction-Three-Fibre组件。


 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>
</>
);

And then use the data inside the useFrame hook from the react-three-cannon library.

然后使用来自Reaction-Three-Canon库的useFrame挂钩中的数据。


src/three/components/node.tsx

Src/Three/Components/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>
);
}

I am aware that it is not a good idea to mutate state inside of for loops, but I am failing to see another way around.

我知道在for循环中改变状态不是一个好主意,但我没有看到另一种方法。


I tried to play with instancedMesh for the objects, but I failed to see how I could have more control over the specific properties of each object (color, mass, velocity etc).

我试图为对象使用instancedMesh,但我看不出如何才能更好地控制每个对象的特定属性(颜色、质量、速度等)。


更多回答
优秀答案推荐

You are using the immer produce() to generate a new state. You make all mutations inside the produce() callback by "mutating" the old state (particles), and getting a new state, which you then set.

您正在使用immer Product()来生成一个新状态。通过“突变”旧状态(粒子)并获得一个新状态(然后设置),您可以在Product()回调中进行所有的突变。


We can extract the entire update function (updateParticles), because it's not dependent on outside data. Since you're currying the producer, it returns a function that will be called with the base state. You can do all the loops inside this function. You can now use the function as the updater function of useState.

我们可以提取整个更新函数(updateParticles),因为它不依赖于外部数据。因为你正在curry生产者,所以它返回一个函数,这个函数将被基状态调用。你可以在这个函数中执行所有的循环。现在可以将该函数用作useState的更新器函数。


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,
];
}
});

Your useEffect doesn't have a dependency array currently, which means that it's executed on every render (when particles are updated). This means that the cleanup function is called, and a new interval is created. You're actually using setInterval as setState.

您的useEffect当前没有依存关系数组,这意味着它在每次渲染时执行(当粒子更新时)。这意味着调用Cleanup函数,并创建一个新的时间间隔。您实际上正在使用setInterval作为setState。


Since the state update is not dependent on the particles state, because it's passed to the updatedPrticles from setParticles, you can set an empty dependency array ([]) to useEffect, and the interval would only be cleared when the component unmounts:

由于状态更新不依赖于粒子状态,因为它是从setPraces传递给updatdPrticle的,因此可以设置一个空的依存关系数组([])以使用Effect,并且只有在组件卸载时才会清除间隔:


export function App() {
const [particles, setParticles] = useState<Particle[]>([]);

useEffect(() => {
const positionTracker = setInterval(() => {
setParticles(updateParticles); // use the updater function
}, 1000);

return () => clearInterval(positionTracker);
}, []);

return (
...
);
}

An alternative is to use setTimeout, and let the change in particles triggers the useEffect, and starts a new step:

另一种方法是使用setTimeout,并让粒子中的更改触发useEffect,并开始新的步骤:


export function App() {
const [particles, setParticles] = useState<Particle[]>([]);

useEffect(() => {
const positionTracker = setTimeout(() => {
setParticles(updateParticles(particles)); // create new state and update
}, 1000);

return () => clearTimeout(positionTracker);
}, [particles]);

return (
...
);
}

I'm not familiar enough with React Three Fiber, so I can't help you with that.

我对Reaction Three Fibre还不够熟悉,所以我帮不了你。


更多回答

Hey, thanks for your answer. I learned some good practices and good tips. I am encountering an issue with the setInterval in the useEffect tho: despite me trying to catch the current value of particles it is still using a stale one. jsx const positionTracker = setInterval(() => { setParticles((particles) => updateParticles(particles)); }, 1000); I have updated the updateParticles appropriately jsx const updateParticles = <T extends Particle[]>(prevState: T) => produce(prevState, (draft) => {...} It works perfectly for two other repro I set up.

嘿,谢谢你的回答。我学到了一些好的做法和好的提示。我在Use Effect tho中遇到了setInterval的问题:尽管我试图捕捉粒子的当前值,但它仍然使用过时的粒子。JSX常量位置跟踪器=setInterval(()=>{set颗粒物((粒子)=>更新颗粒物(粒子);},1000);我已经相应地更新了更新颗粒物JSX常量更新颗粒物=(PrevState:T)=>Product(PrevState,(Draft)=>{...}它非常适用于设置的另外两个reproI。

The updated particles is passed by setParticles, add a console.log(particles) to the body of the component (not inside useEffect), and see if it actually changes. Calling setParticles(updateParticles) is enough, because it's a function that accepts a particles state. No need to wrap it in an arrow function.

更新的粒子通过setPraces传递,向组件的主体(而不是在useEffect中)添加一个console.log(粒子),并查看它是否实际发生了更改。因为它是一个接受粒子状态的函数,所以调用setPraces(UpdatePraces)就足够了。不需要将其包装在箭头函数中。

Also see 2nd option by using setTimeout().

另请使用setTimeout()查看第二个选项。

For me it seems to be stuck with stale data from the first useEffect run. Like my specific project refuses to update correctly, my other test repos work with the pattern you presented.

对我来说,它似乎受困于第一次运行useEffect时的陈旧数据。就像我的特定项目拒绝正确更新一样,我的其他测试报告也使用您提供的模式。

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com