LINUX.ORG.RU

Перемещение камеры с учётом только одного угла вращения

 , , кватернионы,


0

2

Есть камера, повёрнутая вокруг оси Y, что смотрит вверх, и вокруг оси Z (в движке она является одной из горизонтальных). При достижении края экрана необходимо взять вектор, повёрнутый так же, как текущий поворот вокруг Y, но параллельный террэйну, т.е. не учитывающий прочие повороты. Пробовал, как здесь приводить к единичному виду все повороты, кроме Y, но получалось что-то совсем не то. Если убирать только одну из ненужных горизонтальных осей, то нормально, но до первого разворота на 180 градусов - дальше начинает ездить в направлении глобальной оси X или Z, а не локального «в бок» или «прямо». Сейчас убрал обнуление осей, и теперь всегда берётся глобальная ось, хотя перемещение и идёт в локальных координатах (у translate есть дополнительный параметр с пространством перемещения):

void LevelCamera::VerticalTranslate(float amount)
{
    Quaternion currentRot = _cameraNode->GetRotation();
    Matrix3 rotation = currentRot.Inverse().RotationMatrix();
    Vector3 rotated = rotation * Vector3(0,0,amount);
    _cameraNode->Translate(rotated);
}

void LevelCamera::HorizontalTranslate(float amount)
{
    Quaternion currentRot = _cameraNode->GetRotation();
    Matrix3 rotation = currentRot.Inverse().RotationMatrix();
    Vector3 rotated = rotation * Vector3(amount,0,0);
    _cameraNode->Translate(rotated);
}
void LevelCamera::Update()
{
    UI* ui = GetSubsystem<UI>();
    Input* input = GetSubsystem<Input>();
    Graphics *graphics = GetSubsystem<Graphics>();

    if (ui->GetFocusElement())
        return;

    if(input->GetMousePosition().y_ < BORDER_OFFSET)
        VerticalTranslate(CAMERA_VELOCITY);
    if(input->GetMousePosition().y_ >  graphics->GetHeight() - BORDER_OFFSET)
        VerticalTranslate(-CAMERA_VELOCITY);
    if(input->GetMousePosition().x_ < BORDER_OFFSET)
        HorizontalTranslate(CAMERA_VELOCITY);
    if(input->GetMousePosition().x_ >  graphics->GetWidth() - BORDER_OFFSET)
        HorizontalTranslate(-CAMERA_VELOCITY);
    if (input->GetMouseButtonDown(MOUSEB_RIGHT))
    {
        IntVector2 mouseMove = input->GetMouseMove();
        _yaw += MOUSE_SENSITIVITY * mouseMove.x_;
        _pitch += MOUSE_SENSITIVITY * mouseMove.y_;
        Vector3 hitPos;
        Drawable* hitDrawable;
        if(Raycast(250.0f, hitPos, hitDrawable, true))
        {
            _cameraNode->RotateAround(hitPos, Quaternion(_yaw, Vector3(0,1,0)), TS_WORLD);
            if(abs((int)_pitch) < 20 )
            {
                if(_cameraNode->GetRotation().PitchAngle() < 45 &&  _pitch < 0)
                    _cameraNode->RotateAround(hitPos, Quaternion(_pitch, _cameraNode->GetDirection().CrossProduct(Vector3(0,1,0))), TS_WORLD);
                if(_cameraNode->GetRotation().PitchAngle() > 10 && _pitch > 0)
                    _cameraNode->RotateAround(hitPos, Quaternion(_pitch, _cameraNode->GetDirection().CrossProduct(Vector3(0,1,0))), TS_WORLD);
            }
        }

        _yaw = 0;
        _pitch = 0;
    }
    int wheelMove = input->GetMouseMoveWheel();
    if( wheelMove != 0 && abs((int)wheelMove) < 2)
    {
        Camera *camera = _cameraNode->GetComponent<Camera>();
        float oldZoom = camera->GetZoom();
        if(oldZoom > 0.5f && wheelMove < 0)
            camera->SetZoom(oldZoom + wheelMove*WHEEL_SENSITIVITY);
        if(oldZoom < 4 && wheelMove > 0)
            camera->SetZoom(oldZoom + wheelMove*WHEEL_SENSITIVITY);
    }
}

