Skip to content

Commit

Permalink
quaternion panning, aligned to identity, FPS
Browse files Browse the repository at this point in the history
  • Loading branch information
mayakraft committed Sep 16, 2014
1 parent 4d4b811 commit b379e2e
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 107 deletions.
4 changes: 2 additions & 2 deletions Panorama.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down Expand Up @@ -416,7 +416,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
Expand Down
106 changes: 81 additions & 25 deletions Panorama/PanoramaView.h
Original file line number Diff line number Diff line change
@@ -1,41 +1,97 @@
//
// PanoramaView.h
// Panorama
//
// Created by Robby Kraft on 8/24/13.
// Copyright (c) 2013 Robby Kraft. All Rights Reserved.
//

#import <Foundation/Foundation.h>

/**
* @class Panorama View
* @author Robby Kraft
* @date 8/24/13
*
* @availability iOS (5.0 and later)
*
* @discussion a dynamic GLKView with a touch and motion sensor interface to align and immerse the perspective inside an equirectangular panorama projection
*/
@interface PanoramaView : GLKView

-(id) init; // recommended init method. if landscape, corrects aspect-ratio. auto full-screen.
-(void) draw;
-(void) setImage:(NSString*)fileName; // path or bundle. will check at both

/* all operatons are in RADIANS */
-(id) init; // recommended init method

-(void) draw; // place in GLKViewController's glkView:drawInRect:

/// Set image by path or bundle - will check at both
-(void) setImage:(NSString*)fileName;

/* projection */
@property (nonatomic) float fieldOfView;
@property (nonatomic) BOOL pinchToZoom; // pinch to change field of view
-(CGPoint) imagePixelAtScreenLocation:(CGPoint)point; // which pixel did you touch?

/* orientation */
@property (nonatomic) BOOL orientToDevice; // YES: activate accel/gyro. NO: use touch pan gesture
@property (nonatomic) BOOL touchToPan; // default: YES
// below uses gl look at, with a fixed up-vector, so flipping will occur at the poles

/// forward vector axis (into the screen)
@property (nonatomic, readonly) GLKVector3 lookVector;

/// forward horizontal azimuth (-π to π)
@property (nonatomic, readonly) float lookAzimuth;

/// forward vertical altitude (-.5π to .5π)
@property (nonatomic, readonly) float lookAltitude;


// At this point, it's still recommended to activate either OrientToDevice or TouchToPan, not both
// it's possible to have them simultaneously, but the effect is confusing and disorienting


/// Activates accelerometer + gyro orientation
@property (nonatomic) BOOL orientToDevice;

/// Enables UIPanGestureRecognizer to affect view orientation
@property (nonatomic) BOOL touchToPan;

/// Fixes up-vector during panning. (trade off: no panning past the poles)
//@property (nonatomic) BOOL preventHeadTilt;

/**
* Align Z coordinate axis (into the screen) to a GLKVector.
* (due to a fixed up-vector, flipping will occur at the poles)
*
* @param GLKVector3 can be non-normalized
*/
-(void) orientToVector:(GLKVector3)vector;

/**
* Align Z coordinate axis (into the screen) to azimuth and altitude.
* (due to a fixed up-vector, flipping will occur at the poles)
*
* @param Azimuth(-π to π) Altitude(-.5π to .5π)
*/
-(void) orientToAzimuth:(float) azimuth Altitude:(float)altitude;

@property (nonatomic, readonly) GLKVector3 lookVector; // forward vector
@property (nonatomic, readonly) float lookAzimuth; // -π to π
@property (nonatomic, readonly) float lookAltitude; // -.5π to .5π

/* touches */
@property (nonatomic) BOOL showTouches; // overlay latitude longitude lines
/* projection & touches */


@property (nonatomic, readonly) NSSet *touches;

@property (nonatomic, readonly) NSInteger numberOfTouches;
-(bool) touchInRect:(CGRect)rect; // hotspot touchInRect validation. rect defined by image pixel coordinates

/// Field of view in DEGREES
@property (nonatomic) float fieldOfView;

/// Enables UIPinchGestureRecognizer to affect FieldOfView
@property (nonatomic) BOOL pinchToZoom;

/// Dynamic overlay of latitude and longitude intersection lines for all touches
@property (nonatomic) BOOL showTouches;
/**
* Converts screen coordinate to image pixel coordinate
*
* @param CGPoint device screen coordinate
* @return CGPoint image coordinate in pixels, or betwee 0.0 and 1.0 if no image
*/
-(CGPoint) imagePixelAtScreenLocation:(CGPoint)point;
/**
* Hit-detection for all active touches
*
* @param CGRect defined in image pixel coordinates
* @return YES if touch is inside CGRect, NO otherwise
*/
-(BOOL) touchInRect:(CGRect)rect;


