基于SceneKit,先导入SceneKit.framework
首先说一下本人对全景图的理解,所谓全景图,就是一个球体,在球体表面贴上图片,在不同位置看就会产生不同的效果。
比如如果把摄像机放在球心,这时看球面上的图片就是全景图(鱼眼图)的效果,如果放在球外边,看到的就是一个完整的球,放在球面上,看到的就是小行星的效果。
其中的翻转图片代码可根据需要使用,比如要在球内外切换的情况。
话不多说,直接上代码。
.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