★★★★

Поправил методом тыка, перемещается теперь как надо, но почему-то замедляется, если угол равен +-90 или около:( Чую, если пришлось лезть внутрь матрицы, значит, что-то я делаю совсем не так.


void LevelCamera::VerticalTranslate(float amount)
{
    Quaternion currentRot = _cameraNode->GetRotation();
    Matrix3 rotation = currentRot.Inverse().RotationMatrix();

    rotation.m00_ = 1;
    rotation.m10_ = 0;
    rotation.m20_ = 0;
    rotation.m01_ = 0;
    rotation.m02_ = 0;
    LOGINFO(String(currentRot.YawAngle()));
    if(currentRot.YawAngle() < -90 || currentRot.YawAngle() > 90)
            amount *= -1;
    Vector3 rotated = rotation * Vector3(0,0,amount);
    _cameraNode->Translate(rotated);
}

void LevelCamera::HorizontalTranslate(float amount)
{
    Quaternion currentRot = _cameraNode->GetRotation();
    Matrix3 rotation = currentRot.Inverse().RotationMatrix();
    rotation.m00_ = 1;
    rotation.m10_ = 0;
    rotation.m20_ = 0;
    rotation.m01_ = 0;
    rotation.m02_ = 0;
    Vector3 rotated = rotation * Vector3(amount,0,0);
    _cameraNode->Translate(rotated);
} 

wingear ★★★★
() автор топика

Понял, как правильно - надо не выпалывать ненужные вращения, а взять нужное и на его основе сделать новый кватернион:

void LevelCamera::VerticalTranslate(float amount)
{
    float currentRot = _cameraNode->GetRotation().YawAngle();
    Quaternion distilledRot;
    distilledRot.FromAngleAxis(currentRot, Vector3(0,1,0));
    Vector3 rotated = distilledRot.RotationMatrix() * Vector3(0,0,amount);
    _cameraNode->Translate(rotated, TS_WORLD);
}

void LevelCamera::HorizontalTranslate(float amount)
{
    float currentRot = _cameraNode->GetRotation().YawAngle();
    Quaternion distilledRot;
    distilledRot.FromAngleAxis(currentRot, Vector3(0,1,0));
    Vector3 rotated = distilledRot.RotationMatrix() * Vector3(amount,0,0);
    _cameraNode->Translate(rotated, TS_WORLD);
}

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

bool LevelCamera::Raycast(Vector3 &hitPos, Drawable *&hitDrawable)
{
    hitDrawable = 0;

    UI* ui = GetSubsystem<UI>();

    Graphics* graphics = GetSubsystem<Graphics>();
    Camera *camera = _cameraNode->GetComponent<Camera>();
    Ray cameraRay = camera->GetScreenRay(0.5f, 0.5f);
    // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
    PODVector<RayQueryResult> results;
    RayOctreeQuery query(results, cameraRay, RAY_TRIANGLE, 1000, DRAWABLE_GEOMETRY);
    _sceneNode->GetComponent<Octree>()->RaycastSingle(query);
    if (results.Size())
    {
        RayQueryResult& result = results[0];
        hitPos = result.position_;
        hitDrawable = result.drawable_;
        return true;
    }

    return false;

}

...

if(Raycast(hitPos, hitDrawable))
        {
            _cameraNode->RotateAround(hitPos, Quaternion(_yaw, Vector3(0,1,0)), TS_WORLD);
            if(abs((int)_pitch) < 20 )
            {
                if(_cameraNode->GetRotation().PitchAngle() < 45 &&  _pitch < 0)
                    _cameraNode->RotateAround(hitPos, Quaternion(_pitch, _cameraNode->GetDirection().CrossProduct(Vector3(0,1,0))), TS_WORLD);
                if(_cameraNode->GetRotation().PitchAngle() > 10 && _pitch > 0)
                    _cameraNode->RotateAround(hitPos, Quaternion(_pitch, _cameraNode->GetDirection().CrossProduct(Vector3(0,1,0))), TS_WORLD);
            }
        }

wingear ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.