iOS簡單實現全景圖小行星和魚眼模式

基于SceneKit,先導入SceneKit.framework

首先說一下本人對全景圖的理解,所謂全景圖,就是一個球體,在球體表面貼上圖片,在不同位置看就會產生不同的效果。

比如如果把攝像機放在球心,這時看球面上的圖片就是全景圖(魚眼圖)的效果,如果放在球外邊,看到的就是一個完整的球,放在球面上,看到的就是小行星的效果。

其中的翻轉圖片代碼可根據需要使用,比如要在球內外切換的情況。

話不多說,直接上代碼。


iOS全景圖

.h文件:

#import#import@interface ViewController : UIViewController

/** 圖片模型 */

@property (nonatomic,strong) XLPhotoModel *pModel;

/** 3D視圖 */

@property (weak, nonatomic) IBOutlet SCNView *sceneView;

/** 自動播放動畫 */

@property (strong, nonatomic) IBOutlet UIButton *autoPlayBtn;

@end


.m文件

/** 全景模式 */

typedef enum {

fisheye, //魚眼

asteroid, //小行星

ball? ? //球

}panoramaModel;

@interface XLPicDetailVC ()

{

/** 球 */

SCNSphere *_sphere;

/** 相機 */

SCNNode *_cameraNode;

/** 球節點 */

SCNNode *_sphereNode;

/** 重力感應 */

CMMotionManager *_motionManager;

/** 原始圖片 */

UIImage *_originalImage;

/** 翻轉后的圖片 */

UIImage *_reversalImage;

/** 是否上邊界 */

BOOL _isUpBoundary;

/** 是否下邊界 */

BOOL _isDownBoundary;

}

/** 全景模式 */

@property (nonatomic,assign) panoramaModel panoramaModel;

@end

@implementation XLPicDetailVC

- (void)viewDidLoad {

[super viewDidLoad];

[self layoutNavi];

//? ? [self layoutView];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

- (void)setupSceneView:(NSString *)filePath

{

filePath = [[NSBundle mainBundle] pathForResource:@"diqiu" ofType:@"jpg"];

//? ? filePath = [[NSBundle mainBundle] pathForResource:@"YaJunWei1" ofType:@"jpeg"];

_originalImage = [UIImage imageWithContentsOfFile:filePath];

//壓縮圖片

_originalImage = [UIImage imageWithData:[_originalImage compressImageWithScale:1.0 width:1000]];

//水平翻轉圖片

_reversalImage = [UIImage reversalImage:_originalImage];

// Set the scene

self.sceneView.scene = [[SCNScene alloc]init];

self.sceneView.showsStatistics = NO;

self.sceneView.allowsCameraControl = YES;

//修改手勢

NSArray *array = self.sceneView.gestureRecognizers;

for (UIGestureRecognizer *gesture in array) {

if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {

//修改最大手指數,防止拖動模型

((UIPanGestureRecognizer *)gesture).maximumNumberOfTouches = 1;

[self.sceneView removeGestureRecognizer:gesture];

}

}

//自定義拖動手勢,實現圖片上下左右界限

UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]init];

panGesture.maximumNumberOfTouches = 1;

[panGesture addTarget:self action:@selector(panGesture:)];

[self.sceneView addGestureRecognizer:panGesture];

if (@available(iOS 11, *)) {

//? ? ? ? self.sceneView.defaultCameraController.maximumHorizontalAngle = M_PI * 2;

}

//Create node, containing a sphere, using the panoramic image as a texture

_sphere = [SCNSphere sphereWithRadius:20.0];

_sphere.firstMaterial.doubleSided = YES;

_sphere.firstMaterial.diffuse.contents = _reversalImage;

_sphereNode = [SCNNode nodeWithGeometry:_sphere];

_sphereNode.position = SCNVector3Make(0,0,0);

[self.sceneView.scene.rootNode addChildNode:_sphereNode];

// Camera, ...

_cameraNode = [[SCNNode alloc]init];

_cameraNode.camera = [[SCNCamera alloc]init];

[self.sceneView.scene.rootNode addChildNode:_cameraNode];

//約束

SCNTransformConstraint *constraint = [SCNTransformConstraint transformConstraintInWorldSpace:YES withBlock:^SCNMatrix4(SCNNode * _Nonnull node, SCNMatrix4 transform) {

//? ? ? ? transform = SCNMatrix4MakeRotation(0, -M_PI_2, 0, 0);

return transform;

}];

_sphereNode.constraints = @[constraint];

//? ? [self.sceneView.scene.rootNode addObserver:self forKeyPath:@"eulerAngles" options:NSKeyValueObservingOptionNew context:nil];

//重力感應

//? ? _motionManager = [[CMMotionManager alloc]init];

//

//? ? if (_motionManager.isDeviceMotionAvailable) {

//

//? ? ? ? _motionManager.deviceMotionUpdateInterval = 1.0 / 60.0;

//? ? ? ? [_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {

//

//? ? ? ? ? ? CMAttitude *attitude = motion.attitude;

//

//? ? ? ? ? ? _cameraNode.eulerAngles = SCNVector3Make(attitude.roll - M_PI/2.0, attitude.yaw, attitude.pitch);

