na pewno powiedzieć, że jest to idealny przypadek użycie silnika fizyki. Wykonanie tej symulacji bez silnika fizyki brzmi jak prawdziwy kłopot, więc "włączając w to cały silnik fizyki" nie wydaje mi się tak dużym kosztem. Większość silników fizyki JavaScript, które znalazłem, i tak jest ciężarem. Będzie jednak wymagać dodatkowej mocy obliczeniowej dla obliczeń fizyki!
Usiadłem i próbowałem stworzyć coś podobnego do tego, co opisujesz za pomocą silnika fizyki CANNON.js. Przeprowadzenie podstawowej symulacji było dość łatwe, ale uzyskanie poprawnych parametrów jest nieco trudniejsze i wymaga więcej regulacji.
Wspomniałeś, że próbowałeś już tego, ale nie mogłeś zmusić cząstek do grawitacji w kierunku punktu, z CANNON.js (i prawdopodobnie z większości innych silników fizycznych) można to osiągnąć, przykładając siłę do obiektu w negatywie kierunek pozycji:
function pullOrigin(body){
body.force.set(
-body.position.x,
-body.position.y,
-body.position.z
);
}
jest również łatwe do osiągnięcia zachowań gdzie korpusy są przesunięte w kierunku pewnego obiektu nadrzędnego, który z kolei jest ciągnąć w kierunku średniej pozycji wszystkich innych obiektów nadrzędnych. W ten sposób możesz tworzyć całe cząsteczki.
Jedną z trudnych spraw było umożliwienie elektronom krążenia protonów i neutronów na odległość. Aby to osiągnąć, daję im niewielką siłę w kierunku pochodzenia, a następnie niewielką siłę od wszystkich protonów i neutronów w tym samym czasie. Ponadto, na początku symulacji, daje im małe pchnięcie w bok, aby rozpocząć krążenie środka.
Proszę dać mi znać, jeśli chcesz, abym wyjaśnił jakąś część.
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
mam postać modułów cząstek do obiektu atomie mogą być pobierane z funkcji Atom. Dodałem też więcej komentarzy do kodu, jeśli nie masz pewności co. Radziłbym naprawdę przestudiować kod i sprawdzić CANNON.js documentation (to naprawdę thourogh). Treść związana z użyciem siły znajduje się w pliku Cannon.js pod numerem Body class. Wszystko, co zrobiłem, to połączenie THREE.Mesh i CANNON.Body w pojedynczy obiekt (dla każdej cząstki). Następnie symuluję wszystkie ruchy na CANNON.Body, a tuż przed renderowaniem TRÓJKĄTNEJ, kopiuję pozycje i obroty z CANNON.Body na TRZY.Mesh.
Jest to funkcja Atom (zmiana niektórych aswell fizyki elektron):
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;
}
I z niego korzystać:
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();
Nie jest całkowicie jasne, jakie ograniczenia chcesz zastosować, np. czy protony będą jak najdalej od innych protonów ?, czy przede wszystkim chcesz dobrze wyglądać na zewnątrz ?, czy chcesz osiągnąć jak najgęstsze uszczelnienie? Z szybkiego spojrzenia wydaje się, że nie ma nawet szorstkiej geometrycznej formuły z fizyki, która pozwala przewidzieć statyczne pozycje protonów i neutronów w jądrze atomowym (https://en.wikipedia.org/wiki/Atomic_nucleus). Również [tutaj] (http://physics.stackexchange.com/questions/35724/what-is-an-intuitive-picture-motion-of-nucleons). – steveOw
(1) Google "opakowanie kuli". (2) Powtórz post na stronie matematycznej, jeśli nie możesz znaleźć algorytmu lub heurystyki. – WestLangley