计算单个球体位置以创建由球体

时间:2016-03-30 19:15:14

标签: javascript three.js formula

我试图用THREE.js重新创建一个原子,并且我遇到了第一个问题 - 因为每种类型的原子都有不同数量的质子/中子,我'我试图找到一种方法来自动定位它们以便没有碰撞,因此它们的最终结果将使得尽可能接近球体的东西 - 请参阅此图像以获取示例

img http://www.alternativephysics.org/book/img/ANuc-ball-nucleus.jpg

有没有办法计算这个并用公式轻松分配每个中子/质子位置?或者我是否必须让物理引擎参与到将球体挤压在一起并希望每次运行时获得最佳效果?

我还没有任何代码,因为我只想弄清楚从哪一部分开始。

修改

我还应该注意,我希望球体在较大球体的空间内被挤压在一起。我并不是想让所有球体都在更大球体的半径上。

编辑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变量,就意味着我将球体放置在球体形状中。然后,我将loopcount乘以3,以了解下次运行需要多少个球体。我将l设置为0,以便重置球体的定位,并且循环将增加,从而导致下一行球体被放置1个单位x轴。
  4. (对不起这里的术语,很难解释。请参阅代码。)

        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数学一点也不好。所以任何帮助都会非常有帮助。另外,我的定位方法很可能在各方面都是绝对错误的。谢谢!

    您可以看到code live here

3 个答案:

答案 0 :(得分:5)

我肯定会说这是物理引擎的完美用例。在没有物理引擎的情况下进行这种模拟听起来像是一个真正的麻烦,所以&#34;包括整个物理引擎&#34;对我而言,这样的成本并不高。我发现的大多数JavaScript物理引擎无论如何都是轻量级的。但是,它会为物理计算需要一些额外的CPU功率!

我坐下来尝试创建类似于您使用物理引擎CANNON.js描述的内容。让基本模拟工作变得非常容易,但是为了获得恰到好处的参数,看起来有点棘手,需要更多的调整。

你提到你已经尝试了这个但是不能让粒子吸引到一个点,使用CANNON.js(可能还有大多数其他物理引擎)这可以通过对物体施加一个力来实现。负面方向:

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

实现将身体拉向某个父对象的行为也很容易,而父对象又会拉向所有其他父对象的平均位置。这样你就可以创造出完整的分子。

一个棘手的问题是让电子在一定距离内循环质子和中子。为了达到这个目的,我向它们施加一个轻微的力量,同时向所有质子和中子施加一点力。最重要的是,我还在模拟开始时给他们一个小推动,以便他们开始循环中心。

如果您希望我澄清任何特定部分,请告诉我。

&#13;
&#13;
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();
&#13;
<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>
&#13;
&#13;
&#13;

修改

我已将粒子模块化为可从Atom函数检索的Atom对象。如果您不确定任何内容,还会在代码中添加更多注释。我建议你真正学习代码,并检查CANNON.js documentation(它真的是thourogh)。与力相关的东西在Cannon.js的Body class中。我所做的就是将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();

答案 1 :(得分:1)

我一直面临同样的问题,也使用Cannon.js做出了解决方案。但是,当渲染较重的元素时,这可能会导致相当大的负担,尤其是在移动设备上。

我提出了一个想法,即在核子结束后捕获核子的最终位置并将其保存在所有元素的json文件中。

然后可以使核子在没有物理学的情况下线性地绕核运行。

答案 2 :(得分:0)

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

您可以找到广告usefoul算法here

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