Подтвердить что ты не робот

Поворотное поле холста

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

JS

var c = document.getElementById('stars'),
    ctx = c.getContext("2d"),
    t = 0; // time

c.width = 300;
c.height = 300;

var w = c.width,
    h = c.height,
    z = c.height,
    v = Math.PI; // angle of vision

(function animate() {

    Math.seedrandom('bg');
    ctx.globalAlpha = 1;

    for (var i = 0; i <= 100; i++) {

        var x = Math.floor(Math.random() * w), // pos x
            y = Math.floor(Math.random() * h), // pos y
            r = Math.random()*2 + 1, // radius
            a = Math.random()*0.5 + 0.5, // alpha

            // linear
            d = (r*a),       // depth
            p = t*d;         // pixels per t

        x = x - p;       // movement
        x = x - w * Math.floor(x / w); // go around when x < 0

        (function draw(x,y) {
            var gradient = ctx.createRadialGradient(x, y, 0, x + r, y + r, r * 2);
            gradient.addColorStop(0, 'rgba(255, 255, 255, ' + a + ')');
            gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');

            ctx.beginPath();
            ctx.arc(x, y, r, 0, 2*Math.PI);
            ctx.fillStyle = gradient;
            ctx.fill();

            return draw;

        })(x, y);

    }

    ctx.restore();
    t += 1;

    requestAnimationFrame(function() {
        ctx.clearRect(0, 0, c.width, c.height);
        animate();
    });
})();

HTML

<canvas id="stars"></canvas>

CSS

canvas {
    background: black;
}

JSFiddle

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

Используйте p = t;, чтобы все звезды двигались с одинаковой скоростью.

ВОПРОС

Я ищу четко определенную модель, где скорости дают иллюзию звезд, вращающихся вокруг счетчика, определяемых в терминах центра вращения cX, cY, и угла зрения v, что и есть может быть видна доля 2π (если центр круга не является центром экрана, радиус должен быть как минимум самой большой частью). Я изо всех сил пытаюсь найти способ, который применяет этот косинус к скорости движения звезды, даже для центрированного круга с вращением π.

Эти диаграммы могут объяснить, что мне нужно:

Центрированный круг:

центр зрения в x, y

Non-центрированный:

сдвинутый центр

Разный угол обзора:

другой угол обзора

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

Спасибо


UPDATE

Я сделал некоторый прогресс с этим кодом:

        // linear
        d = (r*a)*z,   // depth
        v = (2*Math.PI)/w,
        p = Math.floor( d * Math.cos( t * v ) );     // pixels per t

    x = x + p;       // movement
    x = x - w * Math.floor(x / w); // go around when x < 0

JSFiddle

Где p - координата x частицы с равномерным круговым движением, а v - скорость angular, но это создает эффект маятника. Я не уверен, как изменить эти уравнения, чтобы создать иллюзию, что вместо этого поворачивается наблюдатель.


ОБНОВЛЕНИЕ 2:

Почти там. Один пользователь в канале ## Math freenode был достаточно любезен, чтобы предложить следующий расчет:

        // linear
        d = (r*a),       // depth
        p = t*d;         // pixels per t

    x = x - p;       // movement
    x = x - w * Math.floor(x / w); // go around when x < 0

    x = (x / w) - 0.5;
    y = (y / h) - 0.5;

    y /= Math.cos(x);

    x = (x + 0.5) * w;
    y = (y + 0.5) * h;

JSFiddle

Это обеспечивает визуальный эффект, но не соответствует четко определенной модели с точки зрения переменных (она просто "взламывает" эффект), поэтому я не вижу прямого способа сделать различные реализации (изменить центр, угол обзора). Реальная модель может быть очень похожа на эту.


ОБНОВЛЕНИЕ 3

После ответа Iftah я смог использовать Sylvester, чтобы применить матрицу вращения к звездам, которые необходимо сохранить сначала в массиве. Также определяется каждая координата звезды z, а радиус r и непрозрачность a выводятся из нее. Код существенно отличается и более длинным, поэтому я не размещаю его, но это может быть шаг в правильном направлении. Я не могу заставить это вращаться непрерывно. Использование матричных операций для каждого кадра кажется дорогостоящим с точки зрения производительности.

JSFiddle

4b9b3361

Ответ 1

Здесь некоторый псевдокод, который делает то, о чем вы говорите.

Make a bunch of stars not too far but not too close (via rejection sampling)
Set up a projection matrix (defines the camera frustum)
Each frame
    Compute our camera rotation angle
    Make a "view" matrix (repositions the stars to be relative to our view)
    Compose the view and projection matrix into the view-projection matrix
    For each star
        Apply the view-projection matrix to give screen star coordinates
        If the star is behind the camera skip it
        Do some math to give the star a nice seeming 'size'
        Scale the star coordinate to the canvas
        Draw the star with its canvas coordinate and size

