OpenGL ES2 缩放移动

OpenGL ES Transformations with Gestures

Ricardo Rendon Cepeda on December 10, 2013

Gestures: Intuitive, sophisticated and easy to implement!

In this tutorial, you’ll learn how to use gestures to control OpenGL ES transformations by building a sophisticated model viewer app for 3D objects.

For this app, you’ll take maximum advantage of the iPhone’s touchscreen to implement an incredibly intuitive interface. You’ll also learn a bit of 3D math and use this knowledge to master basic model manipulation.

The hard work has already been done for you, specifically in our tutorial series How To Export Blender Models to OpenGL ES, allowing you to concentrate on nothing but transformations and gestures. The aforementioned series is an excellent build-up to this tutorial, since you’ll be using virtually the same code and resources. If you missed it, you’ll also be fine if you’ve read our OpenGL ES 2.0 for iPhone or Beginning OpenGL ES 2.0 with GLKit tutorials.

Note: Since this is literally a “hands-on” tutorial that depends on gestures, you’ll definitely need an iOS device to fully appreciate the implementation. The iPhone/iPad Simulator can’t simulate all the gestures covered here.

Getting Started

First, download the starter pack for this tutorial.

As mentioned before, this is essentially the same project featured in our Blender to OpenGL ES tutorial series. However, the project has been refactored to present a neat and tidy GLKit View Controller class—MainViewController—that hides most of the OpenGL ES shader implementation and 3D model rendering.

Have a look at MainViewController.m to see how everything works, and then build and run. You should see the screen below:

The current model viewer is very simple, allowing you to view two different models in a fixed position. So far it’s not terribly interesting, which is why you’ll be adding the wow factor by implementing gesture recognizers!

Gesture Recognizers

Any new iPhone/iPad user will have marveled at the smooth gestures that allow you to navigate the OS and its apps, such as pinching to zoom or swiping to scroll. The 3D graphics world is definitely taking notice, since a lot of high-end software, including games, requires a three-button mouse or double thumbsticks to navigate their worlds. Touchscreen devices have changed all this and allow for new forms of input and expression. If you’re really forward-thinking, you may have already implemented gestures in your apps.

An Overview

Although we’re sure you’re familiar with them, here’s quick overview of the four gesture recognizers you’ll implement in this tutorial:

Pan (One Finger)

Pan (Two Fingers)

Pinch

Rotation

The first thing you need to do is add them to your interface.

Adding Gesture Recognizers

Open MainStoryboard.storyboard and drag a Pan Gesture Recognizer from your Object library and drop it onto your GLKit View, as shown below:

Next, show the Assistant editor in Xcode with MainStoryboard.storyboard in the left window and MainViewController.m in the right window. Click on your Pan Gesture Recognizer and control+drag a connection from it to MainViewController.m to create an Action in the file. Enter pan for the Name of your new action and UIPanGestureRecognizer for the Type. Use the image below as a guide:

Repeat the process above for a Pinch Gesture Recognizer and a Rotation Gesture Recognizer. The Action for the former should have the Name pinch with Type UIPinchGestureRecognizer, while the latter should have the Name rotation with Type UIRotationGestureRecognizer. If you need help, use the image below:

Solution Inside: Adding Pinch and Rotation Gesture Recognizers Show
 
   
 

Revert Xcode back to your Standard editor view and open MainStoryboard.storyboard. Select your Pan Gesture Recognizer and turn your attention to the right sidebar. Click on the Attributes inspector tab and set the Maximum number of Touches to 2, since you’ll only be handling one-finger and two-finger pans.

Next, open MainViewController.m and add the following lines to pan::

// Pan (1 Finger)
if(sender.numberOfTouches == 1)
{
    NSLog(@"Pan (1 Finger)");
}
 
// Pan (2 Fingers)
else if(sender.numberOfTouches == 2)
{
    NSLog(@"Pan (2 Fingers)");
}

Similarly, add the following line to pinch::

NSLog(@"Pinch");

And add the following to rotation::

