g33rt
Topic Author
Posts: 23
Joined: 14 Jan 2014, 16:02

Map Viewer

28 Jan 2014, 09:20

This example shows how a touch-based map viewer can be implemented with little code using noesisGUI. As the video shows, the visual component is very fluid and responsive without the need to implement any threading in Unity3D:



The video above was shot using an iPad 2. The code is work-in-progress, but please feel free to play with it and test it on other devices. Let us know if you've made improvements or have any suggestions.

Geert.
<Grid x:name="root" ClipToBounds="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
  
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Canvas x:Name="imageCanvas" RenderTransform="0.001 0 0 0.001 0 0" HorizontalAlignment="Left" VerticalAlignment="Top" IsManipulationEnabled="True"/>
</Grid>
using UnityEngine;
using Noesis;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;

namespace GWA
{
	public class Tuple<T1,T2,T3>
	{
		public static Tuple<T1,T2,T3> Create<T1,T2,T3>(T1 item1,T2 item2,T3 item3)
		{
			return new Tuple<T1,T2,T3>(item1, item2, item3);
		}

		Tuple(T1 item1,T2 item2,T3 item3)
		{
			Item1 = item1;
			Item2 = item2;
			Item3 = item3;
		}

		public readonly T1 Item1;
		public readonly T2 Item2;
		public readonly T3 Item3;
	}

	
	public static class MapExtensions
	{
		public static float GetSurface(this Noesis.Rect rect)
		{
			return rect.width * rect.height;
		}

		public static float SurfaceIntersect(Noesis.Rect rect1,Noesis.Rect rect2)
		{
			return rect1.Intersect(rect2).GetSurface();
		}

		public static int Compare(this Noesis.Rect refRect, Noesis.Rect rect1, Noesis.Rect rect2)
		{
			float i1 = SurfaceIntersect(refRect,rect1);
			float i2 = SurfaceIntersect(refRect,rect2);

			if (i1 > i2) 
			{
				return -1;
			}
			if (i1 < i2) 
			{
				return 1;
			}

			var refMidX = refRect.x + refRect.width/2f;
			var refMidY = refRect.y + refRect.height/2f;

			var rect1MidX = rect1.x + rect1.width/2f;
			var rect1MidY = rect1.y + rect1.height/2f;

			var rect2MidX = rect2.x + rect2.width/2f;
			var rect2MidY = rect2.y + rect2.height/2f;

			var d1 = (refMidX - rect1MidX) * (refMidX - rect1MidX) + (refMidY - rect1MidY) * (refMidY - rect1MidY);
			var d2 = (refMidX - rect2MidX) * (refMidX - rect2MidX) + (refMidY - rect2MidY) * (refMidY - rect2MidY);

			if (d1 < d2) 
			{
				return -1;
			}
			if (d1 > d2) 
			{
				return 1;
			}

			return 0;
		}

	}

	public class VisibleTiles
	{
		internal List<GuiMapTile> ProcessList = new List<GuiMapTile>();
		internal List<GuiMapTile> ProcessedList = new List<GuiMapTile>();

		public GuiMapTile Take()
		{
			var tile = ProcessList.First();
			ProcessList.RemoveAt(0);
			return tile;
		}

		
		public void Process(GuiMapTile tile, Noesis.Rect rootRect)
		{
			for (int i = 0; i < ProcessList.Count; i++)
			{
				if (rootRect.Compare(ProcessList[i].ImageRect,tile.ImageRect) >= 0)
				{
					ProcessList.Insert(i,tile);
					return;
				}
			}

			ProcessList.Add(tile);
		}

		public void Processed(GuiMapTile tile)
		{
			ProcessedList.Add(tile);
		}

		public void Add(GuiMapTile tile, Noesis.Rect rootRect)
		{
			if (rootRect.IntersectsWith(tile.ImageRect))
			{
				Process(tile,rootRect);
			}
			else
			{
				Processed(tile);
			}
		}
		
