Pervasive Touristic Location Based Service (LBS)
Transcript
Pervasive Touristic Location Based Service (LBS)
Università degli studi di Trento Facoltà di Scienze Matematiche, Fisiche e Naturali Corso di Laurea Magistrale in Computer Science TESI Pervasive Touristic Location Based Service (LBS) App With Social Perspective RELATORE: LAUREANDO: Raffaele De Amicis Umberto Di Staso CORRELATORI: Gabrio Girardi Anno Accademico 2010 - 2011 ABSTRACT Nel corso degli ultimi anni, i mercati mondiali hanno reso accessibili alle masse dispositivi dotati di ottime prestazioni uniti a prezzi davvero contenuti. L’introduzione di questo tipo di strumenti, sopratutto nel settore mobile, unito a tariffe sempre più convenienti per l’accesso ad internet tramite operatori telefonici, ha creato un nuovo tipo di mercato, il mercato delle applicazioni mobile, la cui espansione viaggia di pari passo alla diffusione di smartphone e tablet. La necessità e la possibilità da parte dell’utente finale di disporre di informazioni dettagliate riguardanti un qualsiasi argomento, indipendentemente dalla locazione in cui esso si trova, è quindi aumentata, ed un settore che ha particolarmente beneficiato di tale tecnologia è quello del turismo. La giovinezza di tale mercato è tale da fornire applicazioni turistiche ad uno stato primordiale di sviluppo: l’obiettivo di questo documento è quello di fornire le specifiche necessarie per l’implementazione di un un software che costituisca una nuova generazione di programmi riguardanti la visualizzazione di informazioni georeferenziate su modelli tridimensionali creati in tempo reale (on-the-fly). ‘Non è forte chi non cade mai, ma colui che cadendo ha la forza di rialzarsi’ Johann Wolfgang Von Goethe Indice 1 Introduzione 1 2 Il Progetto 2.1 Application Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Middleware Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Data Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 4 5 5 3 Lo Stato dell’Arte 3.1 Unity3D Game Engine . . . . . . . . . . . . . 3.2 NASA World Wind Server . . . . . . . . . . . 3.2.1 GetCapabilities . . . . . . . . . . . . . 3.2.2 GetMap . . . . . . . . . . . . . . . . . 3.3 PostGIS GeoDatabase . . . . . . . . . . . . . 3.4 PHP: Hypertext Preprocessor . . . . . . . . . 3.4.1 PHP: Cenni Storici . . . . . . . . . . . 3.4.2 Perchè PHP, le limitazioni di Unity3D 4 Il modello basato su Tile 4.0.3 Il Zero Level Tile Delta . . 4.1 Struttura delle Tile . . . . . . . . 4.2 Level Of Detail . . . . . . . . . . 4.2.1 Frustum Culling . . . . . . 4.2.2 Divisibilità . . . . . . . . . 4.2.3 L’algoritmo di LOD . . . . 4.3 Costruzione Dinamica delle Mesh 4.3.1 Le Skirts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 8 10 10 11 14 17 17 18 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 20 23 23 24 24 26 28 5 Contenuti Aggiuntivi 5.1 I Layers . . . . . . . . . . . . . . . . . . . . 5.1.1 Il Z-Fighting . . . . . . . . . . . . . . 5.2 Punti Di Interesse . . . . . . . . . . . . . . . 5.2.1 Approccio Tramite Immagini Raster 5.2.2 Approccio Tramite GameObject . . . 5.2.3 Approccio Tramite BillBoard . . . . 5.2.4 Il Picking . . . . . . . . . . . . . . . 5.2.5 Il Fattore di Scala . . . . . . . . . . . 5.2.6 La Precisione dei Float . . . . . . . . 5.2.7 Inserimento/Aggiornamento PDI . . 5.3 Le Informazioni Meteoreologiche . . . . . . . 5.3.1 I Particle Systems . . . . . . . . . . . 5.4 Il GeoCoding . . . . . . . . . . . . . . . . . 5.5 I Percorsi Guidati . . . . . . . . . . . . . . . 5.5.1 Il formato GPX . . . . . . . . . . . . 5.5.2 Migrazione dei Dati . . . . . . . . . . 5.5.3 Rappresentazione del Percorso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 31 32 32 33 33 34 35 37 38 40 41 41 42 44 44 45 47 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.4 5.5.5 5.5.6 5.5.7 L’Algoritmo di Douglas Peucker . Proiezione in Altezza . . . . . . . Visualizzazione delle Informazioni Attraversamento Aereo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 53 53 55 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 57 58 58 60 7 Il Sistema di Navigazione 7.1 Camera Navigation Basata su Target . . . . . . . . . . . . . . . . . . . . 63 64 8 Natural User Interface 8.1 L’Evoluzione delle Interfacce Utente: da CLI a NUI . . . . . . . . . . . . 8.2 Struttura della NUI utilizzata . . . . . . . . . . . . . . . . . . . . . . . . 8.2.1 Menù Contestuale . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 67 68 69 9 Conclusioni 9.1 Campi di Impiego . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2 Sviluppi Futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 71 10 Ringraziamenti 73 6 Funzionalità Sociali 6.1 Flickr . . . . . . . . . . . . . . . . 6.1.1 Integrazione . . . . . . . . . 6.1.2 API di Sviluppo . . . . . . . 6.1.3 Filtraggio Basato sul Colore . . . . . . . . . . . . Appendices A Tile Generation 75 B BIL Reading 87 C Mesh Generation 89 D Flickr Photo Downloader 101 8 Elenco delle figure 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Features offerte dal progetto SMART-ISLANDS. . . . . . . . . . . . . . . Architettura del progetto SMART-ISLANDS. . . . . . . . . . . . . . . . Editor fornito da Unity3D Game Engine. . . . . . . . . . . . . . . . . . . Esempio di una richiesta relativa alle funzionalità esposte dal server WMS. File in formato XML in risposta alla richiesta di GetCapabilities. Come si può notare sono presenti tag atti a descrivere nel dettaglio il tipo di dato esposto dal servizio, come ad esempio: il formato dell’immagine che può essere generato, una descrizione, il tipo di proiezione e l’area di validità della richiesta (Bounding Box) . . . . . . . . . . . . . . . . . . . . . . . . Esempio di una richiesta relativa ad un’immagine in formato PNG del layer ‘mapName’ della porzione di territorio nel bounding box 45-11/46-12. . . Esempio di visualizzazione 3D del modello digitale di elevazione realizzato attraverso l’interpolazione di isoipse. . . . . . . . . . . . . . . . . . . . . Esempio di una richiesta relativa ad un file di altezze in formato Bil16 della porzione di territorio nel bounding box 45-11/46-12. . . . . . . . . . . . . Esempio di un file BIL mappato su una porzione di territorio. . . . . . . Esempio di geometria. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Esempio di rappresentazione spaziale. . . . . . . . . . . . . . . . . . . . . Raffigurazione dello strato intermedio rappresentato da PHP. . . . . . . . Class Diagram riguardante la classe Tile. . . . . . . . . . . . . . . . . . . Esempio di struttura gerarchica delle Tile. . . . . . . . . . . . . . . . . . Frustum culling relativo ad una generica camera. . . . . . . . . . . . . . Frustum culling, caso reale. . . . . . . . . . . . . . . . . . . . . . . . . . Funzionamento dell’algoritmo di LOD. . . . . . . . . . . . . . . . . . . . Dettaglio del macro blocco riguardante la visualizzazione delle Tile. . . . Esempio di Tile senza skirts. . . . . . . . . . . . . . . . . . . . . . . . . . Esempio di Tile con skirts. . . . . . . . . . . . . . . . . . . . . . . . . . . Costruzione di un layer: si parte dalla duplicazione della mesh base con applicazione della texture, sucessivamente si passa alla sovrapposizione degli oggetti. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sequenza di azioni effettuate per la visualizzazione di PDI in formato Raster. Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando GameObject. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando BillBoard. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Esempio a runtime di visualizzazione di PDI utilizzando BillBoard. . . . Esempio di Particle Emitter. . . . . . . . . . . . . . . . . . . . . . . . . . 3D-Meteo: sole, pioggia e neve. . . . . . . . . . . . . . . . . . . . . . . . Esempio di una richiesta di GeoCoding dell’indirizzo: via Sommarive 14, Povo (TN). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XML in risposta alla richiesta effettuata in figura 28. . . . . . . . . . . . Esempio rappresentazione di un percorso secondo il formato GPX. . . . . Esempio LINESTRING di tipo semplice. . . . . . . . . . . . . . . . . . . Esempio LINESTRING di tipo complesso. . . . . . . . . . . . . . . . . . 9 3 4 8 11 11 12 13 13 14 15 15 18 20 22 23 24 25 27 29 29 32 34 35 36 37 42 42 43 43 45 46 47 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 Vertici ausiliari per la costruzione della mesh riguardante un percorso. . . Triangolarizzazione dei vertici ausiliari ottenuti in precedenza. . . . . . . Algoritmo di Douglas-Peucker in esecuzione. . . . . . . . . . . . . . . . . Esempio di costruzione dinamica della mesh di un percorso: i triangoli verdi costituiscono il collider generato sui punti filtrati, in blu il LineRendereer utilizzante il dataset originale. . . . . . . . . . . . . . . . . . . . . . . . . Esempio di costruzione dinamica della proiezione in altezza del percorso ‘Altopiano delle Pale’. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Template di riferimento per la visualizzazione delle informazioni riguardanti i percorsi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Esempio a runtime di visualizzaione delle informazioni riguardanti un percorso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Percorso completo di tragitto e punti di interesse. . . . . . . . . . . . . . Fotogramma dell’animazione riguardante l’attraversamento aereo di un percorso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . In rosso il perimetro della zona effettiva delle Tile, il restante spazio nero rappresenta le zone in ombra facente parte del bounding box . . . . . . . Punti di interesse fotografici posizionati su zone in ombra . . . . . . . . . Punti di interesse fotografici filtrati in base al colore . . . . . . . . . . . . Interfacce per l’acquisizione dati di movimento. . . . . . . . . . . . . . . Esempio di struttura gerarchica. . . . . . . . . . . . . . . . . . . . . . . . Struttura gerarchica del sistema di navigazione utilizzato. . . . . . . . . . Evoluzione delle interfacce utente. . . . . . . . . . . . . . . . . . . . . . . Screenshot raffigurante la struttura della NUI progettata in modalità all-off. Screenshot raffigurante la struttura della NUI progettata in modalità all-on. Menù contestuale riferito ad un percorso. . . . . . . . . . . . . . . . . . . 10 48 49 51 52 53 54 55 55 56 60 61 61 63 64 65 67 69 69 70 1 Introduzione Il corso di laurea in Informatica, dell’Università degli studi di Trento, prevede che, prima del conseguimento del titolo scelto, lo studente effettui uno stage formativo all’interno della facoltà stessa o presso un’azienda esterna. A differenza di quanto ho scelto ormai due anni fà per il conseguimento della Laurea Triennale in Informatica, per la laurea Magistrale, ho deciso di affidarmi ad una fondazione di ricerca che si è fatta notare negli anni, per gli splendidi lavori realizzati nel campo della Computer Grafica, settore che da sempre mi ha affascinato. La Fondazione GraphiTech, Center for Advanced Computer Graphics Tecnologies, è stata costituita nel 2002 come una joint venture fra la Fondazione INI-GraphicsNet1 , l’Istituto Trentino di Cultura2 e l’Università degli studi di Trento3 al fine di condurre attività di ricerca e sviluppo su ‘Advanced Computer Graphics’ (ACG), ‘Virtual Reality’ (VR), ‘Mixed Reality’ (MR) ed infine dalle tecnologie dell’informazione e comunicazione ‘Information and Communication Systems’ (ICS), allo scopo di favorire il trasferimento tecnologico e lo sviluppo della cultura imprenditoriale in questo settore disciplinare, adattando il Know-How acquisito all’area della Provincia Autonoma di Trento, ma senza che ciò comporti limitazioni territoriali. A tal fine, Graphitech partecipa anche a progetti finanziati dall’Unione Europea (EU), dalla Repubblica Italiana (RI) e da altri enti ed organizzazioni, sia pubblici che privati, che mirano a favorire il trasferimento tecnologico alle imprese favorendone avviamento e sviluppo. 1 http://www.inigraphics.net/ http://www.fbk.eu/ 3 http://www.unitn.it/ 2 1 2 2 Il Progetto Il progetto sviluppato e oggetto di questo documento, ha come aree di dominio due scienze perfettamente integrate fra loro: l’Human Computer Iterperfaction, scienza che studia le tecniche di iterazione fra uomo e macchina, Computer Graphics. Il lavoro svolto, infatti, costituisce una parte molto importante del progetto Europeo chiamato Smart-Islands4 , in cui la Fondazione Graphitech ha assunto un ruolo leader, che tratta lo sviluppo di un applicazione per dispositivi mobili per la visualizzazione di informazioni territoriali, disposte su opportuni modelli tridimensionali, delle principali isole del Mar Mediterraneo, in maniera tale da offrire informazioni e supporto in un contesto prevalentemente turistico. I servizi offerti spaziano dalla conformazione territoriale dell’isola stessa, meteo, punti di interesse, mappe e tutto ciò che potrebbe essere utile ad un qualsiasi turista, con l’aggiunta di una serie di funzionalità dedicate agli addetti ai lavori, quali: statistiche territoriali, particelle catastali e via dicendo. Figura 1: Features offerte dal progetto SMART-ISLANDS. Un’isola è una particolare tipologia di terreno con confini ben definiti, chiusi, con una capitale ed un network di piccole città e paesi. La scarsa disponibilità di informazioni 4 http://www.smart-islands.eu 3 spaziali riguardanti le sue infrastrutture, l’ambiente urbano e socio-economico sono fattori critici per un qualsiasi luogo che fa del turismo la sua ragion d’essere. Le isole del Mar Mediterraneo sono solitamente numerose, di piccole dimensioni e con infrastrutture che non sono rese note facilmente al visitatore. Il progetto Europeo Smart-Islands ha come punto chiave quello di offrire supporto a tali attività, in maniera tale da migliorare l’economia dell’intera zona interessata. L’architettura di Smart-Islands si basa sull’utilizzo di informazioni reperite online da diversi server appositamente creati, i quali offriranno informazioni in tempo reale su svariate categorie, le quali andranno poi visualizzate sul dispositivo mobile ove l’applicazione è in esecuzione. La figura 2 mostra una chiara visione dell’architettura a tre livelli del progetto SmartIslands. Figura 2: Architettura del progetto SMART-ISLANDS. 2.1 Application Level Il primo strato consiste nel client Smart-Islands. Il livello applicazione sarà composto da una serie di componenti modulari, uniti da un interfaccia avanzata il cui ruolo è quello di facilitare l’iterazione con le funzionalità offerte dallo strato inferiore. La scelta di decetrare parte della business logic dal livello preso in esame a quello inferiore garantisce flessibilità 4 e scalabilità, infatti, in questo modo risulterà molto semplice creare le stesse versioni del software per diverse piattaforme, limitando al minimo i cambiamenti necessari. 2.2 Middleware Level Il livello intermedio, chiamato middleware level, è a sua volta formato da due insiemi di componenti: il primo è composto da una lista di componenti statici accessibili da remoto in base alle features abilitate dall’utente. Il secondo blocco contiene un insieme di script di gestione delle richieste in arrivo dall’application level, che devono essere smistate al livello sottostante. 2.3 Data Level Al livello inferiore il framework Smart-Islands offre una serie di repository di dati e informazioni aggiornate in tempo reale, o in maniera piuttosto frequente, da provider esterni. L’obiettivo di tale livello è quello di offrire supporto per l’accesso ai database principali dei provider dei servizi lasciando a loro il mantenimento dei dati. 5 6 3 Lo Stato dell’Arte Con la locuzione stato dell’arte si intende il più alto livello di sviluppo o conoscenza finora raggiunto da una tecnologia o da un campo d’indagine scientifico, sinonimo di ‘all’avanguardia’, ‘dell’ultima generazione’, ‘eccellenza’. In questo capitolo verranno elencati e descritti tutte le componenti allo stato dell’arte che sono state utilizzate per il raggiungimento dell’obiettivo prefissato. Le tecnologie che sono stati impiegate sono quattro: 1. Un Game Engine di alto livello per lo sviluppo delle componenti tridimensionali (Unity3D); 2. un WMS Server utilizzato per la trasmissione di dati DTM5 , Digital Terrain Model ed immagini georeferenziate (NASA World Wind Server); 3. un GeoDatabase per il contenimento e trasmissione di dati georeferenziati; 4. un linguaggio di scripting, utilizzato per la programmazione Web, ovvero la realizzazione di pagine web dinamiche (PHP). 5 http://it.wikipedia.org/wiki/Modello_digitale_di_elevazione 7 3.1 Unity3D Game Engine Unity3D6 è un authoring tool sviluppato per la creazione di videogames in tre dimensioni ed applicazioni multicontenuto interattive. Unity ha la particolarità di poter essere eseguito sia su macchine equipaggiate con Microsoft Windows7 che Mac OS X8 e consiste essenzialmente in un editor grafico di sviluppo/design accoppiato ad un Game Engine di alto livello. Le ragioni per le quali Unity è stato scelto, come abiente di sviluppo, a discapito di altri Game Engine concorrenti sono le seguenti: • Ambiente di sviluppo integrato con gerarchie di oggetti, editor grafico visuale, inspector dettagliato dei vari elementi instanziati e preview del prodotto finale live; Figura 3: Editor fornito da Unity3D Game Engine. • Possibilità di compilazione dello stesso progetto per varie piattaforme: 1. Microsoft Windows; 2. Mac OS X; 6 http://unity3d.com/ http://www.microsoft.com/ 8 http://www.apple.com/it/macosx/ 7 8 3. Web Player via Unity Web Player plugin disponibile per Internet Explorer, Firefox, Safari, Mozilla, Netscape, Opera, Google Chrome e Camino; 4. Nintendo Wii; 5. iPhone/iPad; 6. Google Android; 7. Microsoft Xbox 360; 8. Sony playstation 3. • Compatibilità con i più comuni formati di modelli 3D/2D (3ds, obj, fbx, psp, tiff); • Supporto di speciali effetti grafici quali particle engine, bump, reflection mapping, dynamic shadows e shaderLab language (Renderer Engine); • Motore fisico integrato, basato su NVIDIA PhysX; • GUI System, chiamato UnityGUI. UnityGUI è un sistema di sviluppo integrato utilizzato per la creazione di interfaccie utente; • Engine per la costruzione di terreni e relativa vegerazione; • Scripting sviluppato via Mono, l’implementazione open source del framework .NET. Gli sviluppatori possono programmare tramite UnityScript (un linguaggio di scripting molto simile al JavaScript), C# oppure Boo (Python-inspired syntax); • Motore grafico utilizzante Direct3D (Windows), OpenGL (Mac, Windows), OpenGL ES (iOS, Android). • Versione ‘Educational’ di Unity3D scaricabile gratuitamente dal sito di riferimento. Per questa serie di motivi tecnico/tecnologici, uniti alla semplicità di apprendimento all’uso del Game Engine stesso e alla velocità di sviluppo, Unity3D è stato scelto come componente fondamentale per lo sviluppo del progetto finale. 9 3.2 NASA World Wind Server NASA World Wind Server9 è la versione server del celebre NASA World Wind, globo virtuale Open Source sviluppato dalla NASA e dalla comunità open source per l’uso su personal computers, per la trasmissione di informazioni geografiche secondo protocollo WMS10 , acronimo che stà per Web Map Service. Un server WMS produce dinamicamente mappe di dati spazialmente riferiti a partire da informazioni geografiche. Questo standard definisce una mappa come rappresentazione di informazioni geografiche restituendo un’immagine digitale idonea. Generalmente le mappe prodotte da un servizio WMS sono restituite in un formato immagine quale PNG, GIF o JPEG. Lo standard WMS definisce tre operazioni: 1. Restituisce metadati a livello di servizio; 2. Restituisce una mappa dai parametri geografici e dimensionali definiti; 3. Restituisce informazioni sugli oggetti della cartografia visualizzata (opzionale). Le operazioni del Web Map Service vengono invocate usando un client che supporti il protocollo HTTP, in forma di Uniform Resource Locators (URL). Le operazioni richieste al server vengono specificate nell’URL. Nel dettaglio, sono due i tipi di richieste che sono state utilizzate nel contesto del progetto di tesi descritto: GetCapabilities e GetMap. 3.2.1 GetCapabilities La prima delle due richieste che verranno analizzate riguarda quella denominata ‘GetCapabilities’, la quale serve a fornire al richiedente la lista delle funzionalità abilitate sul server WMS interrogato. Attraverso questa lista, formattata secondo lo standard XML11 è possibile ottenere la lista dei layers disponibili, il formato delle immagini in output supportate, le aree ove i dati sono disponibili (bounding box) e le descrizioni in linguaggio naturale delle informazioni esposte. Un esempio di richiesta GetCapabilities è mostrata in figura numero 4 mentre la figura 5 mostra il file in formato XML in risposta ad una richiesta. 9 http://worldwind.arc.nasa.gov/ http://it.wikipedia.org/wiki/Web_Map_Service 11 http://it.wikipedia.org/wiki/XML 10 10 Figura 4: Esempio di una richiesta relativa alle funzionalità esposte dal server WMS. Figura 5: File in formato XML in risposta alla richiesta di GetCapabilities. Come si può notare sono presenti tag atti a descrivere nel dettaglio il tipo di dato esposto dal servizio, come ad esempio: il formato dell’immagine che può essere generato, una descrizione, il tipo di proiezione e l’area di validità della richiesta (Bounding Box) 3.2.2 GetMap La seconda richiesta che andremo ad analizzare riguarda quella denominata ‘GetMap’, utilizzata quando si vogliono ottenere immagini di una porzione di territorio. In questo caso, il richiedente specifica nella chiamata HTTP una serie di parametri che verranno successivamente processati dal server WMS in maniera tale da ottenere la corretta immagine in risposta. Le informazioni base che devono essere specificate sono: la porzione dei terreno che deve essere rappresentata, il sistema di coordinate desiderato, il formato e le dimensioni dell’immagine di output. L’immagine 6 mostra una tipica chiamata HTTP al server WMS chiedendo in risposta un’immagine in formato PNG. Qualora due o più mappe siano prodotte con gli stessi parametri geografici e di dimensione dell’immagine, i risultati possono essere sovrapposti per produrre una mappa composita. L’uso di formati che supportano la trasparenza (GIF o PNG per esempio) permette di visualizzare le parti di mappa sottostanti; inoltre mappe diverse possono es11 Figura 6: Esempio di una richiesta relativa ad un’immagine in formato PNG del layer ‘mapName’ della porzione di territorio nel bounding box 45-11/46-12. sere richieste a differenti server. In questa maniera il Web Map Service abilita la creazione di una rete di server cartografici che l’utente può utilizzare per costruire mappe personalizzate. Parallelamente alla distribuzione (streaming) di immagini, Nasa World Wind Server permette l’esposizione di risorse chiamate ‘elevation files’ o DTM che, come il nome stesso suggerisce, riguardano le altezze di una particolare zona, specificata nella chiamata HTTP nella stessa maniera in cui vengono richieste le immagini. Un modello digitale di elevazione (anche noto come DEM, dall’inglese Digital Elevation Model) è la rappresentazione della distribuzione delle quote di un territorio, o di un’altra superficie, in formato digitale. Il modello digitale di elevazione viene in genere prodotto in formato raster associando a ciascun pixel in un’immagine satellitare l’attributo relativo alla quota assoluta. Il DTM può essere prodotto con tecniche diverse. I modelli più raffinati sono in genere realizzati attraverso tecniche di telerilevamento che prevedono l’elaborazione di dati acquisiti attraverso un sensore montato su un satellite, un aeromobile o una stazione a terra. Ad esempio, analizzando il segnale di fase registrato da un Radar ad Apertura Sintetica (SAR: Synthetic Aperture Radar) installato su un satellite, è possibile produrre un modello digitale di elevazione. Una tecnica delle tecniche per la produzione di DEM consiste nell’interpolazione delle curve di livello che possono essere prodotte anche attraverso il rilevamento diretto sul terreno. Nell’ immagine 7 è possibile osservare un esempio di visualizzazione 3D del modello digitale di elevazione realizzato attraverso l’interpolazione di isoipse. L’immagine 8 mostra una tipica chiamata HTTP al server WMS richiedendo un file DTM in risposta in formato Bil16. Il formato file BIL è un formato non-standard per la trasmissione di dati DEM (Digital Elevation Model) utilizzato in Nasa World Wind il cui scopo è quello di offrire informazioni riguardanti le altezze di una specifica porzione di territorio. Ogni file BIL è composto 12 Figura 7: Esempio di visualizzazione 3D del modello digitale di elevazione realizzato attraverso l’interpolazione di isoipse. Figura 8: Esempio di una richiesta relativa ad un file di altezze in formato Bil16 della porzione di territorio nel bounding box 45-11/46-12. da un bitmap la cui risoluzione dipende dai parametri ‘altezza’ e ‘larghezza’ specificati nella chiamata al server WMS Nasa World Wind. Ogni pixel che compone l’immagine in risposta, codificato opportunamente come intero a 16 bit (bil16), descrive l’altezza in metri sul livello del mare di un preciso punto nello spazio. Per comprendere al meglio come utilizzare un file BIL per la rappresentazione dei dati relativi alle altezze, facciamo riferimento all’immagine 8: come possiamo osservare, in questa chiamata viene specificata una zona che va da 45 a 46 gradi di longitudine e da 11 a 12 gradi di latitudine. La porzione di territorio viene descritta da una matrice di pixel 8x8, che andrà mappata sul terreno come mostrato in figura 9. Richiedendo al server WMS file BIL più fitti in termini di densità di punti è possibile rappresentare porzioni di territtorio con maggiore fedeltà ripetto al mondo reale. 13 Figura 9: Esempio di un file BIL mappato su una porzione di territorio. 3.3 PostGIS GeoDatabase Un database spaziale (Spatial Database Management System o SDBMS) è un database ottimizzato per memorizzare e interrogare i dati relativi agli oggetti nello spazio, includendo punti, linee e poligoni. A differenza dei tipici database relazionali, in grado di analizzare vari tipi di caratteri numerici e di dati, in questo nuovo tipo di database sono necessarie funzionalità aggiuntive, per processare i nuovi tipi di dati, chiamate geometrie o funzionalità. Gli indici che i database tradizionali usano per velocizzare la ricerca di dati e di valori, nel caso dei database spaziali sono chiamati indicispaziali (le tipologie più usate sono gli R-tree). Oltre alle classiche tipologie di query (come ad esempio quelle di SELECT), in questo caso ne sono presenti anche delle altre come, ad esempio, funzioni di misurazione delle distanze, e di calcolo delle aree. Il termine database spaziale è entrato in uso solo negli ultimi anni in seguito ad un ciclo di conferenze, chiamato ‘Symposium on Large Spatial Databases (SSD)’, a partire dal 1989; e sta proprio ad indicare che tale database sia un contenitore di oggetti nello spazio invece che di immagini o figure di uno spazio. Nel corso degli anni, con lo sviluppo di applicazioni sempre più complesse, le quali sono 14 andate avanti di pari passo con la disponibilità di componenti hardware più performanti ad un prezzo sempre più contenuto, si è sentita la necessità di creare questa nuova tipologia di database, in quanto ci si era resi conto di avere un’ altra tipologia di dati da dover rappresentare che non poteva essere gestita con i metodi tradizionali, in particolare: 1. Oggetti nello spazio: interessati a diverse entità distinte fra loro, ognuna delle quali ha la proprio descrizione geometrica. La figura 10 mostra un chiaro esempio di descrizione geometrica. Figura 10: Esempio di geometria. 2. Spazio: per descrivere lo spazio stesso, che sarebbe a dire essere in grado di descrivere ogni suo punto, esempio in figura 11. Figura 11: Esempio di rappresentazione spaziale. Un GeoDatabase è un particolare tipo di database spaziale progettato per immagazzinare, interrogare e manipolare informazioni geografiche con bassa dimensionalità. Costituisce infatti un database spaziale particolarmente ottimizzato per informazioni in due o tre dimensioni, dati di tipo raster e distanze Euclidee. 15 Utilizzando un Geodatabase, possono essere salvate strutture dati Vettoriali come punti, linee o poligoni, opportunamente georeferenziati. Un qualsiasi record di un GeoDatabase può utilizzare geometrie come tipo di dato, per la rappresentazione di luoghi o oggetti appartenenti al mondo fisico, oltre che mantenere le classiche funzionalità di un database relazionale. La maggior parte dei GeoDatabase uniscono infatti le funzionalità per le interrogazioni spaziali al classico motore SQL utilizzato dalla maggior parte dei database relazionali, in questo modo è possibile effettuare query del tipo ‘trova tutti i residenti in una determinata area che sono nati dal 1984’. Ll GeoDatabase utilizzato è stato PostGIS, una particolare estensione al database relazione PostgreSQL, che lo abilita all’interrogazione e manipolazione di strutture dati spaziali (GIS). 16 3.4 PHP: Hypertext Preprocessor PHP: Hypertext Preprocessor, comunemente chiamato PHP, è un linguaggio di scripting interpretato, originariamente concepito per la programmazione Web, ovvero la realizzazione di pagine web dinamiche. Attualmente è utilizzato principalmente per sviluppare applicazioni web lato server ma può essere usato anche per script a riga di comando o applicazioni stand-alone con interfaccia grafica. L’elaborazione di codice PHP sul server produce codice HTML da inviare al browser dell’utente che ne fa richiesta. Il vantaggio dell’uso di PHP rispetto al classico HTML deriva dalle differenze profonde che sussistono tra Web dinamico e Web statico. 3.4.1 PHP: Cenni Storici Nato nel 1994 ad opera del danese Rasmus Lerdorf, PHP era in origine una raccolta di script CGI che permettevano una facile gestione delle pagine personali. Il significato originario dell’acronimo era Personal Home Page. Il pacchetto originario venne in seguito esteso e riscritto dallo stesso Lerdorf in C, aggiungendo funzionalità quali il supporto al database mSQL e prese a chiamarsi PHP/FI, dove FI stava per Form Interpreter (interprete di form), prevedendo la possibilità di integrare il codice PHP nel codice HTML in modo da semplificare la realizzazione di pagine dinamiche. In quel periodo, 50.000 domini Internet annunciavano di aver installato PHP. A questo punto il linguaggio cominciò a godere di una certa popolarità tra i progetti open source del web, e venne così notato da due giovani programmatori: Zeev Suraski e Andi Gutmans. I due collaborarono nel 1998 con Lerdorf allo sviluppo della terza versione di PHP (il cui acronimo assunse il significato attuale) riscrivendone il motore che fu battezzato Zend da una contrazione dei loro nomi. Le caratteristiche chiave della versione PHP 3.0, frutto del loro lavoro, erano la straordinaria estensibilità, la connettività ai database e il supporto iniziale per il paradigma a oggetti. Verso la fine del 1998 PHP 3.0 era installato su circa il 10% dei server web presenti su Internet. La versione 4 di PHP venne rilasciata nel 2000 e prevedeva notevoli migliorie. Attualmente siamo alla quinta versione, sviluppata da un team di programmatori, che comprende ancora Lerdorf, oltre a Suraski e Gutmans. La popolarità del linguaggio PHP è in costante crescita grazie alla sua flessibilità: nel Giugno 2001, ha superato il milione di siti che lo utilizzano. Nell’ottobre 2002, più del 45% dei server Apache usavano PHP. Nel 2005 la configurazione LAMP (Linux, Apache, MySQL, PHP) supera il 50% del totale dei server sulla rete mondiale. Nel 2008 PHP 5 è diventata l’unica versione stabile in fase di sviluppo. A partire da PHP 5.3.0, PHP implementa una funzione chiamata ‘late static binding’ che può essere utilizzata per fare riferimento alla classe chiamata in un contesto di eredità statica. A partire dal 5 febbraio 2008, a causa 17 dell’iniziativa GoPHP5, sostenuta da una serie di sviluppatori PHP, molti dei progetti open-source di alto profilo cessano di supportare PHP 4 nel nuovo codice e promuovono il passaggio da PHP 4 a PHP 5. 3.4.2 Perchè PHP, le limitazioni di Unity3D Come accennato in sezione 3.1, uno degli enormi punti di forza che hanno fatto sì che la scelta del GameEngine da utilizzare sia caduta su Unity3D, stà nella possibilità di compilare lo stesso codice per una varietà molto ampia di piattaforme. Purtroppo però, è garantita la compatibilità di esecuzione delle funzioni per le sole operazioni incluse nel Core stesso del GameEngine. Ciò vuol dire che, se volessimo utilizzare una libreria esterna, o una funzione base della classe System di C Sharp, sulla versione per PC Desktop il software prodotto potrebbe non avere problemi in esecuzione, ma ciò potrebbe non essere vero per le versioni eseguite su dispositibi mobili. E’ stata quindi una delle maggiori criticità quella di generare codice multipiattaforma che potesse essere eseguito senza problemi su qualsiasi dispositivo. Uno dei limiti attualmente esistenti del sistema Unity3D, è quello di non offrire funzionalità all’interno del suo Core per la connessione ed interrogazione ai Database, sia in locale che in remoto. Per questo motivo è stato scelto di creare uno strato intermedio fra dati e client, del quale abbiamo già avuto modo di parlare in sezione sec:phpl evel, dedicatoall0 interscambiodiinf ormazionif ral0 applicazionef inaleedildatalevel.Inaltrepar Loschemadellastrutturaprogettatarappresentatainf igura12. Figura 12: Raffigurazione dello strato intermedio rappresentato da PHP. 18 4 Il modello basato su Tile Il primo problema che bisogna affrontare quando si progettano applicazioni che producono in tempo reale terreni tridimensionali è strettamente legato alla generazione dinamica, ed efficiente, degli elementi costituenti il mondo virtuale ottenuto come risultato finale. Il modello più efficiente per la generazione di modelli basati su informazioni geografiche è quello a Tile, dove Tile è un termine in lingua Inglese che, in Italiano, potrebbe essere tradotto con buona fedeltà dalla parola ‘tassellazione’. Il modello a Tile propone quindi una suddivisione del territorio, dove ogni componente, o tassello, è chiamato appunto Tile. 4.0.3 Il Zero Level Tile Delta Prima di iniziare con la definizione della struttura a Tile, è necessario che il lettore padroneggi alcuni concetti base, fra i quali quello di Zero Level Tile Delta, che da qui in avanti sarà abbreviato con l’acronimo ZLTD. Il ZLTD è il valore che risponde alla seguente domanda ‘Che area in gradi viene coperta da una Tile di livello zero?’. Detta in questa maniera, è normale che ci si possa sentire un pò spaesati, facciamo ulteriore chiarezza, definendo inanzitutto il significato di Tile di livello zero: esse non sono altro che le foto aeree di una porzione di territorio alla minor risoluzione disponibile, in altre parole, quelle che, scattate da una maggior altitudine, corrispondono ad un livello di dettaglio definito per conformità a zero. Avendo ora compreso cosa si intende per Tile di livello zero passiamo ora con la definizione dei gradi che esse ricoprono, per avere una completa comprensione del concetto di ZLTD. Com’è facilmente intuibile, la dimensione di ogni tassello non è impostata casualmente, ma secondo una rigorosa logica, che si basa sui gradi che essi vanno a coprire della superficie terrestre. Tile molto piccole e quindi molto definite, copriranno zone di dimensioni ridotte mentre quelle ad una minor risoluzione rappresenteranno spazi più ampi. Il ZLTD non è altro che il numero di gradi della superficie terrestre delle Tile di livello zero. E’ possibile avere un’idea del chilometraggio della superficie coperta da ogni orthophoto utilizzando la seguente formula matematica: P 360 zltd Dove P è il perimetro della terra (40.000km circa se preso all’equatore, non essendo il globo perfettamente sferico), 360 sono i gradi totali e ZLTD è il zero level Tile Delta. Per 19 fare un esempio, supponendo di avere Tile di livello zero ad un grado (come quelle del dataset di test utilizzato), ogni immagine alla sua minor definizione coprirà circa 111km del perimetro terrestre all’ equatore. 4.1 Struttura delle Tile Il Tiling, è quindi il concetto fondamentale sul quale tutto l’algoritmo di visualizzazione ruota, ed è stata creata una classe apposita per l’immagazzinamento delle informazioni riguardanti ogni tassello. Figura 13 mostra il Class Diagram riguardante la classe Tile. Figura 13: Class Diagram riguardante la classe Tile. Come abbiamo avuto modo di osservare, ogni Tile contiene una grande quantità di informazioni, le più importanti definite nell’elenco sottostante: • public int level : rappresenta il livello in cui la Tile risiede. • public int tileX : rappresenta il codice della Tile in X, calcolato con (int) longitude + 180 zltd 2l • public int tileY : rappresenta il codice della Tile in Y, calcolato con (int) latitude + 90 zltd 2l 20 • public float GLOBALSCALE : rappresenta il fattore di scala utilizzato, parametro del quale parleremo ampiamente in sezione 5.2.5. • public float latitude : esprime la latitudine del punto in basso a sinistra della Tile. • public float longitude : esprime la longitudine del punto in basso a sinistra della Tile. • public float size : dimensione della Tile, calcolata in base a livello e zltd. La dimensione di ogni Tile viene calcolata con zltd ∗ GLOBALSCALE 2l • public float shift : parametro identico a size, duplicato per una miglior lettura del codice. • public float zltd : contiene il valore riguardante il Zero Level Tile Delta, parametro del quale abbiamo parlato in sezione 4.0.3 • public Vector3 tileLLPoint : indica il punto in basso a sinistra della Tile, espresso come vettore in tre dimensioni. • public Vector3 tileULPoint : indica il punto in alto a sinistra della Tile, espresso come vettore in tre dimensioni. • public Vector3 tileLRPoint : indica il punto in basso a sinistra della Tile, espresso come vettore in tre dimensioni. • public Vector3 tileURPoint : indica il punto in alto a sinistra della Tile, espresso come vettore in tre dimensioni. • public Vector3 tileMidPoint : indica il punto centrale della Tile, espresso come vettore in tre dimensioni. • public float boundingSphereRadius : rappresenta il raggio della sfera di contenimento della Tile, l’utilizzo di questo dato verrà approfondito in sezione 4.2.1. • public float normalizedTileX : indica la posizione della Tile nella sua Tile di livello 0 in X. Questo dato viene calcolato tramite la formula tileX ∗ zltd ∗ GLOBALSCALE 2l 21 • public float normalizedTileY : indica la posizione della Tile nella sua Tile di livello 0 in Y. Questo dato viene calcolato tramite la formula tileY ∗ zltd ∗ GLOBALSCALE 2l • public string tileCode : codice identificativo della Tile, costituito dalla concetenazione delle stringhe riguardanti livello, tileX e tileY. • public string parentTile : codice della Tile padre. Il Tiling si basa fondamentalmente su un concetto molto semplice ma performante: ogni tassello componente la porzione di mondo rappresentata può essere suddiviso in quattro Tile figlie ogni volta che si richiede una maggior fedeltà di riproduzione visiva. Ogni tile risulta essere quindi indipendente, avendo la possibilità di suddividersi a piacimento qualora ce ne fosse la necessità. La figura numero 14 mostra come una Tile viene suddivisa ricorsivamente in quattro Tile figlie. Figura 14: Esempio di struttura gerarchica delle Tile. Il Tiling risulta essere una tecnica molto potente per la gestione di informazioni visive riguardanti il territtorio, tant’è che i più famosi geoVisualizzatori, come per esempio Google Earth12 o lo stesso Nasa World Wind versione Client basano i loro algoritmi proprio su questo principio. Avendo acquisito le nozioni basilati riguardanti la tassellazione ed il suo funzionamento, la domanda che nasce è la seguente: ‘In base a che parametri ogni Tile deve essere 12 http://www.google.com/intl/it/earth/ 22 suddivisa, dando vita quindi ai suoi quattro figli, che a loro volta possono suddividersi o meno?’. Il problema sopra esposto prende il nome di Level Of Detail, o, usando un acronimo, LOD. 4.2 Level Of Detail Prima di ragionare sulla risposta al problema del LOD, i concetti fondamentali che il lettore deve necessariamente padroneggiare sono due: il concetto di Frustum Culling e quello di Divisibilità. 4.2.1 Frustum Culling Il frustum culling è una tecnica di culling basata sul volume visivo della matrice di proiezione utilizzata per la trasformazione delle coordinate. Una matrice di proiezione prospettica genera un frustum, che può essere definito da un’insieme di sei piani relativi alla camera interessata: Left, Right, Top, Bottom, Near, Far. Figura 15: Frustum culling relativo ad una generica camera. Conoscendo i sei piani che definiscono il frustum è possibile stabilire se un qualsiasi punto nello spazio è all’interno del frustum stesso, e quindi visibile o meno, semplicemente controllando la distanza fra l’oggetto interessato e i sei piani definiti precedentemente. Un esempio reale del risultato di questa tecnica è visibile in immagine . 23 Figura 16: Frustum culling, caso reale. 4.2.2 Divisibilità Il concetto di divisibilità è strettamente legato alla distanza che intercorre fra il centro di ogni Tile e il nostro punto di vista (la camera utilizzata). Conoscere la distanza che separa questi due elementi è fondamentale per decidere quando suddividere ogni elemento per aumentarne la risoluzione o, al contrario, compattare più figli in caso ci si stia allontanando dal nostro oggetto di riferimento. 4.2.3 L’algoritmo di LOD Avendo acquisito un background necessario per la completa comprensione dell’algoritmo di LOD, passiamo ora alla visualizzazione di un semplice diagramma che ne schematizza il funzionamento, visibile in figura 17. Come la stessa immagine suggerisce, l’algoritmo che si prende in carico la gestione del Level Of Detail è gestito ricorsivamente, secondo i seguenti steps: 1. In input viene passata una qualsiasi Tile, una struttura dati contenente i parametri fondamentali per la gestione della stessa quali latitudine, longitudine, codice, zltd, ecc; 2. Successivamente la Tile in input viene sottoposta al check di visibilità, tramite la tecnica del Frustum Culling analizzata nella sezione 4.2.1. A questo punto le strade da prendere sono le seguenti: 24 Figura 17: Funzionamento dell’algoritmo di LOD. (a) Se la Tile non è visibile allora non ha senso procedere con ulteriori passi, l’algoritmo viene interrotto; (b) Altrimenti passa allo step successivo, ossia il test di divisibilità. 3. Test di divisibilità, basato su quanto detto in sezione 4.2.2. Anche in questo caso, l’esito del test garantisce il proseguo dell’algoritmo su due strade differenti: (a) Se la distanza che intercorre fra la Tile e la camera non richiede una visualizzazione di dati ad una più alta risoluzione, allora visualizza la Tile in input; (b) Altrimenti (passo ricorviso) dividi l’input in quattro figli (Lower_Left - Lower_Right - Upper_Left - Upper_Right) e, su ciascuno di essi, chiama l’algoritmo di LOD passando come input ciascun figlio creato. In questo modo la sola visualizzazione di elementi ad una corretta risoluzione è garantita. Inoltre è bene evidenziare che non tutte le Tile possono essere mostrate alla medesima definizione: pensiamo per esempio ad una porzione di terreno esplorata ad una qualsiasi angolazione, in questo caso le Tile più vicine avranno una maggior definizione, mentre quelle più lontane saranno visualizzate in minor qualità. 25 Lo scopo dell’algoritmo di LOD è quindi quello di ottimizzare le performance in base alla percezione della definizione che si ha guardando oggetti posti a distanze differenti: sarebbe inutile renderizzare una geometria posta molto lontano ad una risoluzione pari a quella degli oggetti in primo piano, in quando l’occhio umano non ne percepirebbe la differenza. Costruendo modelli tridimensionali distanti a minor qualità si ottimizzano le performance mantenendo lo stesso risultato estetico finale. Concludendo la sezione riguardante l’algoritmo di LOD, è molto importante comprendere che esso viene eseguito ad ogni frame, in maniera tale da avere una situazione costantemente aggiornata di ciò che si deve renderizzare. 4.3 Costruzione Dinamica delle Mesh ‘Una mesh poligonale è una collezione di vertici, spigoli e facce che definiscono la forma di un oggetto poliedrico nella computer grafica 3D e nella modellazione solida. Le facce consistono solitamente di triangoli, quadrilateri od altri semplici poligoni convessi, dal momento che ciò semplifica il rendering, ma possono essere composti anche da poligoni concavi più generici, o poligoni con buchi. Le mesh sono primitive grafiche che consentono di risolvere con grande efficienza i procedimenti di visualizzazione delle forme modellate: sono strisce di triangoli o maglie di quadrilateri con cui rappresentiamo un poliedro qualsiasi o con cui approssimiamo superfici curve.’ 13 Uno dei punti di forza del sistema di geoVisualizzazione creato è quello di adattarsi completamente, e in tempo reale, alle mappe che identificano la porzione di territorio da visualizzare. Sarebbe un lavoro lunghissimo ed estremamente poco flessibile creare ad-hoc i modelli tridimensioni di ogni Tile in base alla mappa in input, considerando che, passando da un livello all’altro, il numero di tasselli cresce secondo potenza di 2. La cosa migliore da fare è quindi creare on-the-fly le mesh necessarie, partendo dalle informazioni appartenenti ai dataset in ingresso. Facendo riferimento all’immagine 17, ecco come si presenta nel dettaglio, in figura 18, il macro-blocco riguardante la visualizzazione di ogni Tile e conseguente creazione di ogni mesh. Nel dettaglio, ecco i passi che compongono la visualizzazione di ogni Tile: 1. Un primo controllo viene effettuato sull’esistenza o meno del modello tridimensionale di ciascuna Tile da visualizzare in memoria: un sistema di indicizzazione basato su HashMap garantisce un’efficace e rapida reperibilità degli oggetti precedente13 http://it.wikipedia.org/wiki/Mesh_poligonale 26 Figura 18: Dettaglio del macro blocco riguardante la visualizzazione delle Tile. mente creati. In questo modo è possibile tenere in memoria ciò che è stato già creato, utilizzandolo nuovamente se necessario. 2. In base al controllo precedente, vengono prese due strade: la prima, consiste semplicemente nella renderizzazione dell’elemento precedentemente creato, la seconda viene spiegata successivamente. 3. Un altro controllo stabilisce se la texture da applicare sulla mesh è presente in memoria o meno. In caso il check fallisca l’immagine viene scaricata automaticamente da un apposito server. 4. Verificata la presenza della texture in cache si passa alla verifica o download del file BIL relativo alle altezze. La formattazione del file BIL e della richiesta HTTP sono state abbondantemente approfondite in sezione 3.2.2. 5. L’ultimo step riguarda la crezione vera e propria del modello tridimesionale appartenente alla Tile oggetto. 27 4.3.1 Le Skirts Creando dinamicamente mesh appartenenti ad un terreno, è assai probabile che la superficie rappresentata non sia perfettamente piana. A questo punto, una componente di fondamentale importanza per un ottimo risultato finale, è rappresentata dalla precisione dei dati DEM: utilizzando dati ad alta risoluzione, infatti, vengono ridotti i problemi che sorgono naturalmente quando due Tile vengono accostati. Supponiamo per esempio di avere punti con risoluzione a 90 metri (da un elemento all’altro ci sono quindi 90 metri), la stessa offerta dai server NASA per l’intero globo terrestre (dataset dataset SRTM30plus). Supponiamo inoltre di avvicinarci molto al terreno, in prossimità della linea di congiunzione fra due Tile, chiedendo quindi una buona fedeltà di riproduzione del terreno. Cosa succede a questo punto? La risposta è molto semplice: le altezze dei punti collocati sulla linea di congiunzione delle due Tiles saranno distanziati da 90 metri, risoluzione che potrebbe creare molti problemi se si stà visualizzando una zona montuosa (in tale distanza potrebbe esserci un dislivello importante). Un tipico esempio è mostrato in figura 19. Un altro problema noto riguarda l’accostamento di Tile a diversa risoluzione, in quanto l’algoritmo di LOD, del quale abbiamo parlato in sezione 4.2.3, gestisce l’aumento o la diminuzione della risoluzione in base alla distanza di ciascun elemento dal punto di vista in uso. Prendiamo nuovamente come esempio la figura 14: come è possibile osservare, l’elemento numero 1 genera i figli numero 2-3-4-5. Anche se la dimensione di ogni mesh figlia è la metà, sia in altezza che in larghezza, rispetto a quella padre, il numero di punti che la costituisce rimane invariato, indipendente dalla risoluzione in uso. Quindi, quando ogni Tile viene suddivisa, il numero di punti che definiscono la stessa porzione di territorio viene elevato al quadrato rispetto al tassello padre. Pare quindi chiaro che, se due elemementi a diversa risoluzione vengono accostati, verranno evidenziati problemi sulla linea di congiunzione delle due mesh, dovuti alla differenza di risoluzione in punti (doppia) di un elemento rispetto ai due adiacenti. Utilizzare dati ad alta risoluzione potrebbe quindi attenuarne l’effetto, ma non sempre questa tipologia di dataset sono disponibili. Esistono comunque varie soluzioni al problema, quello che è stato scelto in questo contesto è rappresentato dalle skirts. Le skirts non sono altro che delle geometrie aggiuntive collocate ai bordi di ciascun modello tridimensionale, come se formassero una gonna. Utilizzando dei poligoni aggiuntivi, collocati opportunamente, è possibile coprire i buchi che vanno a formarsi quando la coordinata Y sui bordi delle Tile non coincide perfettamente. Un esempio di skirt è mostrato in figura 20. 28 Figura 19: Esempio di Tile senza skirts. Figura 20: Esempio di Tile con skirts. In appendice, sezione B (BilReader.cs), è possibile analizzare la parte di codice riguardante la lettura dei files DEM in formato BIL16. In appendice, sezione C (MeshGenerator.cs), è possibile analizzare la parte di codice riguardante la costruzione della mesh riguardante ciascuna Tile. 29 30 5 Contenuti Aggiuntivi Il sistema così progettato permette quindi la visualizzazione di un qualsiasi terreno esistente in tre dimensioni. Ma, spesso, questo non è sufficiente: la conoscenza che l’utente riceve si limita alla sola conformazione territoriale, quindi è sorta la necessità di mostrare altri tipi di informazioni riguardanti lo spazio rappresentato, in maniera da incrementare l’user-experience. Per questo sono stati introdotti i seguenti componenti aggiuntivi: 1. I layers; 2. i punti di interesse, PDI; 3. informazioni meteo; 4. il geoCoding; 5. visualizzatore di percorsi. 5.1 I Layers Un tipo di informazione molto utile, e molto gradevole da vedere, è costituita dai layers. Un layer non è altro che un tematismo applicato sul terreno, capace di mostrare le informazioni di più svariate tipologie (dai fiumi, ai confini territoriali, i parchi, le strade e così via). Come abbiamo avuto modo di vedere nella sezione 3.2.2, NASA World Wind Server permette la sovrapposizione di più immagini immagazzinate nella sua memoria ma, per un discorso di cashing (i dati scaricati vengono infatti salvati in memoria, un’immagine formata da più livelli sovrapposti risulta essere poco flessibile per utilizzi futuri), è stato scelto un altro tipo di approccio per l’ottenimento del medesimo risultato. La soluzione utilizzata si basa sulla duplicazione delle sole mesh appartenenti alle sole Tile visibili ad un determinato istante di tempo, l’applicazione della texture del layer alla mesh duplicata e la sovrapposizione dei risultati finali. Il numero di layer che possono essere sovrapposti è potenzialmente infinito, l’unica limitazione è costituita dalle prestazioni della macchina ove il software è in esecuzione. La figura numero 21 mostra un esempio di applicazione di un layer sulla mesh base. In appendice, sezione C (Layer.cs), è possibile analizzare la parte di codice riguardante la duplicazione della mesh di ciascuna Tile. 31 Figura 21: Costruzione di un layer: si parte dalla duplicazione della mesh base con applicazione della texture, sucessivamente si passa alla sovrapposizione degli oggetti. 5.1.1 Il Z-Fighting La sovrapposizione di uno o più layer comporta, nativamente, un problema a cui bisogna far fronte, chiamato in gergo tecnico flicker. Il flicker è un termine proveniente dal mondo dell’elettrotecnica che, tradotto letteralmente, significa ‘sfarfallio’. Lo shader in uso infatti, quando due elementi sono perfettamente sovrapposti, non riesce a capire quale elemento debba essere visualizzato prima o dopo, di conseguenza l’effetto finale sarà appunto quello di uno sfarfallio di texture, dovuto al non corretto ordine di renderizzazione. Per risolvere il problema l’approccio è stato il seguente: 1. Per ogni livello viene creato un nuovo materiale; 2. Per ogni Tile duplicata appartenente ad un livello viene impostato il relativo materiale; 3. Per ogni materiale viene impostata la relativa rendererQueue, una proprietà che indica alla scheda grafica l’ordine in cui gli oggetti devono essere renderizzati. In questa maniera il problema del flickering è stato completamente risolto. 5.2 Punti Di Interesse Un’altro tipo di informazione molto importante riguardante il territorio è costituita dai punti di interesse, che da qui in avanti chiameremo PDI/POI. Un punto di interesse non è altro che marker posizionato sul terreno indicante un qualsiasi oggetto reale, collocato in quella esatta posizione, che potrebbe essere rilevante per l’utente: un monumento, un edificio pubblico, un luogo di culto ma anche un hotel, un parco o una fontana. Generalmente i PDI vengono suddivisi per categorie e caricati da un GeoDatabase remoto, ove sono opportunamente georeferenziati. 32 L’implementazione dei punti di interesse potrebbe sembrare molto semplice, ma in realtà è stata una dei punti più critici dell’intero progetto, in quanto la visualizzazione contemporanea di migliaia di gameObject sul terreno (uno per punto di interesse) ha un pesante effetto negativo sulle prestazioni globali. Gli approcci testati sono stati tre, alla fine l’ultimo è stato scelto come definitivo. 5.2.1 Approccio Tramite Immagini Raster Il primo test è stato eseguito utilizzando, per la rappresentazione di ciascun PDI, una normalissima immagine in due dimensioni proiettata a schermo come se fosse una componente della GUI. Anche se la visualizzazione un oggetto GUI in due dimensioni potrebbe sembrare un’operazione molto leggera, è stato comprovato che così in effetti non è, e la motivazione è la seguente: la funzione OnGui() di Unity3D, nella quale deve essere collocato tutto il codice per la generazione di elementi grafici dell’interfaccia utente, è stata implementata nativamente per essere eseguita due volte per ogni frame. Questa tipologia di approccio, sebbene sia la più corretta da un punto di vista ‘ideale’, è risultata inefficiente una volta implementata: la scelta di eseguire due volte per frame il codice, la cui sequenza di azioni è mostrata in figura 22, della GUI di Unity3D penalizza fortemente le prestazioni generali. Inoltre, gli elementi GUI vengono mostrati in primo piano, quindi risultava impossibile nascondere un punto di interesse che, per esempio, si trovava dietro una montagna, e quindi non visibile se la visuale in uso non è perpendicolare al terreno. 5.2.2 Approccio Tramite GameObject Il secondo test è stato effettuato utilizzando dei normalissimi gameObject tridimensionali, per i quali è stato costruito un apposito modello in formato FBX rappresentante un marker. Sebbene l’utilizzo di oggetti tridimensionali, formati da mesh complesse e provvisti di collider potrebbe sembrare un processo molto più pesante dal punto di vista computazionale, così in realtà non è stato riscrontrato: a differenza dell’approccio precedente, il gameObject una volta che è stato instanziato viene esclusivamente acceso/spento all’occorrenza. Lo scaling inoltre è automatico. La figura 23 mostra la sequenza di azioni che viene eseguita una volta per ogni frame relativa all’implementazione dei PDI tramite GameObject. Sebbene dal punto di vista prestazionale questa soluzione non presentava particolari problemi, è stata scartata da un punto di vista del risultato estetico finale: le icone relative 33 Figura 22: Sequenza di azioni effettuate per la visualizzazione di PDI in formato Raster. ai PDI sono state modellizate per essere in due dimensioni, quindi la loro applicazione su un modello 3D produceva risultati deludenti. 5.2.3 Approccio Tramite BillBoard La terza ed ultima soluzione testata, che poi si è rivelata la migliore in base ad un rapporto tra qualità visiva e prestazioni, è stata quella che ha introdotto l’utilizzo delle BillBoard. Una BillBoard non è altro che un elemento tridimensionale per la rappresentazione di immagini in due dimensioni nello spazio. Utilizzando questo tipo di componente quindi, si hanno i vantaggi del GameObject (profondità, collider, scaling, riproduzione una volta per frame) ma anche quelli dell’elemento GUI, in quanto l’icona 2D viene automaticamente ruotata a favore della telecamera. L’immagine numero 24 mostra la sequenza di azioni che viene eseguita una volta per ogni frame relativa all’implementazione dei PDI tramite BillBoard. La figura numero 25 mostra un esempio a runtime dell’implementazione dei punti di interesse: com’è possibile vedere, i PDI più vicini alla camera appaiono con uno scaling maggiore rispetto a quelli più distanti; gli elementi parzialmente visibili sono parzialmente visualizzati mentre alcun elemento nascosto da montagne è visualizzato. 34 Figura 23: Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando GameObject. 5.2.4 Il Picking Con il termine Picking (dall’inglese, raccolta) vengono indicate la serie di operazioni che vengono effettuate quando, con l’utilizzo di un qualsiasi tipologia di puntatore a video, che può essere sia un mouse che un tocco su un display touchscreen, vengono raccolte o visualizzate informazioi riguardanti l’oggetto che il puntatore ha selezionato. Elemento fondamentale per l’implementazione del picking è uno strumento messo a disposizione del GameEngine Unity3D, chiamato RayCast. Come, dal nome stesso dell’oggetto, è facilmente intuibile, il RayCast è, letteralmente, un raggio, che restituisce informazioni riguardante l’eventuale oggetto che colpisce durante il suo tragitto. Per rapportarlo ad un oggetto reale, pensiamo ad un sensore ad infrarossi per la determinazione della chiusura sicura delle porte di un ascensore: quando il raggio viene interrotto dal passaggio di un qualsiasi oggetto, scatta un evento, la riapertura delle porte. Nel dettaglio, ecco i parametri necessari per instanziare un RayCast, presi direttamente dallo script reference di Unity3D: • origin: rappresenta l’origine del raggio. Nel contesto di utilizzo, esso è rappresentato 35 Figura 24: Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando BillBoard. dal centro della camera. • direction: la direzione del raggio. • distance: la sua lunghezza, impostata per comodità ad infinito. • hitInfo: struttura contenente le informazioni riguardanti l’oggetto o il punto colpito (nome, posizione, distanza, ecc) • layerMask: rappresenta l’insieme degli oggetti che devono essere coinvolti nella collisione. Ogni componente ha la possibilità di essere associato ad un layer numerico, con il parametro layerMask è possibile specificare il gruppo di oggetti che devono essere coinvolti nella collisione con il raggio. Risulta quindi di facile comprensione come i RayCast siano stati fondamentali nell’implementazione del Picking sui PDI: generato un raggio, avente come layerMask l’intero rappresentante il gruppo dei punti di interesse, su un quasiasi punto a video, in caso di collisione con un GameObject PDI viene reperito il suo nome (codice) tramite la strut36 Figura 25: Esempio a runtime di visualizzazione di PDI utilizzando BillBoard. tura hitInfo generata come output. Grazie ad esso è molto semplice acquisire le informazioni aggiuntive associate. 5.2.5 Il Fattore di Scala Una delle problematiche che sono state affrontate durante lo sviluppo del progetto è legato alla dimensione stessa di ciascun grigliato componente ogni mesh. Sappiamo che, il modello a Tile definito in sezione 4 prevede che ogni tile figlia sia esattamente 1/4 di quella padre. La domanda che, a questo punto, dovrebbe venire spontanea dovrebbe essere riferita alla dimensione delle primissime Tile visualizzate, chiamate anche Tile di livello zero. La dimensione delle Tile di livello zero è definita da un parametro strettamente legato alle mappe che vogliono essere rappresentate, chiamato Zero Level Tile Delta, del quale abbiamo già discusso in sezione 4.0.3. Il dataset utilizzato per il testing dell’applicazione prevedeva l’uso di immagini aeree del Trentino, con ZLTD fissato ad 1ř e sei Tile a livello zero. Partendo quindi dal ZLTD, è stato scelto, per un discorso riguardante la rappresentazione in scala 1:1 del territorio, di utilizzare questo parametro come dimensione iniziale delle Tile di livello 0. Come spesso accade, la fase di testing ha evidenziato un preciso problema riguardante questo tipo di scelta: partendo da Tile di livello zero molto piccole, il mondo virtuale creato avrà dimensioni ridotte, e questo ha creato un problema di stabilità numerica. I due componenti infatti, venivano a contatto prima di aver suddiviso le Tile in maniera tale da visualizzare i dati alla massima risoluzione disponibile. Un ulteriore problema era legato all’utilizzo dei RayCast su oggetti di dimensioni ridotte: è stato constatato che le collisioni non vengono più determinate sotto una determinata soglia. La soluzione più immediata tale da comportare un aumento della distanza 37 fra camera e terreno, e aumento della dimensione generale di ogni oggetto disposto, è stata quella di immettere un fattore di scala. In questo modo è possibile ingrandire (o rimpicciolire) il mondo virtuale in base set di dati da visualizzare. 5.2.6 La Precisione dei Float L’introduzione di un fattore di scala ha risolto il problema indicato in sezione 5.2.5, ma ha evidenziato la presenza di un ulteriore bug, la cui risoluzione è prevista solo negli sviluppi futuri del progetto, in quanto prevede una completa riprogettazione del sistema di navigazione e di creazione delle Tile. L’intero sistema è stato progettato in maniera da collocare, nello spazio virtuale offerto da Unity3D, i GameObject delle Tile ad una proiezione della loro effettiva posizione nel mondo reale. In questo modo, è possibile caricare più mappe corrispondenti a porzioni di territtorio differenti, senza che esse vengano sovrapposte. Facciamo un esempio: considerando un fattore di scala settato ad 1, la posizione della Tile contenente la città di Trento sarà a circa X=195 e Y=136 nel mondo di Unity3D, corrispondente a longitudine 195 e latitudine 136 del mondo reale (coordinate normalizzate). Supponiamo ora l’utilizzo di un fattore di scala impostato a 10: la posizione della Tile precedente sarà in X=1950 e Y=1360 nel mondo virtuale di Unity3D. Più grande è il fattore di scala utilizzato, più importanti sono i numeri che vengono coinvolti per la determinazione della esatta collocazione degli oggetti di scena. Questo ha sollevato un problema nuovo: la precisione dei numeri float a singola precisione che Unity3D utilizza per la collocazione degli elementi nel suo mondo. Per una migliore comprensione, analizziamo la struttura dei numeri float a 32 bit, secondo lo standard IEEE 75414 : Un numero in virgola mobile, secondo lo standard IEEE è rappresentato su parole di 32, 64 o 128 bit divisi in tre parti: 1. un bit di segno s; 2. un campo di esponente e; 3. un campo di mantissa m. Gli n bit di una parola sono indicizzati in modo decrescente con numeri interi da 0 a n-1. In un numero in questo standard, l’importanza del bit decresce col suo indice. Di seguito è rappresentato un numero in una parola di 32 bit: 14 http://en.wikipedia.org/wiki/IEEE_754 38 1 8 23 +−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+ S Esp . Mantissa +−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+ 31 30 22 0 lunghezza in bi t indice dei bit Proviamo ora a convertire un numero 118.625 secondo lo standard IEEE_754: dobbiamo determinarne il segno, l’esponente e la mantissa. Poiché è un numero positivo, il primo bit è ‘0’. Poi scriviamo il numero in forma binaria: 1110110.101. Successivamente spostiamo la virgola verso sinistra, lasciando solo un 1 alla sua sinistra: 1110110,101 = 1,110110101 per 2 elevato alla 6. La mantissa è la parte a destra della virgola, riempita con zeri a destra fino a riempire i 23 bit: 11011010100000000000000. L’esponente è pari a 6, ma dobbiamo convertirlo in forma binaria e adattarlo allo standard. Per la precisione singola, dobbiamo aggiungere 127. Quindi 6 + 127 = 133. In forma binaria: 10000101. Assemblando il tutto: 1 8 23 +−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+ S Exp Fraction 0 10000101 11011010100000000000000 +−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+ 31 30 22 0 Capiamo immediatamente come, al crescere della parte intera del numero da convertire, aumentino i bit utilizzati per rappresentare tale valore nella mantissa. Nell’esempio precedente, per rappresentare il numero 118 sono stati occupati 6 bit, su 23. Utilizzando un fattore di scala pari a 10, i bit della mantissa vanno utilizzati per la rappresentazione della parte intera di ciascuna posizione sono 10; con fattore a 100, essi diventano 14. Diminuiscono quindi i bit utilizzati per la conversione della parte decimale della posizione, assai più importanti quando il livello di dettaglio sale, e la dimensione di ciascuna Tile diminuisce. Con fattori di scala e di dettaglio elevati, sono quindi sorti problemi di sovrapposizione degli elementi, dovuti ad arrotondamenti dovuti alla precisione dei numeri float 32. La soluzione a tale problema, la cui implementazione è attesa nei prossimi sviluppi del software creato, citata all’inizio di questa sezione, stà nella traslazione dell’intero mondo creato nell’origine dello spazio virtuale offerto da Unity3D. Durante un qualsiasi movimento, non viene in realtà spostata la camera in funzione del 39 modello tridimensionale, ma esattamente il contrario: la camera rimarrà fissa nei movimento longitudinali, il terreno si sposterà in funzione di essa. In questo modo, essendo la Tile che si stà osservando, e quelle adiacenti, in prossimità della coordinata (0,0,0) in tre dimensioni, aumenterà la precisione della parte decimale di ogni Tile figlia, in quanto non vengono ‘sprecati’ bit per la sua parte intera (che è, appunto, in prossimità dello zero), risolvendo quindi in gran parte il problema che l’introduzione di un fattore di scala ha sollevato e dell’utilizzo dei numeri float a 32 bit. 5.2.7 Inserimento/Aggiornamento PDI Uno dei punti critici dell’intera applicazione, che vanno al di là dei puri limiti tecnologici affrontati, è rappresentato dalla reperibilità e qualità dei dati da visualizzare. I punti di interesse sono infatti molteplici, un numero molto elevato di oggetti per ogni categoria e, soprattutto, la cui descrizione potrebbe variare da utente ad utente, in base all’esperienza passata: prendiamo per esempio un PDI riferito ad un hotel, potrebbe accadere che la recensione di due utenti possa essere diametralmente opposta. Oppure, potrebbe accadere la nascita di un nuovo punto di interesse, come per esempio la costruzione di un nuovo museo. Si è ritenuto quindi necessario offrire funzionalità per l’inserimento o l’aggiornamento di un nuovo punto di interesse. Il problema principale sorto nell’implementazione di questo tipo di feature è costituito dal fatto che, in fase di design, è stato scelto di costruire tutte le funzionalità offerte come blocchi a sè stanti: in questa maniera, aggiornamento ed estensione dei vari moduli in futuro risulterà più semplice ed efficiente. Visualizzazione ed aggiornamento/inserimento di nuovi punti di interesse costituiscono due blocchi di codice completamente separati, facendo però riferimento alla stessa struttura dati. In fase di inserimento di nuove informazioni sarà quindi il sistema stesso, lato server, a decidere se accorpare i dati ad un punto di interesse già esistente, o crearne uno completamente nuovo. Il principio secondo il quale la scelta precedentemente illustrata viene evasa si basa sul risultato di una particolare tipologia di interrogazione che un GeoDatabase offre, chiamata query di prossimità: acquisite latitudine e longitudine tramite il sistema di input utilizzato sulla geometria tridimensionale del territorio visualizzato, categoria e titolo del nuovo punto di interesse, vengono cercati PDI le cui informazioni sono simili in un intorno molto piccolo delle coordinate inserite e, in base al risultato di questa operazione, viene deciso se il PDI è nuovo o già esistente. 40 5.3 Le Informazioni Meteoreologiche All’interno dei requisiti funzionali del progetto SmartIslands, una parte fondamentale riguardava la rappresentazione di informazioni metereologiche sul territorio rappresentato nel modello tridimensionale virtuale. Fortunatamente, è stata lasciata carta bianca sul modo in cui esse potessero essere visualizzate, così è stato deciso di utilizzare dei veri e propri modelli tridimensionali per ciascuna condizione da rappresentare. Un informazione meteo è acquisita nella medesima maniera in cui sono scaricati i punti di interesse, oggetti dei quali abbiamo parlato in sezione 5.2, ovviamente, la differenza sostanziale stà che i PDI rappresentano informazioni del territorio, quindi sono collocati su esso, così non è per i dati meteo. Il meteo in tre dimensioni mi ha dato la possibilità di lavorare con una tipologia di GameObject offerto da Unity3D molto interessante, chiamato Particle System, ossia sistema di particelle. 5.3.1 I Particle Systems I Particle Systems sono essenzialmente immagini in due dimensioni renderizzate in uno spazio tridimensionale. Sono utilizzati principalmente per la rappresentazione di effetti tipo fumo, fuoco, acqua, gocce, foglie ed effetti atmosferici. Un Particle completo è formato da diversi componenti: Particle Emitter, Particle Animator ed infine un Particle Renderer. Il Particle Emitter genera i sistemi di particelle, l’Animator genera il movimento ed il Renderer le visualizza a schermo. Il funzionamento di base è il seguente: visualizzando l’immagine passata come input più volte, secondo dei parametri impostabili nell’Emitter, è possibile creare effetti caotici utili per la rappresentazione di forme irregolari. I parametri utilizati precedentemente, che determinano il comportamento del sistema di particelle generato, sono i seguenti: • Emit: parametro booleano, determina l’accensione/spegnimento del Particle. • Min Size: rappresenta la dimensione minima di ogni particella. • Max Size: rappresenta la dimensione massima di ogni particella. • Min Energy: indica il tempo di vita minimo di ogni particella generata, in secondi. • Max Energy: indica il tempo di vita massimo di ogni particella generata, in secondi. • Min Emission: è il numero minimo di particelle emesso ogni secondo. • Max Emission: è il numero massimo di particelle emesso ogni secondo. 41 • World Velocity: Vector3, indica la direzione, espressa come vettore, in cui le particelle sono generate. • Ellipsoid: dimensione del sistema di particelle. Figura 26: Esempio di Particle Emitter. Settando questi parametri, ed associando ai Particle creati le opportune texture, sono stati creati i tre principali eventi metereologici, le nuvole, la pioggia e la neve, la cui intensità può essere facilmente modificata come spiegato precedentemente. Per il Sole, invece è stata utilizzata una normalissima sfera. In immagine 27 è possibile visionare il risultato finale dell’implementazione del meteo in tre dimensioni. Figura 27: 3D-Meteo: sole, pioggia e neve. 5.4 Il GeoCoding Per geocoding si intende l’attribuzione a un dato di un’informazione relativa alla sua dislocazione geografica o viceversa. Tale posizione è espressa in un particolare sistema geodetico di riferimento, normalmente via latitudine e longitudine. La georeferenziazione 42 è usata nei sistemi GIS, tanto da essere applicata sostanzialmente ad ogni elemento presente: pixel componenti un’immagine raster, elementi vettoriali come punti, linee o poligoni e persino annotazioni. Una delle funzionalità che sono state implementate ed aggiunte al Core delle operazioni disponibili, stà nella conversione di un qualsiasi indirizzo abitativo in coordinate di latitudine e longitudine, con conseguente zoom della camera nella zona interessata. Per effettuare questo tipo di servizio sono state utilizzate delle API fornite da Google che, tramite una richiesta HTTP formattata come figura 28 mostra, restituiscono in risposta un documento XML, contenente le informazioni richieste. Un esempio XML ottenuto è mostrato in figura 29. Figura 28: Esempio di una richiesta di GeoCoding dell’indirizzo: via Sommarive 14, Povo (TN). Figura 29: XML in risposta alla richiesta effettuata in figura 28. Effettuato il parsing dell’output ottenuto, ottenute le informazioni di latitudine e longitudine relative all’indirizzo immesso, è quindi molto semplice convertire tali dati nel sistema di coordinate utilizzate nel mondo virtuale sviluppato, e puntare su tale locazione. P osX = longitude + 180 ∗ GLOBALSCALE P osZ = latitude + 90 ∗ GLOBALSCALE 43 5.5 I Percorsi Guidati Uno dei contenuti aggiuntivi che offrono una tipologia di informazione di importanza strategica elevatissima per un qualsiasi turista è costituito dall’implementazione di un sistema di visualizzazione di percorsi, la cui tipologia può essere la più varia: da un sentiero montano ad un tour enogastronomico passando per un tragitto culturale. Un percorso è fondamentalmente costituito da due componenti: 1. Una lista di punti che identificano il tragitto; 2. una lista di tappe sensibili. 5.5.1 Il formato GPX GPX o GPS eXchange Format15 è uno schema XML progettato per il trasferimento di dati GPS tra applicazioni software. Può essere usato per descrivere waypoint (punti), tracce e percorsi (routes). I suoi tag contengono queste tipologie di informazioni: location (luogo), elevation (elevazione), e time (tempo). Questo fa sì che possa essere utilizzato come formato di interscambio fra ricevitori GPS e pacchetti software. Il formato GPX presenta un insieme di punti che può essere senza alcuna relazione sequenziale, oppure ordinati. Nel primo caso si parla di waypoints, nel secondo di tracks (tracce) o routes (percorsi). I waypoints sono dei singoli punti di cui si conoscono le coordinate di latitudine e longitudine più l’elevazione (es. i numeri civici di una strada). Tracce e routes invece si presentano come un insieme di punti sequenziali, nel primo caso vengono archiviate informazioni spazio-temporali (= il percorso fatto dal dispositivo gps attraverso le coordinate, l’elevazione e la marcatura temporale - timestamp), il secondo invece si occupa solo di registrare le informazioni geografiche al fine di fornire percorsi utili a terzi (es. sentieri o percorsi stradali o ...). Le proprietà indispensabili presenti in un file GPX sono la latitudine e la longitudine di un singolo waypoint. Tutte le altre variabili sono opzionali. Di seguito è possibile visionare un estratto di file GPX contenente alcuni waypoints riguardanti un percorso di test. <gpx xmlns : x s i ="http : / /www. w3 . o rg /2001/XMLSchema−i n s t a n c e " xmlns : xsd="http : / /www. w3 . or g /2001/XMLSchema" v e r s i o n ="1.1" c r e a t o r ="Base3 . GeoTools " xmlns="htt p : / /www. t o p o g r a f i x . com/GPX/1/1"> <trk > <name>TNCSOLT−01</name> 15 http://www.topografix.com/gpx.asp, http://it.wikipedia.org/wiki/GPS_eXchange_Format 44 <desc>Giro Test Via S o l t e r i </desc> <t r k s e g > <t r k p t l a t ="46.086418870836496" l o n ="11.119476584717631" > <e l e >219.3179931640625 </ e l e > </t r k p t > <t r k p t l a t ="46.086375117301941" l o n ="11.119383377954364" > <e l e >218.8372802734375 </ e l e > </t r k p t > </t r k s e g > </trk > </gpx> In immagine 30 è possibile vedere come un percorso viene rappresentato utilizzando i waypoints. Figura 30: Esempio rappresentazione di un percorso secondo il formato GPX. 5.5.2 Migrazione dei Dati Una delle limitazioni che i file in formato gpx possiedono è che essi offrono informazioni che non possono essere interrogate in alcun modo. Ciò vuol dire che, una volta acquisito il file, il programmatore non può, utilizzando metodi standard, effettuare qualsiasi tipo di query su esso. Data questa importantissima motivazione, è stato scelto di migrare tutti i contenuti dei file 45 spaziali gpx su un database PostGreSQL al quale è stata installata l’estensione PostGIS16 per l’elaborazione dei dati georeferenziati. In questo modo, è possibile l’implementazione di query spaziali del calibro di: • Ottenimento di tutti i percorsi nel raggio di X metri da un punto inserito dall’utente; • Ottenimento della distanza spaziale da un percorso ad un altro. Per la rappresentazione di un tragitto, formato da una lista di elementi espressi in latitudine e longitudine, PostGis mette a disposizione un’opportuna struttura dati, chiamata LINESTRING, che prende in input una lista di punti in due o tre dimensioni, senza alcun vincolo sulla corrispondenza fra primo ed ultimo dato immesso. LINESTRING(0 0,1 1,1 2) Esistono fondamentalmente due tipi di LINESTRING: semplice o complessa. 1. Una LINESTRING si dice semplice se passa per ogni punto che la costituisce una ed una sola volta; 2. una LINESTRING si dice complessa nel caso opposto, ossia quello in cui un punto può essere percorso più volte in un singolo tragitto. Figura 31 mostra una serie di LINESTRING semplici, figura 32 mostra LINESTRING complesse. Figura 31: Esempio LINESTRING di tipo semplice. Associato ad ogni file gpx contenente i dati di ciascun percorso è stato fornito anche un documento in formato XML di descrizione del relativo tragitto. Le informazioni ad associate ad ogni elemento sono: 16 http://postgis.refractions.net/ 46 Figura 32: Esempio LINESTRING di tipo complesso. 1. code: codice identificativo del percorso. Tramite questo codice è possibile risalire al relativo file in formato gpx; 2. title: nome del percorso; 3. subtitle: sottotitolo del percorso; 4. length: distanza espressa in chilometri; 5. ttime: durata del tragitto effettuato a piedi, espresso in ore; 6. description: descrizione del percorso; 7. altitude: link ad un’immagine contenente la visualizzazione grafica del dislivello da affrontare; 8. img: link ad un’immagine fotografica significativa del percorso; 9. logo: logo del comprensorio di appartenenza. 10. pdi: lista di punti di interesse, georeferenziati, associati al tragitto. Anche questa tipologia di dati sono stati migrati su un database di tipo relazionale, in maniera tale da poterle reperire in maniera più veloce e funzionale. 5.5.3 Rappresentazione del Percorso La rappresentazione del percorso è stata una delle parti più complesse dell’intero progetto, in quanto la mesh identificante ogni tragitto, da costruire on-the-fly, non è basata su una forma regolare come è stato per la costruzione dinamica delle delle Tile 4.3, dove sono tutte di forma regolare (quadrate), ma su un percorso, con tutte le sue curve e variazioni di altezza. 47 Il problema principale che è emerso nella costruzione dinamica di questo tipo di mesh è nato dalla necessità di triangolizzare ciò che in realtà non è altro che una serie di segmenti, come spiegato in sezione 5.5.2. E’ stato quindi necessario sviluppare una funzione che, presa in ingresso la lista di punti, restituisca un’altra lista, di dimensione doppia rispetto all’originale, contenente una serie di punti che, presi due a due, includono l’elemento originale sul segmento che li unisce. In questo modo è stato possibile simulare una sorta di ‘spessore’ della carreggiata, utilizzato per triangolizzare i vertici che compogono la geometria del percorso da rappresentare. In figura 33 è possibile osservare il tipo di risultato che la funzione sviluppata deve offrire mentre il listato sottostante mostra come i nuovi dati sono stati calcolati: P0 e P1 rappresentano i punti in ingresso mentre V0, V1, V2, V3 costituiscono i quattro vertici restituiti. Figura 33: Vertici ausiliari per la costruzione della mesh riguardante un percorso. Vector2 d i r e c t i o n = P1 . xy − P0 . xy Vector2 normal = n o r m a l i z e ( Vector2 ( d i r e c t i o n . y , −d i r e c t i o n . x ) ) Vector2 Vector2 Vector2 Vector2 V0 V1 V2 V3 = = = = P0 . xy P1 . xy P0 . xy P1 . xy − − + + ( normal ( normal ( normal ( normal ∗ ∗ ∗ ∗ meshDinstance ) meshDinstance ) meshDinstance ) meshDinstance ) Successivamente ogni vertice creato è stato ricollocato all’opportuna altezza, utilizzando lo strumento RayCast del quale è stato parlato in sezione 5.2.4. Ottenuti, per ogni elemento costituente il percorso, la nuova lista di vertici, collocati all’opportuna altezza, 48 è stato possibile creare l’opportuna mesh trangolarizzando gli elementi in output, come mostrato in figura 34. Figura 34: Triangolarizzazione dei vertici ausiliari ottenuti in precedenza. L’utilizzo di una mesh è dovuto al fatto che a questo tipo di struttura è associato un gestore di collisioni calcolato sui vertici creati, utile per il picking. 5.5.4 L’Algoritmo di Douglas Peucker Lo sviluppo ed il successivo testing dell’algoritmo di generazione automatica della mesh di ogni percorso ha sollevato un problema molto comune nel mondo della computer grafica: la generazione on-the-fly dei numerosi vertici necessari comporta una serie di conseguenze che limitano le prestazioni della macchina sulla quale il programma sviluppato è in esecuzione. Se si tratta di un computer Desktop i rallentamenti risultano essere impercettibili ma, se lo stesso percorso è visualizzato su un tablet per esempio, allora i cali di performance risultano essere significativi. Il maggior sforzo computazionale è richiesto per calcolo delle componenti che stanno dietro la generazione di un collider, l’elemento base che permette il picking su un qualsiasi oggetto tridimensionale tramite raycast: un collider assume la stessa identica forma della mesh, ovviamente, quindi riducendo il numero di triangoli che compongono un percorso vengono automaticamente ridotti gli sforzi computazionali necessari per il calcolo del collider. E’ stato quindi necessario trovare un algoritmo in grado di filtrare, in base ad un parametro di accuratezza, la lista dei punti componente ciascun percorso, e successivamente utilizzare il set nuovo filtrato per il calcolo dei vertici ausiliari per la costruzione della mesh: l’algoritmo perfetto per tale scopo è quello di Douglas-Peucker. L’algoritmo di Douglas-Peucker è un algoritmo utilizzato per la riduzione del numero 49 di punti che compongono una curva o retta, approssimandola ad una serie di segmenti. La forma iniziale di questo processo fù suggerita da Urs Ramer nel 1972, nel 1973 fù pubblicata la forma definitiva da David Douglas e Thomas Peucker. L’algoritmo è conosciuto anche con le seguenti nominazioni: Ramer-Douglas-Peucker algorithm, iterative end-point fit algorithm o split-and-merge algorithm. L’obiettivo di tale algoritmo è: data in ingresso una curva composta da un insieme di segmenti, trovare una curva simile con un numero di punti minore. Il punto chiave è basato sulla distanza che intercorre fra la curva originale e quella semplificata. La curva semplificata consiste in un subset di punti di quell originale dove un fattore definisce il grado di approssimazione. Di seguito è possibile visionare lo pseudocodice dell’algoritmo di Douglas-Peucker per il campionamento dei punti17 . f u n c t i o n DouglasPeucker ( P o i n t L i s t [ ] , e p s i l o n ) // Find th e p o i n t with t h e maximum d i s t a n c e dmax=0 i n d e x=0 f o r i =2 t o ( l e n g t h ( P o i n t L i s t ) −1) d=P e r p e n d i c D i s t ( P o i n t L i s t [ i ] , Line ( P o i n t L i s t [ 1 ] , P o i n t L i s t [ end ] ) ) i f d>dmax i n d e x=i dmax=d end end // I f max d i s t a n c e i s g r e a t e r than e p s i l o n , r e c u r s i v e l y s i m p l i f y i f dmax>=e p s i l o n // R e c u r s i v e c a l l r e c R e s u l t s 1 [ ] = DouglasPeucker ( P o i n t L i s t [ 1 . . . i n d e x ] , e p s i l o n ) r e c R e s u l t s 2 [ ] = DouglasPeucker ( P o i n t L i s t [ i n d e x . . . end ] , e p s i l o n ) // B u i l d t he r e s u l t l i s t R e s u l t L i s t [ ] = { r e c R e s u l t s 1 [ 1 . . . end −1] r e c R e s u l t s 2 [ 1 . . . end ] } else R e s u l t L i s t [ ] = { P o i n t L i s t [ 1 ] , P o i n t L i s t [ end ] } end 17 http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 50 // Return t he r e s u l t return ResultList [ ] end Avendo avuto modo di visionare lo pseudocodice, vediamo l’algoritmo di DouglasPeucker per la riduzione dei punti all’opera, prendendo come esempio la curva visibile in figura 35. Figura 35: Algoritmo di Douglas-Peucker in esecuzione. La curva con indice 0 rappresenta la curva originale mentre quella approssimta è visionabile in indice 4. L’algoritmo, in primo luogo, marca il primo ed ultimo punto della lista in input, in maniera tale che essi siano mantenuti nell’output finale. Successivamente viene cercato il punto più lontano fra i due marcati nella precedente operazione. Se la distanza fra il punto trovato e i due endpoints è maggiore del fattore , esso viene salvato e l’algoritmo chiamato, ricorsivamente, sulla curva che va dal primo punto a quello trovato, e dal punto trovato all’ultimo punto della curva. Se la distanza fra il punto e gli endpoints è minore di , esso viene scartato. Il risultato finale di tale procedura sarà un’insieme di punti, approssimanti la curva in input tramite il fattore . Concludendo con un cenno sulla complessità, essa è stimata per il caso peggiore con O[n2 ]. 51 Utilizzando l’algoritmo di Douglas Peucker è stato quindi possibile ridurre, con una buona approssimazione, il numero di punti che descrivono l’andamento di un percorso: prendiamo per esempio il percorso turistico chiamato ‘Altopiano delle Pale’, in Val di Fiemme, nei dintorni di San Martino di Castrozza: il numero di punti del file gpx, migrato successivamente su GeoDatabase, associato a tale escursione presenta 918 punti di controllo. Ipotizzando ora che venga utilizzato il set originale per la costruzione della mesh, il numero di vertici finale corrisponderà al doppio del numero delle componenti in input, quindi 1836 punti di controllo, un numero decisamente elevato. Filtrando il dataset originale tramite l’algoritmo di Douglas Peucker, con un fattore impostato a , è stato possibile passare dai 918 elementi originali a soli 15 punti più significativi. L’affermazione che ora dovrebbe nascere naturalmente deriva dalla qualità visiva della rappresentazione nel mondo virtuale di un qualsiasi percorso: prendendo sempre per esempio l’‘Altopiano delle Pale’, sarebbe impensabile credere che la qualità di 918 punti originali e i 15 filtrati sia la stessa. Per ovviare a questo problema è stato scelto l’utilizzo di una componente fornita da Unity3D chiamata LineRendereer, uno strumento molto leggero utilizzato per disegnare linee nello spazio tridimensionale. Disegnando una linea tramite il LineRendereer utilizzando il dataset originale riguardante ciascun percorso e collocando il risultato ottenuto sopra la mesh costruita utilizzando i vertici filtrati, alla quale verrà applicata ua texture trasparente, è stato raggiunto un perfetto equilibrio tra performance e gradimento visivo. In immagine 36 è possibile vedere il risultato finale di questa serie di operazioni applicate al percorso ‘Altopiano delle Pale’. Figura 36: Esempio di costruzione dinamica della mesh di un percorso: i triangoli verdi costituiscono il collider generato sui punti filtrati, in blu il LineRendereer utilizzante il dataset originale. In appendice, sezione C (WalkGenerator.cs), è possibile analizzare la parte di codice riguardante la creazione della mesh di ciascun percorso e relativo LineRendereer. 52 5.5.5 Proiezione in Altezza In determinate situazioni, sebbene ciascun percorso sia disegnato direttamente sul modello tridimensionale del terreno, potrebbe accadere che la planimetria del percorso non sia immediatamente recepita dall’utente, quindi è stato deciso di fornire uno strumento aggiuntivo per meglio comprendere questo tipo di informazione. Il problema precedentemente illustrato può essere facilmente risolto costruendo una mesh tridimensionale del percorso interessato provvisto di un fattore di altezza, ed elevata rispetto al terreno. Per effettuare questo tipo di operazione è stato utilizzato in buona parte il codice scritto per la costruzione della mesh base del percorso, il cui procedimento è stato ampiamente illustrato in sezione 5.5.3, ovviamente, con un livello di dettaglio maggiore, indicato dal fattore impostato durante la procedura di decimazione dei punti. Per ottenere il fattore ‘altezza’ del modello tridimensionale, è bastato duplicare ciascun punto con un offset sommato all’asse Y di ciascun vertice duplicato. In figura 37 è possibile vedere il risultato della proiezione in altezza del percorso ‘Altopiano delle Pale’. Figura 37: Esempio di costruzione dinamica della proiezione in altezza del percorso ‘Altopiano delle Pale’. In appendice, sezione C (WalkAltitudeGenerator.cs), è possibile analizzare la parte di codice riguardante la creazione della mesh di ciascuna proiezione in altezza. 5.5.6 Visualizzazione delle Informazioni In sezione 5.5.2 è stato affrontato il problema della migrazione dei dati da file in formato gpx ed xml ad un database di tipo relazionale al quale è stato installato il relativo plugin per la gestione di dati georeferenziati. In sezione 5.5.3, è stato spiegata la costruzione della mesh relativa ad ogni percorso, con relativo gestore delle collisioni, utile per il picking, del quale abbiamo parlato in 5.2.4. 53 In questa sezione verrà mostrato come le informazioni relative a ciascuna escusione sono rese disponibili all’utilizzatore, unendo le conoscenze apprese in precedenza. Considerando il fatto che il software sviluppato dovrà essere platform-indipendent è stato scelto di abilitare la visualizzazione delle informazioni aggiuntive tramite un evento associabile sia ad un single mouseclick che ad un single touch su un display touchscreen. Utilizzando la coordinata acquisita tramite uno dei due metodi indicati precedentemente, verrà controllato se l’elemento corrispondente a tale posizione è uno dei percorsi abilitati (picking) e, in caso affermativo, un popup la cui struttura è mostrata in figura 38 è visualizzato. Figura 38: Template di riferimento per la visualizzazione delle informazioni riguardanti i percorsi. Interfacciandosi ai database utilizzando delle pagine PHP create appositamente per tale scopo, è stato possibile reperire i dati relativi a ciascun percorso cui è richiesta la visualizzazione di informazioni aggiuntive. Prendendo sempre come esempio l’escusione utilizzata nelle precedenti sezioni, chiamata ‘Altopiano delle Pale’ ecco in figura 39 come si presenta la visualizzazione delle informazioni complete a runtime. Per quanto concerne la rappresentazione della lista riguardante i checkpoints, è stato scelto di utilizzare nuovamente il materiale prodotto per i PDI, essendo le tappe sensibili nient’altro che dei punti di interesse sul percorso, che sono stati ampiamente descritti in sezione 5.2. Il risultato finale dell’unione delle due componenti è mostrato in figura 40. 54 Figura 39: Esempio a runtime di visualizzaione delle informazioni riguardanti un percorso. Figura 40: Percorso completo di tragitto e punti di interesse. 5.5.7 Attraversamento Aereo L’ultima funzionalità che è stata sviluppata per la rappresentazione dei percorsi riguarda l’attraversamento aereo del tragitto creato. Visualizzando questo tipo di animazione, l’utente si troverà immerso nella ricostruzione tridimensionale del territorio che circonda il percorso selezionato, rendendosi conto del panorama che andrà a vedere affrontando tale escursione. Fortunatamente, Unity3D mette a disposizione uno script, il cui nome è iTween, la cui esecuzione produce esattamente l’effetto deriderato: dato come input una serie di punti e dei parametri quali velocità e tipo di interpolazione, è possibile muovere un qualsiasi GameObject, in questo caso la Camera principale, lungo la curva che unisce i vertici del path specificato. 55 Anche in questo caso, l’efficiente decimazione dei punti offerta dall’algoritmo di DouglasPeucker, impostato con un parametro molto elevato, ha permesso che la lista di checkpoint fosse composta dai soli punti più significativi del percorso, rendendo il risultato finale più fluido. In immagine 41 è possibile vedere un fotogramma dell’animazione effettuara sul percorso ‘Altopiano delle Pale’. Figura 41: Fotogramma dell’animazione riguardante l’attraversamento aereo di un percorso. 56 6 Funzionalità Sociali I social network ormai sono divetati strumenti preziosissimi, che ogni designer di software deve tenere in considerazione durante la progettazione del proprio prodotto. E’ infatti chiaro che, la presenza di funzionalità per poter interagire con i principali social network, Facebook, Twitter e Flickr, aiuta l’imposizione di un prodotto sul mercato. Nel corso dello sviluppo del software oggetto di questa tesi, è stato temporaneamente coinvolto solo uno dei tre social network citati precedentemente, quello che permette l’utilizzatore di ottenere una quantità maggiore di informazioni sul territorio: Flickr. 6.1 Flickr Flickr18 è un sito web multilingua che permette agli iscritti di condividere fotografie personali con chiunque abbia accesso a Internet, in un ambiente web 2.0. Il sito, di proprietà del gruppo Yahoo!19 , ha una libreria in continua espansione contando ogni minuto più di duemila nuove foto inserite da parte dei suoi sette milioni di utenti. Flickr è stato sviluppato dalla Ludicorp, una compagnia canadese di Vancouver fondata nel 2002. Nel marzo del 2005, sia la Ludicorp che Flickr sono stati comprati da Yahoo!: i server, quindi, sono stati trasferiti dal Canada agli Stati Uniti. Il 16 maggio 2006 Flickr conclude la fase di sviluppo beta, definendosi in stato ‘Gamma’, ossia non più in fase di prova ma in stato di perpetua evoluzione. Il 29 dicembre 2006 sono stati ritoccati i limiti portando a 100 MB al mese (dai precedenti 20 MB) per gli account gratuiti ed a 2 GB al mese per gli utenti a pagamento. Nel gennaio 2007 Flickr ha completato la migrazione alla piattaforma Yahoo! facendo trasferire forzatamente i vecchi utenti con account aperto prima dell’acquisizione del 2005 ad un account della piattaforma Yahoo!. L’utilizzo del servizio negli anni è cambiato parecchio. Inizialmente nato come strumento per ospitare le proprie immagini da pubblicare su altri siti, ha avuto grande successo grazie al fenomeno dei blog. In seguito si è evoluto, diventando esso stesso una comunità virtuale grazie ai gruppi tematici ed ai forum; anche grazie alla crescita tecnologica ed al largo numero di strumenti fotografici, viene utilizzato per raccogliere la digigrafia della propria vita e rimanere aggiornati su quella dei propri conoscenti ed amici20 . 18 http://www.flickr.com/ http://www.yahoo.com/ 20 http://it.wikipedia.org/wiki/Flickr 19 57 6.1.1 Integrazione Avendo ormai compreso il funzionamento e lo scopo di Flickr passiamo a descrivere l’integrazione del social network con il software sviluppato. 6.1.2 API di Sviluppo Il social network Flickr mette a disposizione un completo pacchetto di API per tutti gli sviluppatori che hanno la necessità di interagire con il loro database fotografico. Le query vengono effettuate in maniera molto semplice ed intuitiva, contattando un loro server con una normalissima chiamata HTTP, nella quale vengono specificati i parametri tramite i quali la risposta ottenuta sarà filtrata. Nel dettaglio, le API che sono state utilizzate sono le seguenti. flickr.photos.search: Restituisce una lista di foto le quali corrispondono a determinati criteri di filtraggio. Le foto restituite saranno solo ed esclusivamente quelle i cui permessi in lettura sono pubblici. Della lista di argomenti disponibili, visionabile su http://www.flickr.com/services/ api/flickr.photos.search.html, solo i seguenti parametri sono stati utilizzati in questo contesto: • api_key (Obbligatorio): codice numerico associato a ciascun account sviluppatore; • min_taken_date (Facoltativo): specifica la data minima tramite la quale una foto viene presa in considerazione. In questo modo è possibile effettuare un filtro basato sulla data; • bbox (Facoltativo): bounding box per le foto georeferenziate; • has_geo (Facoltativo): abilita la ricerca per le sole immagini georeferenziate; • page (Facoltativo): numero della pagina, in quanto le foto in risposta vengono offerte a blocchi di 250, e spesso il loro numero totale è di gran lunga superiore a questo limite. Questo metodo ritorna lŠXML della lista di foto standard: <photos page ="2" pages ="89" perpage ="10" t o t a l ="881"> <photo i d ="2636" owner ="47058503995@N01" s e c r e t ="a123456 " s e r v e r ="2" t i t l e ="test_04 " i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" /> 58 <photo i d ="2635" owner ="47058503995@N01" s e c r e t ="b123456 " s e r v e r ="2" t i t l e ="test_03 " i s p u b l i c ="0" i s f r i e n d ="1" i s f a m i l y ="1" /> <photo i d ="2633" owner ="47058503995@N01" s e c r e t ="c123456 " s e r v e r ="2" t i t l e ="test_01 " i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" /> <photo i d ="2610" owner ="12037949754@N01" s e c r e t ="d123456 " s e r v e r ="2" t i t l e ="00 _ t a l l " i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" /> </photos> Concatenando opportunamente gli attributi di ciascun elemento, secondo questa forma: http://farmfarm-id.staticflickr.com/server-id/id_secret.jpg è possibile ottenere l’url di ciascuna immagine che corrisponde ai criteri di ricerca specificati. flickr.photos.getInfo: Con questo metodo è possibile ottenere i dettagli di una foto, utilizzando il suo id come parametro di ricerca, ottenuto dalla query precedente. L’esempio di risposta ottenuta è presente nel listato sottostante <photo i d ="2733" s e c r e t ="123456" s e r v e r ="12"> <owner n s i d ="12037949754@N01" username="Bees"/> < t i t l e >o r f o r d _ c a s t l e _ t a s t e r </ t i t l e > <d e s c r i p t i o n >h e l l o !</ d e s c r i p t i o n > < v i s i b i l i t y i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" /> <d a t e s p o s t e d ="1100897479" taken ="2004−11−19 12:51:19"/ > <p e r m i s s i o n s permcomment="3" permaddmeta="2" /> < e d i t a b i l i t y cancomment="1" canaddmeta="1" /> <comments>1</comments> <l o c a t i o n > <l a t i t u d e ></l a t i t u d e > <l o n g i t u d e ></l o n g i t u d e > </ l o c a t i o n > </photo> 59 Come è possibile osservare, per ciascuna immagine è possible acquisire una serie di informazioni che, trattate oppurtunamente, trasformano ogni singolo elemento ottenuto in un particolare punto di interesse, dei quali è già stato parlato in sezione 5.2. E’ stato quindi scelto di rappresentare ogni elemento scaricato come un PDI, e posizionarlo direttamente sul territorio. 6.1.3 Filtraggio Basato sul Colore Essendo i dati forniti da Flickr non strutturati ad-hoc per il progetto oggetto di questa tesi, sono sorti problemi inevitabili riguardanti la quantità di dati reperibili dai server del servizio. Infatti, il parametro principale tramite il quale le foto in risposta vengono filtrate è costituito dal bounding box. Per comprendere meglio il problema, facciamo riferimento all’immagine 42: come è possibile osservare, il terreno utilizzato non occupa l’intero spazio offerto dalle Tile, lasciando numerosi spazi inutilizzati, il cui colore è rappresentato dal nero. Figura 42: In rosso il perimetro della zona effettiva delle Tile, il restante spazio nero rappresenta le zone in ombra facente parte del bounding box Sebbene la porzione di territorio in nero non sia utilizzata, essa va a far parte del bounding box di livello zero per la mappa caricata quindi, quando questi dati vengono utilizzati per la richiesta preliminare della lista di foto georeferenziate, vengono restituiti codici di immagini che, in effetti, andrebbero a collocarsi su zone in ombra. La quantità di informazioni lette potrebbe essere ben superiore a quella necessarie e, sopratutto, il rischio maggiore è quello di vedere posizionati punti di interesse fotografici su zone nere, 60 fenomeno visibile in figura . Figura 43: Punti di interesse fotografici posizionati su zone in ombra Per ovviare a questo tipo di problema è stata scelta una soluzione che fa del colore posto al di sotto di ciascun PDI la sua forza: se il pixel disposto sotto ogni elemento è di colore nero, il punto di interesse si trova al di fuori della zona di iteresse, quindi può essere distrutto. In figura 44, è possibile osservare il risultato finale del filtraggio basato sul colore. Figura 44: Punti di interesse fotografici filtrati in base al colore 61 62 7 Il Sistema di Navigazione Il secondo punto critico, cui è stato speso buona parte del tempo di realizzazione del progetto, è stato lo studio di un sistema di navigazione che accordasse più sistemi di input, profondamente diversi fra loro, quali mouse, tablet multitouch capacitivo e display multitouch basato su sensori. La decisione presa, anche tenendo conto di possibili sviluppi futuri, è stata quella di isolare il più possibile il codice relativo alla navigazione da quello che prende in ingresso gli input in base all’hardware utilizzato tramite l’utilizzo di varie interfacce. La figura 45 mostra la soluzione adottata in maniera grafica. Figura 45: Interfacce per l’acquisizione dati di movimento. Com’è possibile vedere, un primo controllo viene effettuato in quanto il controller della camera deve necessariamente conoscere a priori da quale sistema di input i dati verranno passati. Questo per questioni di performace: non avrebbe senso creare collegamenti virtuali con hardware non utilizzati. Una volta conosciuto il sistema di riferimento, inizia l’acquisizione dei dati di navigazione, con il sistema che attende i relativi input per poi riprodurli a video. 63 7.1 Camera Navigation Basata su Target Unity3D, come tutti i GameEngine di ultima generazione, mette a disposizione dello sviluppatore una struttura chiamata albero di scena. Un albero di scena rappresenta tutti gli oggetti instanziati e tutti gli oggetti che verranno instanziati a runtime, con relativo grado di parentela. E’ proprio basato sul grado di parentela fra oggetti che il sistema di navigazione è stato progettato e sviluppato. Per comprendere al meglio il concetto di grado di parentela fra oggetti facciamo un esempio utilizzando oggetto che esula dal mondo della Computer Graphics e coinvolge quello della meccanica: una macchina. Tutti conosciamo la struttura di una macchina, visionabile in maniera molto molto semplificata in figura 46: l’elemento portante, padre quindi, dell’intera struttura è il telaio, componente fondamentale al quale verranno collegati i restanti componenti dell’autovettura, come per esempio il motore, il gruppo di sospensioni anteriori e posteriori. Agli ultimi due elementi elencati nel precedente periodo andrano poi attaccate la coppia di ruote anteriori e posteriori. Figura 46: Esempio di struttura gerarchica. L’utilizzo di una struttura di questo tipo fornisce un importantissimo vantaggio nella gestione dell’oggetto in questione: muovendo l’elemento padre, il telaio appunto, tutti i restanti componenti a sè collegati, la cui posizione è referenziata al rispettivo elemento padre, vengono spostati di conseguenza, in quanto legati gerarchicamente alla radice dell’albero di componenti. E’ proprio su questo principio che è stato costruito il sistema di navigazione dell’intero software: una gerarchia di componenti, dove l’ultimo risulta essere proprio la camera in uso, permette i movimenti di: 1. Spostamento longitudinale; 2. Rotazione secondo l’asse Y; 64 3. Rotazione secondo l’asse X; 4. Rotazione su pivot. L’albero riferito alla camera è mostrato nel dettaglio in figura 47. Figura 47: Struttura gerarchica del sistema di navigazione utilizzato. Ma, purtroppo, tutto questo non si è rivelato sufficiente per l’ottenimento di risultati soddisfacenti, quindi partendo dalla struttura base descritta, si è creata un’ulteriore struttura ad albero, identica alla precedente, denominata struttura target. La necessità di avere una struttura target è stata data dalla possibilità di utilizzare una delle funzioni messe a disposizione dal core di Unity3D, chiamata Lerp, della classe Vector3. Lerp è un acronimo che stà per ‘Linear-Interpolation’, in quanto, preso in ingresso un parametro iniziale, uno finale ed una unità di misura temporale, permette al parametro iniziale di raggiungere quello finale nella unità di tempo indicata, con andamento lineare. 65 66 8 Natural User Interface In Computer Science, la Natural User Interface, da qui in avanti chiamata NUI, è il termine utilizzato da designer e sviluppatori di interfacce per software per fare riferimento ad una interfaccia utente che è completamente invisibile, oppure lo diventa con successive interazioni, ai suoi utenti. La parola ‘naturale’ è usata perché la maggior parte dei computer usa dispositivi di controllo artificiale il cui funzionamento deve essere appreso. Una NUI è rivolta ad un utente in grado di effettuare movimenti relativamente naturali, azioni e gesti (gestures) che devono controllare lŠapplicazione del computer o manipolare contenuti sullo schermo. La principale caratteristica di una NUI è la mancanza di una tastiera fisica e/o mouse. 8.1 L’Evoluzione delle Interfacce Utente: da CLI a NUI Negli anni 70, 80 e 90 furono sviluppate una serie di strategie per l’ottimizzazione di interfacce utente che avrebbero sostituito le allora in voga, command line user interface, CLI, e graphical user interface, GUI. Furono proprio questi gli anni in cui vennero coniati termini Natural User Interfaces, Direct User Interfaces e Metaphor-Free Computing. Ma fù solo il 2006 l’anno in cui Christian Moore creò una comunità di ricerca dedita all’espansione delle discussioni in merito alle tecnologie NUI. Nel 2008 invece, durante la conferenza chiamata Predicting the Past, Microsoft dichiarò in via ufficiale come le NUI avrebbero costituito la terza generazione di user interface. Figura 48: Evoluzione delle interfacce utente. Nelle command line interface, gli utenti dovevano necessariamente imparare una serie di comandi, l’utilizzo della tastiera e di input codificati, che restringevano molto l’utilizzo di un personal computer alle persone meno esperte e meno propense all’apprendimento. Con l’avvento del mouse naquero le GUI, i requisiti minimi per l’apprendimento dell’uso di un qualsiasi programma diminuirono molto, in quanto l’utilizzo di interfacce grafiche permetteva una migliore e facile esplorazione dei contenuti. Strumenti quali il ‘Desktop’ 67 o azioni tipo ‘Drag’, diventarono familiari al mondo intero. Una NUI è rivolta ad un utente in grado di effettuare movimenti relativamente naturali, azioni e gesti che devono controllare l’applicazione del computer o manipolare contenuti sullo schermo. Poiché la NUI è così veloce da imparare, l’aggettivo ’intuitivo‘ viene usato da molti per descrivere come gli utenti interagiscono con esso. Il Consumer Electronics Show del 2010 ha dimostrato diversi nuovi prodotti che utilizzano interfacce NUI. Ora una nuova ondata di prodotti è destinata ad essere dotata di interfacce naturali utente, dispositivi elettronici rivolti a un pubblico sempre più ampio. 8.2 Struttura della NUI utilizzata Il fatto che, il software progettato, debba necessariamente essere platform-indipendent, ha comportato un inevitabile progettazione ad-hoc dell’interfaccia di gestione, tenendo conto delle seguenti problematiche: 1. Tutte le operazioni devono essere gestite usando il single mouseclick/single touch; 2. La precisione di un mouse e di un dito è differente; 3. Un touch prolungato genera una serie di punti nell’intorno di quello ottenuto nel primo istante il cui l’evento è stato lanciato, è quasi impossibile ottenere due volte la stessa coordinata in frame immediatamente successivi; 4. Gli elementi sensibili dell’interfaccia devono essere sufficientemente grandi e distanti fra loro, tale da non permettere sovrapposizione di eventi; 5. L’interfaccia deve essere naturale, il tempo dedicato all’apprendimento nell’utilizzo minimo. In base ai concetti precedentemente espressi, figura 49 mostra la struttura della NUI utilizzata per la gestione delle features offerte dal sistema progettato: come è possibile osservare, le operazioni disponibili vengono attivate/disabilitate tramite l’utilizzo di ‘linguette’ disposte ai bordi del display attivabili con un semplice tocco su di esse. Acquisito l’evento che determina l’attivazione della features indicata, verranno mostrate, tramite un pannello scorrevole, le opzioni disponibili. Figura 50 mostra la struttura della NUI utilizzata in modalità completamente attiva. 68 Figura 49: Screenshot raffigurante la struttura della NUI progettata in modalità all-off. Figura 50: Screenshot raffigurante la struttura della NUI progettata in modalità all-on. 8.2.1 Menù Contestuale Alcune features implementate, includono una serie di sotto-funzionalità che vanno oltre alla semplice creazione di un oggetto tridimensionale a video, quindi è sorta la necessità di creare un sistema grazie al quale l’utente possa scegliere in maniera intuitiva quale delle funzionalità offerte abilitare o disabilitare. Un menù che possa offrire opzioni differenti in base al contesto di utilizzo prende il nome di ‘menù contestuale’. Nell’applicazione descritta in questo documento è presente un singolo menù di questo genere, per la gestione delle sotto-funzionalità offerte dalla visualizzazione di percorsi: • proiezione in altezza; • punti di interesse; • visuale aerea; • informazioni generali, delle quali abbiamo parlato in sezione 5.5. Le opzioni vengono abilitate nel momento in cui viene effettuato il picking su una qualsiasi 69 parte del percorso. Questo tipo di evento genera il completo bloccaggio della gestione della navigazione, in questo modo viene anche disabilitato il RayCast generato dalla pressione effettuata su un qualsiasi bottone, in quanto esso confonderebbe l’evento come un tentativo di picking. Figura 51: Menù contestuale riferito ad un percorso. 70 9 9.1 Conclusioni Campi di Impiego Nel corso di questo elaborato abbiamo avuto modo di vedere come il sistema progettato e realizzato abbia basato il suo intero ciclo di vita su un concetto molto importante: flessibilità. Nato come componente fondamentale del progetto Europeo Smart-Islands, il visualizzatore di informazioni territoriali può elaborare qualsiasi tipo di mappa passata in ingresso, semplicemente configurando i parametri fondamentali, legati al dataset in input, necessari ad una sua corretta riproduzione. Avendo dunque un mondo virtuale autogenerato, i possibili impieghi sono pressochè infiniti: si può passare dall’applicazione turistica che sfutta le funzionalità già implementate, aggiornando solamente i dati da esporre, alla realizzazione di un videogioco di strategia in tempo reale basato su scenari realmente esistenti. Si ha quindi a disposizione un mondo, vuoto. L’unica limitazione sul come riempirlo deriva dalla fantasia del team che ne proseguirà lo sviluppo. 9.2 Sviluppi Futuri Il progetto, come già detto, è pressochè allo stadio iniziale del suo sviluppo e, la flessibilità intorno alla quale esso è stato progettato, permette una sua personalizzazione in base al contesto in cui lo si vorrà inserire. Essendo stato scritto utilizzado un GameEngine molto conosciuto dagli addetti ai lavori, il cui sviluppo continua inesorabile ormai da anni, è possibile implementare aggiornamenti in maniera molto funzionale, lasciando carta bianca su possibili personalizzazioni. Come risultato di un lavoro di tesi il risultato finale può considerarsi pienamente soddisfacente ma il software è lungi dall’essere definito perfetto: sono tutt’ora presenti problemi non risolti. Il primo risulta essere legato alla precisione dei numeri float a 32 bit, discusso in sezione 5.2.6. In fase di testing è stato trovata una soluzione a tale problema, ma che comporterebbe un intera riprogettazione del sistema di collocazione delle Tile, e del sistema di navigazione. La soluzione più comoda è quella di attendere una versione di Unity3D a 64 bit. Il secondo, la cui risoluzione è molto più semplice rispetto al problema illustrato precedentemente, è legato alla visibilità di ciascuna Tile: nella versione corrente del software viene creata una sfera di contenimento avente raggio pari a metà diagonale di ciascun 71 tassello. Questa approssimazione non tiene conto dell’elemento altezza: è stato provato in fase di testing infatti, che i rilievi montuosi aventi altitudini importanti fuoriescono dalla sfera virtuale creata. Questo, in alcuni casi limite (come l’attraversamento aereo) potrebbe comportare un errato calcolo della visibilità della Tile. La soluzione a tale problema consiste nell’effettuare il check di visilità non più su una sfera, ma su un cilindro avente raggio pari a metà della diagonale di ogni Tile e come altezza quella massima della geometria contenuta. 72 10 Ringraziamenti Con questo ultimo capitolo, siamo giunti alla fine, queste sono le ultime righe che andrò a scrivere come studente, e le volevo dedicare ai ringraziamenti. Ai miei genitori, perchè senza di loro non avrei mai potuto essere ciò che oggi sono, anche se in certe occasioni potrebbero non prendere questa mia frase come un complimento. Ai miei amici, vecchi e nuovi, che mi sono stati vicini nei momenti belli e brutti, grazie di cuore ragazzi. Ai miei colleghi di facoltà, che ho imparato a conoscere sotto un diverso punto di vista solo nell’ultimo anno, e di questo mi rammarico un pò. Ai ragazzi di GraphiTech, che sono sempre stati molto gentili, ma sopratutto pazienti, nei miei confronti. ...e ultima, ma non per importanza, vorrei ringraziare la mia professoressa di Italiano delle scuole medie (G.G.): se avessi seguito ciò che volevi impormi, con la minaccia di una bocciatura, adesso non sarei dove sono. Come vedi a 13 anni avevo le idee già piuttosto chiare sul mio futuro. 73 Appendices 74 A Tile Generation Main.cs: costruzione ricorsiva delle Tile. public c l a s s Main : MonoBehaviour { private b o o l c h e c k V i s i b i l i t y ( Vector3 midPoint , f l o a t r a d i u s ) { for ( int i =0; i <t h i s . f r u s t u m P l a n e s . Length ; i ++){ f l o a t d i n s = t h i s . f r u s t u m P l a n e s [ i ] . GetDistanceToPoint ( midPoint ) ; i f ( d in s <−r a d i u s ) return f a l s e ; } return true ; } private b o o l c h e c k D i n s t a n c e ( T i l e t i l e ) { Vector3 midPoint = t i l e . t i l e M i d P o i n t ; Vector3 p o s i t i o n = Camera . main . t r a n s f o r m . p o s i t i o n ; f l o a t d i n s = Vector3 . D i s t a n c e ( p o s i t i o n , midPoint ) ; f l o a t logDinstanceFromcam = Mathf . Log10 ( d i n s ) ; f l o a t l o g C e l l S i z e = Mathf . Log10 ( t i l e . s i z e ) ; return ( l o g C e l l S i z e >= ( logDinstanceFromcam − t h i s .K) ) ; } private b o o l haveChildrenBIL ( T i l e p a r e n t T i l e ) { int n e x t L e v e l = p a r e n t T i l e . l e v e l + 1 ; /∗ F i g l i o i n b a s s o a s i n i s t r a ∗/ int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; /∗ F i g l i o i n b a s s o a d e s t r a ∗/ int childLRTileX = c h i l d L L T i l e X +1; int childLRTileY = c h i l d L L T i l e Y ; 75 /∗ F i g l i o i n a l t o a s i n i s t r a ∗/ int childULTileX = c h i l d L L T i l e X ; int childULTileY = c h i l d L L T i l e Y +1; /∗ F i g l i o i n a l t o a d e s t r a ∗/ int childURTileX = c h i l d L L T i l e X +1; int childURTileY = c h i l d L L T i l e Y +1; string string string string childLLCode childLRCode childULCode childURCode = = = = childLLTileY childLRTileY childULTileY childURTileY + + + + "_" "_" "_" "_" + + + + childLLTileX ; childLRTileX ; childULTileX ; childURTileX ; s t r i n g childLLDownloaderCode = " B i l ␣" + c h i l d L L T i l e X + "−" + c h i l d L L T i l e Y + "−" + n e x t L e v e l ; s t r i n g childLRDownloaderCode = " B i l ␣" + childLRTileX + "−" + childLRTileY + "−" + n e x t L e v e l ; s t r i n g childULDownloaderCode = " B i l ␣" + childULTileX + "−" + childULTileY + "−" + n e x t L e v e l ; s t r i n g childURDownloaderCode = " B i l ␣" + childURTileX + "−" + childURTileY + "−" + n e x t L e v e l ; s t r i n g childLLBilPath = this . bilPath childLLTileY s t r i n g c hi ld LR Bi lP at h = t h i s . b i l P a t h childLRTileY s t r i n g childULBilPath = t h i s . b i l P a t h childULTileY s t r i n g childURBilPath = t h i s . b i l P a t h childURTileY + + + + + + + + "/" "/" "/" "/" "/" "/" "/" "/" + + + + + + + + n e x t L e v e l + "/" childLLCode ; n e x t L e v e l + "/" childLRCode ; n e x t L e v e l + "/" childULCode ; n e x t L e v e l + "/" childURCode ; /∗ C o n t r o l l o s e i l BIL r i c h i e s t o Ãĺ d i s p o n i b i l e ∗/ i f ( t h i s . a v a i l a b l e B I L s . Contains ( childLLCode ) && t h i s . a v a i l a b l e B I L s . Contains ( childLRCode ) && t h i s . a v a i l a b l e B I L s . Contains ( childULCode ) && t h i s . a v a i l a b l e B I L s . Contains ( childURCode ) ) return true ; 76 + + + + /∗ Se non Ãĺ d i s p o n i b i l e , s e non Ãĺ i n coda d e i download ma i l ∗ f i l e e s i s t e v u o l d i r e che Ãĺ s t a t o appena s c a r i c a t o ∗/ i f ( ! t h i s . dQueue . ContainsKey ( childLLDownloaderCode ) && ! t h i s . dQueue . ContainsKey ( childLRDownloaderCode ) && ! t h i s . dQueue . ContainsKey ( childULDownloaderCode ) && ! t h i s . dQueue . ContainsKey ( childURDownloaderCode ) ) { bool bool bool bool childLLExists childLRExists childULExists childURExists = = = = File File File File . Exists ( childLLBilPath ) ; . E x i s t s ( c hi ld LR Bi lP at h ) ; . E x i s t s ( childULBilPath ) ; . E x i s t s ( childURBilPath ) ; i f ( c h i l d L L E x i s t s&&c h i l d L R E x i s t s&&c h i l d U L E x i s t s&&c h i l d U R E x i s t s ) { a v a i l a b l e B I L s . Add( childLLCode ) ; a v a i l a b l e B I L s . Add( childLRCode ) ; a v a i l a b l e B I L s . Add( childULCode ) ; a v a i l a b l e B I L s . Add( childURCode ) ; return true ; } else { i f (HAVEINTERNET) { /∗ c o n t r o l l o d i r L e U∗/ s t r i n g dirLPath = t h i s . b i l P a t h + "/" + n e x t L e v e l + "/" + c h i l d L L T i l e Y + "/" ; s t r i n g dirUPath = t h i s . b i l P a t h + "/" + n e x t L e v e l + "/" + childULTileY + "/" ; i f ( ! D i r e c t o r y . E x i s t s ( dirLPath ) ) D i r e c t o r y . C r e a t e D i r e c t o r y ( dirLPath ) ; i f ( ! D i r e c t o r y . E x i s t s ( dirUPath ) ) D i r e c t o r y . C r e a t e D i r e c t o r y ( dirUPath ) ; /∗ F i g l i o i n b a s s o a s i n i s t r a ∗/ i f ( ! childLLExists ) t h i s . downloadBIL ( childLLDownloaderCode , childLLCode ) ; 77 /∗ F i g l i o i n b a s s o a d e s t r a ∗/ i f ( ! childLRExists ) t h i s . downloadBIL ( childLRDownloaderCode , childLRCode ) ; /∗ F i g l i o i n a l t o a s i n i s t r a ∗/ i f ( ! childULExists ) t h i s . downloadBIL ( childULDownloaderCode , childULCode ) ; /∗ F i g l i o i n a l t o a d e s t r a ∗/ i f ( ! childURExists ) t h i s . downloadBIL ( childURDownloaderCode , childURCode ) ; } } } return f a l s e ; } private b o o l haveChildrenIMG ( T i l e p a r e n t T i l e ) { int n e x t L e v e l = p a r e n t T i l e . l e v e l + 1 ; /∗ F i g l i o i n b a s s o a s i n i s t r a ∗/ int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; /∗ F i g l i o i n b a s s o a d e s t r a ∗/ int childLRTileX = c h i l d L L T i l e X +1; int childLRTileY = c h i l d L L T i l e Y ; /∗ F i g l i o i n a l t o a s i n i s t r a ∗/ int childULTileX = c h i l d L L T i l e X ; int childULTileY = c h i l d L L T i l e Y +1; /∗ F i g l i o i n a l t o a d e s t r a ∗/ int childURTileX = c h i l d L L T i l e X +1; int childURTileY = c h i l d L L T i l e Y +1; 78 string string string string childLLCode childLRCode childULCode childURCode = = = = childLLTileY childLRTileY childULTileY childURTileY + + + + "_" "_" "_" "_" + + + + childLLTileX ; childLRTileX ; childULTileX ; childURTileX ; s t r i n g childLLDownloaderCode="Img␣"+t h i s . mapName+"−"+c h i l d L L T i l e X+ "−"+c h i l d L L T i l e Y+"−"+n e x t L e v e l ; s t r i n g childLRDownloaderCode="Img␣"+t h i s . mapName+"−"+childLRTileX+ "−"+childLRTileY+"−"+n e x t L e v e l ; s t r i n g childULDownloaderCode="Img␣"+t h i s . mapName+"−"+childULTileX+ "−"+childULTileY+"−"+n e x t L e v e l ; s t r i n g childURDownloaderCode="Img␣"+t h i s . mapName+"−"+childURTileX+ "−"+childURTileY+"−"+n e x t L e v e l ; s t r i n g c h i l d L L B i l P a t h = t h i s . mapPath childLLTileY s t r i n g c hi ld LR Bi lP at h = t h i s . mapPath childLRTileY s t r i n g childULBilPath = t h i s . mapPath childULTileY s t r i n g childURBilPath = t h i s . mapPath childURTileY + + + + + + + + "/" "/" "/" "/" "/" "/" "/" "/" + + + + + + + + nextLevel + childLLCode nextLevel + childLRCode nextLevel + childULCode nextLevel + childURCode "/" + + " . jpg " ; "/" + + " . jpg " ; "/" + + " . jpg " ; "/" + + " . jpg " ; /∗ C o n t r o l l o s e i l BIL r i c h i e s t o Ãĺ d i s p o n i b i l e ∗/ i f ( t h i s . a v a i l a b l e I M G s . Contains ( childLLCode ) && t h i s . a v a i l a b l e I M G s . Contains ( childLRCode ) && t h i s . a v a i l a b l e I M G s . Contains ( childULCode ) && t h i s . a v a i l a b l e I M G s . Contains ( childURCode ) ) return true ; /∗ Se non Ãĺ d i s p o n i b i l e , s e non Ãĺ i n coda d e i download ma i l ∗ e s i s t e v u o l d i r e che Ãĺ s t a t o appena s c a r i c a t o ∗/ i f ( ! t h i s . dQueue . ContainsKey ( childLLDownloaderCode ) && ! t h i s . dQueue . ContainsKey ( childLRDownloaderCode ) && ! t h i s . dQueue . ContainsKey ( childULDownloaderCode ) && 79 file ! t h i s . dQueue . ContainsKey ( childURDownloaderCode ) ) { bool bool bool bool childLLExists childLRExists childULExists childURExists = = = = File File File File . Exists ( childLLBilPath ) ; . E x i s t s ( c hi ld LR Bi lP at h ) ; . E x i s t s ( childULBilPath ) ; . E x i s t s ( childURBilPath ) ; i f ( c h i l d L L E x i s t s&&c h i l d L R E x i s t s&&c h i l d U L E x i s t s&&c h i l d U R E x i s t s ) { a v a i l a b l e I M G s . Add( childLLCode ) ; a v a i l a b l e I M G s . Add( childLRCode ) ; a v a i l a b l e I M G s . Add( childULCode ) ; a v a i l a b l e I M G s . Add( childURCode ) ; return true ; } else { i f (HAVEINTERNET) { /∗ c o n t r o l l o d i r L e U∗/ s t r i n g dirLPath = t h i s . mapPath + "/" + n e x t L e v e l + "/" + c h i l d L L T i l e Y + "/" ; s t r i n g dirUPath = t h i s . mapPath + "/" + n e x t L e v e l + "/" + childULTileY + "/" ; i f ( ! D i r e c t o r y . E x i s t s ( dirLPath ) ) D i r e c t o r y . C r e a t e D i r e c t o r y ( dirLPath ) ; i f ( ! D i r e c t o r y . E x i s t s ( dirUPath ) ) D i r e c t o r y . C r e a t e D i r e c t o r y ( dirUPath ) ; /∗ F i g l i o i n b a s s o a s i n i s t r a ∗/ i f ( ! childLLExists ) t h i s . downloadIMG ( childLLDownloaderCode , childLLCode ) ; /∗ F i g l i o i n b a s s o a d e s t r a ∗/ i f ( ! childLRExists ) t h i s . downloadIMG ( childLRDownloaderCode , childLRCode ) ; /∗ F i g l i o i n a l t o a s i n i s t r a ∗/ i f ( ! childULExists ) 80 t h i s . downloadIMG ( childULDownloaderCode , childULCode ) ; /∗ F i g l i o i n a l t o a d e s t r a ∗/ i f ( ! childURExists ) t h i s . downloadIMG ( childURDownloaderCode , childURCode ) ; } } } return f a l s e ; } private void c r e a t e C h i l d r e n ( T i l e p a r e n t T i l e ) { int n e x t L e v e l = p a r e n t T i l e . l e v e l +1; /∗ F i g l i o i n b a s s o a s i n i s t r a ∗/ int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/ ( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ; f l o a t childLLLon = ( ( c h i l d L L T i l e X / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −180; f l o a t chi ldLLLat = ( ( c h i l d L L T i l e Y / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −90; T i l e c h i l d L L = new T i l e ( childLLLat , childLLLon , n e x t L e v e l , p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ; a d e s t r a ∗/ c h i l d L L T i l e X +1; childLLTileY ; ( ( childLRTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −180; f l o a t childLRLat = ( ( childLRTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −90; /∗ F i g l i o i n b a s s o int childLRTileX = int childLRTileY = f l o a t childLRLon = T i l e childLR = new T i l e ( childLRLat , childLRLon , n e x t L e v e l , p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ; 81 /∗ F i g l i o i n a l t o a s i n i s t r a ∗/ int childULTileX = c h i l d L L T i l e X ; int childULTileY = c h i l d L L T i l e Y +1; f l o a t childULLon = ( ( childULTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −180; f l o a t childULLat = ( ( childULTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −90; T i l e childUL = new T i l e ( childULLat , childULLon , n e x t L e v e l , p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ; /∗ F i g l i o i n a l t o a d e s t r a ∗/ int childURTileX = c h i l d L L T i l e X +1; int childURTileY = c h i l d L L T i l e Y +1; f l o a t childURLon = ( ( childURTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −180; f l o a t childURLat = ( ( childURTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗ p a r e n t T i l e . z l t d ) −90; T i l e childUR = new T i l e ( childURLat , childURLon , n e x t L e v e l , p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ; /∗ A t t i v o r i c o r s i o n e su f i g l i ∗/ this . checkRecursion ( childLL ) ; t h i s . c h e c k R e c u r s i o n ( childLR ) ; t h i s . c h e c k R e c u r s i o n ( childUL ) ; t h i s . c h e c k R e c u r s i o n ( childUR ) ; } private void c h e c k R e c u r s i o n ( T i l e t i l e ) { /∗ s e c h e c k d i n s t a n c e && h o t e x t u r e d i l i v e l l o s e ho i f i l e BIL d e i f i g l i 1) r i m u o v i da a r r a y v i s i b i l i 2) d i v i d i 82 altrimenti 1) a s s o c i a t e x t u r e 2) a c c e n d i ( d i d e f a u l t s p e n t o ) 3) i n s e r i s c i n e l v e t t o r e d e i r e n d e r altrimenti s e NON Ãĺ i n h a s h t a b l e a g g i u n g i i n cima l i s t a o g g e t t i aggiungi hastable aggiorna g l i a l t r i elementi d e l l a l i s t a s c h i f t a n d o l i in giu aggiorno g l i a l t r i i n d i c i d e l l ’ hashtable incrementandoli d i uno ( t r a n n e q u e l l o che ho a g g i u n t o ) 1) a s s o c i a t e x t u r e 2) a c c e n d i ( d i d e f a u l t s p e n t o ) 3) i n s e r i s c i n e l v e t t o r e d e i r e n d e r ∗/ b o o l i s V i s i b l e=c h e c k V i s i b i l i t y ( t i l e . t i l e M i d P o i n t , t i l e . boundingSphereRad i f ( i s V i s i b l e ){ int n e x t L e v e l = t i l e . l e v e l + 1 ; i f ( t h i s . c h e c k D i n s t a n c e ( t i l e ) && ( n e x t L e v e l<=t h i s . m a x A v a i l a b l e L e v e l ) ) { i f ( haveChildrenIMG ( t i l e ) ) { i f ( t h i s . haveChildrenBIL ( t i l e ) ) { this . removeOnVisibles ( t i l e . tileCode ) ; this . createChildren ( t i l e ) ; } else visualizeTile ( tile ); } else visualizeTile ( tile ); } else { /∗ Se a r r i v o q u i v u o l d i r e che i l t i l e deve e s s e r e r e n d e r i z z a t o ∗ 1) c o n t r o l l o s e c ’ Ãĺ n e l l a mappa ∗ 2) s e non c ’ Ãĺ l o c r e o 83 ∗ 3) a g g i o r n o mappa , o b j e c t e v i s i b l e s ∗/ visualizeTile ( tile ); i f ( t i l e . l e v e l > this . maxVisibleLevel ) this . maxVisibleLevel = t i l e . l e v e l ; } } } private void v i s u a l i z e T i l e ( T i l e t i l e ) { i f ( ! t h i s . map . ContainsKey ( t i l e . t i l e C o d e ) ) { GameObject t e r r a i n ; Quaternion ID = Quaternion . i d e n t i t y ; t e r r a i n = I n s t a n t i a t e ( t h i s . t i l e P r e f a b , t i l e . t i l e M i d P o i n t , ID ) ; t e r r a i n . t r a n s f o r m . l o c a l S c a l e=new Vector3 ( t i l e . s i z e , 1 , t i l e . s i z e ) ; t e r r a i n . name = " T i l e ␣" + t i l e . t i l e C o d e ; t e r r a i n . transform . parent = this . Layer0Container . transform ; this . insertOnBuffer ( t e r r a i n ) ; t h i s . insertOnMap ( t i l e . t i l e C o d e ) ; } i f ( ! t h i s . v i s i b l e s . Contains ( t i l e . t i l e C o d e ) ) { t h i s . applyTexture ( t i l e ) ; this . i n s e r t O n V i s i b l e s ( t i l e . tileCode ) ; t h i s . GizmosTilesArray . Add( t i l e ) ; } } // Update i s c a l l e d once per frame void Update ( ) { f r u s t u m P l a n e s = G e o m e t r y U t i l i t y . C a l c u l a t e F r u s t u m P l a n e s ( Camera . main ) ; int l v 0 = 0 ; for ( int i =0; i <t h i s . L e v e l 0 T i l e s . Count ; i ++){ s t r i n g name = ( s t r i n g ) t h i s . L e v e l 0 T i l e s [ i ] ; int t i l e X = System . Convert . ToInt32 ( ( s t r i n g ) ( name . S p l i t ( ’_ ’ ) ) [ 1 ] ) ; 84 int t i l e Y = System . Convert . ToInt32 ( ( s t r i n g ) ( name . S p l i t ( ’_ ’ ) ) [ 0 ] ) ; f l o a t l o n = ( ( t i l e X / Mathf . Pow( 2 , l v 0 ) ) ∗ t h i s . z l t d ) −180; f l o a t l a t = ( ( t i l e Y / Mathf . Pow( 2 , l v 0 ) ) ∗ t h i s . z l t d ) −90; string string string string string code = t i l e Y + "_" + t i l e X ; b i l C o d e = " B i l ␣" + t i l e X + "−" + t i l e Y + "−" + l v 0 ; imgCode = "Img␣"+t h i s . mapName+"−"+t i l e X+"−"+t i l e Y+"−"+l v 0 ; b i l P a t h= t h i s . b i l P a t h+"/"+l v 0+"/"+t i l e Y+"/"+code ; imgPath = t h i s . mapPath+"/"+l v 0+"/"+t i l e Y+"/"+code+" . j p g " ; i f ( t h i s . a v a i l a b l e I M G s . Contains ( code ) ) { i f ( t h i s . a v a i l a b l e B I L s . Contains ( code ) ) { T i l e t i l e = new T i l e ( l a t , lon , lv0 , t h i s . z l t d , null ,GLOBALSCALE) ; this . checkRecursion ( t i l e ) ; } else { i f ( ! t h i s . dQueue . ContainsKey ( b i l C o d e ) ) i f ( File . Exists ( bilPath )) t h i s . a v a i l a b l e B I L s . Add( code ) ; else { t h i s . downloadBIL ( bilCode , code ) ; } } } else { i f ( ! t h i s . dQueue . ContainsKey ( imgCode ) ) i f ( F i l e . E x i s t s ( imgPath ) ) t h i s . a v a i l a b l e I M G s . Add( code ) ; else { t h i s . downloadIMG ( imgCode , code ) ; } } } } } 85 86 B BIL Reading BilReader.cs: lettura dei file DTM in formato BIL. public c l a s s MeshGenerator : MonoBehaviour { private void l o a d A L t i t u d e s ( ) { t h i s . a l t i t u d e s = new f l o a t [ h e i g h t ∗ width ] ; byte [ ] byteArray = t h i s . r e a d F i l e ( t h i s . BILpath ) ; ; // Questo c o n t r o l l o s e r v e s o l o per l e t i l e d i l i v e l l o 0 , // i n quanto non hanno padre , d i c o n s e g u e n z a i c o n t r o l l i // che vengono f a t t i s u i f i g l i i n HavechildBIL vengono s a l t a t i // e c ’ Ãĺ i l r i s c h i o che s i acceda a l f i l e BIL prima che e s s o // s i a s c a r i c a t o completamente while ( byteArray==null ) { System . Threading . Thread . S l e e p ( 1 0 ) ; byteArray = t h i s . r e a d F i l e ( t h i s . BILpath ) ; } f l o a t [ ] a l t i t u d e A r r a y = new f l o a t [ h e i g h t ∗ width ] ; // R i f l e t t o i n X per a v e r e c o r r i s p o n d e n z a 1 : 1 con l a mia g r i g l i a int i n d e x =0; for ( int i =( byteArray . Length /2) −1; i >=0; i −−){ a l t i t u d e A r r a y [ i ]= System . B i t C o n v e r t e r . ToInt16 ( byteArray , i n d e x ) ; i n d e x +=2; } // R i f l e t t o i n Y per a v e r e c o r r i s p o n d e n z a 1 : 1 con l a mia g r i g l i a for ( int y=0;y<h e i g h t ; y++){ for ( int x=0;x<width ; x++){ a l t i t u d e s [ y∗ width+x]= a l t i t u d e A r r a y [ y∗ width+(width−1−x ) ] ; } } } 87 public byte [ ] r e a d F i l e ( s t r i n g f i l e P a t h ) { byte [ ] b u f f e r ; int count ; int sum = 0 ; try { FileStream f i l e S t r e a ; f i l e S t r e a m=new F i l e S t r e a m ( f i l e P a t h , FileMode . Open , F i l e A c c e s s . Read ) ; int l e n g t h = ( int ) f i l e S t r e a m . Length ; b u f f e r = new byte [ l e n g t h ] ; while ( ( count=f i l e S t r e a m . Read ( b u f f e r , sum , l e n g t h −sum)) >0) sum += count ; f i l e S t re a m . Close ( ) ; return b u f f e r ; } catch ( IOException e ) { Debug . Log ( e . Message ) ; } return null ; } } 88 C Mesh Generation MeshGenerator.cs: costruzione Tile mesh. public c l a s s MeshGenerator : MonoBehaviour { private void generateHeightmap ( ) { // A c q u i s i z i o n e d e l l a mesh d a l component Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ; int i n d e x=h e i g h t ∗ width ; // C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e ) Vector3 [ ] v e r t i c e s ; Vector2 [ ] uv ; i f ( a c t i v a t e S k i r t s ){ v e r t i c e s=new Vector3 [ ( h e i g h t ∗ width )+(2∗ h e i g h t +2∗width ) ] ; uv=new Vector2 [ ( h e i g h t ∗ width )+(2∗ h e i g h t +2∗width ) ] ; } else { v e r t i c e s=new Vector3 [ ( h e i g h t ∗ width ) ] ; uv=new Vector2 [ ( h e i g h t ∗ width ) ] ; } int [ ] s k i r t=new int [ h e i g h t ∗ width ] ; // C o s t r u z i o n e d e i v e r t i c i d e l piano for ( int y=0;y<h e i g h t ; y++){ for ( int x=0;x<width ; x++){ // Normalizzo X ed Y i n maniera t a l e da f a r l i e n t r a r e // i n un range compreso f r a 0 ed 1 , c o s i ho i l piano 1 x1 f l o a t s c a l e d X =( f l o a t ) x / ( f l o a t ) ( width −1); f l o a t s c a l e d Y =( f l o a t ) y / ( f l o a t ) ( h e i g h t −1); f l o a t a l t i t u d e =( t h i s . a l t i t u d e s [ y∗ width + x ] / 1 0 0 0 ) / a l t S c a l e ; 89 // Se vado o l t r e i l bbox i l s e r v e r mi r e s t i t u i s c e // a l t e z z a −3000 mt i f ( a l t i t u d e <0) a l t i t u d e =0.0 f ; v e r t i c e s [ y∗ width+x]=new Vector3 ( scaledX , a l t i t u d e , s c a l e d Y )+ new Vector3 ( −0.5 f , 0 , − 0 . 5 f ) ; uv [ y∗ width+x]=new Vector2 ( scaledX , s c a l e d Y ) ; i f ( a c t i v a t e S k i r t s ){ // C o s t r u z i o n e d e l l a s k i r t i f ( ( x ==0)||( x==width − 1 ) | | ( y ==0)||( y==h e i g h t −1)){ v e r t i c e s [ i n d e x ]=new Vector3 ( scaledX ,− s k i r t S i z e , s c a l e d Y )+ new Vector3 ( −0.5 f , 0 f , −0.5 f ) ; uv [ i n d e x ]=new Vector2 ( scaledX , s c a l e d Y ) ; s k i r t [ y∗ width+x]= i n d e x ; i n d e x++; } else s k i r t [ y∗ width+x]= −1; } } } // B u i l d t r i a n g l e i n d i c e s : 3 i n d i c e s i n t o // v e r t e x a r r a y f o r each t r i a n g l e int [ ] t r i a n g l e s ; if ( activateSkirts ) t r i a n g l e s=new int [ ( ( h e i g h t −1)∗( width −1)∗6)+ ( ( 2 ∗ ( h e i g h t −1)+2∗( width − 1 ) ) ∗ 6 ) ] ; else t r i a n g l e s=new int [ ( ( h e i g h t −1)∗( width − 1 ) ∗ 6 ) ] ; i n d e x =0; for ( int y=0;y<h e i g h t −1;y++){ for ( int x=0;x<width −1;x++){ 90 // For each g r i d c e l l o u t p u t two t r i a n g l e s t r i a n g l e s [ i n d e x ++]=(y∗ width)+ x ; t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x ; t r i a n g l e s [ i n d e x ++]=(y∗ width)+x+1; t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x ; t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x+1; t r i a n g l e s [ i n d e x ++]=(y∗ width)+x+1; } } i f ( a c t i v a t e S k i r t s ){ // S k i r t prima ed u l t i m a r i g a i n d e x =(( h e i g h t −1)∗( width − 1 ) ∗ 6 ) ; for ( int x=0;x<width −1;x++){ // S k i r t prima r i g a t r i a n g l e s [ i n d e x++]= s k i r t [ x ] ; t r i a n g l e s [ i n d e x++]=x ; t r i a n g l e s [ i n d e x++]= s k i r t [ x + 1 ] ; t r i a n g l e s [ i n d e x++]=x ; t r i a n g l e s [ i n d e x++]=x+1; t r i a n g l e s [ i n d e x++]= s k i r t [ x + 1 ] ; // S k i r t u l t i m a r i g a t r i a n g l e s [ i n d e x++]=x+(width ∗( h e i g h t − 1 ) ) ; t r i a n g l e s [ i n d e x++]= s k i r t [ x+(width ∗( h e i g h t − 1 ) ) ] ; t r i a n g l e s [ i n d e x++]=x+1+(width ∗( h e i g h t − 1 ) ) ; t r i a n g l e s [ i n d e x++]= s k i r t [ x+(width ∗( h e i g h t − 1 ) ) ] ; t r i a n g l e s [ i n d e x++]= s k i r t [ x+1+(width ∗( h e i g h t − 1 ) ) ] ; t r i a n g l e s [ i n d e x++]=x+1+(width ∗( h e i g h t − 1 ) ) ; } // S k i r t prima ed u l t i m a c o l o n n a for ( int y=0;y<=(width ∗( h e i g h t − 2 ) ) ; y+=width ) { // S k i r t prima c o l o n n a 91 t r i a n g l e s [ i n d e x++]= s k i r t [ y ] ; t r i a n g l e s [ i n d e x++]= s k i r t [ y+width ] ; t r i a n g l e s [ i n d e x++]=y ; t r i a n g l e s [ i n d e x++]= s k i r t [ y+width ] ; t r i a n g l e s [ i n d e x++]=y+width ; t r i a n g l e s [ i n d e x++]=y ; // S k i r t u l t i m a c o l o n n a t r i a n g l e s [ i n d e x++]=y+width −1; t r i a n g l e s [ i n d e x++]=y+2∗width −1; t r i a n g l e s [ i n d e x++]= s k i r t [ y+width − 1 ] ; t r i a n g l e s [ i n d e x++]=y+2∗width −1; t r i a n g l e s [ i n d e x++]= s k i r t [ y+2∗width − 1 ] ; t r i a n g l e s [ i n d e x++]= s k i r t [ y+width − 1 ] ; } } mesh . v e r t i c e s=v e r t i c e s ; mesh . uv=uv ; mesh . t r i a n g l e s=t r i a n g l e s ; mesh . R e c a l c u l a t e N o r m a l s ( ) ; GetComponent<M e s h C o l l i d e r > ( ) . sharedMesh=mesh ; } } 92 Layer.cs: duplicazione della mesh di ciascuna Tile. public c l a s s Layer : MonoBehaviour { // Update i s c a l l e d once per frame void Update ( ) { t h i s . v i s i b l e s B u f f e r=main . v i s i b l e s B u f f e r ; t h i s . v i s i b l e s=main . v i s i b l e s ; for ( int i =0; i <v i s i b l e s . Count ; i ++){ s t r i n g code=( s t r i n g ) t h i s . v i s i b l e s [ i ] ; i f ( ! t h i s . map . ContainsKey ( code ) ) { int i n d e x =( int ) main . map [ code ] ; GameObject t i l e =(GameObject ) main . b u f f e r [ i n d e x ] ; MeshGenerator meshT ; meshT= t i l e . GetComponent ( " MeshGenerator " ) as MeshGenerator ; b o o l isDone=meshT . isDone ; i f ( isDone ) { i f ( t h i s . haveImage ( code ) ) { GameObject l a y e r ; l a y e r=I n s t a n t i a t e ( t h i s . l a y e r P r e f a b , t i l e . transform . position , Quaternion . i d e n t i t y ) as GameObject ; l a y e r . t r a n s f o r m . l o c a l S c a l e= t i l e . t r a n s f o r m . l o c a l S c a l e ; l a y e r . name=" Layer ␣"+code ; l a y e r . t r a n s f o r m . p a r e n t=t h i s . L a y e r C o n t a i n e r . t r a n s f o r m ; Mesh mesh=t e r r a i n . GetComponent<M e s h F i l t e r > ( ) . mesh ; l a y e r . GetComponent<M e s h F i l t e r > ( ) . sharedMesh=mesh ; this . insertOnBuffer ( l a y e r ) ; t h i s . insertOnMap ( code ) ; t h i s . applyTexture ( l a y e r ) ; } } } } } } 93 WalkGenerator.cs: generazione collider per ciascun percorso e LineRendereer. public c l a s s WalkGenerator : MonoBehaviour { private A r r a y L i s t c a l c u l a t e W i d e L i n e ( A r r a y L i s t d e c i m a t e d P o i n t s ) { int s i z e = d e c i m a t e d P o i n t s . Count ; A r r a y L i s t r e s u l t = new A r r a y L i s t ( ) ; for ( int i =1; i <s i z e ; i ++){ Vector2 p1=( Vector2 ) d e c i m a t e d P o i n t s [ i − 1 ] ; Vector2 p2=( Vector2 ) d e c i m a t e d P o i n t s [ i ] ; Vector2 d i r=p2−p1 ; Vector2 normal=(new Vector2 ( d i r . y,− d i r . x ) ) . n o r m a l i z e d ; Vector2 Vector2 Vector2 Vector2 result result result result v0=p1−(normal ∗walkWidth ) ; v1=p2−(normal ∗walkWidth ) ; v2=p1+(normal ∗walkWidth ) ; v3=p2+(normal ∗walkWidth ) ; . Add( v0 ) ; . Add( v2 ) ; . Add( v1 ) ; . Add( v3 ) ; } return r e s u l t ; } private void g e n e r a t e C o l l i d e r ( ) { // A c q u i s i z i o n e d e l l a mesh d a l component Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ; A r r a y L i s t c u r r e n t = new A r r a y L i s t ( ) ; // C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e ) L i s t <Vector3> v e r t i c e s = new L i s t <Vector3 > ( ) ; L i s t <Vector2> uv = new L i s t <Vector2 > ( ) ; for ( int i =0; i <c o l l i d e r P o i n t s . Count ; i ++){ Vector2 p o i n t = ( Vector2 ) c o l l i d e r P o i n t s [ i ] ; 94 float l a t i t u d e = point . x ; float longitude = point . y ; f l o a t posX=( l o n g i t u d e +180)∗GLOBALSCALE; f l o a t posZ=( l a t i t u d e +90)∗GLOBALSCALE; Vector3 o r i g i n = new Vector3 ( posX , 1 0 f , posZ ) ; Vector3 d i r e c t i o n = −Vector3 . up ; Ray ray = new Ray ( o r i g i n , d i r e c t i o n ) ; RaycastHit h i t ; float altitude = 0.0 f ; int layerMask = ( 1 << 1 0 ) ; i f ( P h y s i c s . Raycast ( ray , out h i t , Mathf . I n f i n i t y , layerMask ) ) { a l t i t u d e = hit . point . y ; } v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e , posZ ) ) ; uv . Add(new Vector2 ( posX , posZ ) ) ; } L i s t <int> t r i a n g l e s = new L i s t <int > ( ) ; for ( int i =0; i <c o l l i d e r P o i n t s . Count −2; i ++){ // For each g r i d c e l l o u t p u t two t r i a n g l e s t r i a n g l e s . Add( i ) ; t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +1); } mesh . v e r t i c e s = v e r t i c e s . ToArray ( ) ; mesh . uv = uv . ToArray ( ) ; mesh . t r i a n g l e s = t r i a n g l e s . ToArray ( ) ; mesh . R e c a l c u l a t e N o r m a l s ( ) ; GetComponent<M e s h C o l l i d e r > ( ) . sharedMesh = mesh ; i f ( ! walk . c u r r e n t T i l e s M a p . ContainsKey ( code ) ) 95 walk . c u r r e n t T i l e s M a p . Add( code , c u r r e n t ) ; else walk . c u r r e n t T i l e s M a p [ code ] = c u r r e n t ; } private void g e n e r a t e L i n e ( ) { L i n e R e n d e r er l i n e = GetComponent<LineRenderer > ( ) ; A r r a y L i s t a l t i t u d e s = new A r r a y L i s t ( ) ; int s i z e = t h i s . l i n e P o i n t s . Count ; l i n e . SetVertexCount ( s i z e ) ; for ( int i =0; i <s i z e ; i ++){ Vector2 p o i n t = ( Vector2 ) t h i s . l i n e P o i n t s [ i ] ; float float float float l a t i t u d e = point . x ; longitude = point . y ; posX=( l o n g i t u d e +180)∗GLOBALSCALE; posZ=( l a t i t u d e +90)∗GLOBALSCALE; Vector3 o r i g i n = new Vector3 ( posX , 1 0 f , posZ ) ; Vector3 d i r e c t i o n = −Vector3 . up ; Ray ray = new Ray ( o r i g i n , d i r e c t i o n ) ; RaycastHit h i t ; float altitude = 0.0 f ; int layerMask = ( 1 << 1 0 ) ; i f ( P h y s i c s . Raycast ( ray , out h i t , Mathf . I n f i n i t y , layerMask ) ) { a l t i t u d e = hit . point . y ; l i n e . S e t P o s i t i o n ( i , new Vector3 ( posX , a l t i t u d e , posZ ) ) ; } } } } 96 WalkAltitudeGenerator.cs: generazione mesh riguardante la proiezione in altezza di ciascun percorso. public c l a s s WalkAltitudeGenerator : MonoBehaviour { private A r r a y L i s t c a l c u l a t e W i d e L i n e ( A r r a y L i s t d e c i m a t e d P o i n t s ) { int s i z e = d e c i m a t e d P o i n t s . Count ; A r r a y L i s t r e s u l t = new A r r a y L i s t ( ) ; for ( int i =1; i <s i z e ; i ++){ Vector2 p1 = ( Vector2 ) d e c i m a t e d P o i n t s [ i − 1 ] ; Vector2 p2 = ( Vector2 ) d e c i m a t e d P o i n t s [ i ] ; Vector2 d i r = p2−p1 ; Vector2 normal = (new Vector2 ( d i r . y , −d i r . x ) ) . n o r m a l i z e d ; Vector2 Vector2 Vector2 Vector2 result result result result v0 v1 v2 v3 = = = = p1 p2 p1 p2 − − + + ( normal ( normal ( normal ( normal ∗ ∗ ∗ ∗ walkWidth ) ; walkWidth ) ; walkWidth ) ; walkWidth ) ; . Add( v0 ) ; . Add( v2 ) ; . Add( v1 ) ; . Add( v3 ) ; } return r e s u l t ; } private void generateMesh ( ) { // A c q u i s i z i o n e d e l l a mesh d a l component Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ; ArrayList a l t i t u d e s = walkInfo . a l t i t u d e s ; // C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e ) L i s t <Vector3> v e r t i c e s = new L i s t <Vector3 > ( ) ; L i s t <Vector2> uv = new L i s t <Vector2 > ( ) ; int i n d e x = 0 ; 97 for ( int i =0; i <meshPoints . Count ; i ++){ Vector2 p o i n t = ( Vector2 ) meshPoints [ i ] ; float float float float float l a t i t u d e = point . x ; longitude = point . y ; posX=( l o n g i t u d e +180)∗GLOBALSCALE; posZ=( l a t i t u d e +90)∗GLOBALSCALE; a l t i t u d e = ( f l o a t ) a l t i t u d e s [ i n d e x ]+ a l t i t u d e O f f s e t ; v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e , posZ ) ) ; uv . Add(new Vector2 ( posX , posZ ) ) ; v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e+walkHeight , posZ ) ) ; uv . Add(new Vector2 ( posX , posZ ) ) ; i f ( i %4==0)i n d e x++; } L i s t <int> t r i a n g l e s = new L i s t <int > ( ) ; /∗ ∗ 1−−−−3 ∗ | | ∗ 0−−−−2 ∗/ for ( int i =0; i <(meshPoints . Count ∗2) −6; i +=4){ // Lato 0−2 t r i a n g l e s . Add( i ) ; t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +4); t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +6); t r i a n g l e s . Add( i +4); // Lato 1−3 t r i a n g l e s . Add( i +1); t r i a n g l e s . Add( i +5); t r i a n g l e s . Add( i +3); t r i a n g l e s . Add( i +3); t r i a n g l e s . Add( i +5); 98 t r i a n g l e s . Add( i +7); // Lato 0−1 t r i a n g l e s . Add( i ) ; t r i a n g l e s . Add( i +4); t r i a n g l e s . Add( i +1); t r i a n g l e s . Add( i +4); t r i a n g l e s . Add( i +5); t r i a n g l e s . Add( i +1); // Lato 2−3 t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +3); t r i a n g l e s . Add( i +6); t r i a n g l e s . Add( i +6); t r i a n g l e s . Add( i +3); t r i a n g l e s . Add( i +7); // Lato d a v a n t i t r i a n g l e s . Add( i ) ; t r i a n g l e s . Add( i +1); t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +2); t r i a n g l e s . Add( i +1); t r i a n g l e s . Add( i +3); // d i e t r o t r i a n g l e s . Add( i +4); t r i a n g l e s . Add( i +6); t r i a n g l e s . Add( i +5); t r i a n g l e s . Add( i +5); t r i a n g l e s . Add( i +6); t r i a n g l e s . Add( i +7); } mesh . v e r t i c e s = v e r t i c e s . ToArray ( ) ; mesh . uv = uv . ToArray ( ) ; mesh . t r i a n g l e s = t r i a n g l e s . ToArray ( ) ; mesh . R e c a l c u l a t e N o r m a l s ( ) ; } } 99 100 D Flickr Photo Downloader getFlickr.php: download informazioni riguardanti le immagini fotografiche su Flickr. <?php f u n c t i o n getImgs ( $apiKey , $bbox , $page , $ c a t e g o r y , $ c o m p r e n s o r i o ) { $ u r l = " h ttp : / / a p i . f l i c k r . com/ s e r v i c e s / r e s t /? method= ␣␣␣␣ f l i c k r . photos . s e a r c h&api_key=" . $apiKey . "&bbox=" . $bbox . "&page=" . $page . "&has_geo=1&s o r t=r e l e v a n c e ␣␣␣␣&min_taken_date =2005 −01 −01%2000:00:00 " ; $xml = s i m p l e x m l _ l o a d _ f i l e ( $ u r l ) ; $photo = $xml−>xpath ( ’ // photo ’ ) ; $count = count ( $photo ) ; $ s t r i n g = "" ; for ( $ i =0; $ i <$count ; $ i ++){ $ s e r v e r = $photo [ $ i ] [ " s e r v e r " ] ; $farm = $photo [ $ i ] [ " farm " ] ; $code = $photo [ $ i ] [ " i d " ] ; $ s e c r e t = $photo [ $ i ] [ " s e c r e t " ] ; $img = " http : / / farm " . $farm . " . s t a t i c . f l i c k r . com/" . $ s e r v e r . "/" . $code . "_" . $ s e c r e t . " . j p g " ; $icon = getIcon ( $category , $comprensorio ) ; $ s t r i n g .= $code . " | " ; $ s t r i n g .= $ i c o n . " | " ; $ s t r i n g .= $img . "\n" ; } $ s t r i n g = substr ( $ s t r i n g , 0 , s t r l e n ( $ s t r i n g ) −1); echo $ s t r i n g ; } 101 f u n c t i o n g e t I n f o s ( $apiKey , $code ) { $ u r l = " h ttp : / / a p i . f l i c k r . com/ s e r v i c e s / r e s t /? method= ␣␣␣␣ f l i c k r . photos . g e t I n f o&api_key=" . $apiKey . "&photo_id=" . $code ; $info = simplexml_load_file ( $url ) ; $ t i t = $ i n f o −>xpath ( ’ // t i t l e ’ ) ; $ d e s c = $ i n f o −>xpath ( ’ // d e s c r i p t i o n ’ ) ; $ t i t l e = s t r _ r e p l a c e ( " | " , "−" , $ t i t [ 0 ] ) ; $ d e s c r i p t i o n = s t r _ r e p l a c e ( " | " , "−" , $ d e s c [ 0 ] ) ; i f ( $ t i t l e == "" ) $ t i t l e = " Nessun ␣ T i t o l o " ; i f ( $ d e s c r i p t i o n == "" ) $ d e s c r i p t i o n = " Nessuna ␣ D e s c r i z i o n e " ; $ l o c a t i o n = $ i n f o −>xpath ( ’ // l o c a t i o n ’ ) ; $latitude = $location [ 0 ] [ " latitude " ] ; $longitude = $location [ 0 ] [ " longitude " ] ; $string $string $string $string .= .= .= .= $latitude . " | " ; $longitude . " | " ; $title . "|"; $description ; $ s t r i n g = substr ( $ s t r i n g , 0 , s t r l e n ( $ s t r i n g ) −1); echo $ s t r i n g ; } ?> 102