NSLog(@"Rotation");

As you might have guessed, these are simple console output statements to test your four new gestures, so let’s do just that: build and run! Perform all four gestures on your device and check the console to verify your actions.

Gesture Recognizer Data

Now let’s see some actual gesture data. Replace both NSLog() statements in pan: with:

CGPoint translation = [sender translationInView:sender.view];
float x = translation.x/sender.view.frame.size.width;
float y = translation.y/sender.view.frame.size.height;
NSLog(@"Translation %.1f %.1f", x, y);

At the beginning of every new pan, you set the touch point of the gesture (translation) as the origin (0.0, 0.0) for the event. While the event is active, you divide its reported coordinates over its total view size (width for x, height for y) to get a total range of 1.0 in each direction. For example, if the gesture event begins in the middle of the view, then its range will be: -0.5 ≤ x ≤ +0.5 from left to right and -0.5 ≤ y ≤ +0.5 from top to bottom.

Pop quiz! If the gesture event begins in the top-left corner of the view, what is its range?

Solution Inside: Pan Gesture Range Show
 
   
 

The pinch and rotation gestures are much easier to handle. Replace the NSLog() statement in pinch: with this:

float scale = [sender scale];
NSLog(@"Scale %.1f", scale);

And replace the NSLog() statement in rotation: with the following:

float rotation = GLKMathRadiansToDegrees([sender rotation]);
NSLog(@"Rotation %.1f", rotation);

At the beginning of every new pinch, the distance between your two fingers has a scale of 1.0. If you bring your fingers together, the scale of the gesture decreases for a zoom-out effect. If you move your fingers apart, the scale of the gesture increases for a zoom-in effect.

A new rotation gesture always begins at 0.0 radians, which you conveniently convert to degrees for this exercise with the function GLKMathRadiansToDegrees(). A clockwise rotation increases the reported angle, while a counterclockwise rotation decreases the reported angle.

Build and run! Once again, perform all four gestures on your device and check the console to verify your actions. You should see that pinching inward logs a decrease in the scale, rotating clockwise logs a positive angle and panning to the bottom-right logs a positive displacement.

Handling Your Transformations

With your gesture recognizers all set, you’ll now create a new class to handle your transformations. Click File\New\File… and choose the iOS\Cocoa Touch\Objective-C class template. Enter Transformations for the class and NSObject for the subclass. Make sure both checkboxes are unchecked, click Next and then click Create.

Open Transformations.h and replace the existing file contents with the following:

#import <GLKit/GLKit.h>
 
@interface Transformations : NSObject
 
- (id)initWithDepth:(float)z Scale:(float)s Translation:(GLKVector2)t Rotation:(GLKVector3)r;
- (void)start;
- (void)scale:(float)s;
- (void)translate:(GLKVector2)t withMultiplier:(float)m;
- (void)rotate:(GLKVector3)r withMultiplier:(float)m;
- (GLKMatrix4)getModelViewMatrix;
 
@end

These are the main methods you’ll implement to control your model’s transformations. You’ll examine each in detail within their own sections of the tutorial, but for now they will mostly remain dummy implementations.

Open Transformations.m and replace the existing file contents with the following:

#import "Transformations.h"
 
@interface Transformations ()
{
    // 1
    // Depth
    float   _depth;
}
 
@end
 
@implementation Transformations
 
- (id)initWithDepth:(float)z Scale:(float)s Translation:(GLKVector2)t Rotation:(GLKVector3)r
{
    if(self = [super init])
    {
        // 2
        // Depth
        _depth = z;
    }
 
    return self;
}
 
- (void)start
{
}
 
- (void)scale:(float)s
{
}
 
- (void)translate:(GLKVector2)t withMultiplier:(float)m
{
}
 
- (void)rotate:(GLKVector3)r withMultiplier:(float)m
{
}
 
- (GLKMatrix4)getModelViewMatrix
{
    // 3
    GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
    modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, -_depth);
 
    return modelViewMatrix;
}
 
@end

