计算每个球的位置以创建由球制成的球

11

我正在尝试使用 THREE.js 重新创建原子,并遇到了第一个问题 - 由于每种类型的原子具有不同数量的质子/中子,因此我正在寻找一种自动定位它们的方法,以便没有碰撞,并且所有这些的最终结果将使它们尽可能靠近球体 - 请参见此图像作为示例。

img
(来源:alternativephysics.org)
.

是否有一种计算方法可以轻松地分配每个中子/质子的位置并采用公式?还是我必须涉及物理引擎才能将球体挤在一起,并希望每次运行都会得到最佳结果?

我还没有任何代码,因为我只是试图弄清楚从哪里开始。

编辑

我还应该指出,我希望球体在较大球体的空间内被挤在一起。 我不是想让所有球体都沿着较大球体的半径移动。

编辑2

我尝试使用物理引擎将它们全部挤在一个小区域内,但我找不到一个引擎,可以让我将场景中的所有对象移动到位置(0,0,0)并带有重力。 所有引擎都只是让重力向下推动一个对象。 我仍然愿意使用公式来定位球体,而不是将整个物理引擎包含到我的项目中。

编辑3,04/06/06

我做了一些实验,但仍然无法得到正确的结果。 这是现在的样子:

enter image description here

但是正如您所见,它看起来十分奇怪。 当我制作铀原子而不是碳原子时(更多质子/中子/电子),就会发生这种情况

enter image description here

可能只是我,但这看起来更像某些花式炖菜而不是铀原子。

我是这样做到的:

(particleObject 是粒子的父级,粒子将相对于此对象移动)

  1. 我添加了所有质子和中子长度,以便可以循环遍历它们全部。
  2. 如果 added number % 2 == 0(对我的测试而言),则设置旋转为 (pi * 2) / 2<-最后两个表示上面的两个。
  3. 每次迭代 i 都会增加变量 l。 (希望)每当 i 等于 loopcount 变量时,它将意味着我已经在球形周围放置了球体。 我
        var PNamount = atomTypes[type].protons + atomTypes[type].neutrons;
        var loopcount = 1;
        if(PNamount % 2 == 0) {
            var rotate = (PI * 2) / 2;
            loopcount = 2;
        }
        var neutrons = 0,
            protons = 0,
            loop = 1,
            l = 0;
        for(var i = 0; i < PNamount; i++) {
    
            if(i == loopcount){
                loopcount = loopcount * 3;
                loop++;
                rotate = (PI * 2) / loopcount;
                l = 0;
            } else {
                l++;
            }
    
            particleObject.rotation.x = rotate * l;
            particleObject.rotation.y = rotate * l;
            particleObject.rotation.z = rotate * l;
            particle.position.x = loop;
        }
    

    说实话,我并不擅长3D数学。所以任何帮助都会非常有用。而且,我放置它们的方法很可能完全错误。谢谢!

    您可以在此处查看代码实时演示


并不完全清楚你要应用什么限制,例如质子之间是否尽可能远离?你主要是想让它在外观上看起来好看吗?你想实现最密集的堆积吗?从快速查看中,似乎没有物理上的粗略几何公式可以预测原子核中静止质子和中子的位置。此外,在这里 - steveOw
(1) 谷歌搜索“球体装箱”。 (2) 如果找不到算法或启发式方法,请在数学网站上重新发布。 - WestLangley
3个回答

6
我肯定会说这是物理引擎的完美应用案例。如果没有物理引擎制作这个模拟似乎很麻烦,所以“包含整个物理引擎”对我来说并不像一个很大的成本。我找到的大多数JavaScript物理引擎都很轻量级。但是,这将需要一些额外的CPU计算能力进行物理计算!
我坐下来尝试使用物理引擎CANNON.js创建类似于您描述的东西。得到基本模拟非常容易,但调整参数似乎有点棘手,需要更多的调整。
您提到您已经尝试过了,但无法使粒子向一个点聚集,在CANNON.js(和可能大多数其他物理引擎)中,可以通过在负位置方向施加力来实现这一点:
function pullOrigin(body){
    body.force.set(
        -body.position.x,
        -body.position.y,
        -body.position.z
    );
}

同时,很容易实现物体被拉向某个特定父对象的行为,而该父对象则被拉向所有其他父对象的平均位置。这样,您就可以创建整个分子。

一个棘手的问题是让电子在距离质子和中子一定距离处环绕它们。为了实现这一点,我给它们施加一个轻微的力朝向原点,然后同时远离所有质子和中子。此外,在模拟开始时,我还会给它们一个小的侧向推力,使它们开始环绕中心。

如果需要澄清任何特定部分,请告诉我。

let scene = new THREE.Scene();
let world = new CANNON.World();
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 5;

let camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

let renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

function Proton(){
 let radius = 1;

 return {
  // Cannon
  body: new CANNON.Body({
   mass: 1, // kg
   position: randomPosition(6),
   shape: new CANNON.Sphere(radius)
  }),
  // THREE
  mesh: new THREE.Mesh(
   new THREE.SphereGeometry( radius, 32, 32 ),
   new THREE.MeshPhongMaterial( { color: 0xdd5555, specular: 0x999999, shininess: 13} )
  )
 }
}

function Neutron(){
 let radius = 1;

 return {
  // Cannon
  body: new CANNON.Body({
   mass: 1, // kg
   position: randomPosition(6),
   shape: new CANNON.Sphere(radius)
  }),
  // THREE
  mesh: new THREE.Mesh(
   new THREE.SphereGeometry( radius, 32, 32 ),
   new THREE.MeshPhongMaterial( { color: 0x55dddd, specular: 0x999999, shininess: 13} )
  )
 }
}

function Electron(){
 let radius = 0.2;

 return {
  // Cannon
  body: new CANNON.Body({
   mass: 0.5, // kg
   position: randomPosition(10),
   shape: new CANNON.Sphere(radius)
  }),
  // THREE
  mesh: new THREE.Mesh(
   new THREE.SphereGeometry( radius, 32, 32 ),
   new THREE.MeshPhongMaterial( { color: 0xdddd55, specular: 0x999999, shininess: 13} )
  )
 }
}

function randomPosition(outerRadius){
 let x = (2 * Math.random() - 1 ) * outerRadius,
  y = (2 * Math.random() - 1 ) * outerRadius,
  z = (2 * Math.random() - 1 ) * outerRadius
 return new CANNON.Vec3(x, y, z);
}

function addToWorld(object){
 world.add(object.body);
 scene.add(object.mesh);
}

// create our Atom
let protons = Array(5).fill(0).map( () => Proton() );
let neutrons = Array(5).fill(0).map( () => Neutron() );
let electrons = Array(15).fill(0).map( () => Electron() );

protons.forEach(addToWorld);
neutrons.forEach(addToWorld);
electrons.forEach(addToWorld);


let light = new THREE.AmbientLight( 0x202020 ); // soft white light
scene.add( light );

let directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set( -1, 1, 1 );
scene.add( directionalLight );

camera.position.z = 18;

const timeStep = 1/60;

//Small impulse on the electrons to get them moving in the start
electrons.forEach((electron) => {
 let centerDir = electron.body.position.vsub(new CANNON.Vec3(0, 0, 0));
 centerDir.normalize();
 let impulse = centerDir.cross(new CANNON.Vec3(0, 0, 1));
 impulse.scale(2, impulse);
 electron.body.applyLocalImpulse(impulse, new CANNON.Vec3(0, 0, 0));
});

function render () {
 requestAnimationFrame( render );

 // all particles pull towards the center
 protons.forEach(pullOrigin);
 neutrons.forEach(pullOrigin);
 electrons.forEach(pullOrigin);

 // electrons should also be pushed by protons and neutrons
 electrons.forEach( (electron) => {
  let pushForce = new CANNON.Vec3(0, 0, 0 );

  protons.forEach((proton) => {
   let f = electron.body.position.vsub(proton.body.position);
   pushForce.vadd(f, pushForce);
  });

  neutrons.forEach((neutron) => {
   let f = electron.body.position.vsub(neutron.body.position);
   pushForce.vadd(f, pushForce);
  });

  pushForce.scale(0.07, pushForce);
  electron.body.force.vadd(pushForce, electron.body.force);
 })

 // protons and neutrons slows down (like wind resistance)
 neutrons.forEach((neutron) => resistance(neutron, 0.95));
 protons.forEach((proton) => resistance(proton, 0.95));

 // Electrons have a max velocity
 electrons.forEach((electron) => {maxVelocity(electron, 5)});

 // Step the physics world
 world.step(timeStep);
 // Copy coordinates from Cannon.js to Three.js
 protons.forEach(updateMeshState);
 neutrons.forEach(updateMeshState);
 electrons.forEach(updateMeshState);

 renderer.render(scene, camera);
};

function updateMeshState(object){
 object.mesh.position.copy(object.body.position);
 object.mesh.quaternion.copy(object.body.quaternion);
}

function pullOrigin(object){
 object.body.force.set(
  -object.body.position.x,
  -object.body.position.y,
  -object.body.position.z
 );
}

function maxVelocity(object, vel){
 if(object.body.velocity.length() > vel)
  object.body.force.set(0, 0, 0);
}

function resistance(object, val) {
 if(object.body.velocity.length() > 0)
  object.body.velocity.scale(val, object.body.velocity);
}
render();
<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.min.js"></script>

