Как я могу решить эту проблему JS анимации? - PullRequest
2 голосов
/ 04 мая 2020

Я анимирую около 300 сталкивающихся шаров на javascript внутри HTML холста ... код должен быть в порядке, но я не понимаю причину какого-то странного поведения между шарами, сокрушающими друг друга, что иногда кажется, застряли, немного отстают, и не ведут себя точно так, как они должны. Столкновения управляются с помощью функций ballCollision (), wallCollision () и staticCollision ():

class Ball {
    
    constructor(x, y, dx, dy, radius, color){
        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 = color;
    };    

    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;

                if(ob1.speed() * 160 < 400)
                    ob1.color = 'lightblue';
                else if(ob1.speed() * 160 > 800)
                    ob1.color = 'red';
                else
                    ob1.color = 'orange';

                if(ob2.speed() * 160 < 400)
                    ob2.color = 'lightblue';
                else if(ob2.speed() * 160 > 800)
                    ob2.color = 'red';
                else
                    ob2.color = 'orange';
          
                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 * 1;
        ob.y += ob.dy * 1;
    }    
}

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

let begin = true;
let temperature;

document.getElementById("temp").oninput = function()
{
    temperature = parseInt(document.getElementById("temp").value);
    console.log(temperature);
    generateBalls(temperature);
}

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 *= 20;*/

    if(begin) {
        generateBalls(300);
        begin = false;
    }

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

//work in progress
function setColor(vel)
{
    let red = 255, green = 255, blue = 255;
    let rc;
    green /= (vel * 0.001);
    blue /= (vel * 0.01);
    return rc = "rgb(" + red + ", " + green + ", " + blue + ")";

}

let N = 300;
let m = 2.66e-26;
let T = 5;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls;
let angolox;
let vel;
let color;


function generateBalls(T)
{
    v = 50;

    objArray = [];

    for(let i = 0; i < 39; i++) 
    { //each 50m/s, with dv = 50, until 2000m/s

    //molecules number between v and v+50
    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);
        v += 50;
    }

    v = 50;

    for(let i = 0; i < 39; i++)
    {
        let n = 0;
        balls = 0;
        while(n < probArray[i]) 
        {
            angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
            vel = Math.round((Math.random() * 50) + v) / 160; 
            if(vel * 160 < 400)
                color = 'lightblue';
            else if(vel * 160 > 800)
                color = 'red';
            else
                color = 'orange';
            objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 5, color);
            balls++;
            n++;
        }
    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 onload="generateBalls()" id="myCanvas" width="1225%" height="500" style="border:1px solid black; margin-top: 10px;"></canvas>
  

    <p>
      <input type="range" min="1" max="2001" value="300" step="100" id="temp">
      <strong>[P]</strong>: pause/unpause || <strong>[R]</strong>: [RESET]
    </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>
<script src="gas.js"></script>
</html>

Шары генерируются со скоростями (.dx) от 1 до 20, поэтому они должны быть достаточно маленькими, чтобы не вызывать такого рода задержки. но я все еще испытываю это Любой совет?? Есть ли у вас "более эффективные" решения для анимации?

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