Почему мои объекты перекрываются в моем проекте p5. js? - PullRequest
1 голос
/ 03 мая 2020

Я работаю над проектом, в котором я симулирую физику шарами. Вот ссылка на редактор p5 проекта.

Моя проблема заключается в следующем: когда я добавляю много шаров (например, 200), шары складываются, но некоторые из них будут в конечном итоге рухнуть, и я не знаю почему.

Может кто-нибудь объяснить, почему это происходит и как решить проблему?

Спасибо.

Вот код набросок.

document.oncontextmenu = function () {
    return false;
}

let isFlushing = false;
let isBallDiameterRandom = false;
let displayInfos = true;
let displayWeight = false;
let clickOnce = false;

let FRAME_RATE = 60;
let SPEED_FLUSH = 3;
let Y_GROUND;
let lastFR;

let balls = [];

function setup() {
    frameRate(FRAME_RATE);
    createCanvas(window.innerWidth, window.innerHeight);

    Y_GROUND = height / 20 * 19;
    lastFR = FRAME_RATE;
}

function draw() {
    background(255);

    if (isFlushing) {
        for (let i = 0; i < SPEED_FLUSH; i++) {
            balls.pop();
        }

        if (balls.length === 0) {
            isFlushing = false;
        }
    }

    balls.forEach(ball => {
        ball.collide();
        ball.move();
        ball.display(displayWeight);
        ball.checkCollisions();
    });

    if (mouseIsPressed) {
        let ballDiameter;

        if (isBallDiameterRandom) {
            ballDiameter = random(15, 101);
        } else {
            ballDiameter = 25;
        }

        if (canAddBall(mouseX, mouseY, ballDiameter)) {
            isFlushing = false;
            let newBall = new Ball(mouseX, mouseY, ballDiameter, balls);

            if (mouseButton === LEFT && !clickOnce) {
                balls.push(newBall);
                clickOnce = true;
            }

            if (mouseButton === RIGHT) {
                balls.push(newBall);
            }
        }
    }

    drawGround();

    if (displayInfos) {
        displayShortcuts();
        displayFrameRate();
        displayBallCount();
    }
}

function mouseReleased() {
    if (mouseButton === LEFT) {
        clickOnce = false;
    }
}

function keyPressed() {
    if (keyCode === 32) {//SPACE
        displayInfos = !displayInfos;
    }

    if (keyCode === 70) {//F
        isFlushing = true;
    }

    if (keyCode === 71) {//G
        isBallDiameterRandom = !isBallDiameterRandom;
    }

    if (keyCode === 72) {//H
        displayWeight = !displayWeight;
    }
}

function canAddBall(x, y, d) {
    let isInScreen =
        y + d / 2 < Y_GROUND &&
        y - d / 2 > 0 &&
        x + d / 2 < width &&
        x - d / 2 > 0;

    let isInAnotherBall = false;

    for (let i = 0; i < balls.length; i++) {
        let d = dist(x, y, balls[i].position.x, balls[i].position.y);

        if (d < balls[i].w) {
            isInAnotherBall = true;
            break;
        }
    }

    return isInScreen && !isInAnotherBall;
}

function drawGround() {
    strokeWeight(0);
    fill('rgba(200,200,200, 0.25)');
    rect(0, height / 10 * 9, width, height / 10);
}

function displayFrameRate() {
    if (frameCount % 30 === 0) {
        lastFR = round(frameRate());
    }

    textSize(50);
    fill(255, 0, 0);

    let lastFRWidth = textWidth(lastFR);
    text(lastFR, width - lastFRWidth - 25, 50);
    textSize(10);
    text('fps', width - 20, 50);
}

function displayBallCount() {
    textSize(50);
    fill(255, 0, 0);
    text(balls.length, 10, 50);
    let twBalls = textWidth(balls.length);
    textSize(10);
    text('balls', 15 + twBalls, 50);
}

function displayShortcuts() {
    let hStart = 30;
    let steps = 15;
    let maxTW = 0;
    let controlTexts = [
        'LEFT CLICK : add 1 ball',
        'RIGHT CLICK : add 1 ball continuously',
        'SPACE : display infos',
        'F : flush balls',
        'G : set random ball diameter (' + isBallDiameterRandom + ')',
        'H : display weight of balls (' + displayWeight + ')'
    ];

    textSize(11);
    fill(0);

    for (let i = 0; i < controlTexts.length; i++) {
        let currentTW = textWidth(controlTexts[i]);

        if (currentTW > maxTW) {
            maxTW = currentTW;
        }
    }

    for (let i = 0; i < controlTexts.length; i++) {
        text(controlTexts[i], width / 2 - maxTW / 2 + 5, hStart);
        hStart += steps;
    }

    fill(200, 200, 200, 100);
    rect(width / 2 - maxTW / 2,
        hStart - (controlTexts.length + 1) * steps,
        maxTW + steps,
        (controlTexts.length + 1) * steps - steps / 2
    );
}