编辑

我已经将粒子模块化为一个可以从Atom函数中检索的Atom对象。如果您对任何内容不确定,我还在代码中添加了更多注释。我建议您认真研究代码,并查看CANNON.js文档(它非常详尽)。与力有关的内容位于Cannon.js的Body类中。我所做的是将THREE.Mesh和CANNON.Body组合成单个对象(每个粒子)。然后,我在CANNON.Body上模拟所有运动,并在渲染THREE.Mesh之前将位置和旋转从CANNON.Body复制到THREE.Mesh。

这是Atom函数(也更改了一些电子物理性质):

function Atom(nProtons, nNeutrons, nElectrons, pos = new CANNON.Vec3(0, 0, 0)){

    //variable to move the atom, which att the particles will pull towards
    let position = pos;

    // create our Atom
    let protons = Array(nProtons).fill(0).map( () => Proton() );
    let neutrons = Array(nNeutrons).fill(0).map( () => Neutron() );
    let electrons = Array(nElectrons).fill(0).map( () => Electron() );

    // Public Functions
    //=================
    // add to a three.js and CANNON scene/world
    function addToWorld(world, scene) {
        protons.forEach((proton) => {
            world.add(proton.body);
            scene.add(proton.mesh);
        });
        neutrons.forEach((neutron) => {
            world.add(neutron.body);
            scene.add(neutron.mesh);
        });
        electrons.forEach((electron) => {
            world.add(electron.body);
            scene.add(electron.mesh);
        });
    }

    function simulate() {

        protons.forEach(pullParticle);
        neutrons.forEach(pullParticle);

        //pull electrons if they are further than 5 away
        electrons.forEach((electron) => { pullParticle(electron, 5) });
        //push electrons if they are closer than 6 away
        electrons.forEach((electron) => { pushParticle(electron, 6) });

        // give the particles some friction/wind resistance
        //electrons.forEach((electron) => resistance(electron, 0.95));
        neutrons.forEach((neutron) => resistance(neutron, 0.95));
        protons.forEach((proton) => resistance(proton, 0.95));

    }

    function electronStartingVelocity(vel) {
        electrons.forEach((electron) => {
            let centerDir = electron.body.position.vsub(position);
            centerDir.normalize();
            let impulse = centerDir.cross(new CANNON.Vec3(0, 0, 1));
            impulse.scale(vel, impulse);
            electron.body.applyLocalImpulse(impulse, new CANNON.Vec3(0, 0, 0));
        });
    }

    // Should be called after CANNON has simulated a frame and before THREE renders.
    function updateAtomMeshState(){
        protons.forEach(updateMeshState);
        neutrons.forEach(updateMeshState);
        electrons.forEach(updateMeshState);
    }


    // Private Functions
    // =================

    // pull a particale towards the atom position (if it is more than distance away)
    function pullParticle(particle, distance = 0){

        // if particle is close enough, dont pull more
        if(particle.body.position.distanceTo(position) < distance)
            return false;

        //create vector pointing from particle to atom position
        let pullForce = position.vsub(particle.body.position);

        // same as: particle.body.force = particle.body.force.vadd(pullForce)
        particle.body.force.vadd(   // add particle force
            pullForce,              // to pullForce
            particle.body.force);   // and put it in particle force
    }

    // Push a particle from the atom position (if it is less than distance away)
    function pushParticle(particle, distance = 0){

        // if particle is far enough, dont push more
        if(particle.body.position.distanceTo(position) > distance)
            return false;

        //create vector pointing from particle to atom position
        let pushForce = particle.body.position.vsub(position);

        particle.body.force.vadd(   // add particle force
            pushForce,              // to pushForce
            particle.body.force);   // and put it in particle force
    }

    // give a partile some friction
    function resistance(particle, val) {
        if(particle.body.velocity.length() > 0)
            particle.body.velocity.scale(val, particle.body.velocity);
    }

    // Call this on a particle if you want to limit its velocity
    function limitVelocity(particle, vel){
        if(particle.body.velocity.length() > vel)
            particle.body.force.set(0, 0, 0);
    }

    // copy ratation and position from CANNON to THREE
    function updateMeshState(particle){
        particle.mesh.position.copy(particle.body.position);
        particle.mesh.quaternion.copy(particle.body.quaternion);
    }


    // public API
    return {
        "simulate":                 simulate,
        "electrons":                electrons,
        "neutrons":                 neutrons,
        "protons":                  protons,
        "position":                 position,
        "updateAtomMeshState":      updateAtomMeshState,
        "electronStartingVelocity": electronStartingVelocity,
        "addToWorld":               addToWorld

    }
}

