Как мне моделировать поведение газа в JavaScript? - PullRequest
2 голосов
/ 01 мая 2020

Я все еще учусь в старшей школе, и мои знания в области программирования самоучки совсем не точны. Кстати, я работал над многими проектами (я пишу на C, C ++, Python, HTML, JavaScript), и тот, над которым я сейчас работаю, доставлял мне немало хлопот: симулятор для поведения образца газа (следуя закону распределения Максвелла-Больцмана по скоростям). Распределение MB - это довольно жесткий и продвинутый физический аргумент, особенно для моих школьных стандартов, но я все же сумел понять его функционирование и нашел уравнение, которое дает число молекул со скоростью между v и v + dv: Уравнение МБ здесь . Теперь происходит программирование части JS, и в следующем фрагменте я вставил часть конструктора шаров, основную функцию draw () и часть, в которой я порождаю шары (100, просто в качестве теста, поскольку их должно быть около 1000) согласно уравнению MB; теперь я думаю, что все должно быть хорошо, но порожденные шары ведут себя очень странно, изменяя направление, когда хотят, и иногда застревают друг с другом, вместо того, чтобы упруго отскакивать ... не могли бы вы взглянуть на "нерестовую" часть и посмотреть, если что-то не так? Затем я также загрузил весь код HTML + JS + CSS, чтобы вы могли его попробовать, а также проверил JS функции для столкновений шарик-шарик и шарик-стена и тому подобное. Я был бы очень признателен, если бы кто-нибудь мог помочь мне ... Я в замешательстве. Сердечно благодарю за любой ответ, Грег.

class Ball {
    
    constructor(x, y, dx, dy, radius){
        this.radius = radius;
        this.x = x;
        this.y = y;
        
        this.dx = dx;
        this.dy = dy;

        // mass is that of a sphere as opposed to circle
        // it *does* make a difference in how realistic it looks
        this.mass = this.radius * this.radius * this.radius;
        this.color = 'red';
    };    

    draw() {
        ctx.beginPath();
        ctx.arc(Math.round(this.x), Math.round(this.y), this.radius, 0, 2 * Math.PI);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.strokeStyle = this.color;
        ctx.stroke();
        ctx.closePath();
    };

    speed() {
        // magnitude of velocity vector
        return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
    };
    angle() {
        // velocity's angle with the x axis
        return Math.atan2(this.dy, this.dx);
    };
    onGround() {
        return (this.y + this.radius >= canvas.height)
    };
};

function draw() {
    
    currentTime = (new Date()).getTime();
    dt = (currentTime - lastTime) / 1000; // delta time in seconds
    
    // dirty and lazy solution
    // instead of scaling down every velocity vector
    // we decrease the speed of time
    dt *= 0.1;

    if (clearCanv) clearCanvas();
    canvasBackground();
    
    if (!paused) {
        moveObjects();
        ballCollision();
    }
    
    drawObjects();
    
    //logger();
    
    lastTime = currentTime;
    window.requestAnimationFrame(draw);
}

let N = 500;
let m = 2.66e-26;
let T = 300;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls = 0;
let anglex;

for(let i = 0; i < 29; i++) { //each 50m/s, with dv = 50, until 1500m/s, 
    probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);//molecules num between v e v+50
    v += 50;
}
v = 50;
for(let i = 0; i < 29; i++){
    let n = 0;
    while(n < probArray[i] && balls < 100) {
        anglex = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
        vel = Math.round((Math.random() * 50) + v); 
        objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(anglex) * vel, Math.sin(anglex) * vel, 3);
        n++;
        balls++;
    }
    v += 50;
    }

class Ball {
    
    constructor(x, y, dx, dy, radius){
        this.radius = radius;
        this.x = x;
        this.y = y;
        
        this.dx = dx;
        this.dy = dy;

        // mass is that of a sphere as opposed to circle
        // it *does* make a difference in how realistic it looks
        this.mass = this.radius * this.radius * this.radius;
        this.color = 'red';
    };    

    draw() {
        ctx.beginPath();
        ctx.arc(Math.round(this.x), Math.round(this.y), this.radius, 0, 2 * Math.PI);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.strokeStyle = this.color;
        ctx.stroke();
        ctx.closePath();
    };

    speed() {
        // magnitude of velocity vector
        return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
    };
    angle() {
        // velocity's angle with the x axis
        return Math.atan2(this.dy, this.dx);
    };
    onGround() {
        return (this.y + this.radius >= canvas.height)
    };
};


//FUNCTIONS

