基于SceneKit蹈胡,先導(dǎo)入SceneKit.framework
首先說(shuō)一下本人對(duì)全景圖的理解撕贞,所謂全景圖更耻,就是一個(gè)球體,在球體表面貼上圖片捏膨,在不同位置看就會(huì)產(chǎn)生不同的效果酥夭。
比如如果把攝像機(jī)放在球心,這時(shí)看球面上的圖片就是全景圖(魚(yú)眼圖)的效果脊奋,如果放在球外邊熬北,看到的就是一個(gè)完整的球,放在球面上诚隙,看到的就是小行星的效果讶隐。
其中的翻轉(zhuǎn)圖片代碼可根據(jù)需要使用,比如要在球內(nèi)外切換的情況久又。
話不多說(shuō)巫延,直接上代碼。
.h文件:
#import#import@interface ViewController : UIViewController
/** 圖片模型 */
@property (nonatomic,strong) XLPhotoModel *pModel;
/** 3D視圖 */
@property (weak, nonatomic) IBOutlet SCNView *sceneView;
/** 自動(dòng)播放動(dòng)畫(huà) */
@property (strong, nonatomic) IBOutlet UIButton *autoPlayBtn;
@end
.m文件
/** 全景模式 */
typedef enum {
fisheye, //魚(yú)眼
asteroid, //小行星
ball? ? //球
}panoramaModel;
@interface XLPicDetailVC ()
{
/** 球 */
SCNSphere *_sphere;
/** 相機(jī) */
SCNNode *_cameraNode;
/** 球節(jié)點(diǎn) */
SCNNode *_sphereNode;
/** 重力感應(yīng) */
CMMotionManager *_motionManager;
/** 原始圖片 */
UIImage *_originalImage;
/** 翻轉(zhuǎn)后的圖片 */
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]];
//水平翻轉(zhuǎn)圖片
_reversalImage = [UIImage reversalImage:_originalImage];
// Set the scene
self.sceneView.scene = [[SCNScene alloc]init];
self.sceneView.showsStatistics = NO;
self.sceneView.allowsCameraControl = YES;
//修改手勢(shì)
NSArray *array = self.sceneView.gestureRecognizers;
for (UIGestureRecognizer *gesture in array) {
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
//修改最大手指數(shù)地消,防止拖動(dòng)模型
((UIPanGestureRecognizer *)gesture).maximumNumberOfTouches = 1;
[self.sceneView removeGestureRecognizer:gesture];
}
}
//自定義拖動(dòng)手勢(shì)炉峰,實(shí)現(xiàn)圖片上下左右界限
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];
//重力感應(yīng)
//? ? _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;
//把球轉(zhuǎn)回來(lái)
[_sphereNode runAction:[SCNAction rotateToX:0 y:0 z:0 duration:0]];
//移除自動(dòng)旋轉(zhuǎn)動(dòng)畫(huà)
[_sphereNode removeActionForKey:@"YaJunWei"];
_autoPlayBtn.selected = NO;
switch (_panoramaModel) {
case fisheye: //魚(yú)眼
{
[btn setImage:[UIImage imageNamed:@"figlhg"] forState:UIControlStateNormal];
[btn setTitle:@"魚(yú)眼" forState:UIControlStateNormal];
_cameraNode.position = SCNVector3Make(0, 0, 0);
//設(shè)置相機(jī)視角大小
[_cameraNode.camera setYFov:60];
//水平翻轉(zhuǎn)圖片
_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);
//設(shè)置相機(jī)視角大小
[_cameraNode.camera setYFov:120];
NSLog(@"小行星");
//水平翻轉(zhuǎn)圖片
_sphere.firstMaterial.diffuse.contents = _reversalImage;
//把球沿x軸轉(zhuǎn)-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);
//設(shè)置相機(jī)視角大小
[_cameraNode.camera setYFov:60];
//水平翻轉(zhuǎn)圖片
_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 - 導(dǎo)航欄
- (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]];
}];
}
//自動(dòng)旋轉(zhuǎn)
- (IBAction)autoPlay:(UIButton *)btn
{
btn.selected = !btn.selected;
if (btn.selected) {
NSLog(@"添加動(dòng)畫(huà)");
switch (_panoramaModel) {
case fisheye: //魚(yú)眼
{
[_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(@"移除動(dòng)畫(huà)");
[_sphereNode removeActionForKey:@"YaJunWei"];
}
}
#pragma mark - 自定義手勢(shì)脉执,實(shí)現(xiàn)圖片上下左右界限
- (void)panGesture:(UIPanGestureRecognizer *)panGesture
{
CGPoint pt = [panGesture translationInView:self.sceneView];
NSLog(@"pt.x = %f,pt.y = %f",pt.x,pt.y);
//旋轉(zhuǎn)
//距離
//? ? CGFloat distance = MAX(fabs(pt.x), fabs(pt.y));
CGFloat distance = fabs(pt.x) > fabs(pt.y) ? pt.x : pt.y;
//已旋轉(zhuǎn)角度
SCNVector4 vector = _sphereNode.rotation;
//? ? NSLog(@"vector:x:%f y:%f z:%f w:%f",vector.x,vector.y,vector.z,vector.w);
//判斷滑動(dòng)方向
if (fabs(pt.x) >= fabs(pt.y)) {
//水平滑動(dòng)
switch (_panoramaModel) {
case fisheye: //魚(yú)眼
{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
[_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.width * 2 * M_PI? z:0 duration:0.01]];
break;
}
case asteroid: //小行星
{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
[_sphereNode runAction:[SCNAction rotateByX:0 y:0 z:distance / self.sceneView.width * 2 * M_PI duration:0.01]];
break;
}
case ball: //球
{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
[_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.width * 2 * M_PI? z:0 duration:0.01]];
break;
}
default:
break;
}
}else{
//垂直移動(dòng)
switch (_panoramaModel) {
case fisheye: //魚(yú)眼
{
/*
if (panGesture.state == UIGestureRecognizerStateChanged)
{
//即將超出邊界
if (vector.w >= M_PI_2 && !_isUpBoundary && !_isDownBoundary)
{
if (pt.y < 0){
//到達(dá)或超出邊界疼阔,轉(zhuǎn)到邊界
NSLog(@"到達(dá)上邊界");
_isUpBoundary = YES;
[_sphereNode runAction:[SCNAction rotateToX:-M_PI_2 y:vector.y z:vector.z duration:0]];
}else if (pt.y > 0){
//到達(dá)或超出邊界,轉(zhuǎn)到邊界
NSLog(@"到達(dá)下邊界");
_isDownBoundary = YES;
[_sphereNode runAction:[SCNAction rotateToX:M_PI_2 y:vector.y z:vector.z duration:0]];
}
}else{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
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: //小行星
{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
[_sphereNode runAction:[SCNAction rotateByX:0 y:0 z:distance / self.sceneView.height * 2 * M_PI duration:0.01]];
break;
}
case ball: //球
{
//算出對(duì)應(yīng)旋轉(zhuǎn)角度
//? ? ? ? ? ? ? ? [_sphereNode runAction:[SCNAction rotateByX:0 y:distance / self.sceneView.height * 2 * M_PI? z:0 duration:0.01]];
break;
}
default:
break;
}
}
//每次移動(dòng)完半夷,將移動(dòng)量置為0婆廊,否則下次移動(dòng)會(huì)加上這次移動(dòng)量
[panGesture setTranslation:CGPointMake(0, 0) inView:self.sceneView];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context
{
NSLog(@"%@",change);
}
@end