iOS OpenGL ES выполняет масштабирование в двухмерном мире

Я делаю 2D-приложение OpenGL на iPad. И мне нужно реализовать щипок/масштабирование.

Я хочу переместить камеру в плоскости (x,y) и управлять значением камеры x,y и z жестом щипка.

Каждый кадр в методе update я делаю матрицу вида (камеру) такой

lookAt = GLKMatrix4MakeLookAt(view_x, view_y, view_z, view_x, view_y, 0.0f, 0.0f, 1.0f, 0.0f);

где view_x, view_y и view_z определяются при запуске программы следующим образом: view_x = view_y = 0.0f; view_z = kStartZoom; kStartZoom равно 3000. Таким образом, камера находится в точке (0,0,3000) и смотрит на (0,0,0)

Практически работающее решение для обработки событий захвата:

- (IBAction) handlePinch:(UIPinchGestureRecognizer*) recognizer {
switch (recognizer.state)
{
    case UIGestureRecognizerStateBegan:
    {
        if (recognizer.numberOfTouches == 2)
        {
            prevTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
            prevTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];
        }
    } break;
    case UIGestureRecognizerStateChanged:
    {
        if (recognizer.numberOfTouches == 2)
        {
            CGFloat newDistance, oldDistance;

            oldDistance = distanceBetweenTwoCGPoints(&prevTouchOrigin1, &prevTouchOrigin2);
            currTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
            currTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];

            newDistance = distanceBetweenTwoCGPoints(&currTouchOrigin1, &currTouchOrigin2);

            if (newDistance == 0 || oldDistance == 0)
            {
                scaleFactor = 1;
            } else {
                scaleFactor = oldDistance / newDistance;
            }

            GLfloat check = view_z * scaleFactor;
            if (check < kMinZoom || check > kMaxZoom)
                return;

            view_z *= scaleFactor;

            // translate

            // formula: newPos = currTouchOrigin + (objectOrigin - prevTouchOrigin) * scaleFactor

            static CGPoint translationDelta;
            GLfloat z_ratio = view_z_old / view_z;

            newPos1.x = currTouchOrigin1.x - ((prevTouchOrigin1.x - view_x) * scaleFactor);
            newPos1.y = currTouchOrigin1.y - ((prevTouchOrigin1.y - view_y) * scaleFactor);

            newPos2.x = currTouchOrigin2.x - ((prevTouchOrigin2.x - view_x) * scaleFactor);
            newPos2.y = currTouchOrigin2.y - ((prevTouchOrigin2.y - view_y) * scaleFactor);

            midpoint = CGPointMidpoint(&newPos1, &newPos2);

            translationDelta = CGPointMake(midpoint.x - view_x, midpoint.y - view_y);

            view_x += translationDelta.x;
            view_y -= translationDelta.y;

            prevTouchOrigin1 = currTouchOrigin1;
            prevTouchOrigin2 = currTouchOrigin2;
        }
    } break;
    case UIGestureRecognizerStateEnded:
    {
    } break;
    default :
    {
    }
}}

Почти рабочий.

У меня больше движения по x, y, чем мне нужно, поэтому камера вертится.

Проблема в том, что я не применяю некоторые преобразования из координат экрана в мировые координаты?

В чем может быть проблема? Другие примеры, которые я рассматривал, изменяют только положение камеры в зависимости от расстояния между предыдущим и последним положениями пальцев, что я и делаю.


person Martin Berger    schedule 10.04.2013    source источник


Ответы (3)


Это мое решение:

У меня есть два класса, один из которых отвечает за все, что связано с OpenGL (RenderViewController), а другой — за все распознаватели жестов и связь между частью OpenGL и другими частями приложения (EditViewController).

Это код, касающийся жестов:

Эдторвиевконтроллер

Он фиксирует жесты и отправляет информацию о них в RenderViewController. Вы должны быть осторожны из-за различных систем координат.

