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