Periodic table

Share on Twitter Share on Facebook Share on LinkedIn

Periodic table

Description

In this proof of concept, I am showing the periodic table of elements in Augmented Reality.

I am also employing grouping of actions/animations as well as using the SkiaSharp graphics library to generate images.


Video


Code

using System;
using System.Collections.Generic;
using System.Linq;
using ARKit;
using Foundation;
using SceneKit;
using SkiaSharp;
using SkiaSharp.TextBlocks;
using SkiaSharp.Views.iOS;
using UIKit;

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

        int elements = 118;
        int columns = 18;
        int rows = 10;
        float width = 0.1f;
        float height = 0.1f;
        float margin = 0.02f;

        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);

            var configuration = new ARWorldTrackingConfiguration
            {
                AutoFocusEnabled = true,
                PlaneDetection = ARPlaneDetection.Horizontal,
                LightEstimationEnabled = true,
                WorldAlignment = ARWorldAlignment.Gravity,
            };

            this.sceneView.Session.Run(configuration, ARSessionRunOptions.ResetTracking | ARSessionRunOptions.RemoveExistingAnchors);

            // Get elements
            var elements = Element.GetAll();

            // new root node
            var node = new SCNNode();

            var random = new Random();
            float randomX = random.Next(1, 18) * (width + margin);
            float randomY = random.Next(1, 10) * (height + margin);
            float randomZ = random.Next(1, 5) * 0.1f;

            double randomDuration;

            foreach(var element in elements)
            {
                float x = (element.Column * (width + margin));
                float y = (element.Row * (height + margin));
                float z = -1.1f; // Final

                var elementNode = new ElementNode(x, -y, z, width, height, element);
                elementNode.Opacity = 0;
               
                randomDuration = random.NextDouble() * 10;
                var waitAction = SCNAction.Wait(randomDuration);
                var opacityAction = SCNAction.FadeIn(0.5);

                var moveZAction = SCNAction.MoveTo(new SCNVector3(x, -y, -0.9f), 0.5);
                var fadeAndMoveInAction = SCNAction.Group(new[] { opacityAction, moveZAction });
                var waitAndFadeAction = SCNAction.Sequence(new[] { waitAction, fadeAndMoveInAction });
                waitAndFadeAction.TimingMode = SCNActionTimingMode.EaseInEaseOut;

                elementNode.RunAction(waitAndFadeAction);

                node.AddChildNode(elementNode);
            }

            // Set new position
            var newX = (columns * width) / 2;
            var newY = (rows * height) / 2;

            node.Position = new SCNVector3(-newX, newY, 0);

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

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

            this.sceneView.Session.Pause();
        }

        public override void TouchesEnded(NSSet touches, UIEvent evt)
        {
            base.TouchesEnded(touches, evt);

            if (touches.AnyObject is UITouch touch)
            {
                var point = touch.LocationInView(this.sceneView);

                var hitTestOptions = new SCNHitTestOptions();

                var hits = this.sceneView.HitTest(point, hitTestOptions);
                var hit = hits.FirstOrDefault();

                if (hit == null)
                    return;

                var node = hit.Node;

                if (node == null)
                    return;

                ((ElementNode)node).ToggleSelection();
            }
        }
    }

    public class Element
    {
        public int AtomicNumber { get; set; }
        public string Name { get; set; }
        public string Symbol { get; set; }
        public int Row { get; set; }
        public int Column { get; set; }

        public Element(int atomicNumber, string symbol, string name, int row, int column)
        {
            AtomicNumber = atomicNumber;
            Symbol = symbol;
            Name = name;
            Row = row;
            Column = column;
        }

        public static IEnumerable<Element> GetAll()
        {
            return new Element[]
            {
                // Row 1
                new Element(1,"H", "Hydrogen", 1, 1),
                new Element(2, "He", "Helium", 1, 18),

                // Row 2
                new Element(3, "Li", "Lithium", 2, 1),
                new Element(4, "Be", "Beryllium", 2,2),
                new Element(5, "B", "Boron", 2, 13),
                new Element(6, "C", "Carbon", 2, 14),
                new Element(7, "N", "Nitrogen", 2,15),
                new Element(8, "O", "Oxygen", 2, 16),
                new Element(9, "F", "Fluorine", 2, 17),
                new Element(10, "Ne", "Neon", 2, 18),

                // Row 3
                new Element(11, "Na", "Sodium", 3, 1),
                new Element(12, "Mg", "Magnesium", 3, 2),
                new Element(13, "Al", "Aluminum", 3, 13),
                new Element(14, "Si", "Silicon", 3, 14),
                new Element(15, "P", "Phosphorus", 3, 15),
                new Element(16, "S", "Sulfur", 3, 16),
                new Element(17, "Cl", "Chlorine", 3, 17),
                new Element(18, "Ar", "Argon", 3, 18),

                // Row 4
                new Element(19, "K", "Potassium", 4, 1),
                new Element(20, "Ca", "Calcium", 4, 2),
                new Element(21, "Sc", "Scandium", 4, 3),
                new Element(22, "Ti", "Titanium", 4, 4),
                new Element(23, "V", "Vanadium", 4, 5),
                new Element(24, "Cr", "Chromium", 4, 6),
                new Element(25, "Mn", "Manganese", 4, 7),
                new Element(26, "Fe", "Iron", 4, 8),
                new Element(27, "Co", "Cobalt", 4, 9),
                new Element(28, "Ni", "Nickel", 4, 10),
                new Element(29, "Cu", "Copper", 4, 11),
                new Element(30, "Zn", "Zinc", 4, 12),
                new Element(31, "Ga", "Gallium", 4, 13),
                new Element(32, "Ge", "Germanium", 4, 14),
                new Element(33, "As", "Arsenic", 4, 15),
                new Element(34, "Se", "Selenium", 4, 16),
                new Element(35, "Br", "Bromine", 4, 17),
                new Element(36, "Kr", "Krypton", 4, 18),

                // Row 5
                new Element(37, "Rb", "Rubidium", 5, 1),
                new Element(38, "Sr", "Strontium", 5, 2),
                new Element(39, "Y", "Yttrium", 5, 3),
                new Element(40, "Zr", "Zirconium", 5, 4),
                new Element(41, "Nb", "Niobium", 5, 5),
                new Element(42, "Mo", "Molybdenum", 5, 6),
                new Element(43, "Tc", "Technetium", 5, 7),
                new Element(44, "Ru", "Ruthenium", 5, 8),
                new Element(45, "Rh", "Rhodium", 5, 9),
                new Element(46, "Pd", "Palladium", 5, 10),
                new Element(47, "Ag", "Silver", 5, 11),
                new Element(48, "Cd", "Cadmium", 5, 12),
                new Element(49, "In", "Indium", 5, 13),
                new Element(50, "Sn", "Tin", 5, 14),
                new Element(51, "Sb", "Antiumony", 5, 15),
                new Element(52, "Te", "Tellurium", 5, 16),
                new Element(53, "I", "Iodine", 5, 17),
                new Element(54, "Xe", "Xenon", 5, 18),

                // Row 6
                new Element(55, "Cs", "Cesium", 6, 1),
                new Element(56, "Ba", "Barium", 6, 2),
                new Element(57, "La", "Lanthanum", 6, 3),
                new Element(72, "Hf", "Hafnium", 6, 4),
                new Element(73, "Ta", "Tantalum", 6, 5),
                new Element(74, "W", "Tungsten", 6, 6),
                new Element(75, "Re", "Rhenium", 6, 7),
                new Element(76, "Os", "Osmium", 6, 8),
                new Element(77, "Ir", "Iridium", 6, 9),
                new Element(78, "Pt", "Platinum", 6, 10),
                new Element(79, "Au", "Gold", 6, 11),
                new Element(80, "Hg", "Mercury", 6, 12),
                new Element(81, "Tl", "Thallium", 6, 13),
                new Element(82, "Pb", "Lead", 6, 14),
                new Element(83, "Bi", "Bismuth", 6, 15),
                new Element(84, "Po", "Polonium", 6, 16),
                new Element(85, "At", "Astatine", 6, 17),
                new Element(86, "Rn", "Radon", 6, 18),

                // Row 7
                new Element(87, "Fr", "Francium", 7, 1),
                new Element(88, "Ra", "Radium", 7, 2),
                new Element(89, "Ac", "Actinium", 7, 3),
                new Element(104, "Rf", "Rutherfordium", 7, 4),
                new Element(105, "Db", "Dubnium", 7, 5),
                new Element(106, "Sg", "Seaborgium", 7, 6),
                new Element(107, "Bh", "Bohrium", 7, 7),
                new Element(108, "Hs", "Hassium", 7, 8),
                new Element(109, "Mt", "Meitnerium", 7, 9),
                new Element(110, "Ds", "Darmstadtium", 7, 10),
                new Element(111, "Rg", "Roentgenium", 7, 11),
                new Element(112, "Cn", "Copernicium", 7, 12),
                new Element(113, "Nh", "Nihonium", 7, 13),
                new Element(114, "Fl", "Flerovium", 7, 14),
                new Element(115, "Mc", "Moscovium", 7, 15),
                new Element(116, "Lv", "Livermorium", 7, 16),
                new Element(117, "Ts", "Tennessine", 7, 17),
                new Element(118, "Og", "Oganesson", 7, 18),


                // Row 9
                new Element(58, "Ce", "Cerium", 9, 4),
                new Element(59, "Pr", "Praseodymium", 9, 5),
                new Element(60, "Nd", "Neodymium", 9, 6),
                new Element(61, "Pm", "Promethium", 9, 7),
                new Element(62, "Sm", "Samarium", 9, 8),
                new Element(63, "Eu", "Europium", 9, 9),
                new Element(64, "Gd", "Gadolinium", 9, 10),
                new Element(65, "Tb", "Terbium", 9, 11),
                new Element(66, "Dy", "Dysprosium", 9, 12),
                new Element(67, "Ho", "Holmium", 9, 13),
                new Element(68, "Er", "Erbium", 9, 14),
                new Element(69, "Tm", "Thulium", 9, 15),
                new Element(70, "Yb", "Ytterbium", 9, 16),
                new Element(71, "Lu", "Lutetium", 9, 17),

                // Row 10
                new Element(90, "Th", "Thorium",10,4),
                new Element(91, "Pa", "Protactiunium",10,5),
                new Element(92, "U", "Uranium",10,6),
                new Element(93, "Np", "Neptunium",10,7),
                new Element(94, "Pu", "Plutonium",10,8),
                new Element(95, "Am", "Americium",10,9),
                new Element(96, "Cm", "Curium",10,10),
                new Element(97, "Bk", "Berkelium",10,11),
                new Element(98, "Cf", "Californium",10,12),
                new Element(99, "Es", "Einsteinium",10,13),
                new Element(100, "Fm", "Fermium",10,14),
                new Element(101, "Md", "Mendelevium",10,15),
                new Element(102, "No", "Nobelium",10,16),
                new Element(103, "Lr", "Lawrencium",10,17)
            };
        }
    }

    public class ElementNode : SCNNode
    {
        public Element Element { get; set; }
        public int AnimationDelayInSeconds { get; set; }
        public bool IsSelected { get; set; }

        public ElementNode(float x, float y, float z, float width, float height, Element element)
        {
            Element = element;

            var material = new SCNMaterial();
            material.Diffuse.Contents = UIColor.Green;
            material.DoubleSided = true;

            Position = new SCNVector3(x, y, z);
            Geometry = SCNPlane.Create(width, height);
            Geometry.Materials = new[] { material };

            var image = GetImage(element, SKColors.DarkCyan.WithAlpha(200));
            Geometry.FirstMaterial.Diffuse.Contents = image;
        }

        private UIImage GetImage(Element element, SKColor color)
        {
            var fontSymbolBold = new Font(200, true);
            var fontNameBold = new Font(50, true);
            var fontNumberBold = new Font(80, true);

            // Create Skiasharp image
            using (var Surface = SKSurface.Create(new SKImageInfo(width: 400, 400, SKImageInfo.PlatformColorType, SKAlphaType.Premul)))
            {
                var canvas = Surface.Canvas;
                canvas.Clear(color);

                // Symbol
                var rectSymbol = new SKRect(0, 100, 400, 0);
                var textSymbol = new TextBlock(fontSymbolBold, SKColors.White, element.Symbol, SkiaSharp.TextBlocks.Enum.LineBreakMode.Center);

                // Name
                var rectName = new SKRect(0, 300, 400, 0);
                var textName = new TextBlock(fontNameBold, SKColors.White, element.Name, SkiaSharp.TextBlocks.Enum.LineBreakMode.Center);

                // Number
                var rectNumber = new SKRect(25, 25, 400, 0);
                var textNumber = new TextBlock(fontNumberBold, SKColors.White, element.AtomicNumber.ToString());

                canvas.DrawTextBlock(textSymbol, rectSymbol);
                canvas.DrawTextBlock(textName, rectName);
                canvas.DrawTextBlock(textNumber, rectNumber);

                return Surface.Snapshot().ToUIImage();
            }
        }

        public void ToggleSelection()
        {
            IsSelected = !IsSelected;
            UIImage newImage;
            SCNAction moveZAction;

            if(IsSelected)
            {
                // Change to red
                newImage = GetImage(this.Element, SKColors.Maroon.WithAlpha(200));
                moveZAction = SCNAction.MoveTo(new SCNVector3(Position.X, Position.Y, -0.8f), 0.5);
                moveZAction.TimingMode = SCNActionTimingMode.EaseInEaseOut;
            }
            else
            {
                // Change to green
                newImage = GetImage(this.Element, SKColors.DarkCyan.WithAlpha(200));
                moveZAction = SCNAction.MoveTo(new SCNVector3(Position.X, Position.Y, -0.9f), 0.5);
                moveZAction.TimingMode = SCNActionTimingMode.EaseInEaseOut;
            }

            this.Geometry.FirstMaterial.Diffuse.Contents = newImage;
            this.RunAction(moveZAction);
        }
    }
}

Next Step : Lighting and shadows

After you have mastered this you should try Lighting and shadows