//? ? ? ? }];

//? ? }

}

- (IBAction)switchBtn:(UIButton *)btn

{

_panoramaModel = (_panoramaModel + 1) % 3;

//把球轉回來

[_sphereNode runAction:[SCNAction rotateToX:0 y:0 z:0 duration:0]];

//移除自動旋轉動畫

[_sphereNode removeActionForKey:@"YaJunWei"];

_autoPlayBtn.selected = NO;

switch (_panoramaModel) {

case fisheye: //魚眼

{

[btn setImage:[UIImage imageNamed:@"figlhg"] forState:UIControlStateNormal];

[btn setTitle:@"魚眼" forState:UIControlStateNormal];

_cameraNode.position = SCNVector3Make(0, 0, 0);

//設置相機視角大小

[_cameraNode.camera setYFov:60];

//水平翻轉圖片

_sphere.firstMaterial.diffuse.contents = _reversalImage;

break;

}

case asteroid: //小行星

{

[btn setImage:[UIImage imageNamed:@"xiaoxing"] forState:UIControlStateNormal];

[btn setTitle:@"小行星" forState:UIControlStateNormal];

_cameraNode.position = SCNVector3Make(0, 0, 20);

//設置相機視角大小

[_cameraNode.camera setYFov:120];

NSLog(@"小行星");

//水平翻轉圖片

_sphere.firstMaterial.diffuse.contents = _reversalImage;

//把球沿x軸轉-M_PI_2,使 “天” 朝上

SCNAction *rotationAction = [SCNAction rotateByX:-M_PI_2 y:0 z:0 duration:0.5];

[_sphereNode runAction:rotationAction];

break;

}

case ball: //球

{

[btn setImage:[UIImage imageNamed:@"figlhg"] forState:UIControlStateNormal];

[btn setTitle:@"球" forState:UIControlStateNormal];

_cameraNode.position = SCNVector3Make(0, 0, 45);

//設置相機視角大小

[_cameraNode.camera setYFov:60];

//水平翻轉圖片

_sphere.firstMaterial.diffuse.contents = _originalImage;

break;

}

default:

break;

}

self.sceneView.scene = [[SCNScene alloc]init];

[self.sceneView.scene.rootNode addChildNode:_sphereNode];

[self.sceneView.scene.rootNode addChildNode:_cameraNode];

}

#pragma mark - 導航欄

- (void)layoutNavi

{

self.navigationController.navigationBar.tintColor = [UIColor clearColor];

self.navigationItem.hidesBackButton = YES;

UIView *naviView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, UI_SCREEN_WIDTH, 32)];

self.navigationItem.titleView = naviView;

UIImageView *theImageView = [[UIImageView alloc] init];

theImageView.frame = CGRectMake(-8, -20, UI_SCREEN_WIDTH + 16, 52);

theImageView.backgroundColor = defaultColor;

[naviView addSubview:theImageView];

//返回按鈕

UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];

[backBtn setFrame:CGRectMake(0, 0, 40, 32)];

//? ? [backBtn setBackgroundImage:[UIImage imageNamed:@"a_return"] forState:UIControlStateNormal];

//? ? [backBtn setBackgroundImage:[UIImage imageNamed:@"a_return"] forState:UIControlStateHighlighted];

backBtn.titleLabel.font = [UIFont systemFontOfSize:15];

[backBtn setTitle:@"返回" forState:UIControlStateNormal];

[backBtn setTitleColor:TITLE_COLOR forState:UIControlStateNormal];

[backBtn addTarget:self action:@selector(backBtnClick:) forControlEvents:UIControlEventTouchUpInside];

[naviView addSubview:backBtn];

//titleLabel

UILabel *titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 6, 300, 20)];

titleLabel.centerX = naviView.centerX - 11;

titleLabel.font = [UIFont systemFontOfSize:15];

titleLabel.textColor = TITLE_COLOR;

titleLabel.text = self.title;

titleLabel.textAlignment = NSTextAlignmentCenter;

[naviView addSubview:titleLabel];

//右上角按鈕

UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];

[rightBtn setFrame:CGRectMake(UI_SCREEN_WIDTH - 60, 6, 40, 20)];

[rightBtn setImage:[UIImage imageNamed:@"3"] forState:UIControlStateNormal];

[rightBtn setTitleColor:TITLE_COLOR forState:UIControlStateNormal];

rightBtn.titleLabel.font = [UIFont systemFontOfSize:15];

[rightBtn addTarget:self action:@selector(shareBtnClick:) forControlEvents:UIControlEventTouchUpInside];

[naviView addSubview:rightBtn];

}

- (void)backBtnClick:(UIButton *)btn

{

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)shareBtnClick:(UIButton *)btn

{

}

- (void)setPModel:(XLPhotoModel *)pModel