Я сделал реализацию выше. Он использует библиотеку j-матриц gl-matrix для обработки некоторой математической матрицы. Это хороший материал. (Здесь сценарий или ниже).

var c = document.getElementById('c');
var n = c.getContext('2d');

// View matrix, defines where you're looking
var viewMtx = mat4.create();

// Projection matrix, defines how the view maps onto the screen
var projMtx = mat4.create();

// Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api
function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) {
    // We'll assume input parameters are sane.
    field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians
    var frustum_depth = far_dist - near_dist;
    var one_over_depth = 1 / frustum_depth;
    var e11 = 1.0 / Math.tan(0.5 * field_of_view);
    var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio;
    var e22 = far_dist * one_over_depth;
    var e32 = (-far_dist * near_dist) * one_over_depth;
    return [
        e00, 0, 0, 0,
        0, e11, 0, 0,
        0, 0, e22, e32,
        0, 0, 1, 0
    ];
}

// Make a view matrix with a simple rotation about the Y axis (up-down axis)
function ComputeViewMtx(angle) {
    angle = angle * Math.PI / 180.0; // Convert degrees to radians
    return [
        Math.cos(angle), 0, Math.sin(angle), 0,
        0, 1, 0, 0,
        -Math.sin(angle), 0, Math.cos(angle), 0,
        0, 0, 0, 1
    ];
}

projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true);

var angle = 0;

var viewProjMtx = mat4.create();

var minDist = 100;
var maxDist = 1000;

function Star() {
    var d = 0;
    do {
        // Create random points in a cube.. but not too close.
        this.x = Math.random() * maxDist - (maxDist / 2);
        this.y = Math.random() * maxDist - (maxDist / 2);
        this.z = Math.random() * maxDist - (maxDist / 2);
        var d = this.x * this.x +
                this.y * this.y +
                this.z * this.z;
    } while (
         d > maxDist * maxDist / 4 || d < minDist * minDist
    );
    this.dist = Math.sqrt(d);
}

Star.prototype.AsVector = function() {
    return [this.x, this.y, this.z, 1];
}

var stars = [];
for (var i = 0; i < 5000; i++) stars.push(new Star());

var lastLoop = Date.now();

function loop() {
    
    var now = Date.now();
    var dt = (now - lastLoop) / 1000.0;
    lastLoop = now;
    
    angle += 30.0 * dt;

    viewMtx = ComputeViewMtx(angle);
    
    //console.log('---');
    //console.log(projMtx);
    //console.log(viewMtx);
    
    mat4.multiply(viewProjMtx, projMtx, viewMtx);
    //console.log(viewProjMtx);
    
    n.beginPath();
    n.rect(0, 0, c.width, c.height);
    n.closePath();
    n.fillStyle = '#000';
    n.fill();
    
    n.fillStyle = '#fff';
    
    var v = vec4.create();
    for (var i = 0; i < stars.length; i++) {
        var star = stars[i];
        vec4.transformMat4(v, star.AsVector(), viewProjMtx);
        v[0] /= v[3];
        v[1] /= v[3];
        v[2] /= v[3];
        //v[3] /= v[3];
        
        if (v[3] < 0) continue;

        var x = (v[0] * 0.5 + 0.5) * c.width;
        var y = (v[1] * 0.5 + 0.5) * c.height;
        
        // Compute a visual size...
        // This assumes all stars are the same size.
        // It also doesn't scale with canvas size well -- we'd have to take more into account.
        var s = 300 / star.dist;
        
        
        n.beginPath();
        n.arc(x, y, s, 0, Math.PI * 2);
        //n.rect(x, y, s, s);
        n.closePath();
        n.fill();
    }
    
    window.requestAnimationFrame(loop);
}

loop();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script>
<canvas id="c" width="500" height="500"></canvas>

Ответ 2

Вы перебрасываете звезды по 2 позиции на каждый кадр, затем перемещаете звезды (в зависимости от того, сколько времени и скорости каждой звезды) - это плохой путь для достижения вашей цели. Как вы обнаружили, он становится очень сложным, когда вы пытаетесь расширить это решение до большего количества сценариев.

Лучшим способом было бы установить трехмерное местоположение звезд только один раз (при инициализации), затем переместить "камеру" в каждый кадр (в зависимости от времени). Когда вы хотите отобразить 2d-изображение, вы затем вычисляете местоположение звезд на экране. Расположение на экране зависит от местоположения трех звезд и текущего местоположения камеры. Это позволит вам перемещать камеру (в любом направлении), поворачивать камеру (под любым углом) и отображать правильное положение звезд и сохранять ваше здоровье.