		public void Remove(GuiMapTile tile)
		{
			ProcessList.Remove(tile);
			ProcessedList.Remove(tile);
		}

		public void Reset(Noesis.Rect rootRect)
		{
			List<GuiMapTile> processList = ProcessList;
			List<GuiMapTile> processedList = ProcessedList;

			ProcessList = new List<GuiMapTile>();
			ProcessedList = new List<GuiMapTile>();

			foreach (var tile in processList.Concat(processedList))
			{
				if (rootRect.IntersectsWith(tile.ImageRect)) 
				{
					ProcessList.Add(tile);
				}
				else
				{
					ProcessedList.Add(tile);
				}
			}

			ProcessList.Sort((t1,t2) => rootRect.Compare(t1.ImageRect,t2.ImageRect));
		}
	}


	
	public class GuiMapTile
	{
		internal readonly int X;
		internal readonly int Y;
		internal readonly int Z;
		
		internal readonly float Scale;
		internal readonly Noesis.Rectangle Image;
		internal readonly ImageBrush ImageBrush;
		internal readonly TextureSource TextureSource;
		internal readonly Texture2D Texture;
		internal readonly GuiMapTile Parent;
		internal readonly Canvas ImageCanvas;
		internal readonly Canvas ChildrenCanvas;
		internal readonly MatrixTransform Transform;
		
		internal List<GuiMapTile> Children;


		public GuiMapTile(int x, int y, int z, Texture2D texture, Canvas imageCanvas, GuiMapTile parent,MatrixTransform transform, Dictionary<GuiMapTile,float> imageFadein)
		{
			X = x;
			Y = y;
			Z = z;
			Parent = parent;
			Texture = texture;
			ImageCanvas = imageCanvas;
			Transform = transform;

			TextureSource = new TextureSource(texture);

			Scale = Mathf.Pow(2f,(float)(Map.MaxZoom-z));
			float xLoc = Scale * (float)(x%2);
			float yLoc = Scale * (float)(y%2);

			Image = new Noesis.Rectangle();

			Image.SetWidth(Scale);
			Image.SetHeight(Scale);

			Canvas.SetLeft(Image, xLoc);
			Canvas.SetTop(Image, yLoc);

			ImageBrush = new Noesis.ImageBrush();
			ImageBrush.SetImageSource(TextureSource);
			ImageBrush.SetOpacity(0f);

			Image.SetFill(ImageBrush);

			ImageCanvas.GetChildren().Add(Image);
			Canvas.SetZIndex(Image,0);

			ChildrenCanvas = new Canvas();

			ChildrenCanvas.SetWidth(Scale);
			ChildrenCanvas.SetHeight(Scale);

			Canvas.SetLeft(ChildrenCanvas, xLoc);
			Canvas.SetTop(ChildrenCanvas, yLoc);

			imageCanvas.GetChildren().Add(ChildrenCanvas);
			Canvas.SetZIndex(ChildrenCanvas,1);

			imageFadein[this] = Time.time;
		}


		public Noesis.Rect ImageRect
		{
			get
			{
				var x = Scale * (float)X;
				var y = Scale * (float)Y;
				
				Noesis.Rect imageRect = new Noesis.Rect(x,y,x + Scale,y + Scale);
				imageRect.Transform(Transform.GetMatrix());
				
				return imageRect;
			}
		}