There are a few interesting things happening with _depth, so let’s take a closer look:

  1. _depth is a variable specific to Transformations which will determine the depth of your object in the scene.
  2. You assign the variable z to _depth in your initializer, and nowhere else.
  3. You position your model-view matrix at the (x,y) center of your view with the values (0.0, 0.0) and with a z-value of -_depth. You do this because, in OpenGL ES, the negative z-axis runs into the screen.

That’s all you need to render your model with an appropriate model-view matrix. :]

Open MainViewController.m and import your new class by adding the following statement to the top of your file:

#import "Transformations.h"

Now add a property to access your new class, right below the @interface line:

@property (strong, nonatomic) Transformations* transformations;

Next, initialize transformations by adding the following lines to viewDidLoad:

// Initialize transformations
self.transformations = [[Transformations alloc] initWithDepth:5.0f Scale:1.0f Translation:GLKVector2Make(0.0f, 0.0f) Rotation:GLKVector3Make(0.0f, 0.0f, 0.0f)];

The only value doing anything here is the depth of 5.0f. You’re using this value because the projection matrix of your scene has near and far clipping planes of 0.1f and 10.0f, respectively (see the function calculateMatrices), thus placing your model right in the middle of the scene.

Locate the function calculateMatrices and replace the following lines:

GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, -2.5f);

With these:

GLKMatrix4 modelViewMatrix = [self.transformations getModelViewMatrix];

Build and run! Your starship is still there, but it appears to have shrunk!

You’re handling your new model-view matrix by transformations, which set a depth of 5.0 units. Your previous model-view matrix had a depth of 2.5 units, meaning that your starship is now twice as far away. You could easily revert the depth, or you could play around with your starship’s scale…

The Scale Transformation

The first transformation you’ll implement is also the easiest: scale. Open Transformations.m and add the following variables inside the @interface extension at the top of your file:

// Scale
float   _scaleStart;
float   _scaleEnd;

All of your transformations will have start and end values. The end value will be the one actually transforming your model-view matrix, while the start value will track the gesture’s event data.

Next, add the following line to initWithDepth:Scale:Translation:Rotation:, inside the if statement:

// Scale
_scaleEnd = s;

And add the following line to getModelViewMatrix, after you translate the model-view matrix—transformation order does matter, as you’ll learn later on:

modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, _scaleEnd, _scaleEnd, _scaleEnd);

With that line, you scale your model-view matrix uniformly in (x,y,z) space.

To test your new code, open MainViewController.m and locate the function viewDidLoad. Change the Scale: initialization of self.transformations from 1.0f to 2.0f, like so:

self.transformations = [[Transformations alloc] initWithDepth:5.0f Scale:2.0f Translation:GLKVector2Make(0.0f, 0.0f) Rotation:GLKVector3Make(0.0f, 0.0f, 0.0f)];

Build and run! Your starship will be twice as big as your last run and look a lot more proportional to the size of your scene.

Back in Transformations.m, add the following line to scale::

_scaleEnd = s * _scaleStart;

As mentioned before, the starting scale value of a pinch gesture is 1.0, increasing with a zoom-in event and decreasing with a zoom-out event. You haven’t assigned a value to _scaleStart yet, so here’s a quick question: should it be 1.0? Or maybe s?

The answer is neither. If you assign either of those values to _scaleStart, then every time the user performs a new scale gesture, the model-view matrix will scale back to either 1.0 or s before scaling up or down. This will cause the model to suddenly contract or expand, creating a jittery experience. You want your model to conserve its latest scale so that the transformation is continuously smooth.

To make it so, add the following line to start:

_scaleStart = _scaleEnd;

You haven’t called start from anywhere yet, so let’s see where it belongs. Open MainViewController.m and add the following function at the bottom of your file, before the @end statement:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // Begin transformations
    [self.transformations start];
}

touchesBegan:withEvent: is the first method to respond whenever your iOS device detects a touch on the screen, before the gesture recognizers kick in. Therefore, it’s the perfect place to call start and conserve your scale values.