function Proton(){
    let radius = 1;

    return {
        // Cannon
        body: new CANNON.Body({
            mass: 1, // kg
            position: randomPosition(0, 6), // random pos from radius 0-6
            shape: new CANNON.Sphere(radius)
        }),
        // THREE
        mesh: new THREE.Mesh(
            new THREE.SphereGeometry( radius, 32, 32 ),
            new THREE.MeshPhongMaterial( { color: 0xdd5555, specular: 0x999999, shininess: 13} )
        )
    }
}

function Neutron(){
    let radius = 1;

    return {
        // Cannon
        body: new CANNON.Body({
            mass: 1, // kg
            position: randomPosition(0, 6), // random pos from radius 0-6
            shape: new CANNON.Sphere(radius)
        }),
        // THREE
        mesh: new THREE.Mesh(
            new THREE.SphereGeometry( radius, 32, 32 ),
            new THREE.MeshPhongMaterial( { color: 0x55dddd, specular: 0x999999, shininess: 13} )
        )
    }
}

function Electron(){
    let radius = 0.2;

    return {
        // Cannon
        body: new CANNON.Body({
            mass: 0.5, // kg
            position: randomPosition(3, 7), // random pos from radius 3-8
            shape: new CANNON.Sphere(radius)
        }),
        // THREE
        mesh: new THREE.Mesh(
            new THREE.SphereGeometry( radius, 32, 32 ),
            new THREE.MeshPhongMaterial( { color: 0xdddd55, specular: 0x999999, shininess: 13} )
        )
    }
}


function randomPosition(innerRadius, outerRadius){

    // get random direction
    let x = (2 * Math.random() - 1 ),
        y = (2 * Math.random() - 1 ),
        z = (2 * Math.random() - 1 )

    // create vector
    let randVec = new CANNON.Vec3(x, y, z);

    // normalize
    randVec.normalize();
    // scale it to the right radius
    randVec = randVec.scale( Math.random() * (outerRadius - innerRadius) + innerRadius); //from inner to outer
    return randVec;
}

并使用它:

let scene = new THREE.Scene();
let world = new CANNON.World();
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 5;

let camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

let renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

// create a Atom with 3 protons and neutrons, and 5 electrons
// all circulating position (-4, 0, 0)
let atom = Atom(3, 3, 5, new CANNON.Vec3(-4, 0, 0));

// move atom (will not be instant)
//atom.position.x = -2;

// add to THREE scene and CANNON world
atom.addToWorld(world, scene);

let light = new THREE.AmbientLight( 0x202020 ); // soft white light
scene.add( light );

let directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set( -1, 1, 1 );
scene.add( directionalLight );

camera.position.z = 18;

const timeStep = 1/60;

// give the atoms electrons some starting velocity
atom.electronStartingVelocity(2);

function render () {
    requestAnimationFrame( render );

    // calculate all the particles positions
    atom.simulate();

    // Step the physics world
    world.step(timeStep);

    //update the THREE mesh
    atom.updateAtomMeshState();

    renderer.render(scene, camera);
};


render();

如果你不喜欢电子或者发现很难正确处理它们,你可以使用一个小的物理引擎来处理质子和中子,并使用你当前的代码来处理电子。 - micnil
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Darryl Huffman
我只有一个问题 - 我以前从未使用过物理引擎,所以这段代码对我来说看起来有点奇怪...我该如何使其能够动态添加原子?这样我就可以做出新的原子('碳',位置)。抱歉,语法让我有些困惑!在我之前构建原子的版本中,我将每个原子作为一个对象。 - Darryl Huffman
@DarrylHuffman 更新了我的答案。新版本仍然存在一些问题。例如,原子的起始位置始终在原点附近,然后它们被拉向它们的中心位置。randomPosition() 函数需要进行修改以解决这个问题。希望这正是您想要实现的 :) - micnil

1
我一直面临同样的问题,并使用Cannon.js制作了解决方案。然而,当渲染更重的元素时,这可能会导致相当大的负载,特别是在移动设备上。
我想到一个主意,即在核子定居后捕获其最终位置,并将其保存在所有元素的json文件中。
然后,可以使核子以线性方式围绕原子核轨道运动,而不需要物理学。

1
这是我一直在使用的解决方案。 我在一个文件中获取了前100个球体的所有位置(https://darrylhuffman.com/atom/bin/js/spherePos.js),然后如果您超过100,它会将位置增加20个球体。 这种方法有些不太流畅和非正式,但我希望能够以数学方式解决它。 - Darryl Huffman

0

一个解决方案是使用icosphere算法,通过生成球体的顶点来计算中子/质子的位置。

您可以在这里找到有用的算法。

点之间的距离在整个表面上保持相等。


这样做只会告诉我立方体沿着球体半径应该放在哪里,但我希望它们也能够在内部紧密地挤在一起。 - Darryl Huffman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接