- (void) generateGestureRecognizers {

    //Setup gesture recognizers
    UIRotationGestureRecognizer *twoFingersRotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersRotate:)];
    [self.hitView addGestureRecognizer:twoFingersRotate];

    UIPinchGestureRecognizer *twoFingersScale = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersScale:)];
    [self.hitView addGestureRecognizer:twoFingersScale];

    UIPanGestureRecognizer *oneFingerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerPan:)];
    [self.hitView addGestureRecognizer:oneFingerPan];


    [twoFingersRotate setDelegate:self];
    [twoFingersScale setDelegate:self];
    [oneFingerPan setDelegate:self];
}

- (void) oneFingerPan:(UIPanGestureRecognizer *) recognizer {    

    //Handle pan gesture
    CGPoint translation = [recognizer translationInView:self.hitView];
    CGPoint location = [recognizer locationInView:self.hitView];

    //Send info to renderViewController
    [self.renderViewController translate:traslation];

    //Reset recognizer so change doesn't accumulate
    [recognizer setTranslation:CGPointZero inView:self.hitView];    
}

- (void) twoFingersRotate:(UIRotationGestureRecognizer *) recognizer {  

    //Handle rotation gesture
    CGPoint locationInView = [recognizer locationInView:self.hitView];
    locationInView = CGPointMake(locationInView.x - self.hitView.bounds.size.width/2, locationInView.y - self.hitView.bounds.size.height/2);

    if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged) {

        //Send info to renderViewController
        [self.renderViewController rotate:locationInView degrees:recognizer.rotation];

        //Reset recognizer
        [recognizer setRotation:0.0];
    }
}

- (void) twoFingersScale:(UIPinchGestureRecognizer *) recognizer {

    //Handle scale gesture
    CGPoint locationInView = [recognizer locationInView:self.hitView];
    locationInView = CGPointMake(locationInView.x - self.hitView.bounds.size.width/2, locationInView.y - self.hitView.bounds.size.height/2);

    if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged) {

        //Send info to renderViewController
        [self.renderViewController scale:locationInView ammount:recognizer.scale];

        //reset recognizer
        [recognizer setScale:1.0];
    }

}

//This allows gestures recognizers to happen simultaniously
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer.view != otherGestureRecognizer.view)
        return NO;

    if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
        return NO;

    return YES;
}

Рендервиевконтроллер

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

- (void) setup {

    //Creates the modelViewMatrix from the initial position, rotation and scale
    translatemt = GLKMatrix4Translate(GLKMatrix4Identity, initialPosition.x, initialPosition.y, 0.0);
    scalemt = GLKMatrix4Scale(GLKMatrix4Identity, initialScale, initialScale, 1.0);
    rotatemt = GLKMatrix4Rotate(GLKMatrix4Identity, initialRotation, 0.0, 0.0, 1.0);
    self.modelViewMatrix = GLKMatrix4Multiply(GLKMatrix4Multiply(GLKMatrix4Multiply(translatemt, rotatemt), scalemt), GLKMatrix4Identity);

    //set these back to identities to take further modifications (they'll update the modelViewMatrix)
    scalemt = GLKMatrix4Identity;
    rotatemt = GLKMatrix4Identity;
    translatemt = GLKMatrix4Identity;


    //rest of the OpenGL setup
    [self setupOpengGL];

}

//public interface
- (void) translate:(CGPoint) location {
    //Update the translation temporary matrix
    translatemt = GLKMatrix4Translate(translatemt, location.x, -location.y, 0.0);
}

//public interface
- (void) rotate:(CGPoint) location degrees:(CGFloat) degrees {
    //Update the rotation temporary matrix
    rotatemt = GLKMatrix4Translate(GLKMatrix4Identity, location.x, -location.y, 0.0);
    rotatemt = GLKMatrix4Rotate(rotatemt, -degrees, 0.0, 0.0, 1.0);
    rotatemt = GLKMatrix4Translate(rotatemt, -location.x, location.y, 0.0);
}

//public interface
- (void) scale:(CGPoint) location ammount:(CGFloat) ammount {
    //Update the scale temporary matrix
    scalemt = GLKMatrix4Translate(GLKMatrix4Identity, location.x, -location.y, 0.0);
    scalemt = GLKMatrix4Scale(scalemt, ammount, ammount, 1.0);
    scalemt = GLKMatrix4Translate(scalemt, -location.x, location.y, 0.0);
}