		public IEnumerator LoadChildren(Noesis.Rect rootRect,Dictionary<GuiMapTile,float> imageFadein)
		{
			if (Children == null)
			{
				Children = new List<GuiMapTile>();

				if (Z < Map.MaxZoom)
				{
					int z = Z+1;

					List<Tuple<Noesis.Rect,int,int>> l = new List<Tuple<Noesis.Rect, int, int>>();

					float width = Scale / 2f;
					float height = Scale / 2f;

					for (int x = X*2; x <= X*2 + 1; x++)
					{
						for (int y = Y*2; y <= Y*2 + 1; y++)
						{
							var actualX = height * (float)x;
							var actualY = width * (float)y;

							var r = new Noesis.Rect(actualX,actualY,actualX+width,actualY+height);
							r.Transform(Transform.GetMatrix());

							l.Add(Tuple<Noesis.Rect,int,int>.Create(r,x,y));
						}
					}

					l.Sort((t1,t2) => rootRect.Compare(t1.Item1,t2.Item1));

					foreach (var t in l)
					{
						var x = t.Item2;
						var y = t.Item3;

						WWW www = new WWW( String.Format(Map.url,x,y,z));
						
						yield return www;
						
						Children.Add(new GuiMapTile(x, y, z, www.texture, ChildrenCanvas, this, Transform, imageFadein));
						www.Dispose ();
					}
				}
			}
		}

		public void UnloadChildren(VisibleTiles tiles, Dictionary<Texture2D,float> scheduled4Destruction)
		{
			if (Children != null)
			{
				foreach (var child in Children)
				{
					child.UnloadChildren(tiles,scheduled4Destruction);
					tiles.Remove(child);

					child.Destroy(scheduled4Destruction);
				}

				Children = null;
			}
		}

		public void Destroy(Dictionary<Texture2D,float> scheduled4Destruction)
		{
			ImageCanvas.GetChildren().Remove(this.Image);
			ImageCanvas.GetChildren().Remove(this.ChildrenCanvas);

			this.Image.Dispose();
			this.ChildrenCanvas.Dispose();

			this.TextureSource.Dispose();

			scheduled4Destruction[this.Texture] = Time.time;
		}

	}


	public class Map : MonoBehaviour
	{
		public const int MaxZoom = 20;
	    private Grid rootGrid;

		// map providers: http://www.neongeo.com/wiki/doku.php?id=map_servers

		//public static string url = "http://maptile.maps.svc.ovi.com/maptiler/maptile/newest/normal.day/{2}/{0}/{1}/256/png8";
		public static string url = "http://mt1.google.com/vt/lyrs=y&x={0}&y={1}&z={2}";
		//public static string url = "http://mt0.google.com/vt/x={0}&y={1}&z={2}";
		//public static string url = "http://a.tile.openstreetmap.org/{2}/{0}/{1}.png";
		//public static string url = "http://otile1.mqcdn.com/tiles/1.0.0/sat";

		Dictionary<Texture2D,float> scheduled4Destruction = new Dictionary<Texture2D,float>();
		Dictionary<GuiMapTile,float> imageFadein = new Dictionary<GuiMapTile,float>();

		VisibleTiles activeTiles = new VisibleTiles();
		GuiMapTile rootTile;
		Canvas imageCanvas;

		void Update()
		{
			foreach (var kvp in scheduled4Destruction.ToArray())
			{
				if (Time.time - kvp.Value > 1f) 
				{
					Resources.UnloadAsset(kvp.Key);
					scheduled4Destruction.Remove(kvp.Key);
				}
			}

			foreach (var kvp in imageFadein.ToArray())
			{
				float diff = (Time.time - kvp.Value) * 2f;
				if (diff > 1f) 
				{
					kvp.Key.ImageBrush.SetOpacity(1f);
					imageFadein.Remove(kvp.Key);
				}
				else
				{
					kvp.Key.ImageBrush.SetOpacity(diff);
				}
			}
		}