Next, locate the function pinch: and replace the NSLog() statement with:

[self.transformations scale:scale];

Build and run! Pinch the touchscreen to scale your model up and down. :D

That’s pretty exciting!

The Translation Transformation

Just like a scale transformation, a translation needs two variables to track start and end values. Open Transformations.m and add the following variables inside your @interface extension:

// Translation
GLKVector2  _translationStart;
GLKVector2  _translationEnd;

Similarly, you only need to initialize _translationEnd in initWithDepth:Scale:Translation:Rotation:. Do that now:

// Translation
_translationEnd = t;

Scroll down to the function getModelViewMatrix and change the following line:

modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, -_depth);

To this:

modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, _translationEnd.x, _translationEnd.y, -_depth);

Next, add the following lines to translate:withMultiplier::

// 1
t = GLKVector2MultiplyScalar(t, m);
 
// 2
float dx = _translationEnd.x + (t.x-_translationStart.x);
float dy = _translationEnd.y - (t.y-_translationStart.y);
 
// 3
_translationEnd = GLKVector2Make(dx, dy);
_translationStart = GLKVector2Make(t.x, t.y);

Let’s see what’s happening here:

  1. m is a multiplier that helps convert screen coordinates into OpenGL ES coordinates. It is defined when you call the function from MainViewController.m.
  2. dx and dy represent the rate of change of the current translation in x and y, relative to the latest position of _translationEnd. In screen coordinates, the y-axis is positive in the downwards direction and negative in the upwards direction. In OpenGL ES, the opposite is true. Therefore, you subtract the rate of change in y from _translationEnd.y.
  3. Finally, you update _translationEnd and _translationStart to reflect the new end and start positions, respectively.

As mentioned before, the starting translation value of a new pan gesture is (0.0, 0.0). That means all new translations will be relative to this origin point, regardless of where the model actually is in the scene. It also means the value assigned to _translationStart for every new pan gesture will always be the origin.

Add the following line to start:

_translationStart = GLKVector2Make(0.0f, 0.0f);

Everything is in place, so open MainViewController.m and locate your pan: function. Replace the NSLog() statement inside your first if conditional for a single touch with the following:

[self.transformations translate:GLKVector2Make(x, y) withMultiplier:5.0f];

Build and run! Good job—you can now move your starship around with the touch of a finger! (But not two.)

A Quick Math Lesson: Quaternions

Before you move onto the last transformation—rotation—you need to know a bit about quaternions. This lesson will thankfully be pretty quick, though, since GLKit provides an excellent math library to deal with quaternions.

Quaternions are a complex mathematical system with many applications, but for this tutorial you’ll only be concerned with their spatial rotation properties. The main advantage of quaternions in this respect is that they don’t suffer from gimbal lock, unlike Euler angles.

Euler angles are a common representation for rotations, usually in (x,y,z) form. When rotating an object in this space, there are many opportunities for two axes to align with each other. In these cases, one degree of freedom is lost since any change to either of the aligned axes applies the same rotation to the object being transformed—that is, the two axes become one. That is a gimbal lock, and it will cause unexpected results and jittery animations.

A gimbal lock, from Wikipedia.

One reason to prefer Euler angles to quaternions is that they are intrinsically easier to represent and to read. However, GLKQuaternion simplifies the complexity of quaternions and reduces a rotation to four simple steps:

  1. Create a quaternion that represents a rotation around an axis.
  2. For each (x,y,z) axis, multiply the resulting quaternion against a master quaternion.
  3. Derive the 4×4 matrix that performs an (x,y,z) rotation based on a quaternion.
  4. Calculate the product of the resulting matrix with the main model-view matrix.

You’ll be implementing these four simple steps shortly. :]

Quaternions and Euler angles are very deep subjects, so check out these summaries from CH Robotics if you wish to learn more: Understanding Euler Angles and Understanding Quaternions.

The Rotation Transformation: Overview

