3d photo gallery surround

Share on Twitter Share on Facebook Share on LinkedIn

3d photo gallery surround

Description

In this lesson, we are..

  • Adding an invisible node at the centre of the scene (world origin)
  • Creating 7 rows of 2d planes spaced at regular intervals
  • Applying a look at constraint to all the 2d planes to point towards the central invisible node
  • Calling the Unsplash API in a separate thread as not to affect the main UI thread
  • Updating the 2d planes with images returned from the Unsplash API call


Video


Code

using ARKit;
using Foundation;
using Newtonsoft.Json;
using SceneKit;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using UIKit;

namespace XamarinArkitSample
{
    public partial class ViewController : UIViewController
    {
        private readonly ARSCNView sceneView;

        public string unsplashAccessKey = "<INSERT_YOUR_OWN_ACCESSKEY_HERE>";

        List<ImagePlaneNode> imagePlaneNodes = new List<ImagePlaneNode>();
        ConcurrentBag<UIImage> uiImages = new ConcurrentBag<UIImage>();

        SCNNode centerNode;
        float imageWidth = 0.3f;
        float imageHeight = 0.2f;
        float verticalMargin = 0.01f;
        string searchTerm = "beach";

        public ViewController(IntPtr handle) : base(handle)
        {
            this.sceneView = new ARSCNView
            {
                AutoenablesDefaultLighting = true
            };

            this.View.AddSubview(this.sceneView);
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            this.sceneView.Frame = this.View.Frame;
        }

        public override void ViewDidAppear(bool animated)
        {
            base.ViewDidAppear(animated);

            this.sceneView.Session.Run(new ARWorldTrackingConfiguration
            {
                AutoFocusEnabled = true,
                PlaneDetection = ARPlaneDetection.Horizontal,
                LightEstimationEnabled = true,
                WorldAlignment = ARWorldAlignment.GravityAndHeading,
                FrameSemantics = ARFrameSemantics.PersonSegmentationWithDepth
            }, ARSessionRunOptions.ResetTracking | ARSessionRunOptions.RemoveExistingAnchors);

            var radius = 1.25f; // 1.25m away from world origin
            var sides = 21; // images per row
            var rows = 7;

            centerNode = new SCNNode(); 
            centerNode.Position = new SCNVector3(0, 0, 0);
            this.sceneView.Scene.RootNode.AddChildNode(centerNode);

            AddBlankRow((imageHeight * 3) + (verticalMargin * 3), radius-0.15f, sides);
            AddBlankRow((imageHeight * 2) + (verticalMargin * 2), radius-0.075f, sides);
            AddBlankRow(imageHeight + verticalMargin, radius, sides);
            AddBlankRow(0, radius, sides);
            AddBlankRow(0 - imageHeight - verticalMargin, radius, sides);
            AddBlankRow(0 - (imageHeight * 2) - (verticalMargin * 2), radius-0.075f, sides);
            AddBlankRow(0 - (imageHeight * 3) - (verticalMargin * 3), radius-0.15f, sides);

            Task.Run(async () =>
            {
                var imageUrls = await GetUrlsFromUnSplashApi(searchTerm, sides * rows);

                int x = 0;

                foreach(var imageUrl in imageUrls)
                {
                    var taskA = LoadImage(imageUrl);

                    await taskA.ContinueWith(cw =>
                    {
                        var image = cw.Result;
                        uiImages.Add(image);

                        BeginInvokeOnMainThread(() =>
                        {
                            if (x < (sides*rows))
                            {
                                imagePlaneNodes[x].UpdateImage(image);
                                x++;
                            }
                        });
                    });
                }
            });
        }

        private void AddBlankRow(float y, float radius, int sides)
        {
            for (int i = 0; i < sides; i++)
            {
                var imagePlaneNode = new ImagePlaneNode(imageWidth, imageHeight);

                float x = (float)(radius * Math.Cos(2 * Math.PI * i / sides));
                var z = (float)(radius * Math.Sin(2 * Math.PI * i / sides));

                imagePlaneNode.Position = new SCNVector3(x, y, z);

                var lookConstraint = SCNLookAtConstraint.Create(centerNode);
                lookConstraint.GimbalLockEnabled = true;
                imagePlaneNode.Constraints = new SCNConstraint[] { lookConstraint };

                imagePlaneNodes.Add(imagePlaneNode);

                this.sceneView.Scene.RootNode.AddChildNode(imagePlaneNode);
            }
        }

        private async Task<string[]> GetUrlsFromUnSplashApi(string searchTerm, int perPage)
        {
            var urls = new List<string>();

            var client = new WebClient();

            for (int page = 1; page <= 5; page++)
            {
                var url = $"https://api.unsplash.com/search/photos?client_id={unsplashAccessKey}&page={page}&per_page={perPage}&orientation=landscape&query={searchTerm}";

                var response = await client.DownloadStringTaskAsync(url);

                dynamic array = JsonConvert.DeserializeObject(response);

                foreach (var result in array["results"])
                {
                    urls.Add(result.urls.small.ToString());
                }
            }
            
            return urls.ToArray();
        }

        public override void ViewDidDisappear(bool animated)
        {
            base.ViewDidDisappear(animated);

            this.sceneView.Session.Pause();
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
        }

        public async Task<UIImage> LoadImage(string url)
        {
            var httpClient = new WebClient();
            Task<byte[]> contentsTask = httpClient.DownloadDataTaskAsync(url);
            var contents = await contentsTask;

            return UIImage.LoadFromData(NSData.FromArray(contents));
        }
    }

    public class ImagePlaneNode : SCNNode
    {
        public ImagePlaneNode(float width, float height)
        {
            Geometry = CreateGeometry(width, height);
            Opacity = 0.2f;
        }

        private static SCNGeometry CreateGeometry(float width, float height)
        {
            var material = new SCNMaterial();
            material.Diffuse.Contents = UIColor.White;
            material.DoubleSided = true;

            var geometry = SCNPlane.Create(width, height);
            geometry.Materials = new[] { material };

            return geometry;
        }

        internal void UpdateImage(UIImage uIImage)
        {
            Geometry.FirstMaterial.Diffuse.Contents = uIImage;

            this.RunAction(SCNAction.FadeIn(1));
        }
    }
}

Next Step : Place webview in scene

After you have mastered this you should try Place webview in scene