@end

118 changes: 54 additions & 64 deletions Panorama/PanoramaView.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,30 @@
//

#import <CoreMotion/CoreMotion.h>
#import <OpenGLES/ES1/gl.h>
#import <GLKit/GLKit.h>
#import "PanoramaView.h"

#define FPS 60
#define FOV_MIN 1
#define FOV_MAX 155
#define Z_NEAR 0.1f
#define Z_FAR 100.0f

// LINEAR for smoothing, NEAREST for pixelized
#define IMAGE_SCALING GL_LINEAR // GL_NEAREST, GL_LINEAR

// this appears to be the best way to grab orientation. if this becomes formalized, just make sure the orientations match
#define SENSOR_ORIENTATION [[UIApplication sharedApplication] statusBarOrientation] //enum 1(NORTH) 2(SOUTH) 3(EAST) 4(WEST)

// this really should be included in GLKit
GLKQuaternion GLKQuaternionFromTwoVectors(GLKVector3 u, GLKVector3 v){
GLKVector3 w = GLKVector3CrossProduct(u, v);
GLKQuaternion q = GLKQuaternionMake(w.x, w.y, w.z, GLKVector3DotProduct(u, v));
q.w += GLKQuaternionLength(q);
return GLKQuaternionNormalize(q);
}

@interface Sphere : NSObject

-(bool) execute;
Expand All @@ -32,9 +45,9 @@ @interface PanoramaView (){
CMMotionManager *motionManager;
UIPinchGestureRecognizer *pinchGesture;
UIPanGestureRecognizer *panGesture;
GLKMatrix4 _projectionMatrix, _attitudeMatrix, _panOffsetMatrix;
GLKMatrix4 _projectionMatrix, _attitudeMatrix, _offsetMatrix;
float _aspectRatio;
GLfloat circlePoints[64*3]; // touch lines
GLfloat circlePoints[64*3]; // meridian lines
}
@end