In this tutorial, you’ll use two different types of gesture recognizers to control your rotations: two-finger pan and rotation. The reason for this is that your iOS device doesn’t have a single gesture recognizer that reports three different types of values, one for each (x,y,z) axis. Think about the ones you’ve covered so far:

  • Pinch produces a single float, perfect for a uniform scale across all three (x,y,z) axes.
  • One-finger pan produces two values corresponding to movement along the x-axis and the y-axis, just like your translation implementation.

No gesture can accurately represent rotation in 3D space. Therefore, you must define your own rule for this transformation.

Rotation about the z-axis is very straightforward and intuitive with the rotation gesture, but rotation about the x-axis and/or y-axis is slightly more complicated. Thankfully, the two-finger pan gesture reports movement along both of these axes. With a little more effort, you can use it to represent a rotation.

Let’s start with the easier one first. :]

Z-Axis Rotation With the Rotation Gesture

Open Transformations.m and add the following variables inside your @interface extension:

// Rotation
GLKVector3      _rotationStart;
GLKQuaternion   _rotationEnd;

This is slightly different than your previous implementations for scale and translation, but it makes sense given your new knowledge of quaternions. Before moving on, add the following variable just below:

// Vectors
GLKVector3      _front;

As mentioned before, your quaternions will represent a rotation around an axis. This axis is actually a vector, since it specifies a direction—it’s not along z, it’s either front-facing or back-facing.

Complete the vector’s implementation by initializing it inside initWithDepth:Scale:Translation:Rotation: with the following line:

// Vectors
_front = GLKVector3Make(0.0f, 0.0f, 1.0f);

As you can see, the vector is front-facing because its direction is towards the screen.

Note: Previously, I mentioned that in OpenGL ES, negative z-values go into the screen. This is because OpenGL ES uses a right-handed coordinate system. GLKit, on the other hand (pun intended), uses the more conventional left-handed coordinate system.

Left-handed and right-handed coordinate systems, from Learn OpenGL ES

Next, add the following lines to initWithDepth:Scale:Translation:Rotation:, right after the code you just added above:

r.z = GLKMathDegreesToRadians(r.z);
_rotationEnd = GLKQuaternionIdentity;
_rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(-r.z, _front), _rotationEnd);

These lines perform the first two steps of the quaternion rotation described earlier:

  • You create a quaternion that represents a rotation around an axis by using GLKQuaternionMakeWithAngleAndVector3Axis().
  • You multiply the resulting quaternion against a master quaternion using GLKQuaternionMultiply().

All calculations are performed with radians, hence the call to GLKMathDegreesToRadians(). With quaternions, a positive angle performs a counterclockwise rotation, so you send in the negative value of your angle: -r.z.

To complete the initial setup, add the following line to getModelViewMatrix, right after you create modelViewMatrix:

GLKMatrix4 quaternionMatrix = GLKMatrix4MakeWithQuaternion(_rotationEnd);

Then, add the following line to your matrix calculations, after the translation and before the scale:

modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, quaternionMatrix);

These two lines perform the last two steps of the quaternion rotation described earlier:

  • You derive the 4×4 matrix that performs an (x,y,z) rotation based on a quaternion, using GLKMatrix4MakeWithQuaternion().
  • You calculate the product of the resulting matrix with the main model-view matrix using GLKMatrix4Multiply().

Note: The order of your transformations is not arbitrary. Imagine the following instructions given to two different people:

  1. Starting from point P: take n steps forward; turn to your left; then pretend to be a giant twice your size.
  2. Starting from point P: pretend to be a giant twice your size; turn to your left; then take n steps forward.

See the difference below:

Even though the instructions have the same steps, the two people end up at different points, P’1 and P’2. This is because Person 1 first walks (translation), then turns (rotation), then grows (scale), thus ending n paces in front of point P. With the other order, Person 2 first grows, then turns, then walks, thus taking giant-sized steps towards the left and ending 2n paces to the left of point P.

Open MainViewController.m and test your new code by changing the z-axis initialization angle of self.transformations to 180.0 inside viewDidLoad:

