2016-03-30 39 views
9

Próbuję odtworzyć atom z THREE.js, a ja napotykam na swój pierwszy problem - ponieważ każdy typ atomu ma inną liczbę protonów/Neutrony, staram się znaleźć sposób na ich automatyczne ustawienie, tak aby nie było kolizji, a więc ostateczny wynik ich wszystkich razem uczyni coś tak bliskiego kuli jak to możliwe - zobacz ten obraz na przykładObliczanie pojedynczej pozycji sferycznej w celu utworzenia sfery ze sfer

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

Czy istnieje sposób na obliczenie tego i przypisanie każdej pozycji Neutron/Proton z łatwością za pomocą formuły? Czy będę musiał zaangażować silnik fizyki, aby ścisnąć kule razem i mieć nadzieję na najlepszy wynik w każdym biegu?

Nie mam jeszcze na ten temat żadnego kodu, ponieważ staram się tylko dowiedzieć, od czego zacząć tę część.

EDIT

Należy również pamiętać, że chcę sfery być spłaszczony razem w przestrzeni większej kuli. NIE próbuję po prostu sprawić, aby wszystkie kule poruszały się w promieniu większej sfery.

EDIT 2

Spojrzałem w użyciu silnika fizyki do squish je wszystkie na małej powierzchni, ale nie mogę znaleźć silnik, który pozwoli mi przenieść wszystkie obiekty w moim sceny pozycja (0,0,0) z siłą grawitacyjną. Wszystkie silniki po prostu naciskają grawitacyjnie na obiekt. W dalszym ciągu wolałbym raczej używać formuły do ​​pozycjonowania sfer, niż włączyć do projektu cały silnik fizyki.

EDIT 3, 04/06/06

Zrobiłem trochę eksperymentować, ale nadal nie mogę zrobić to dobrze. Oto jak to wygląda teraz:

enter image description here

Ale jak widać, wygląda naprawdę off. To co się dzieje, gdy robię atom uranu zamiast jednego węgla (więcej protonów/neutronów/elektrony)

enter image description here

To może być tylko mnie, ale to wygląda bardziej jak wymyślnej ratatouille niż uranu atom.

Jak mam tutaj:

I próbował zrobić to, czego szukałem przez okres powyżej, a tutaj jest przesłanką:

(particleObject jest rodzicem cząstki, cząstka porusza się względem ten obiekt)

  1. Dodałem razem wszystkie długości protonów i neutronów, dzięki czemu mogłem je wszystkie przepuścić przez .
  2. Jeśli added number % 2 == 0, (który jest dla mojego testowania) ustawiłbym obrót na (pi * 2)/2 < - ostatnie dwa są tam, aby reprezentować dwie powyższe.
  3. Każda iteracja I będzie zwiększać zmienną l. (miejmy nadzieję) zawsze, gdy i równa się zmiennej loopcount, oznacza to, że umieściłem kulę w kształcie kuli. Następnie pomnożę liczbę o loopcount o 3, aby dowiedzieć się, ile sfer będzie potrzebnych do następnego uruchomienia. Ustawiłbym l na 0, aby położenie kuli zostało zresetowane, a pętla byłaby zwiększana, powodując, że następny rząd kuli miałby umieścić 1 jednostkę na osi X.

(Przepraszamy za terminologii tutaj, to jest bardzo trudne do wyjaśnienia. Patrz kod.)

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

Szczerze mówiąc, nie jestem aż tak wielki w ogóle z 3D matematyki. Tak więc każda pomoc byłaby naprawdę pomocna. Poza tym jest bardzo możliwe, że moja metoda ich pozycjonowania jest absolutnie błędna pod każdym względem. Dzięki!

Możesz zobaczyć code live here.

+0

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

+0

(1) Google "opakowanie kuli". (2) Powtórz post na stronie matematycznej, jeśli nie możesz znaleźć algorytmu lub heurystyki. – WestLangley

Odpowiedz

5

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(); 
+0

A jeśli nie podoba ci się elektrony lub ciężko je zdobyć, możesz użyć małego silnika fizyki tylko dla protonów i neutronów i użyć swojego obecnego kodu dla elektronów – micnil

+0

Nie wiedziałem, że silniki fizyki mogą zrób to, a to rozwiązanie działa pięknie! I dziękuję za kod, jak zaimplementować go w symulacji! Zaraz będę pracował nad tym, żeby zrobić wszystko inne, dzięki! –

+0

Mam tylko jedno pytanie - nigdy wcześniej nie pracowałem z silnikiem fizycznym, więc kod wygląda na trochę funky ... Jak mogę to zrobić, aby dynamicznie dodawać atomy? Mogę więc zrobić nowy atom ("węgiel", lokalizacja); Przepraszam, jestem odrzucany przez składnię! Dla mojej poprzedniej budowy atomu miałem każdy atom jako obiekt. –

0

Jednym z rozwiązań byłoby użycie algorytmu icosfery do obliczenia położenia Neutronów/Protonów za pomocą punktu wierzchołkowego wygenerowanej kuli.

można znaleźć algorytm usefoul ogłoszenie here

odległość między punktami pozostaje równa na całej powierzchni

+0

Czy to nie tylko dałoby mi miejsce, w którym kostki powinny iść wzdłuż promienia kuli? Chcę, żeby również zostały spryskane razem w środku. –

1

I zostały stoi ten sam problem, a także popełnił rozwiązanie przy użyciu Cannon.js. Jednak przy renderowaniu cięższych elementów może to spowodować znaczne obciążenie, zwłaszcza na urządzeniach mobilnych.

Wpadłem na pomysł, aby uchwycić ostateczną pozycję nukleonów po ich ustaleniu i zapisać je w pliku json dla wszystkich elementów.

Następnie można zbudować nukleony na orbitę liniową bez fizyki.

+1

To jest rozwiązanie, z którego korzystałem. Dostałem wszystkie lokalizacje dla pierwszych 100 sfer w jednym pliku, (https://darrylhuffman.com/atom/bin/js/spherePos.js), a następnie je mam, więc jeśli przekroczysz 100, zwiększa liczbę lokalizacji o 20 sfery. Coś niepewnego i nieoficjalnego, ale żałuję, że nie można tego zrobić matematycznie. –