Ich würde auf jeden Fall sagen, dass dies ein perfekter Anwendungsfall eines Physik-Engine finden. Diese Simulation ohne eine Physik-Engine zu machen, klingt nach einem echten Ärger, so dass "einschließlich einer ganzen Physik-Engine" für mich nicht so teuer ist. Die meisten JavaScript-Physik-Engines, die ich gefunden habe, sind sowieso leicht. Es wird jedoch etwas zusätzliche CPU-Leistung für die Physik-Berechnungen benötigt!
Ich habe mich hingesetzt und versucht, etwas ähnliches zu schaffen, was Sie mit der Physik-Engine CANNON.js beschreiben. Es war ziemlich einfach, eine grundlegende Simulation zu erhalten, aber um die Parameter genau richtig zu bekommen, ist es ein bisschen schwierig, und es braucht mehr Anpassung.
Sie haben erwähnt, dass Sie dies bereits versucht haben, aber die Partikel nicht zu einem Punkt hin bewegen konnten. Mit CANNON.js (und wahrscheinlich den meisten anderen Physik-Engines) kann dies erreicht werden, indem eine Kraft auf das Objekt ausgeübt wird Position Richtung:
function pullOrigin(body){
body.force.set(
-body.position.x,
-body.position.y,
-body.position.z
);
}
Es ist auch leicht Verhalten zu erreichen, wo Körper zu einem bestimmten übergeordneten Objekt gezogen werden, der seinerseits auf die durchschnittlichen Position aller anderen übergeordneten Objekte ziehen. Auf diese Weise können Sie ganze Moleküle erzeugen.
Eine schwierige Sache war es, die Elektronen die Protonen und Neutronen aus der Entfernung zirkulieren zu lassen. Um dies zu erreichen, gebe ich ihnen eine leichte Kraft gegen den Ursprung und dann eine leichte Kraft, die gleichzeitig von allen Protonen und Neutronen weg ist. Dazu gebe ich ihnen zu Beginn der Simulation einen kleinen seitlichen Druck, damit sie anfangen, das Zentrum zu zirkulieren.
Bitte lassen Sie mich wissen, wenn Sie möchten, dass ich einen bestimmten Teil zu klären.
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(){
\t let radius = 1;
\t return {
\t \t // Cannon
\t \t body: new CANNON.Body({
\t \t \t mass: 1, // kg
\t \t \t position: randomPosition(6),
\t \t \t shape: new CANNON.Sphere(radius)
\t \t }),
\t \t // THREE
\t \t mesh: new THREE.Mesh(
\t \t \t new THREE.SphereGeometry(radius, 32, 32),
\t \t \t new THREE.MeshPhongMaterial({ color: 0xdd5555, specular: 0x999999, shininess: 13})
\t \t)
\t }
}
function Neutron(){
\t let radius = 1;
\t return {
\t \t // Cannon
\t \t body: new CANNON.Body({
\t \t \t mass: 1, // kg
\t \t \t position: randomPosition(6),
\t \t \t shape: new CANNON.Sphere(radius)
\t \t }),
\t \t // THREE
\t \t mesh: new THREE.Mesh(
\t \t \t new THREE.SphereGeometry(radius, 32, 32),
\t \t \t new THREE.MeshPhongMaterial({ color: 0x55dddd, specular: 0x999999, shininess: 13})
\t \t)
\t }
}
function Electron(){
\t let radius = 0.2;
\t return {
\t \t // Cannon
\t \t body: new CANNON.Body({
\t \t \t mass: 0.5, // kg
\t \t \t position: randomPosition(10),
\t \t \t shape: new CANNON.Sphere(radius)
\t \t }),
\t \t // THREE
\t \t mesh: new THREE.Mesh(
\t \t \t new THREE.SphereGeometry(radius, 32, 32),
\t \t \t new THREE.MeshPhongMaterial({ color: 0xdddd55, specular: 0x999999, shininess: 13})
\t \t)
\t }
}
function randomPosition(outerRadius){
\t let x = (2 * Math.random() - 1) * outerRadius,
\t \t y = (2 * Math.random() - 1) * outerRadius,
\t \t z = (2 * Math.random() - 1) * outerRadius
\t return new CANNON.Vec3(x, y, z);
}
function addToWorld(object){
\t world.add(object.body);
\t 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) => {
\t let centerDir = electron.body.position.vsub(new CANNON.Vec3(0, 0, 0));
\t centerDir.normalize();
\t let impulse = centerDir.cross(new CANNON.Vec3(0, 0, 1));
\t impulse.scale(2, impulse);
\t electron.body.applyLocalImpulse(impulse, new CANNON.Vec3(0, 0, 0));
});
function render() {
\t requestAnimationFrame(render);
\t // all particles pull towards the center
\t protons.forEach(pullOrigin);
\t neutrons.forEach(pullOrigin);
\t electrons.forEach(pullOrigin);
\t // electrons should also be pushed by protons and neutrons
\t electrons.forEach((electron) => {
\t \t let pushForce = new CANNON.Vec3(0, 0, 0);
\t \t protons.forEach((proton) => {
\t \t \t let f = electron.body.position.vsub(proton.body.position);
\t \t \t pushForce.vadd(f, pushForce);
\t \t });
\t \t neutrons.forEach((neutron) => {
\t \t \t let f = electron.body.position.vsub(neutron.body.position);
\t \t \t pushForce.vadd(f, pushForce);
\t \t });
\t \t pushForce.scale(0.07, pushForce);
\t \t electron.body.force.vadd(pushForce, electron.body.force);
\t })
\t // protons and neutrons slows down (like wind resistance)
\t neutrons.forEach((neutron) => resistance(neutron, 0.95));
\t protons.forEach((proton) => resistance(proton, 0.95));
\t // Electrons have a max velocity
\t electrons.forEach((electron) => {maxVelocity(electron, 5)});
\t // Step the physics world
\t world.step(timeStep);
\t // Copy coordinates from Cannon.js to Three.js
\t protons.forEach(updateMeshState);
\t neutrons.forEach(updateMeshState);
\t electrons.forEach(updateMeshState);
\t renderer.render(scene, camera);
};
function updateMeshState(object){
\t object.mesh.position.copy(object.body.position);
\t object.mesh.quaternion.copy(object.body.quaternion);
}
function pullOrigin(object){
\t object.body.force.set(
\t \t -object.body.position.x,
\t \t -object.body.position.y,
\t \t -object.body.position.z
\t);
}
function maxVelocity(object, vel){
\t if(object.body.velocity.length() > vel)
\t \t object.body.force.set(0, 0, 0);
}
function resistance(object, val) {
\t if(object.body.velocity.length() > 0)
\t \t 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>
EDIT
I die Partikel in einem Atom-Objekt modularisiert haben, die von dem Atom-Funktion abgerufen werden können. Außerdem wurden einige weitere Kommentare im Code hinzugefügt, wenn Sie sich über irgendetwas unsicher sind. Ich würde Ihnen empfehlen, den Code wirklich zu studieren, und überprüfen Sie die CANNON.js documentation (es ist wirklich thourgh). Das kraftbezogene Zeug befindet sich in der Body class von Cannon.js. Alles, was ich getan habe, ist eine THREE.Mesh und eine CANNON.Body in ein einzelnes Objekt (für jedes Partikel) zu kombinieren. Dann simuliere ich alle Bewegungen auf der CANNON.Body, und kurz bevor ich die DREI.Mesh rendere, kopiere ich die Positionen und Drehungen von CANNON.Body auf THREE.Mesh.
Dies ist die Atom-Funktion (ein Teil der Elektronenphysik aswell geändert):
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;
}
Und es zu verwenden:
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();
Es ist nicht ganz klar, welche Einschränkungen Sie anwenden möchten, z. Werden Protonen so weit wie möglich von anderen Protonen entfernt sein? Willst du vor allem, dass es außen gut aussieht? Willst du die dichteste Packung erreichen? Aus einem kurzen Blick scheint es nicht einmal eine grobe geometrische Formel aus der Physik zu geben, die statische Protonen- und Neutronenpositionen in einem [Atomkern] vorhersagt (https://en.wikipedia.org/wiki/Atomic_nucleus). Auch [hier] (http://physics.stackexchange.com/questions/35724/what-is-antinuitive-bild-of-the-motion-of-nucleons). – steveOw
(1) Google "Kugelverpackung". (2) Re-Post auf einer Math-Site, wenn Sie einen Algorithmus oder eine Heuristik nicht finden können. – WestLangley