self.transformations = [[Transformations alloc] initWithDepth:5.0f Scale:2.0f Translation:GLKVector2Make(0.0f, 0.0f) Rotation:GLKVector3Make(0.0f, 0.0f, 180.0f)];

Build and run! You’ve caught your starship in the middle of a barrel roll.

After you’ve verified that this worked, revert the change, since you would rather have your app launch with the starship properly oriented.

The next step is to implement the rotation with your rotation gesture. Open Transformations.m and add the following lines to rotate:withMultiplier::

float dz = r.z - _rotationStart.z;
_rotationStart = GLKVector3Make(r.x, r.y, r.z);
_rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(-dz, _front), _rotationEnd);

This is a combination of your initialization code and your translation implementation. dz represents the rate of change of the current rotation about the z-axis. Then you simply update _rotationStart and _rotationEnd to reflect the new start and end positions, respectively.

There is no need to convert r.z to radians this time, since the rotation gesture’s values are already in radians. r.x and r.y will be passed along as 0.0, so you don’t need to worry about them too much—for now.

As you know, a new rotation gesture always begins with a starting value of 0.0. Therefore, all new rotations will be relative to this zero angle, regardless of your model’s actual orientation. Consequently, the value assigned to _rotationStart for every new rotation gesture will always be an angle of zero for each axis.

Add the following line to start:

_rotationStart = GLKVector3Make(0.0f, 0.0f, 0.0f);

To finalize this transformation implementation, open MainViewController.m and locate your rotation: function. Replace the NSLog() statement with the following:

[self.transformations rotate:GLKVector3Make(0.0f, 0.0f, rotation) withMultiplier:1.0f];

Since a full rotation gesture perfectly spans 360 degrees, there is no need to implement a multiplier here, but you’ll find it very useful in the next section.

Lastly, since your calculations are expecting radians, change the preceding line:

float rotation = GLKMathRadiansToDegrees([sender rotation]);

To this:

float rotation = [sender rotation];

Build and run! You can now do a full barrel roll. :D

X- and Y-Axis Rotation With the Two-Finger Pan Gesture

This implementation for rotation about the x-axis and/or y-axis is very similar to the one you just coded for rotation about the z-axis, so let’s start with a little challenge!

Add two new variables to Transformations.m, _right and _up, and initialize them inside your class initializer. These variables represent two 3D vectors, one pointing right and the other pointing up. Take a peek at the instructions below if you’re not sure how to implement them or if you want to verify your solution:

Solution Inside: Right and Up Vectors Show
 
   
 

For an added challenge, see if you can initialize your (x,y) rotation properly, just as you did for your z-axis rotation with the angle r.z and the vector _front. The correct code is available below if you need some help:

Solution Inside: Rotation Initialization Show
 
   
 

Good job! There’s not a whole lot of new code here, so let’s keep going. Still in Transformations.m, add the following lines to rotate:withMultiplier:, just above dz:

float dx = r.x - _rotationStart.x;
float dy = r.y - _rotationStart.y;

Once again, this should be familiar—you’re just repeating your z-axis logic for the x-axis and the y-axis. The next part is a little trickier, though…

Add the following lines to rotate:withMultiplier:, just after _rotationStart:

_rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dx*m, _up), _rotationEnd);
_rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dy*m, _right), _rotationEnd);

For the z-axis rotation, your implementation rotated the ship about the z-axis and all was well, because that was the natural orientation of the gesture. Here, you face a different situation. If you look closely at the code above, you’ll notice that dx rotates about the _up vector (y-axis) and dy rotates about the _right vector (x-axis). The diagram below should help make this clear:

And you finally get to use m! A pan gesture doesn’t report its values in radians or even degrees, but rather as 2D points, so m serves as a converter from points to radians.

Finish the implementation by opening MainViewController.m and replacing the contents of your current two-touch else if conditional inside pan: with the following:

const float m = GLKMathDegreesToRadians(0.5f);
CGPoint rotation = [sender translationInView:sender.view];
[self.transformations rotate:GLKVector3Make(rotation.x, rotation.y, 0.0f) withMultiplier:m];

The value of m dictates that for every touch-point moved in the x- and/or y-direction, your model rotates 0.5 degrees.

Build and run! Your model is fully rotational. Woo-hoo!

Nice one—that’s a pretty fancy model viewer you’ve built!

Locking Your Gestures/Transformations

You’ve fully implemented your transformations, but you may have noticed that sometimes the interface accidentally alternates between two transformations—for example, if you remove a finger too soon or perform an unclear gesture. To keep this from happening, you’ll now write some code to make sure your model viewer only performs one transformation for every continuous touch.

Open Transformations.h and add the following enumerator and property to your file, just below your @interface statement:

typedef enum TransformationState
{
    S_NEW,
    S_SCALE,
    S_TRANSLATION,
    S_ROTATION
}
TransformationState;
 
@property (readwrite) TransformationState state;

state defines the current transformation state of your model viewer app, whether it be a scale (S_SCALE), translation (S_TRANSLATION) or rotation (S_ROTATION). S_NEW is a value that will be active whenever the user performs a new gesture.

Open Transformations.m and add the following line to start:

self.state = S_NEW;

See if you can implement the rest of the transformation states in their corresponding methods.

Solution Inside: Transformation States Show
 
   
 

Piece of cake! Now open MainViewController.m and add a state conditional to each gesture. I’ll give you the pan: implementations for free and leave the other two as a challenge. :]

Modify pan: to look like this:

- (IBAction)pan:(UIPanGestureRecognizer *)sender
{
    // Pan (1 Finger)
    if((sender.numberOfTouches == 1) &&
        ((self.transformations.state == S_NEW) || (self.transformations.state == S_TRANSLATION)))
    {
        CGPoint translation = [sender translationInView:sender.view];
        float x = translation.x/sender.view.frame.size.width;
        float y = translation.y/sender.view.frame.size.height;
        [self.transformations translate:GLKVector2Make(x, y) withMultiplier:5.0f];
    }
 
    // Pan (2 Fingers)
    else if((sender.numberOfTouches == 2) &&
        ((self.transformations.state == S_NEW) || (self.transformations.state == S_ROTATION)))
    {
        const float m = GLKMathDegreesToRadians(0.5f);
        CGPoint rotation = [sender translationInView:sender.view];
        [self.transformations rotate:GLKVector3Make(rotation.x, rotation.y, 0.0f) withMultiplier:m];
    }
}

Click below to see the solution for the other two—but give it your best shot first!

Solution Inside: Pinch and Rotation States Show
 
   
 

Build and run! See what cool poses you can set for your model and have fun playing with your new app.

Congratulations on completing this OpenGL ES Transformations With Gestures tutorial!

Where to Go From Here?

Here is the completed project with all of the code and resources from this tutorial. You can also find its repository on GitHub.

If you completed this tutorial, you’ve developed a sophisticated model viewer using the latest technologies from Apple for 3D graphics (GLKit and OpenGL ES) and touch-based user interaction (gesture recognizers). Most of these technologies are unique to mobile devices, so you’ve definitely learned enough to boost your mobile development credentials!

You should now understand a bit more about basic transformations—scale, translation and rotation—and how you can easily implement them with GLKit. You’ve learned how to add gesture recognizers to a View Controller and read their main event data. Furthermore, you’ve created a very slick app that you can expand into a useful portfolio tool for 3D artists. Challenge accepted? ;]

If you have any questions, comments or suggestions, feel free to join the discussion below!

时间: 2024-10-06 00:15:39

OpenGL ES2 缩放移动的相关文章

iOS开发——图形编程OC篇&amp;OpenGL ES2.0编程步骤

