Произвольное вращение вокруг сферы

12

Я кодирую механику, которая позволяет пользователю перемещаться по поверхности сферы. Положение на сфере в настоящее время сохраняется как thetaи phi, где theta- угол между осью z и проекцией xz текущей позиции (то есть вращение вокруг оси y), и phiявляется углом от оси y к этой позиции. Я объяснил это плохо, но это по сути theta = yaw,phi = pitch

Vector3 position = new Vector3(0,0,1);
position.X = (float)Math.Sin(phi) * (float)Math.Sin(theta);
position.Y = (float)Math.Sin(phi) * (float)Math.Cos(theta);
position.Z = (float)Math.Cos(phi);
position *= r;

Я считаю, что это точно, однако я могу ошибаться. Мне нужно иметь возможность перемещаться в произвольном псевдо-двумерном направлении вокруг поверхности сферы в начале мирового пространства с радиусом r. Например, удержание Wдолжно двигаться вокруг сферы в направлении вверх относительно ориентации игрока.

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

По сути, мне нужно заполнить следующий блок:

public void Move(Direction dir)
{   
    switch (dir)
    {
        case Direction.Left:
            // update quaternion to rotate left
            break;
        case Direction.Right:   
            // update quaternion to rotate right
            break;
        case Direction.Up:
            // update quaternion to rotate upward
            break;
        case Direction.Down:
            // update quaternion to rotate downward
            break;
    }
}
агг
источник
Что должно произойти, если игрок достигает полюсов? Я заметил, что вы написали «направление вверх», вы буквально подразумеваете «вверх» (то есть от поверхности сферы), «прямо» или «к северному полюсу» (последние два одинаковы, если игрок нельзя изменить их ориентацию и «перед ними» или «вверх» на экране всегда север)?
Мартин Сойка
Возможно, это было плохо сформулировано. Игрок не должен покидать поверхность сферы и не должен осознавать кардинальную ось. Поэтому, когда вы двигаетесь «вверх», вы перемещаетесь по поверхности сферы в направлении вверх относительно ориентации игрока. Например, если вы находитесь в точке (r, 0,0) и нажмете вверх, вы пойдете к полюсу z +, но если вы продолжите идти, вам следует развернуться и продолжать движение.
агг
Остается один вопрос: может ли игрок изменить ориентацию (повернуть «влево» и «вправо»)?
Мартин Сойка
Может быть, лучший пример того, что я собираюсь сделать: игрок, (1,1,1)удерживающийся влево, будет вращаться вокруг сферы, проходя через (~1.2,0,~-1.2), затем (-1,-1,-1), затем (~-1.2,0,~1.2)и обратно (1,1,1).
агг
1
Если вы намерены всегда отслеживать thetaи phiобновлять свою позицию, вы делаете свою проблему излишне сложной. Гораздо проще просто рассчитать 2 оси вращения каждого кадра (одна из которых (рыскание) никогда не меняется) и Vector3.Transormвокруг сферы. Это упростит вашу проблему, но заставит вас отключиться от phi& theta.
Стив Х

Ответы:

5

На самом деле, оказывается, что вы не можете иметь «оба пути»: если вы не хотите ощущать «абсолютную ориентацию» на сфере (то есть, если игроки не всегда, например, обращены к полюсам) ), тогда вам нужно иметь представление об ориентации игрока. Это потому, что, вопреки тому, что может предположить интуиция, движение на сфере совсем не похоже на движение на плоскости, даже локально (совсем); Внутренняя кривизна сферы означает, что игроки могут выполнять действия, которые будут вращаться сами!

Для самого экстремального примера из того, о чем я говорю, представьте, что игрок начинает с точки на экваторе (для удобства мы представим циферблат часов, нанесенный сверху на экватор, и поместим игрока в 6 часов). ) лицом вверх, то есть к Северному полюсу. Предположим, игрок проходит весь путь до Северного полюса; тогда они будут направлены прямо к точке 12 часов. Теперь позвольте игроку двигаться прямо направо от Северного полюса обратно к экватору; они окажутся в точке 3 часа - но потому что их лицо не меняется, когда они движутся вправо(идея в том, что их облик не меняется независимо от того, как они двигаются), они все равно будут стоять перед точкой 12 часов - теперь они смотрят вдоль экватора! Теперь позвольте им двигаться «назад» назад к их начальной точке (6 часов); тогда они будут по-прежнему обращены вдоль экватора, поэтому они будут направлены к точке 3 часа - простое движение по сфере без изменения их «личной» ориентации заставило их повернуть лицом к северному полюсу в направлении лицом вдоль экватора! В некотором смысле, это продолжение старой шутки «охотник движется на милю к югу, на милю к западу, а затем на милю к северу» - но здесь мы пользуемся искривлением сферы, чтобы изменить направление. Обратите внимание, что тот же эффект все еще происходит даже в гораздо меньших масштабах;