//will remove
function randomColor() {
    let red = Math.floor(Math.random() * 3) * 127;
    let green = Math.floor(Math.random() * 3) * 127;
    let blue = Math.floor(Math.random() * 3) * 127;

    // dim down the small balls
    if (!bigBalls){
        red *= 0.65
        green *= 0.65
        blue *= 0.65
    }

    let rc = "rgb(" + red + ", " + green + ", " + blue + ")";
    return rc;
}

function randomX() {
    let x = Math.floor(Math.random() * canvas.width);
    if (x < 30) {
        x = 30;
    } else if (x + 30 > canvas.width) {
        x = canvas.width - 30;
    }
    return x;
}

function randomY() {
    let y = Math.floor(Math.random() * canvas.height);
    if (y < 30) {
        y = 30;
    } else if (y + 30 > canvas.height) {
        y = canvas.height - 30;
    }
    return y;
}

//will remove
function randomRadius() {
    if (bigBalls) {
        let r = Math.ceil(Math.random() * 10 + 20);
        return r;
    } else {
        let r = Math.ceil(Math.random() * 2 + 2);
        //let r = 5;
        return r;
    }
}

//will remove
function randomDx() {
    let r = Math.floor(Math.random() * 10 - 4);
    return r;
}

//will remove
function randomDy() {
    let r = Math.floor(Math.random() * 10 - 3);
    return r;
}

function distanceNextFrame(a, b) {
    return Math.sqrt((a.x + a.dx - b.x - b.dx)**2 + (a.y + a.dy - b.y - b.dy)**2) - a.radius - b.radius;
}

function distance(a, b) {
    return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2);
}

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");

let objArray = [];
let probArray = [];
let paused = false;
let bumped = false;

let leftHeld = false;
let upHeld = false;
let rightHeld = false;
let downHeld = false;
let arrowControlSpeed = .25;

let gravityOn = false;

let clearCanv = true;

let bigBalls = false;

let lastTime = (new Date()).getTime();
let currentTime = 0;
let dt = 0;

let numStartingSmallBalls = 500;
let numStartingBigBalls = 0;

document.addEventListener("keydown", keyDownHandler);

function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function keyDownHandler(event) {
    if (event.keyCode == 80) { // p
        paused = !paused;
    } else if (event.keyCode == 82) { // r
        objArray = [];
    } else if (event.keyCode == 75) { // k
        clearCanv = !clearCanv;
    } else if (event.keyCode == 88) { // x
        bigBalls = !bigBalls;
    }
}



function canvasBackground() {
    canvas.style.backgroundColor = "rgb(215, 235, 240)";
}

function wallCollision(ball) {
    if (ball.x - ball.radius + ball.dx < 0 ||
        ball.x + ball.radius + ball.dx > canvas.width) {
        ball.dx *= -1;
    }
    if (ball.y - ball.radius + ball.dy < 0 ||
        ball.y + ball.radius + ball.dy > canvas.height) {
        ball.dy *= -1;
    }
    if (ball.y + ball.radius > canvas.height) {
        ball.y = canvas.height - ball.radius;
    }
    if (ball.y - ball.radius < 0) {
        ball.y = ball.radius;
    }
    if (ball.x + ball.radius > canvas.width) {
        ball.x = canvas.width - ball.radius;
    }
    if (ball.x - ball.radius < 0) {
        ball.x = ball.radius;
    }    
}

function ballCollision() {
    for (let i=0; i<objArray.length-1; i++) {
        for (let j=i+1; j<objArray.length; j++) {
            let ob1 = objArray[i]
            let ob2 = objArray[j]
            let dist = distance(ob1, ob2)

            if (dist < ob1.radius + ob2.radius) {              
                let theta1 = ob1.angle();
                let theta2 = ob2.angle();
                let phi = Math.atan2(ob2.y - ob1.y, ob2.x - ob1.x);
                let m1 = ob1.mass;
                let m2 = ob2.mass;
                let v1 = ob1.speed();
                let v2 = ob2.speed();

                let dx1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.cos(phi) + v1*Math.sin(theta1-phi) * Math.cos(phi+Math.PI/2);
                let dy1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.sin(phi) + v1*Math.sin(theta1-phi) * Math.sin(phi+Math.PI/2);
                let dx2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.cos(phi) + v2*Math.sin(theta2-phi) * Math.cos(phi+Math.PI/2);
                let dy2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.sin(phi) + v2*Math.sin(theta2-phi) * Math.sin(phi+Math.PI/2);

                ob1.dx = dx1F;                
                ob1.dy = dy1F;                
                ob2.dx = dx2F;                
                ob2.dy = dy2F;
          
                staticCollision(ob1, ob2)
                
            }            
        }
        wallCollision(objArray[i]);
    }

    if (objArray.length > 0)
        wallCollision(objArray[objArray.length - 1])
}