OpenGL ES2.0编程步骤 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机.PDA和游戏主机等嵌入式设备而设计.该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准. 1. 保存全局变量的数据结构 以下例子程序均基于Linux平台. 1 typedef struct _escontext 2 { 3 void* userData; // P

OpenGL ES2.0 基本编程

1. EGL OpenGL ES命令需要一个rendering context和一个drawing surface. Rendering Context: 保存当前的OpenGL ES状态. Drawing Surface: 是原语(primitive)画图的Surface.它指定了渲染的buffer类型,如:color buffer,depth buffer和stencil buffer:同时它也指定了每个需要的buffer的位深度(bit depth). EGL是OpenGL ES API与

OpenGL ES2.0 入门经典例子

原文链接地址:http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播.同时,转载时不要移除本申明.如产生任何纠纷,均与本博客所有人.发表该翻译稿之人无任何关系.谢谢合作! ps:非常感谢skingTree为我们提供的这篇翻译教程,感谢yy.小狼.北方加入我的教程翻译团队,谢谢你们! 教程截图: OpenGL ES 是

Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤

原文地址: Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤 - 网络资源是无限的 - 博客频道 - CSDN.NET http://blog.csdn.net/fengbingchun/article/details/11192189   1.  先按照http://blog.csdn.net/fengbingchun/article/details/10439281中操作搭建好基本的Android开发环境: 2.  打开Eclipse,-->Window-->

android openGL ES2 一切从绘制纹理开始

纹理,在openGL中,可以理解为加载到显卡显存中的图片.Android设备在2.2开始支持openGL ES2.0,从前都是ES1.0 和 ES1.1的版本.简单来说,openGL ES是为了嵌入设备进行功能剪裁后的openGL版本.ES2.0是和1.x版本不兼容的,区别和兼容性参见android 官方文档. 首先,android使用openGL提供了特殊的view作为基础叫做GLSurfaceView.我们的view需要继承GLSurfaceView.如下简单示例: public class

Android用OpenGL ES2.0显示YUV数据,在手机上需要两种坐标系的解决方案

如题 ,不知道大家看懂了这个题目没有,给个链接:http://blog.csdn.net/wangchenggggdn/article/details/8896453(下称链接①), 里面评论有很多人提到了这个问题,我也是其中一员,但是问遍了所有人,自己也发帖(http://bbs.csdn.net/topics/390769358) 寻求解决方案,却终究没能得到一个可用的方案. 从2014年4月中旬遇到这个问题,纠结了两个多星期,终于在看了好多好多资料之后,于4月的最后一个周一,暂时解决了这么

iOS OpenGL ES2.0 开发实例

本教程源码地址下载:https://github.com/wanglixin1999/HelloGL OpenGL ES 是可以在iphone上实现2D和3D图形编程的低级API. 如果你之前接触过 cocos2d,sparrow,corona,unity 这些框架,你会发现其实它们都是基于OpenGL上创建的. 多数程序员选择使用这些框架,而不是直接调用OpenGL,因为OpenGL实在是太难用了. 而这篇教程,就是为了让大家更好地入门而写的. 在这个系列的文章中,你可以通过一些实用又容易上手

android openGL ES2 一切从绘制纹理開始

纹理.在openGL中,能够理解为载入到显卡显存中的图片.Android设备在2.2開始支持openGL ES2.0.从前都是ES1.0 和 ES1.1的版本号.简单来说,openGL ES是为了嵌入设备进行功能剪裁后的openGL版本号.ES2.0是和1.x版本号不兼容的,差别和兼容性參见android 官方文档. 首先,android使用openGL提供了特殊的view作为基础叫做GLSurfaceView.我们的view须要继承GLSurfaceView.例如以下简单演示样例: publi

Android +NDK+eclipse+opengl ES2.0 开启深度测试

参考:https://www.opengl.org/discussion_boards/showthread.php/172736-OpenGL-ES-Depth-Buffer-Problem 环境:eclipse,ndkr8,opengl es2.0,android 最近使用eclipse和NDK进行android opengl es2.0的开发,发现了绘制的物体显示与深度无关,而与绘制的前后顺序有关.想了一下,应该是深度测试没有开启,开启了glEnable(GL_DEPTH_TEST),但是