{

_pModel = pModel;

[MBProgressHUD showMessage:@"加載中..." toView:self.view];

NSString *url = [NSString stringWithFormat:@"%@/%@",SERVICE_DOMAIN,pModel.panoramaPic];

[XLHttpTool downloadTaskWithURL:url progress:^(NSProgress *downloadProgress) {

NSLog(@"%f",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);

} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {

NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSString *path = [documentPath stringByAppendingPathComponent:@"panorama"];

NSFileManager *fileMgr = [NSFileManager defaultManager];

BOOL isDirectory = NO;

if ([fileMgr fileExistsAtPath:path isDirectory:&isDirectory]) {

if (!isDirectory) {

[fileMgr removeItemAtPath:path error:nil];

[fileMgr createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];

}

}else{

[fileMgr createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];

}

path = [path stringByAppendingPathComponent:response.suggestedFilename];

return [NSURL fileURLWithPath:path];

} completionHandler:^(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error) {

NSLog(@"下載完成");

[MBProgressHUD hideHUDForView:self.view];

[self setupSceneView:[filePath path]];

}];

}

//自動旋轉

- (IBAction)autoPlay:(UIButton *)btn

{

btn.selected = !btn.selected;

if (btn.selected) {

NSLog(@"添加動畫");

switch (_panoramaModel) {

case fisheye: //魚眼

{

[_sphereNode runAction:[SCNAction repeatActionForever:[SCNAction rotateByX:0 y:M_PI z:0 duration:ACTION_DURATION]] forKey:@"YaJunWei"];

break;

}

case asteroid: //小行星

{

[_sphereNode runAction:[SCNAction repeatActionForever:[SCNAction rotateByX:0 y:0 z:M_PI duration:ACTION_DURATION]] forKey:@"YaJunWei"];

break;

}

case ball: //球

{

[_sphereNode runAction:[SCNAction repeatActionForever:[SCNAction rotateByX:0 y:M_PI z:0 duration:ACTION_DURATION]] forKey:@"YaJunWei"];

break;

}

default:

break;

}

}else{

NSLog(@"移除動畫");

[_sphereNode removeActionForKey:@"YaJunWei"];

}

}

#pragma mark - 自定義手勢,實現圖片上下左右界限

- (void)panGesture:(UIPanGestureRecognizer *)panGesture

{

CGPoint pt = [panGesture translationInView:self.sceneView];

NSLog(@"pt.x = %f,pt.y = %f",pt.x,pt.y);

//旋轉

//距離

//? ? CGFloat distance = MAX(fabs(pt.x), fabs(pt.y));

CGFloat distance = fabs(pt.x) > fabs(pt.y) ? pt.x : pt.y;

//已旋轉角度

SCNVector4 vector = _sphereNode.rotation;

//? ? NSLog(@"vector:x:%f y:%f z:%f w:%f",vector.x,vector.y,vector.z,vector.w);

//判斷滑動方向

if (fabs(pt.x) >= fabs(pt.y)) {

//水平滑動

switch (_panoramaModel) {

case fisheye: //魚眼

{

//算出對應旋轉角度

[_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.width * 2 * M_PI? z:0 duration:0.01]];

break;

}

case asteroid: //小行星

{

//算出對應旋轉角度

[_sphereNode runAction:[SCNAction rotateByX:0 y:0 z:distance / self.sceneView.width * 2 * M_PI duration:0.01]];

break;

}

case ball: //球

{

//算出對應旋轉角度

[_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.width * 2 * M_PI? z:0 duration:0.01]];

break;

}

default:

break;

}

}else{

//垂直移動

switch (_panoramaModel) {

case fisheye: //魚眼

{

/*

if (panGesture.state == UIGestureRecognizerStateChanged)

{

//即將超出邊界

if (vector.w >= M_PI_2 && !_isUpBoundary && !_isDownBoundary)

{

if (pt.y < 0){

//到達或超出邊界,轉到邊界

NSLog(@"到達上邊界");

_isUpBoundary = YES;

[_sphereNode runAction:[SCNAction rotateToX:-M_PI_2 y:vector.y z:vector.z duration:0]];

}else if (pt.y > 0){

//到達或超出邊界,轉到邊界

NSLog(@"到達下邊界");

_isDownBoundary = YES;

[_sphereNode runAction:[SCNAction rotateToX:M_PI_2 y:vector.y z:vector.z duration:0]];

}

}else{

//算出對應旋轉角度

if ((_isUpBoundary && pt.y < 0) || (_isDownBoundary && pt.y > 0)) {

}else{

[_sphereNode runAction:[SCNAction rotateByX:distance / self.sceneView.height * 2 * M_PI y:0 z:0 duration:0]];

if (vector.w < M_PI_2) {

_isUpBoundary = _isDownBoundary = NO;

}

}

}

}

*/

break;

}

case asteroid: //小行星

{

//算出對應旋轉角度

[_sphereNode runAction:[SCNAction rotateByX:0 y:0 z:distance / self.sceneView.height * 2 * M_PI duration:0.01]];

break;

}

case ball: //球

{

//算出對應旋轉角度

//? ? ? ? ? ? ? ? [_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.height * 2 * M_PI? z:0 duration:0.01]];

break;

}

default:

break;

}

}

//每次移動完,將移動量置為0,否則下次移動會加上這次移動量

[panGesture setTranslation:CGPointMake(0, 0) inView:self.sceneView];

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context

{

NSLog(@"%@",change);

}

@end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容