Expand Down Expand Up @@ -63,17 +76,27 @@ -(id) initWithFrame:(CGRect)frame context:(EAGLContext *)context{
}
return self;
}
-(void) didMoveToSuperview{
// this breaks MVC, but useful for setting GLKViewController's frame rate
UIResponder *responder = self;
while (![responder isKindOfClass:[GLKViewController class]]) {
responder = [responder nextResponder];
if (responder == nil){
break;
}
}
if([responder respondsToSelector:@selector(setPreferredFramesPerSecond:)])
[(GLKViewController*)[self.window rootViewController] setPreferredFramesPerSecond:FPS];
}
-(void) initDevice{
motionManager = [[CMMotionManager alloc] init];
// pinch disabled by default
pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchHandler:)];
[pinchGesture setEnabled:NO];
[self addGestureRecognizer:pinchGesture];
// pan enabled by default
panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandler:)];
[panGesture setMaximumNumberOfTouches:1];
[panGesture setEnabled:NO];
[self addGestureRecognizer:panGesture];
_touchToPan = YES;
}
-(void)setFieldOfView:(float)fieldOfView{
_fieldOfView = fieldOfView;
Expand Down Expand Up @@ -106,7 +129,7 @@ -(void)initOpenGL:(EAGLContext*)context{
_fieldOfView = 45 + 45 * atanf(_aspectRatio); // hell ya
[self rebuildProjectionMatrix];
_attitudeMatrix = GLKMatrix4Identity;
_panOffsetMatrix = GLKMatrix4Identity;
_offsetMatrix = GLKMatrix4Identity;
[self customGL];
[self makeLatitudeLines];
}
Expand All @@ -123,7 +146,7 @@ -(void) customGL{
glMatrixMode(GL_MODELVIEW);
// glEnable(GL_CULL_FACE);
// glCullFace(GL_FRONT);
glEnable(GL_DEPTH_TEST);
// glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
Expand All @@ -133,14 +156,8 @@ -(void)draw{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix(); // begin device orientation

// this order need to become:
// get device orientation
// add on top of that any offset due to panning (figure out the math of this)
// then [self updateLook]
// multmatrixf(_attitude)

_attitudeMatrix = GLKMatrix4Multiply(_panOffsetMatrix, [self getDeviceOrientationMatrix]);

_attitudeMatrix = GLKMatrix4Multiply([self getDeviceOrientationMatrix], _offsetMatrix);
[self updateLook];

glMultMatrixf(_attitudeMatrix.m);
Expand All @@ -149,7 +166,7 @@ -(void)draw{
[sphere execute];
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, clearColor);

//TODO: add objects here to make them a part of the virtual reality
//TODO: add any objects here to make them a part of the virtual reality
// glPushMatrix();
// // object code
// glPopMatrix();
Expand All @@ -161,24 +178,13 @@ -(void)draw{
glPushMatrix();
CGPoint touchPoint = CGPointMake([(UITouch*)[[_touches allObjects] objectAtIndex:i] locationInView:self].x,
[(UITouch*)[[_touches allObjects] objectAtIndex:i] locationInView:self].y);
[self drawHotspotLines:[self vectorFromScreenLocation:touchPoint]];
[self drawHotspotLines:[self vectorFromScreenLocation:touchPoint inAttitude:_attitudeMatrix]];
glPopMatrix();
}
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

glPopMatrix(); // end device orientation
static int count = 0;
count++;
if(count % 5 == 0){
GLKQuaternion q = GLKQuaternionMakeWithMatrix4(_attitudeMatrix);
NSLog(@" ");
NSLog(@"QQQQ: %.3f, %.3f, %.3f, %.3f",q.q[0], q.q[1], q.q[2], q.q[3]);
NSLog(@"QS, QV: %f, (%.3f, %.3f, %.3f)",q.s, q.v.x, q.v.y, q.v.z);
GLKVector3 v = GLKQuaternionAxis(q);
NSLog(@"Angle:%.3f Axis:(%.3f, %.3f, %.3f)", GLKQuaternionAngle(q),v.x, v.y, v.z);
NSLog(@"AZ:%.3f ALT:%.3f LOOK:(%.3f, %.3f, %.3f)",_lookAzimuth, _lookAltitude, _lookVector.x, _lookVector.y, _lookVector.z);
}
}
#pragma mark- ORIENTATION
-(GLKMatrix4) getDeviceOrientationMatrix{
Expand Down Expand Up @@ -212,13 +218,13 @@ -(GLKMatrix4) getDeviceOrientationMatrix{
else
return GLKMatrix4Identity;
}
//-(void) orientToVector:(GLKVector3)v{
// _attitudeMatrix = GLKMatrix4MakeLookAt(0, 0, 0, v.x, v.y, v.z, 0, 1, 0);
// [self updateLook];
//}
//-(void) orientToAzimuth:(float)azimuth Altitude:(float)altitude{
// [self orientToVector:GLKVector3Make(-cosf(azimuth), sinf(altitude), sinf(azimuth))];
//}
-(void) orientToVector:(GLKVector3)v{
_attitudeMatrix = GLKMatrix4MakeLookAt(0, 0, 0, v.x, v.y, v.z, 0, 1, 0);
[self updateLook];
}
-(void) orientToAzimuth:(float)azimuth Altitude:(float)altitude{
[self orientToVector:GLKVector3Make(-cosf(azimuth), sinf(altitude), sinf(azimuth))];
}
-(void) updateLook{
_lookVector = GLKVector3Make(-_attitudeMatrix.m02,
-_attitudeMatrix.m12,
Expand All @@ -227,10 +233,10 @@ -(void) updateLook{
_lookAltitude = asinf(_lookVector.y);
}
-(CGPoint) imagePixelAtScreenLocation:(CGPoint)point{
return [self imagePixelFromVector:[self vectorFromScreenLocation:point]];
return [self imagePixelFromVector:[self vectorFromScreenLocation:point inAttitude:_attitudeMatrix]];
}
-(GLKVector3) vectorFromScreenLocation:(CGPoint)screenTouch{
GLKMatrix4 inverse = GLKMatrix4Invert(GLKMatrix4Multiply(_projectionMatrix, _attitudeMatrix), nil);
-(GLKVector3) vectorFromScreenLocation:(CGPoint)screenTouch inAttitude:(GLKMatrix4)matrix{
GLKMatrix4 inverse = GLKMatrix4Invert(GLKMatrix4Multiply(_projectionMatrix, matrix), nil);
GLKVector4 screen = GLKVector4Make(2.0*(screenTouch.x/self.frame.size.width-.5),
2.0*(.5-screenTouch.y/self.frame.size.height),
1.0, 1.0);
Expand Down Expand Up @@ -264,7 +270,7 @@ -(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
_touches = event.allTouches;
_numberOfTouches = 0;
}
-(bool)touchInRect:(CGRect)rect{
-(BOOL)touchInRect:(CGRect)rect{
if(_numberOfTouches){
bool found = false;
for(int i = 0; i < [[_touches allObjects] count]; i++){
Expand Down Expand Up @@ -292,39 +298,23 @@ -(void)pinchHandler:(UIPinchGestureRecognizer*)sender{
}
}
-(void) panHandler:(UIPanGestureRecognizer*)sender{
static GLKMatrix4 _startMatrix;
static GLKVector3 touchVector;
static float touchAzimuth, touchAltitude;
if([sender state] == 1){
_startMatrix = _panOffsetMatrix;
touchVector = [self vectorFromScreenLocation:[sender locationInView:sender.view]];
touchAzimuth = -atan2f(-touchVector.z, -touchVector.x);
touchAltitude = asinf(touchVector.y);
NSLog(@"\n(%.3f, %.3f, %.3f)\n(%.3f, %.3f, %.3f)\n(%.3f, %.3f, %.3f)",
_startMatrix.m00,_startMatrix.m01,_startMatrix.m02,
_startMatrix.m10,_startMatrix.m11,_startMatrix.m12,
_startMatrix.m20,_startMatrix.m21,_startMatrix.m22);
NSLog(@"(%.3f, %.3f, %.3f)",touchVector.x, touchVector.y, touchVector.z);
touchVector = [self vectorFromScreenLocation:[sender locationInView:sender.view] inAttitude:_offsetMatrix];
}
else if([sender state] == 2){
GLKVector3 nowVector = [self vectorFromScreenLocation:[sender locationInView:sender.view]];
GLKVector3 diffVector = GLKVector3Subtract(touchVector, nowVector);
GLKVector3 newLook = GLKVector3Add(_lookVector, diffVector);
// [self orientToVector:newLook];
GLKMatrix4 newPanMatrix = GLKMatrix4MakeLookAt(0, 0, 0, newLook.x, newLook.y, newLook.z, 0, 1, 0);
GLKVector3 lv = GLKVector3Make(-newPanMatrix.m02,
-newPanMatrix.m12,
-newPanMatrix.m22);
float rAz = atan2f(lv.x, -lv.z);
float rAlt = asinf(lv.y);
_panOffsetMatrix = _startMatrix;
// _panOffsetMatrix = GLKMatrix4RotateY(<#GLKMatrix4 matrix#>, <#float radians#>)
GLKVector3 nowVector = [self vectorFromScreenLocation:[sender locationInView:sender.view] inAttitude:_offsetMatrix];
GLKQuaternion q = GLKQuaternionFromTwoVectors(touchVector, nowVector);
_offsetMatrix = GLKMatrix4Multiply(_offsetMatrix, GLKMatrix4MakeWithQuaternion(q));
// in progress for preventHeadTilt
// GLKMatrix4 mat = GLKMatrix4Multiply(_offsetMatrix, GLKMatrix4MakeWithQuaternion(q));
// _offsetMatrix = GLKMatrix4MakeLookAt(0, 0, 0, -mat.m02, -mat.m12, -mat.m22, 0, 1, 0);
}
else{
_numberOfTouches = 0;
}
}
#pragma mark- HOTSPOT
#pragma mark- MERIDIANS
-(void) makeLatitudeLines{
for(int i = 0; i < 64; i++){
circlePoints[i*3+0] = -sinf(M_PI*2/64.0f*i);
Expand Down Expand Up @@ -359,7 +349,7 @@ -(void)drawHotspotLines:(GLKVector3)touchLocation{

@interface Sphere (){
// from Touch Fighter by Apple
// also in Pro OpenGL ES for iOS
// in Pro OpenGL ES for iOS
// by Mike Smithwick Jan 2011 pg. 78
GLKTextureInfo *m_TextureInfo;
GLfloat *m_TexCoordsData;
Expand Down
4 changes: 2 additions & 2 deletions Panorama/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ - (void)viewDidLoad{
panoramaView = [[PanoramaView alloc] init];
[panoramaView setImage:@"park_2048.jpg"];
[panoramaView setOrientToDevice:YES];
// [panoramaView setTouchToPan:YES];
[panoramaView setTouchToPan:NO];
[panoramaView setPinchToZoom:YES];
[panoramaView setShowTouches:YES];
[panoramaView setShowTouches:NO];
[self setView:panoramaView];
}

Expand Down
Loading

0 comments on commit b379e2e

Please sign in to comment.