Вот код класса Ball.

class Ball {
    constructor(x, y, w, e) {
        this.id = e.length;
        this.w = w;
        this.e = e;

        this.progressiveWidth = 0;
        this.rgb = [
            floor(random(0, 256)),
            floor(random(0, 256)),
            floor(random(0, 256))
        ];
        this.mass = w;
        this.position = createVector(x + random(-1, 1), y);
        this.velocity = createVector(0, 0);
        this.acceleration = createVector(0, 0);

        this.gravity = 0.2;
        this.friction = 0.5;
    }

    collide() {
        for (let i = this.id + 1; i < this.e.length; i++) {
            let dx = this.e[i].position.x - this.position.x;
            let dy = this.e[i].position.y - this.position.y;
            let distance = sqrt(dx * dx + dy * dy);
            let minDist = this.e[i].w / 2 + this.w / 2;

            if (distance < minDist) {
                let angle = atan2(dy, dx);
                let targetX = this.position.x + cos(angle) * minDist;
                let targetY = this.position.y + sin(angle) * minDist;

                this.acceleration.set(
                    targetX - this.e[i].position.x,
                    targetY - this.e[i].position.y
                );
                this.velocity.sub(this.acceleration);
                this.e[i].velocity.add(this.acceleration);

                //TODO : Effets bizarre quand on empile les boules (chevauchement)

                this.velocity.mult(this.friction);
            }
        }
    }

    move() {
        this.velocity.add(createVector(0, this.gravity));
        this.position.add(this.velocity);
    }

    display(displayMass) {
        if (this.progressiveWidth < this.w) {
            this.progressiveWidth += this.w / 10;
        }

        stroke(0);
        strokeWeight(2);
        fill(this.rgb[0], this.rgb[1], this.rgb[2], 100);
        ellipse(this.position.x, this.position.y, this.progressiveWidth);

        if (displayMass) {
            strokeWeight(1);
            textSize(10);
            let tempTW = textWidth(int(this.w));
            text(int(this.w), this.position.x - tempTW / 2, this.position.y + 4);
        }
    }

    checkCollisions() {
        if (this.position.x > width - this.w / 2) {
            this.velocity.x *= -this.friction;
            this.position.x = width - this.w / 2;
        } else if (this.position.x < this.w / 2) {
            this.velocity.x *= -this.friction;
            this.position.x = this.w / 2;
        }

        if (this.position.y > Y_GROUND - this.w / 2) {
            this.velocity.x -= this.velocity.x / 100;
            this.velocity.y *= -this.friction;
            this.position.y = Y_GROUND - this.w / 2;
        } else if (this.position.y < this.w / 2) {
            this.velocity.y *= -this.friction;
            this.position.y = this.w / 2;
        }
    }
}

1 Ответ

2 голосов
/ 03 мая 2020

Я вижу, что это перекрытие происходит, когда сумма масс шаров становится больше, чем упругость шаров. По крайней мере, так кажется. Я сделал копию с меньшим пулом, поэтому для воспроизведения проблемы не требуется много времени.

В следующем примере, когда 6 шаров (масса 150 единиц) нажимают на базовый ряд, мы видим, что 13 шаров в базовом ряду перекрываются. Базовый ряд имеет ширину ок. 300 пикселей, что достаточно только для 12 шариков диаметром 25. Я думаю, что это свидетельствует об ограничении модели: шарики отображаются круглыми, но на самом деле обладают такой эластичностью, что вместо этого они должны отображаться деформированными. Трудно сказать, как это можно исправить, не выполняя рисование сложных фигур. Может быть, меньше трения?

already 19 balls produce overlapping in the base row

Кстати: замечательный физический движок, который вы там построили: -)

Тем временем я смог сделать еще один скриншот с еще меньшим количеством шаров. Вес трех из них (например, 75 единиц) достаточен для создания перекрытия в базовом ряду.

15 balls are enough to reproduce the issue

Я удвоил размер шариков и изменил размеры пула, чтобы detedt, что есть более серьезная ошибка в двигателе. Я вижу, что шарики нажимаются так сильно под давлением, что им не хватает места для их «объема» (площади). Либо они должны взорваться, либо их сила противодействия c должна оказать большее влияние на всю сцену. Если вы обратите пристальное внимание на движения маятника, выполняемые шариками внизу, которые имеют наименьшее пространство, вы увидите, что они очень жестокие, но, по-видимому, не имеют возможности достичь наружу.

the balls have not enough space for their size

Может ли быть, что ваш порядок оценки

balls.forEach(ball => {
    ball.collide();
    ball.move();
    ball.display(displayWeight);
    ball.checkCollisions();
});

не может распространять коллизии в реалист c образом?

...