		Noesis.Rect RootRect()
		{
			return new Noesis.Rect(0f,0f,rootGrid.GetActualWidth(),rootGrid.GetActualHeight());
		}

		
		IEnumerator Start()
	    {
			rootGrid = GetComponent<NoesisGUIPanel>().GetRoot<Grid>();

			rootGrid.ManipulationStarting += this.ManipulationStarting;
			rootGrid.ManipulationInertiaStarting += this.ManipulationInertiaStarting;
			rootGrid.ManipulationDelta += this.ManipulationDelta;
			rootGrid.ManipulationCompleted += (sender, e) => activeTiles.Reset(RootRect());

			imageCanvas = rootGrid.FindName<Canvas>("imageCanvas");

			WWW www = new WWW(String.Format(url,0,0,0));
			
			yield return www;

			var rootTexture = www.texture;

			imageCanvas.SetWidth(1f);
			imageCanvas.SetHeight(1f);

			rootTile = new GuiMapTile(0, 0, 0, rootTexture, imageCanvas, null ,imageCanvas.GetRenderTransform().As<MatrixTransform>(),imageFadein);
			
			www.Dispose ();
			
			activeTiles.Process(rootTile,RootRect());


			while (true)
			{
				activeTiles.Reset(RootRect());

				while (activeTiles.ProcessList.Any())
				{
					var tile = activeTiles.Take();

					Noesis.Rect imageRect = tile.ImageRect;
					Noesis.Rect rootRect = RootRect();

					bool tileChanged = false;

					if (rootRect.IntersectsWith(imageRect))
					{
						if (imageRect.width >= 2f * rootTexture.width)
						{
							tileChanged = true;

							yield return StartCoroutine(tile.LoadChildren(rootRect,imageFadein));

							tile.Children.ForEach(child => activeTiles.Add(child,rootRect));
						}
						else if (tile.Parent != null && imageRect.width < rootTexture.width) 
						{
							tileChanged = true;
							
							tile.Parent.UnloadChildren(activeTiles,scheduled4Destruction);
							activeTiles.Process(tile.Parent,rootRect);
						}

					}
					else if (tile.Parent != null)
					{
						if (!rootRect.IntersectsWith(tile.Parent.ImageRect))
						{
							tileChanged = true;

							tile.Parent.UnloadChildren(activeTiles,scheduled4Destruction);
							activeTiles.Process(tile.Parent,rootRect);
						}
					}

					if (!tileChanged) 
					{
						activeTiles.Processed(tile);
					}

				}

				yield return new WaitForSeconds(0.1f);
			}
			yield break;
	    }


		void ManipulationStarting(BaseComponent sender, ManipulationStartingEventArgs e)
	    {
	        e.mode = ManipulationModes.All;
			e.manipulationContainer = rootGrid;
	        e.handled = true;
	    }

	    void ManipulationInertiaStarting(BaseComponent sender, ManipulationInertiaStartingEventArgs e)
	    {
	        e.desiredTranslationDeceleration = 100.0f / (1000.0f * 100.0f);
	        e.desiredRotationDeceleration = 360.0f / (1000.0f * 100.0f);
	        e.desiredExpansionDeceleration = 300.0f / (1000.0f * 100.0f);
	        e.handled = true;
	    }

	    void ManipulationDelta(BaseComponent sender, ManipulationDeltaEventArgs e)
	    {
			float rotation = e.deltaManipulation.rotation * Mathf.Deg2Rad;
	        float originX = e.manipulationOrigin.x;
	        float originY = e.manipulationOrigin.y;
	        float scale = e.deltaManipulation.scale;
	        float translationX = e.deltaManipulation.translation.x;
	        float translationY = e.deltaManipulation.translation.y;

			MatrixTransform matrixTransform = imageCanvas.GetRenderTransform().As<MatrixTransform>();
			
			Transform2f newMatrix = new Transform2f(matrixTransform.GetMatrix());
			
			newMatrix.RotateAt(rotation, originX, originY);
			newMatrix.ScaleAt(scale, scale, originX, originY);
			newMatrix.Translate(translationX, translationY);
			
			matrixTransform.SetMatrix(newMatrix);

			e.handled = true;
	    }
	}
}

 
jungrok5
Posts: 3
Joined: 19 Jan 2014, 09:51

Re: Map Viewer

29 Jan 2014, 08:34

:o
wow!

Who is online

Users browsing this forum: No registered users and 11 guests