iOS簡(jiǎn)單實(shí)現(xiàn)全景圖小行星和魚(yú)眼模式

基于SceneKit蹈胡,先導(dǎo)入SceneKit.framework

首先說(shuō)一下本人對(duì)全景圖的理解撕贞,所謂全景圖更耻,就是一個(gè)球體,在球體表面貼上圖片捏膨,在不同位置看就會(huì)產(chǎn)生不同的效果酥夭。

比如如果把攝像機(jī)放在球心,這時(shí)看球面上的圖片就是全景圖(魚(yú)眼圖)的效果脊奋,如果放在球外邊熬北,看到的就是一個(gè)完整的球,放在球面上诚隙,看到的就是小行星的效果讶隐。

其中的翻轉(zhuǎn)圖片代碼可根據(jù)需要使用,比如要在球內(nèi)外切換的情況久又。

話不多說(shuō)巫延,直接上代碼。


iOS全景圖

.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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市巫橄,隨后出現(xiàn)的幾起案子淘邻,更是在濱河造成了極大的恐慌,老刑警劉巖湘换,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宾舅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡彩倚,警方通過(guò)查閱死者的電腦和手機(jī)筹我,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)署恍,“玉大人崎溃,你說(shuō)我怎么就攤上這事《⒅剩” “怎么了袁串?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵概而,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我囱修,道長(zhǎng)赎瑰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任破镰,我火速辦了婚禮餐曼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鲜漩。我一直安慰自己源譬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布孕似。 她就那樣靜靜地躺著踩娘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喉祭。 梳的紋絲不亂的頭發(fā)上养渴,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音泛烙,去河邊找鬼理卑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蔽氨,可吹牛的內(nèi)容都是我干的藐唠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孵滞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼中捆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起坊饶,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殴蓬,沒(méi)想到半個(gè)月后匿级,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡染厅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年痘绎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肖粮。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孤页,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩馆,到底是詐尸還是另有隱情行施,我是刑警寧澤允坚,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站蛾号,受9級(jí)特大地震影響稠项,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鲜结,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一展运、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧精刷,春花似錦拗胜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至误算,卻和暖如春仰美,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儿礼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工咖杂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚊夫。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓诉字,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親知纷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子壤圃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容