美文网首页
iOS简单实现全景图小行星和鱼眼模式

iOS简单实现全景图小行星和鱼眼模式

作者: 修罗大人 | 来源:发表于2017-11-23 10:01 被阅读0次

    基于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

    相关文章

      网友评论

          本文标题:iOS简单实现全景图小行星和鱼眼模式

          本文链接:https://www.haomeiwen.com/subject/qngavxtx.html