- (void)update {

    //this is done before every render update. It generates the modelViewMatrix from the temporary matrices
    self.modelViewMatrix = GLKMatrix4Multiply(GLKMatrix4Multiply(GLKMatrix4Multiply(rotatemt, translatemt), scalemt), self.modelViewMatrix);

    //And then set them back to identities
    translatemt = GLKMatrix4Identity;
    rotatemt = GLKMatrix4Identity;
    scalemt = GLKMatrix4Identity;

    //set the modelViewMatrix for the effect (this is assuming you are using OpenGL es 2.0, but it would be similar for previous versions
    self.effect.transform.modelviewMatrix = self.modelViewMatrix;
}
person Odrakir    schedule 15.04.2013
comment
Я пытался заставить этот подход работать ровно 3 часа и потерпел неудачу :) Но все равно, даже если бы он работал, мне нужно двигать камеру, а не объекты. Когда у нас много объектов, изменение матрицы каждого объекта может дорого обойтись... Спасибо, Одракир, за усилия. Прочитав ваш ответ, я получил идею для решения. Мне нужно установить соотношение для ввода с экрана. Я напишу больше, когда это будет сделано. - person Martin Berger; 15.04.2013
comment
Извините, но у вас не должно быть одной modelViewMatrix для каждого объекта. Это всего лишь одна матрица для всей программы. - person Odrakir; 15.04.2013
comment
я не понимаю. Напомним: для каждого объекта существует одна модельная матрица, которая определяет, где объект находится в мировом пространстве. Тогда у нас есть один вид и одна проекционная матрица для камеры. Вот как я узнал об этом из DirectX. В OpenGL должно быть то же самое, матрица вида определяет, куда смотрит камера, а матрица проекции имеет дело с ближней и дальней плоскостями и другими вещами. Поправьте меня, если вы думаете иначе. - person Martin Berger; 16.04.2013
comment
Да это правильно. Одна модельная матрица для каждого независимо перемещаемого объекта и только одна матрица вида для камеры (в моем случае все объекты статичны или движутся одновременно, поэтому я объединил обе матрицы). Итак, в вашем случае все те вычисления, которые я опубликовал, нужно выполнить только один раз (для матрицы представления), а затем объединить их с матрицей модели для каждого объекта. - person Odrakir; 16.04.2013
comment
Хорошо, решил. Я опубликую код завтра, но я отмечу ваш ответ как правильный, потому что он направил меня в правильном направлении. И я знаю, что вы потратили время на написание всего этого, чтобы получить баллы. - person Martin Berger; 16.04.2013
comment
Спасибо. Рад, что немного помог. - person Odrakir; 17.04.2013

Я не совсем уверен, понимаю ли я ваш вопрос, но для распознавания я использую UIPinchGestureRecongizer:

http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIPinchGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/cl/UIPinchGestureRecognizer

person samb90    schedule 10.04.2013
comment
не знаю как лучше сказать. Мне нужно использовать жест сжатия для масштабирования в OpenGL ES 2.0 Вот и все. А проблемы у меня описаны выше. - person Martin Berger; 11.04.2013

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

Что было необходимо, так это преобразование единиц из экранного пространства, выраженного в точках, в открытое пространство gl, выраженное в дискретных единицах.

Я получил эту константу, нарисовав квадрат 50x50 в единицах opengl на плоскости x, y с камерой в центре x = 0, y = 0, z = 100.

Затем я добавил два UIView и настроил жест панорамирования для перемещения этих двух представлений. С помощью жеста панорамирования я физически центрировал их начало кадра в верхнем правом и нижнем правом углах квадрата opengl. Жест нажатия был установлен на NSLog их происхождение. Вот как я получил эту константу:

static const GLfloat k = 0.125f; // 445 CG units is 50 discrete OpenGL units at Z = 100; 0.112359f

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

Инициализация матрицы в некотором методе init:

aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 5000.0f);
lookAt = GLKMatrix4MakeLookAt(view_x, view_y, view_z, view_x, view_y, 0.0f, 0.0f, 1.0f, 0.0f);

Все ивары:

GLfloat view_x;
GLfloat view_y;
GLfloat view_z;
GLfloat view_z_base;

CGPoint currTouchOrigin1, currTouchOrigin2;
CGPoint prevTouchOrigin1, prevTouchOrigin2;
CGPoint newPos1, newPos2;
CGPoint midpoint;
CGFloat scaleFactor;

Мы перемещаем камеру с помощью переменных view_x, view_y и view_z.

Все константы:

static const GLfloat k              = 0.125f; // 445 CG units is 50 discrete OpenGL units at Z = 100; 0.112359f
static const GLfloat default_z      = 100.0f;
static const GLfloat kMinZoom       = 30.0f;
static const GLfloat kMaxZoom       = 4000.0f;
static const GLfloat kStartZoom     = 200.0f;

Это полный обработчик масштабирования:

- (IBAction) handlePinch:(UIPinchGestureRecognizer*) recognizer {

switch (recognizer.state)
        {
            case UIGestureRecognizerStateBegan:
            {
                if (recognizer.numberOfTouches == 2)
                {
                    prevTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
                    prevTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];
                }
            } break;
            case UIGestureRecognizerStateChanged:
            {
                if (recognizer.numberOfTouches == 2)
                {
                    CGFloat newDistance, oldDistance;

            oldDistance = distanceBetweenTwoCGPoints(&prevTouchOrigin1, &prevTouchOrigin2);
            currTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
            currTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];

            newDistance = distanceBetweenTwoCGPoints(&currTouchOrigin1, &currTouchOrigin2);

            if (newDistance == 0 || oldDistance == 0)
            {
                scaleFactor = 1;
                //break;
            } else {
                //scaleFactor = oldDistance / newDistance;
                scaleFactor = newDistance / oldDistance;
            }

            GLfloat view_z_old = view_z;
            view_z /= scaleFactor;
            if (view_z < kMinZoom || view_z > kMaxZoom)
            {
                view_z = view_z_old;
                return;
            }

            // translate

            // formula: newPos = currTouchOrigin + (objectOrigin - prevTouchOrigin) * scaleFactor

            //static CGPoint tmp1, tmp2;
            static CGPoint translationDelta;

            newPos1.x = currTouchOrigin1.x - ((prevTouchOrigin1.x - (screenRect.size.width / 2)) * scaleFactor);
            newPos1.y = currTouchOrigin1.y - ((prevTouchOrigin1.y - (screenRect.size.height / 2)) * scaleFactor);

            newPos2.x = currTouchOrigin2.x - ((prevTouchOrigin2.x - (screenRect.size.width / 2)) * scaleFactor);
            newPos2.y = currTouchOrigin2.y - ((prevTouchOrigin2.y - (screenRect.size.height / 2)) * scaleFactor);

            midpoint = CGPointMidpoint(&newPos1, &newPos2);

            translationDelta = CGPointMake(midpoint.x - (screenRect.size.width / 2), midpoint.y - (screenRect.size.height / 2));

            static GLfloat r = 0.0f;
            static GLfloat k2 = 0.0f;

            r = view_z / default_z;
            k2 = k * r;

            // In openGL, coord sys if first quadrant based
            view_x += -translationDelta.x * k2;
            view_y +=  translationDelta.y * k2;

            // store current coords for next event
            prevTouchOrigin1 = currTouchOrigin1;
            prevTouchOrigin2 = currTouchOrigin2;
        }
    } break;
    case UIGestureRecognizerStateEnded:
    {

    } break;
    default :
    {

    }
  }
}
person Martin Berger    schedule 17.04.2013
comment
Это решение моего вопроса. Однако я отметил ответ Одракира как правильный, поскольку его ответ помог мне двигаться в правильном направлении. Также он уделял время. Я уже задавал подобные вопросы и не получил никакого ответа. Спасибо, Одракир. - person Martin Berger; 17.04.2013