MARCHING CUBES & EXTENDED MARCHING CUBES =================================================================================== In breve le classi coinvolte sono 3 e sono: * MerchingCubes ed ExtendedMarchingCubes processano una cella alla volta, aggiungendo per ogni chiamata a ProcessCell l'insieme di triangoli approssimante la superficie che interseca la cella * Walker gestisce l'attraversamento del volume, servendo le chiamate effettuate dagli algoritmi di estrazione della superficie al volume e cachandone i risultato * Volume conosce come calcolare il campo scalare all'interno del volume da processare e come calcolare le intersezioni superficie/segmenti. DESCRIZIONE ==================================================================================== Le classi che implementano gli algoritmi MarchingCubes ed ExtendedMarchingCubes sono state implementate così da risultare quanto più generiche possibile: ogni chiamata al metodo ProcessCell(Point3i p1, Point3i p2) analizza la cella del volume individuata dai due punti p1 e p2 e l'analisi di questa cella si conclude esattamente al ritorno da questa chiamata: nel caso infatti la superficie da estrarre attraversi questa cella, all'interno di questa stessa chiamata la mesh viene aggiornata con un opportuno insieme di triangoli. L'assunzione alla base di questa astrazione è l'esistenza di altri due entità, il Walker ed il Volume; sebbene sulla loro implementazione è lasciata la più completa libertà, è utile precisare in quale relazione essi stiano rispetto agli algoritmi di estrazione di superfici. Un esempio che riassume quanto qui esposto è incluso nella libreria: vd. vcg/apps/test/extractors. VOLUME ==================================================================================== Gli algoritmi di estrazione di superfici risalgono alla superficie utilizzando i valori di un campo scalare definito sul volume da processare campionato sui vertici di una qualche griglia. Questo campo scalare sarà generalmente diverso a seconda del tipo di applicazione. Il Volume è appunto quella classe che racchiude il campo scalare e di cui ne conosce le proprietà. In realtà, all'interno dell'algoritmo di estrazione di superfici, non esiste alcun collegamento esplicito con il Volume: tutte le sue chiamate sono rivolte al Walker. Questo perché (per motivi che saranno esposti successivamente a proposito del Walker) il Walker potrebbe già possedere il valore del campo calcolato in un dato punto, che evita così di farselo ricalcolare nuovamente dal Volume: Similmente, quando viene chiesto di calcolare il punto di intersezione della superficie con un segmento, se il Walker dispone già di questa informazione, la restituisce all'algoritmo di estrazione di superfici, altrimenti il calcolo verrà effettuato dal Volume: il punto ottenuto verrà restituito al Walker che potrà quindi soddisfare la richiesta iniziale. Il motivo per cui si è scelto di frapporre un Walker fra gli algoritmi di estrazione di superfici ed il Volume è esclusivamente di ottimizzazione. L'idea di fondo è che il Walker è quell'oggetto attrverso cui avviene la visita del volume: per ogni cella del volume, esso effettua la chiamata ProcessCell. Conoscendo l'ordine di visita, il Walker è anche la classe candidata ad implementare le politiche di caching, in quanto sa esattamente da che momento può essere utile una certa informazione e per quanto a lungo può essere conveniente mantenerla prima di liberarsene. WALKER ==================================================================================== Poiché la politica di visita del volume è realizzata all'interno del walker, è opportuno che sempre all'interno del walker vengano realizzate le politiche di caching rivolte ad ottimizzare l'esecuzione degli algoritmi MC ed EMC. Durante il processing di ogni cella questi algoritmi possono chiamare le seguenti funzioni del Walker: MC EMC ------------------------------------------ V(i, j, k) X X GetXIntercept(p1, p2, v) X X GetYIntercept(p1, p2, v) X X GetZIntercept(p1, p2, v) X X Exist(p1, p2, v) X const float V(int i, int j, int k) const La superficie che attraversa ogni cella viene ricavata dall'algoritmo di estrazione analizzando il valore del campo sugli otto spigoli di ogni voxel del volume; per ogni voxel, il valore del campo sui suoi otto spigoli vengono richiesti dall'algoritmo di estrazione al walker: se questo valore è già stato calcolato e cachato, il walker restituisce direttamente tale valore; altrimenti il valore del campo in questo spigolo viene calcolato (eventualmente cachato) e restituito al walker. In questo modo il valoro del campo ad ogni punto viene calcolato una sola volta anziché 8, questo puo' essere molto utile nel caso si utilizzi dataset volumetrici mantenuti implicitamente. void GetXIntercept(Point3i p1, Point3i p2, VertexPointer v) void GetYIntercept(Point3i p1, Point3i p2, VertexPointer v) void GetZIntercept(Point3i p1, Point3i p2, VertexPointer v) Dall'analisi del valore del campo agli spigoli di un dato voxel, l'algoritmo di estrazione ha rilevato che la superficie interseca lo spigolo avente estremi p1 e p2(a seconda dell'orientazione di questo spigolo, viene chiamato uno dei tre metodi): al termine di una di queste chiamate, v deve puntare al vertice della mesh (di coordinate comprese fra p1 e p2) attraverso cui passa la superficie. Se questo vertice è stato già calcolato ed inserito nella mesh, il walker deve risalire a tale vertice e memorizzarne in v il suo puntatore. Altrimenti deve provvedere ad aggiugnere un nuovo vertice ed a calcolare la sua posizione; v deve puntare in questo caso al vertice appena inserito. Il motivo per cui questo calcolo non viene implementato direttamente negli algoritmi di estrazione (possibile per es. attraverso interpolazione lineare del valore del campo nei punti p1 e p2) è che questo calcolo può essere fatto in maniere molto più precisa conoscendo come il campo viene calcolato, essendo infatti ciò dipendente dall'applicazione. bool Exist(Point3i p1, Point3i p2, VertexPointer v) Questo metodo viene chiamato solamente all'interno dell'algoritmo MarchingCubes. A differenza dei tre motodi precedenti, in questo caso si vuole sapere solamente se esiste già un vertice tra i punti p1 e p2: nel caso tale vertice esista, Exist deve resituire true ed v deve puntare a tale vertice; se invece tale vertice non esiste, Exist deve restituire false e v deve prendere il valore NULL. NB: nel caso in cui il vertice non esiste, alla mesh non deve essere aggiunto alcun nuovo vertice.