function staticCollision(ob1, ob2, emergency = false)
{
    let overlap = ob1.radius + ob2.radius - distance(ob1, ob2);
    let smallerObject = ob1.radius < ob2.radius ? ob1 : ob2;
    let biggerObject = ob1.radius > ob2.radius ? ob1 : ob2;

    // When things go normally, this line does not execute.
    // "Emergency" is when staticCollision has run, but the collision
    // still hasn't been resolved. Which implies that one of the objects
    // is likely being jammed against a corner, so we must now move the OTHER one instead.
    // in other words: this line basically swaps the "little guy" role, because
    // the actual little guy can't be moved away due to being blocked by the wall.
    if (emergency) [smallerObject, biggerObject] = [biggerObject, smallerObject]
    
    let theta = Math.atan2((biggerObject.y - smallerObject.y), (biggerObject.x - smallerObject.x));
    smallerObject.x -= overlap * Math.cos(theta);
    smallerObject.y -= overlap * Math.sin(theta); 

    if (distance(ob1, ob2) < ob1.radius + ob2.radius) {
        // we don't want to be stuck in an infinite emergency.
        // so if we have already run one emergency round; just ignore the problem.
        if (!emergency) staticCollision(ob1, ob2, true)
    }
}

function moveObjects() {
    for (let i=0; i<objArray.length; i++) {
        let ob = objArray[i];
        ob.x += ob.dx * dt;
        ob.y += ob.dy * dt;
    }    
}

function drawObjects() {
    for (let obj in objArray) {
        objArray[obj].draw();
    }
}

function draw() {
    
    currentTime = (new Date()).getTime();
    dt = (currentTime - lastTime) / 1000; // delta time in seconds
    
    // dirty and lazy solution
    // instead of scaling down every velocity vector
    // we decrease the speed of time
    dt *= 0.1;

    if (clearCanv) clearCanvas();
    canvasBackground();
    
    if (!paused) {
        moveObjects();
        ballCollision();
    }
    
    drawObjects();
    
    //logger();
    
    lastTime = currentTime;
    window.requestAnimationFrame(draw);
}

/*
for (i = 0; i<numStartingSmallBalls; i++) {
    objArray[objArray.length] = new Ball(randomX(), randomY(), 3);
}*/

let N = 500;
let m = 2.66e-26;
let T = 300;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls = 0;
let angolox;

for(let i = 0; i < 29; i++) { //ogni 50 velocità, con dv = 50, fino a 1500m/s, 
    probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);//num molecole tra v e v+50
    v += 50;
}
v = 50;
for(let i = 0; i < 29; i++){
    let n = 0;
    while(n < probArray[i] && balls < 100) {
        angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
        vel = Math.round((Math.random() * 50) + v); 
        objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 3);
        n++;
        balls++;
    }
    v += 50;
    }

draw();
body {
    background-color: khaki;
    text-align: center;
    font-family: Ubuntu Mono;
  }

  #title {
    color: black;
    font-size: 200%;
    font-style: normal;
    margin: 1px;
    border: 1px;
  }

  #balls {
    margin-top: 5px;
  }

  #myCanvas {
    margin-top: -20px;
  }

  section.footer {
    color: black;
    font-family: Ubuntu Mono;
    font-style: normal;
    font-size: small;
  }
  #disclaimer {
    font-size: 74%;
    color: gray;
  }
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>2d collision</title>
  <link rel="stylesheet" type="text/css" href="gas.css">
</head>
<body style="text-align: center">
  
    <canvas id="myCanvas" width="1225%" height="650%" style="border:1px solid black; margin-top: 10px;"></canvas>
    <script src="gas.js"></script>
  

    <p>
      <strong>[X]</strong> to toggle SIZE of future balls <br>
      <strong>[K]</strong> to toggle clearCanvas(); <br>
      <strong>[P]</strong>: pause/unpause || <strong>[R]</strong>: [RESET]
    </p>

    <p>
      <a href="https://github.com/miskimit/miskimit.github.io/tree/master/ballsballsballs">source code on github</a>
    </p>

    <div id="disclaimer" align="center" style="word-break: break-word; width: 350px; display:inline-block;">

      <p>
        Make sure to press a few buttons and play around.<br>Made with pure javascript.
      </p>

    </div>

  </section>
</body>
</html>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...