К счастью, кватернионы (как вы сами отметили) справляются с этой ситуацией; поскольку кватернион представляет собой произвольное вращение, он фактически представляет произвольную «точку плюс ориентация» на сфере: представьте, что вы начинаете с «трехосной оси» в начале координат и задаете ей произвольное вращение, а затем перемещаете одну единицу в любом направлении, в котором повернуты оси ». Точки оси Z; небольшая мысль убедит вас в том, что это приведет вас к точке на сферной единице с некоторой «ориентацией» (т. е. некоторым расположением осей X и Y вашей трехоси) и что вы можете добраться до каждой точки + ориентации на единичная сфера таким образом (просто назначьте свою ось Z так, чтобы она указывала вдоль линии от начала координат до точки на сфере, а затем перенесите ваши триаксы обратно в начало координат вдоль этой линии). Более того, поскольку умножение кватернионов соответствует составу вращений, каждая из описываемых вами операций может быть представлена ​​путем умножения вашей «текущей ориентации» на соответствующим образом выбранный кватернион: в частности, поскольку (единичный) кватернион (qx, qy, qz, qw) означает «повернуть вокруг оси (qx, qy, qz) на arccos (qw)», затем (в зависимости от вашего конкретного выбора системы координат и позволить c_a быть cos (alpha) и s_a sin (alpha)) двумя из три кватерниона M_x = (s_a, 0, 0, c_a), M_y = (0, s_a, 0, c_a) и M_z = (0, 0, s_a, c_a) будут представлять 'вращаться (т.е. двигаться) в направлении I Сейчас я нахожусь лицом к альфе »и« вращаюсь в направлении, перпендикулярном тому, с которым я сейчас сталкиваюсь альфа ». (Третий из этих кватернионов будет представлять «вращать моего персонажа вокруг своей оси»Cur_q = M_x * Cur_qесли игрок нажал вверх, или Cur_q = M_y * Cur_qесли игрок нажал вправо (или, возможно, что-то вроде, Cur_q = M_yinv * Cur_qесли игрок нажал влево, где M_yinv - это «инверсия» кватерниона M_y, представляющая вращение в другом направлении). Обратите внимание, что вы должны быть осторожны, к какой «стороне» вы применяете поворот, будь то предварительное или повторное умножение; Откровенно говоря, это может быть проще всего решить методом проб и ошибок, попробовав умножения и выяснив, какие из них работают.

Переход от обновленного кватерниона к точке на сфере (и к ориентации вашего персонажа) также относительно прост: в соответствии с последним параграфом все, что вам нужно сделать, это использовать свой кватернион на базисных векторах (1, 0,0), (0,1,0) и (0,0,1) вашего кадра с помощью операции «повернуть вектор по кватерниону» v → qvq -1 (где умножения здесь являются умножениями на кватернионы, и мы идентифицируем вектор v = (x, y, z) с «вырожденным кватернионом» (x, y, z, 0)). Например, положение на единичной сфере определяется простым преобразованием вектора z: pos = (qx, qy, qz, qw) * (0, 0, 1, 0) * (-qx, -qy, -qz, qw) = (qx, qy, qz, qw) * (qy, -qx, qw, qz) = (2 (qy * qw + qz * qx), 2 (qz * qy-qw * qx), (qz ^ 2 + qw ^ 2) - (qx ^ 2 + qy ^ 2), 0), поэтому(2(qy*qw+qz*qx), 2(qz*qy-qw*qx), (qz^2+qw^2)-(qx^2+qy^2))будут координаты «преобразованного» пользователя на единичной сфере (и, чтобы получить координаты на произвольной сфере, конечно, вы просто умножите их на радиус сферы); аналогичные расчеты работают для других осей, например, для определения направления движения пользователя.

Стивен Стадницки
источник
Именно то, чего я хочу достичь. Я просто не мог придумать правильный способ получить позицию из кватерниона ориентации. Используя то, что вы предоставили, я могу написать Move()процедуру, но чтобы получить нормализованную ось (т.е. мою позицию), я бы просто взял (sin(qx),sin(qy),sin(qw)) * r?
Аз
@ Не совсем точно - я дополню свой пост подробностями, но короткая версия состоит в том, что вы используете свой кватернион для преобразования единичного вектора, например (0,0,1), обычным v -> qvq <sup> -1 </ sup> операция; тот факт, что вы преобразуете простой вектор, означает, что (естественно) здесь есть кратчайший путь, но конечные координаты являются квадратичными в значениях вашего кватерниона, а не линейными.
Стивен Стадницки
1

Я думаю, вы хотите что-то похожее на это http://www.youtube.com/watch?v=L2YRZbRSD1k

Я разработал это для 48-часового игрового джема ... вы можете загрузить код здесь ... http://archive.globalgamejam.org/2011/evil-god

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

    // To add movement
    protected override void LocalUpdate(float seconds)
    {
        Creature.Alfa += Direction.X * seconds * Speed;
        Creature.Beta += Direction.Y * seconds * Speed;            
    }


    // To calculate position
       World.Planet.GetCartesian(Alfa, Beta, out Position); // as you do
       Matrix PositionMatrix = Matrix.CreateTranslation(Position) * World.Planet.RotationMatrix;           
       LastPositionAbsolute = PositionAbsolute;
       Vector3 Up = PositionAbsolute = Vector3.Transform(Vector3.Zero, PositionMatrix);           
       Up.Normalize();
       // This is to add and offset to the creature model position
       PositionAbsolute += Up * 8;  
      // calculate new forward vector if needed

       if ((PositionAbsolute - LastPositionAbsolute).Length() > 0.1f) {
           Forward = PositionAbsolute - LastPositionAbsolute;
           Forward.Normalize();
       }

       // Calculate the world transform with position, forward vector and up vector
       Matrix LocalWorld = Matrix.CreateWorld(PositionAbsolute, Forward, Up); 

       Transform = Matrix.CreateScale(Scale * ScaleFactor) * LocalWorld;
Blau
источник