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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容