-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



sabato 31 agosto 2013

Quel gran 'pezzo' della ... multi 'patch'

Tra le feature 3D abbiamo oltre ai point, polyline e polygon anche le multipatch.
Una feature multipatch è un oggetto GIS che memorizza una collezione di patch per rappresentare l'oggetto 3D. Le patch memorizzano anche la texture, il colore, la trasparenza e le informazioni geometriche che rappresentano le parti della feature (per essere precisi: se la feature è memorizzata in uno shapefile vengono solo memorizzate le informazioni geometriche mentre utilizzando geodatabase la memorizzazione delle informazioni è completa).

Tutte le multipatch memorizzano i valori z come parte delle coordinate utilizzate per costruire le patch.
Il tipo di geometria multipatch fu inizialmente sviluppato per avere geometrie poligonali 3D non vincolate da regole di validità 2D; difatti, per modellare correttamente i rilievi della superficie interna di un poligono 3D, possono essere utilizzate feature multipatch o superfici funzionali come ad esempio la TIN.

Esempi di feature multipatch: edifici con texture, pali, alberi, formazioni geologiche presenti nel sottosuolo, strutture interrate ecc.

Per creare una nuova feature class multipatch selezioniamo dal menu Type Multipatch Features quando definiamo la geometria della feature class mentre per creare feature multipatch possiamo importare modelli esistenti 3D utilizzando gli strumenti di geoprocessing di ArcGIS. Lo strumento Layer3D To Feature Class esporta layer con proprietà 3D definite in multipatch feature class mentre lo strumento Import 3D Files importa modelli 3D (3D Studio Max, VRML, GeoVRML 2.0, SketchUP 6.0, OpenFlight 15.8, COLLADA e billboard (png, jpeg,bmp,tiff,gif ecc.)) in feature class multipatch.

Ma ovviamente le feature multipatch possono essere create utilizzando gli arcobjects ed in questo post vediamo nel dettaglio come fare.

Una multipatch può essere vista come un contenitore di geometrie che rappresenta superfici 3D.
Le geometrie possono essere triangle, triangle fan, triangle strip o gruppi di ring ed una singola multipatch può includere una o più di queste geometrie.


I singoli triangle strip, triangle fan o triangle determinano la superficie stessa mentre uno o più ring possono determinare la superficie da rappresentare.

In questa immagine è rappresentato un triangle strip con 7 punti:
Per generare un triangle strip aggiungeremo i punti 0,1,2,3,4..6 nella IPointCollection cosicché ogni triangle è creato dal precedente. A esempio il secondo triangle utilizza i punti 1 e 2 del primo triangle.

            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
            IPointCollection trianglesPointCollection = new TriangleStripClass();
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-1, -1, 5), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(4, 2, 8), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(6, 9, 6), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(8, 12, 4), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(13, 14, 5), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(12, 18, 8), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(15, 11, 6), ref missing, ref missing);
 
            multiPatchGeometryCollection.AddGeometry(trianglesPointCollection as IGeometryref missing, ref missing);
 
            return multiPatchGeometryCollection as IGeometry;


Il triangle fan è una sequenza di triangle connessi dove ognuno utilizza il primo punto aggiunto alla collection; pertanto la sequenza dei punti sarà 0,1,2,0,3,0,4 ...,0,n


            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            IPointCollection triangleFanPointCollection = new TriangleFanClass();
 
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(0, 0, 7), ref missing, ref missing);
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-6, -6, 0), ref missing, ref missing);
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-6, 6, 0), ref missing, ref missing);
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(6, 6, 0), ref missing, ref missing);
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(6, -6, 0), ref missing, ref missing);
            triangleFanPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-6, -6, 0), ref missing, ref missing);
 
            multiPatchGeometryCollection.AddGeometry(triangleFanPointCollection as IGeometryref missing, ref missing);
 
            return multiPatchGeometryCollection as IGeometry;

Il triangle è una collezione di triangle dove ogni tripletta di vertici definisce un nuovo triangle; pertanto il numero di vertici da aggiungere alla collection deve essere un multiplo di tre. Questo tipo di patch fu introdotto per gestire oggetti 3D con triangle non connessi.


            //Triangles: Six Triangles Lying In Different Planes
 
            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            IPointCollection trianglesPointCollection = new TrianglesClass();
 
            //Triangle 1
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, 7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(10, 7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, 5, 0), ref missing, ref missing);
 
            //Triangle 2
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, 7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, 7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, 5, 0), ref missing, ref missing);
 
            //Triangle 3
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(0, -5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(2.5, -5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(0, -7.5, 0), ref missing, ref missing);
 
            //Triangle 4
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(0, 7.5, 2.5), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(2.5, 7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(0, 7.5, 0), ref missing, ref missing);
 
            //Triangle 5
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, -7.5, 2.5), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, -7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, -7.5, 0), ref missing, ref missing);
 
            //Triangle 6
 
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, -7.5, 2.5), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(10, -7.5, 0), ref missing, ref missing);
            trianglesPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, -7.5, 0), ref missing, ref missing);
 
            multiPatchGeometryCollection.AddGeometry(trianglesPointCollection as IGeometryref missing, ref missing);
 
            return multiPatchGeometryCollection as IGeometry;

La collezione di triangle può anche essere utilizzata per definire un solido chiuso.

I ring sono elementi geometrici poligonali definiti da una sequenza di segmenti connessi chiusa.



Tutti i ring hanno la stessa struttura ma ognuno ha un ruolo ben definito quando definiamo la superficie multipatch. Il ruolo di ogni ring è determinato dal multipatch che lo contiene e non dal ring stesso.
I ruoli sono: Ring (unknown), First Ring, Inner Ring, Outer Ring. Una regola che non è forzata ma che dovrebbe essere seguita quando si crea un gruppo ring è che tutti i ring in un gruppo devono essere complanari. Questo è uno standard OpenGL per grafica 3D.

Pertanto, se dovessimo creare un cubo, dovrà essere composto da 6 superfici rappresentate con un ring o possiamo in alternativa creare una singola superficie utilizzando un triangle strip.

Una sequenza di ring può descrivere una superficie poligonale con 'buchi'. La sequenza tipicamente consiste di un outer ring, che rappresenta il contorno della patch seguita da un numero di inner ring che rappresentano i 'buchi'.

Il First Ring determina l'inizio di un gruppo di ring che viene interrotta con un tipo diverso da Ring.

Quando i tipi di ring sono sconosciuti e rappresentano una patch poligonale con 'buchi' la sequenza deve partire con un First Ring seguito dai ring. Se una sequenza di Ring non è preceduta da un First Ring è trattata come una sequenza di outer ring senza 'buchi'.

L'ordine delle parti è significativo: gli inner ring devono seguire i loro outer ring; una sequenza di ring che rappresenta una singola superficie patch deve iniziare con un ring del tipo First ring.

Un'altra regola è che c'è un solo gruppo per outer ring. Se, ad esempio, c'è un altro ring complanare all'interno di un 'buco', questo dovrebbe essere rappresentato da un altro gruppo. Questo perché è effettivamente un'altra superficie anche se è complanare con l'outer ring e l'inner ring.

Con le regole viste, ad esempio, se dovessimo creare una superficie di una feature multipatch composta dalla sequenza di superfici così definite: triangle strip, triangle fan, ring, ring, ring, first ring, ring verranno così interpretate:
1° superficie: triangle strip
2° superficie: traingle fan
3° superficie: ring
4° superficie: ring
5° superficie: first ring, ring


Qui vediamo una multipatch composta da una superficie rappresentata con un ring


            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            IPointCollection ringPointCollection = new RingClass();
 
            ringPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, 0, 0), ref _missing, ref _missing);
            ringPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, 0, 7.5), ref _missing, ref _missing);
            ringPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, 0, 7.5), ref _missing, ref _missing);
            ringPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(7.5, 0, 0), ref _missing, ref _missing);
            ringPointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-7.5, 0, 0), ref _missing, ref _missing);
 
            multiPatchGeometryCollection.AddGeometry(ringPointCollection as IGeometryref _missing, ref _missing);
 
            return multiPatchGeometryCollection as IGeometry;

Qui vediamo una multipatch composta da una superficie rappresentata con un ring con un 'buco'



            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            IMultiPatch multiPatch = multiPatchGeometryCollection as IMultiPatch;
 
            //Exterior Ring 1
 
            IPointCollection exteriorRing1PointCollection = new RingClass();
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(5, 0, -5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, 0, -5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, 0, 5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(5, 0, 5), ref _missing, ref _missing);
 
            IRing exteriorRing1 = exteriorRing1PointCollection as IRing;
            exteriorRing1.Close();
 
            multiPatchGeometryCollection.AddGeometry(exteriorRing1 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(exteriorRing1, esriMultiPatchRingType.esriMultiPatchOuterRing);
 
            //Interior Ring 1
 
            IPointCollection interiorRing1PointCollection = new RingClass();
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-4, 0, -4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(4, 0, -4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(4, 0, 4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-4, 0, 4), ref _missing, ref _missing);
 
            IRing interiorRing1 = interiorRing1PointCollection as IRing;
            interiorRing1.Close();
 
            multiPatchGeometryCollection.AddGeometry(interiorRing1 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(interiorRing1, esriMultiPatchRingType.esriMultiPatchInnerRing);
 
            return multiPatchGeometryCollection as IGeometry;

Qui vediamo una multipatch con multipli outer ring e multipli inner ring


            //RingGroup: Upright Square Composed Of Multiple Exterior Rings And Multiple Interior Rings
 
            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            IMultiPatch multiPatch = multiPatchGeometryCollection as IMultiPatch;
 
            //Exterior Ring 1
 
            IPointCollection exteriorRing1PointCollection = new RingClass();
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(5, 0, -5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, 0, -5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-5, 0, 5), ref _missing, ref _missing);
            exteriorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(5, 0, 5), ref _missing, ref _missing);
 
            IRing exteriorRing1 = exteriorRing1PointCollection as IRing;
            exteriorRing1.Close();
 
            multiPatchGeometryCollection.AddGeometry(exteriorRing1 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(exteriorRing1, esriMultiPatchRingType.esriMultiPatchOuterRing);
 
            //Interior Ring 1
 
            IPointCollection interiorRing1PointCollection = new RingClass();
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-4, 0, -4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(4, 0, -4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(4, 0, 4), ref _missing, ref _missing);
            interiorRing1PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-4, 0, 4), ref _missing, ref _missing);
 
            IRing interiorRing1 = interiorRing1PointCollection as IRing;
            interiorRing1.Close();
 
            multiPatchGeometryCollection.AddGeometry(interiorRing1 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(interiorRing1, esriMultiPatchRingType.esriMultiPatchInnerRing);
 
            //Exterior Ring 2 
 
            IPointCollection exteriorRing2PointCollection = new RingClass();
            exteriorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(3, 0, -3), ref _missing, ref _missing);
            exteriorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-3, 0, -3), ref _missing, ref _missing);
            exteriorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-3, 0, 3), ref _missing, ref _missing);
            exteriorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(3, 0, 3), ref _missing, ref _missing);
 
            IRing exteriorRing2 = exteriorRing2PointCollection as IRing;
            exteriorRing2.Close();
 
            multiPatchGeometryCollection.AddGeometry(exteriorRing2 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(exteriorRing2, esriMultiPatchRingType.esriMultiPatchOuterRing);
 
            //Interior Ring 2
 
            IPointCollection interiorRing2PointCollection = new RingClass();
            interiorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-2, 0, -2), ref _missing, ref _missing);
            interiorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(2, 0, -2), ref _missing, ref _missing);
            interiorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(2, 0, 2), ref _missing, ref _missing);
            interiorRing2PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-2, 0, 2), ref _missing, ref _missing);
 
            IRing interiorRing2 = interiorRing2PointCollection as IRing;
            interiorRing2.Close();
 
            multiPatchGeometryCollection.AddGeometry(interiorRing2 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(interiorRing2, esriMultiPatchRingType.esriMultiPatchInnerRing);
 
            //Exterior Ring 3 
 
            IPointCollection exteriorRing3PointCollection = new RingClass();
            exteriorRing3PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(1, 0, -1), ref _missing, ref _missing);
            exteriorRing3PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-1, 0, -1), ref _missing, ref _missing);
            exteriorRing3PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(-1, 0, 1), ref _missing, ref _missing);
            exteriorRing3PointCollection.AddPoint(GeometryUtilities.ConstructPoint3D(1, 0, 1), ref _missing, ref _missing);
 
            IRing exteriorRing3 = exteriorRing3PointCollection as IRing;
            exteriorRing3.Close();
 
            multiPatchGeometryCollection.AddGeometry(exteriorRing3 as IGeometryref _missing, ref _missing);
 
            multiPatch.PutRingType(exteriorRing3, esriMultiPatchRingType.esriMultiPatchOuterRing);
 
            return multiPatchGeometryCollection as IGeometry;
 

La principale differenza dal punto di vista dello sviluppatore tra il ring ed il triangle è che quando si accede ad un punto del triangle tramite IPointCollection::GetPoint(index) abbiamo un riferimento all'oggetto e quindi possiamo modificarlo direttamente mentre per il ring faremo riferimento ad una copia dell'oggetto e pertanto per modificare un punto dovremo utilizzare IPointCollection::UpdatePoint.

Un'interfaccia molto utile per la generazione di parti di multipatch dove i vertici sono spaziati ad intervalli fissi è la IVector3D.



Ad esempio se dovessimo generare un cono



potremmo scrivere il seguente codice:

            const double ConeBaseDegrees = 360.0;
            const int ConeBaseDivisions = 36;
            const double VectorComponentOffset = 0.0000001;
            const double ConeBaseRadius = 6;
            const double ConeBaseZ = 0.0;
            const double ConeApexZ = 9.5;
            //Vector3D: Cone, TriangleFan With 36 Vertices             IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();             IPointCollection triangleFanPointCollection = new TriangleFanClass();             //Set Cone Apex To (0, 0, ConeApexZ)             IPoint coneApexPoint = GeometryUtilities.ConstructPoint3D(0, 0, ConeApexZ);             //Add Cone Apex To Triangle Fan             
            triangleFanPointCollection.AddPoint(coneApexPoint, ref _missing, ref _missing);             //Define Upper Portion Of Axis Around Which Vector Should Be Rotated To Generate Cone Base Vertices             IVector3D upperAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, 10);             //Define Lower Portion of Axis Around Which Vector Should Be Rotated To Generate Cone Base Vertices             IVector3D lowerAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, -10);             //Add A Slight Offset To X or Y Component Of One Of Axis Vectors So Cross Product Does Not Return A Zero-Length Vector             
            lowerAxisVector3D.XComponent += VectorComponentOffset;             //Obtain Cross Product Of Upper And Lower Axis Vectors To Obtain Normal Vector To Axis Of Rotation To Generate Cone Base Vertices             IVector3D normalVector3D = upperAxisVector3D.CrossProduct(lowerAxisVector3D) as IVector3D;             //Set Normal Vector Magnitude Equal To Radius Of Cone Base             normalVector3D.Magnitude = ConeBaseRadius;             //Obtain Angle Of Rotation In Radians As Function Of Number Of Divisions Within 360 Degree Sweep Of Cone Base             double rotationAngleInRadians = GeometryUtilities.GetRadians(ConeBaseDegrees / ConeBaseDivisions);
            for (int i = 0; i < ConeBaseDivisions; i++)             
            {
                //Rotate Normal Vector Specified Rotation Angle In Radians Around Either Upper Or Lower Axis
                normalVector3D.Rotate(-1 * rotationAngleInRadians, upperAxisVector3D);                 //Construct Cone Base Vertex Whose XY Coordinates Are The Sum Of Apex XY Coordinates And Normal Vector XY Components                 IPoint vertexPoint = GeometryUtilities.ConstructPoint3D(coneApexPoint.X + normalVector3D.XComponent, coneApexPoint.Y + normalVector3D.YComponent, ConeBaseZ);     
                //Add Vertex To TriangleFan                 triangleFanPointCollection.AddPoint(vertexPoint, ref _missing, ref _missing);             
            }             
            //Re-Add The Second Point Of The Triangle Fan (First Vertex Added) To Close The Fan             triangleFanPointCollection.AddPoint(triangleFanPointCollection.get_Point(1), ref _missing, ref _missing);             
            //Add TriangleFan To MultiPatch             multiPatchGeometryCollection.AddGeometry(triangleFanPointCollection as IGeometryref _missing, ref _missing);             return multiPatchGeometryCollection as IGeometry;


In questo esempio per simulare il cono utilizziamo il triangle fan e per generare i vertici della base ruotiamo in senso orario un vettore sul piano XY intorno all'asse delle Z (asse del cono). Al vettore impostiamo la magnitudine uguale al raggio della base.
Prima impostiamo l'apice del cono aggiungendolo alla collection del triangle fan.
Ora definiamo due vettori di magnitudine arbitraria e ne eseguimo il prodotto vettoriale  per determinare il vettore perpendicolare ad entrambi e ne impostiamo la magnitudine al valore del raggio della base. Per evitare di avere, nel prodotto vettoriale,  un vettore con lunghezza nulla aggiungiamo un piccolo offset alla componente X di uno dei due vettori e impostiamo nel rotate il valore negativo per far ruotare il vettore in senso orario visto che il rotate segue la convenzione matematica e quindi angolo positivo significa senso antiorario.


Ruotando il vettore a x b determiniamo i vertici da aggiungere alla collection. In questo caso abbiamo applicato una definizione con 36 vertici.
Per creare tubi o cilindri possiamo utilizzare la stessa tecnica ma in questo caso utilizzeremo un triangle strip aggiungendo alternativamente alla collezione i vertici creati con la rotazione dei vettori delle relative due basi circolari.


Un'altra interfaccia utile è l'ITransform3D che ti consente di spostare, scalare o ruotare intorno ad un asse una geometria esistente.
L'interfaccia è utile perché possiamo generare la nostra geometria in un frame di riferimento (ad esempio centrata sullo 0,0,0) e poi trasformarla per posizionarla nella posizione corretta.





            const double XScale = 0.5;
            const double YScale = 0.5;
            const double ZScale = 2;
            const double XOffset = -5;
            const double YOffset = -5;
            const double ZOffset = -8;
            const double DegreesOfRotation = 90;
 
            //Transform3D: Cylinder Scaled, Rotated, Repositioned Via Move3D(), Scale3D(), RotateVector3D()
 
            IGeometry geometry = GetCylinder();
 
            ITransform3D transform3D = geometry as ITransform3D;
 
            //Stretch The Cylinder So It Looks Like A Tube
 
            IPoint originPoint = GeometryUtilities.ConstructPoint3D(0, 0, 0);
 
            transform3D.Scale3D(originPoint, XScale, YScale, ZScale);
 
            //Rotate The Cylinder So It Lies On Its Side
 
            IVector3D axisOfRotationVector3D = GeometryUtilities.ConstructVector3D(0, 10, 0);
 
            double angleOfRotationInRadians = GeometryUtilities.GetRadians(DegreesOfRotation);
 
            transform3D.RotateVector3D(axisOfRotationVector3D, angleOfRotationInRadians);
 
            //Reposition The Cylinder So It Is Located Underground
 
            transform3D.Move3D(XOffset, YOffset, ZOffset);
 
            return geometry;

In questo esempio con multiple chiamate ai metodi esposti dal ITransform3D scaliamo, ruotiamo e spostiamo il cilindro che vediamo nella prima immagine.

L'interfaccia IConstructMultiPatch ci consente di estrudere una base 2D o 3D in diversi modi per generare la rappresentazione multipatch.


            const double FromZ = 0;
            const double ToZ = 9.5;
 
            //Extrusion: Square Shaped 2D Polygon Extruded To Generate 3D Building Via ConstructExtrudeFromTo()
 
            IPointCollection polygonPointCollection = new PolygonClass();
 
            polygonPointCollection.AddPoint(GeometryUtilities.ConstructPoint2D(-2, 2), ref _missing, ref _missing);
            polygonPointCollection.AddPoint(GeometryUtilities.ConstructPoint2D(2, 2), ref _missing, ref _missing);
            polygonPointCollection.AddPoint(GeometryUtilities.ConstructPoint2D(2, -2), ref _missing, ref _missing);
            polygonPointCollection.AddPoint(GeometryUtilities.ConstructPoint2D(-2, -2), ref _missing, ref _missing);
 
            IPolygon polygon = polygonPointCollection as IPolygon;
            polygon.Close();
 
            IGeometry polygonGeometry = polygonPointCollection as IGeometry;
 
            ITopologicalOperator topologicalOperator = polygonGeometry as ITopologicalOperator;
            topologicalOperator.Simplify();
 
            IConstructMultiPatch constructMultiPatch = new MultiPatchClass();
            constructMultiPatch.ConstructExtrudeFromTo(FromZ, ToZ, polygonGeometry);
 
            return constructMultiPatch as IGeometry;

In questo esempio abbiamo generato un poligono sul piano xy visto che è generato da sole coordinate x,y (no z-aware). Verifichiamo che il poligono sia topologicamente corretto ed effettuiamo l'estrusione tra due altezze conosciute. In questo caso la multipatch generata sarà composta da due ring (uno in alto ed uno in basso) e da un triangle strip rappresentanti le facce laterali del prisma retto.


            const double CircleDegrees = 360.0;
            const int CircleDivisions = 36;
            const double VectorComponentOffset = 0.0000001;
            const double CircleRadius = 3.0;
            const double BaseZ = 0.0;
            const double RotationAngleInDegrees = 89.9;
 
            //Extrusion: 3D Circle Polyline Extruded Along 3D Vector Via ConstructExtrudeRelative()
 
            IPointCollection pathPointCollection = new PathClass();
 
            IGeometry pathGeometry = pathPointCollection as IGeometry;
 
            GeometryUtilities.MakeZAware(pathGeometry);
 
            IPoint originPoint = GeometryUtilities.ConstructPoint3D(0, 0, 0);
 
            IVector3D upperAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, 10);
 
            IVector3D lowerAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, -10);
 
            lowerAxisVector3D.XComponent += VectorComponentOffset;
 
            IVector3D normalVector3D = upperAxisVector3D.CrossProduct(lowerAxisVector3D) as IVector3D;
 
            normalVector3D.Magnitude = CircleRadius;
 
            double rotationAngleInRadians = GeometryUtilities.GetRadians(CircleDegrees / CircleDivisions);
 
            for (int i = 0; i < CircleDivisions; i++)
            {
                normalVector3D.Rotate(-1 * rotationAngleInRadians, upperAxisVector3D);
 
                IPoint vertexPoint = GeometryUtilities.ConstructPoint3D(originPoint.X + normalVector3D.XComponent,
                                                                      originPoint.Y + normalVector3D.YComponent,
                                                                      BaseZ);
 
                pathPointCollection.AddPoint(vertexPoint, ref _missing, ref _missing);
            }
 
            pathPointCollection.AddPoint(pathPointCollection.get_Point(0), ref _missing, ref _missing);
 
            //Rotate Geometry
 
            IVector3D rotationAxisVector3D = GeometryUtilities.ConstructVector3D(0, 10, 0);
 
            ITransform3D transform3D = pathGeometry as ITransform3D;
            transform3D.RotateVector3D(rotationAxisVector3D, GeometryUtilities.GetRadians(RotationAngleInDegrees));
 
            //Construct Polyline From Path Vertices
 
            IGeometry polylineGeometry = new PolylineClass();
 
            GeometryUtilities.MakeZAware(polylineGeometry);
 
            IPointCollection polylinePointCollection = polylineGeometry as IPointCollection;
 
            for (int i = 0; i < pathPointCollection.PointCount; i++)
            {
                polylinePointCollection.AddPoint(pathPointCollection.get_Point(i), ref _missing, ref _missing);
            }
 
            ITopologicalOperator topologicalOperator = polylineGeometry as ITopologicalOperator;
            topologicalOperator.Simplify();
 
            //Define Vector To Extrude Along
 
            IVector3D extrusionVector3D = GeometryUtilities.ConstructVector3D(10, 0, 5);
 
            //Perform Extrusion
 
            IConstructMultiPatch constructMultiPatch = new MultiPatchClass();
            constructMultiPatch.ConstructExtrudeRelative(extrusionVector3D, polylineGeometry);
 
            return constructMultiPatch as IGeometry;

In questo caso utilizziamo il ConstructExtrudeRelative() definendo un poligono che rappresenta un cerchio, lo ruotiamo di circa 90 gradi e poi lo estrudiamo lungo un dato vettore generando la multipatch.

Se abbiamo superfici funzionali possiamo anche, utilizzando come base un poligono o una polilinea, generare una multipatch tra queste due surperfici.



            const double CircleDegrees = 360.0;
            const int CircleDivisions = 36;
            const double VectorComponentOffset = 0.0000001;
            const double CircleRadius = 9.5;
            const int PointCount = 100;
            const double UpperZMin = 7;
            const double UpperZMax = 10;
            const double LowerZMin = 0;
            const double LowerZMax = 3;
 
            //Extrusion: Circle Shaped Base Geometry Extruded Between Two Different TIN-Based Functional Surfaces
 
            IGeometryCollection multiPatchGeometryCollection = new MultiPatchClass();
 
            //Base Geometry
 
            IPointCollection polygonPointCollection = new PolygonClass();
 
            IPoint originPoint = GeometryUtilities.ConstructPoint3D(0, 0, 0);
 
            IVector3D upperAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, 10);
 
            IVector3D lowerAxisVector3D = GeometryUtilities.ConstructVector3D(0, 0, -10);
 
            lowerAxisVector3D.XComponent += VectorComponentOffset;
 
            IVector3D normalVector3D = upperAxisVector3D.CrossProduct(lowerAxisVector3D) as IVector3D;
 
            normalVector3D.Magnitude = CircleRadius;
 
            double rotationAngleInRadians = GeometryUtilities.GetRadians(CircleDegrees / CircleDivisions);
 
            for (int i = 0; i < CircleDivisions; i++)
            {
                normalVector3D.Rotate(-1 * rotationAngleInRadians, upperAxisVector3D);
 
                IPoint vertexPoint = GeometryUtilities.ConstructPoint2D(originPoint.X + normalVector3D.XComponent,
                                                                        originPoint.Y + normalVector3D.YComponent);
 
                polygonPointCollection.AddPoint(vertexPoint, ref _missing, ref _missing);
            }
 
            IPolygon polygon = polygonPointCollection as IPolygon;
            polygon.Close();
 
            IGeometry baseGeometry = polygon as IGeometry;
 
            ITopologicalOperator topologicalOperator = polygon as ITopologicalOperator;
            topologicalOperator.Simplify();
 
            //Functional Surfaces
 
            IEnvelope envelope = new EnvelopeClass();
            envelope.XMin = -10;
            envelope.XMax = 10;
            envelope.YMin = -10;
            envelope.YMax = 10;
 
            Random random = new Random();
 
            //Upper Functional Surface
 
            ITinEdit upperTinEdit = new TinClass();
            upperTinEdit.InitNew(envelope);
 
            for (int i = 0; i < PointCount; i++)
            {
                double x = envelope.XMin + (envelope.XMax - envelope.XMin) * random.NextDouble();
                double y = envelope.YMin + (envelope.YMax - envelope.YMin) * random.NextDouble();
                double z = UpperZMin + (UpperZMax - UpperZMin) * random.NextDouble();
 
                IPoint point = GeometryUtilities.ConstructPoint3D(x, y, z);
 
                upperTinEdit.AddPointZ(point, 0);
            }
 
            IFunctionalSurface upperFunctionalSurface = upperTinEdit as IFunctionalSurface;
 
            //Lower Functional Surface
 
            ITinEdit lowerTinEdit = new TinClass();
            lowerTinEdit.InitNew(envelope);
 
            for (int i = 0; i < PointCount; i++)
            {
                double x = envelope.XMin + (envelope.XMax - envelope.XMin) * random.NextDouble();
                double y = envelope.YMin + (envelope.YMax - envelope.YMin) * random.NextDouble();
                double z = LowerZMin + (LowerZMax - LowerZMin) * random.NextDouble();
 
                IPoint point = GeometryUtilities.ConstructPoint3D(x, y, z);
 
                lowerTinEdit.AddPointZ(point, 0);
            }
 
            IFunctionalSurface lowerFunctionalSurface = lowerTinEdit as IFunctionalSurface;
 
            IConstructMultiPatch constructMultiPatch = new MultiPatchClass();
            constructMultiPatch.ConstructExtrudeBetween(upperFunctionalSurface, lowerFunctionalSurface, baseGeometry);
 
            return constructMultiPatch as IGeometry;


Come avevamo accennato all'inizio possiamo anche generare multipatch con impostato colore, texture, trasparenza, priorità patch, normale e coordinate della texture, tutte informazioni memorizzate con la geometria. In questo caso per generare la multipatch utilizzeremo IGeneralMultipatchCreator.

Una volta generata la geometria possiamo utilizzarla come simbolo marker 3D per renderizzare un elemento grafico, o salvarlo come template in uno stile o può essere memorizzato in una feature class multipatch.


Vediamo un semplicissimo esempio:


                IGeometryMaterial chiusinoGeometryMaterial = new GeometryMaterialClass();
                chiusinoGeometryMaterial.TextureImage = @"C:\Temp\Multipatch\Data\Images\ChiusinoQuadrato.png";
                IRgbColor color = new RgbColorClass();
                color.Red = 255;
                color.Green = 255;
                color.Blue = 255;
                chiusinoGeometryMaterial.TransparentTextureColor = color;
 
                IGeometryMaterialList geometryMaterialList = new GeometryMaterialListClass();
                geometryMaterialList.AddMaterial(chiusinoGeometryMaterial);
 
                IGeneralMultiPatchCreator generalMultiPatchCreator = new GeneralMultiPatchCreatorClass();
                generalMultiPatchCreator.Init(5, 1, falsefalsefalse, 5, geometryMaterialList);
                generalMultiPatchCreator.SetPatchType(0, esriPatchType.esriPatchTypeOuterRing);
                generalMultiPatchCreator.SetPatchPriority(0, 0);
                generalMultiPatchCreator.SetMaterialIndex(0, 0);
                generalMultiPatchCreator.SetPatchPointIndex(0, 0);
                generalMultiPatchCreator.SetPatchTexturePointIndex(0, 0);
 
                generalMultiPatchCreator.SetPoint(0, GeometryUtilities.ConstructPoint3D(0, 0, height));
                generalMultiPatchCreator.SetPoint(1, GeometryUtilities.ConstructPoint3D(0, yDimension, height));
                generalMultiPatchCreator.SetPoint(2, GeometryUtilities.ConstructPoint3D(xDimension, yDimension, height));
                generalMultiPatchCreator.SetPoint(3, GeometryUtilities.ConstructPoint3D(xDimension, 0, height));
                generalMultiPatchCreator.SetPoint(4, GeometryUtilities.ConstructPoint3D(0, 0, height));
 
                generalMultiPatchCreator.SetTexturePoint(0, GeometryUtilities.ConstructPoint2D(0, 0));
                generalMultiPatchCreator.SetTexturePoint(1, GeometryUtilities.ConstructPoint2D(0, 1));
                generalMultiPatchCreator.SetTexturePoint(2, GeometryUtilities.ConstructPoint2D(1, 1));
                generalMultiPatchCreator.SetTexturePoint(3, GeometryUtilities.ConstructPoint2D(1, 0));
                generalMultiPatchCreator.SetTexturePoint(4, GeometryUtilities.ConstructPoint2D(0, 0));
 
                return generalMultiPatchCreator.CreateMultiPatch();

Innanzitutto creiamo la lista delle texture che desideriamo aggiungere alla multipatch (in questo esempio 1 sola). Per la texture aggiunta impostiamo anche quale colore dovrà essere trasparente (in questo caso il bianco dell'immagine). Potevamo anche impostare la trasparenza dell'immagine ed un colore utilizzato per renderizzare il materiale (utilizzato come efficiente alternativa alla texture quando occorre renderizzare molte multipatch visualizzate a distanza). Il primo parametro del metodo Init rappresenta il numero globale di punti delle geometrie che compongono la multipatch; in questo caso è un poligono con quattro vertici quindi 5 perchè il primo si conta due volte per chiudere il poligono.
Il secondo parametro è il numero di patch che in questo caso è 1 perché abbiamo solo un ring. I tre parametri successivi indicano se si vuole avere a disposizione la M, l'ID e la normale per i vertici della multipatch: ad esempio per un ring che rappresenta un quadrato il primo vertice avrà come M = 0.0, il secondo M=0.25  fino ad arrivare all'ultimo con 1.0 mentre come ID avranno l'indice o l'offset di ciascun punto all'interno della patch.
Infine passiamo il numero di vertici della texture: in questo caso è 5 perchè desiderimo coprire con la texture tutta l'area del poligono e poi la lista della texture.
Impostiamo il tipo di patch: in questo caso solo una - la prima - e quindi passiamo indice 0.
Impostiamo la priorità anche se abbiamo una sola patch e quindi non abbiamo sovrapposizioni. La patch priority può essere utilizzata quando desideriamo specificare l'ordine nella quale le patch devono essere renderizzate, utile quando si hanno sovrapposizioni. Un numero più alto rappresenta priorità più alta.
Impostiamo con SetMaterialIndex l'indice della texture della lista dei materiali al quale fa riferimento la patch e con SetPatchPointIndex e SetPatchTexturePointIndex, gli indici iniziali o offset per la geometria e la texture per relazione tra di loro ogni patch.
Infine impostiamo per ogni singolo vertice la geometria e il relativo 'vertice' per la texture. In questo ultimo caso il valore 1 indica che l'immagine è allungata per riempire la dimensione lungo le due direzioni (0,0) - (1,1). Se avessimo scritto nel range tra (0,0) - (10,3) l'immagine sarebbe ripetuta 10 volte lungo la x e 3 volte lungo la y.


Tra le interfacce implementate dalle multipatch abbiamo IRelationOperator3D2 con i metodi Disjoint3D e IsNear3D che permettono rispettivamente di determinare se due geometrie non hanno punti in comune e se una geometria è entro una certa distanza da un'altra geometria.
Esempio disjoint:


        public static void TestIntersection()
        {
 
            IGeometry polylineGeometry = GetPolylineGeometry();
 
            IGeometry polygonGeometry = GetPolygonGeometry();
 
 
            IRelationalOperator3D relationalOperator3D = polylineGeometry as IRelationalOperator3D;
 
            bool intersect = !(relationalOperator3D.Disjoint3D(polygonGeometry));
 
            //intersect = true
 
        }


IVolume e IArea3D ci permetto di avere il volume chiuso e la superficie delle patch delle multipatch mentre con IProximityOperator3D possiamo determinare il punto più vicino di una multipatch da un determinato punto o farci restituire la distanza minima 3D con un'altra geometria.


        public static void QueryNearestPoint3D()
        {
            IGeometry pointGeometry = GetPointGeometry();
            IGeometry envelopeGeometry = GetEnvelopeGeometry();
            IProximityOperator3D proximityOperator3D = envelopeGeometry as IProximityOperator3D;
            IPoint nearestPoint3D = new PointClass();
            proximityOperator3D.QueryNearestPoint3D(pointGeometry as IPointesriSegmentExtension.esriNoExtension, nearestPoint3D);
            //nearestPoint3D = (5.393, -0.583, -6.043) 
        }



Infine un'interfaccia che ci può essere utile nella costruzione di multipatch è la IRay2. Ray ha un endpoint (la sua origine) e continua all'infinito nell'altra direzione.

Per creare un oggetto ray imposteremo la sua origine e un vettore direzionale. L'interfaccia IRay2 espone metodi per determinare punti di intersezione tra un oggetto ray e una multipatch.


        public static void QueryPointsOfIntersection()
        {
 
            ILine line = new LineClass();
            line.FromPoint = GetPoint();
            line.ToPoint = GetPoint();
 
 
            IRay ray = new RayClass();
            ray.Origin = line.FromPoint;
            ray.Vector = ConstructVector3D(line.ToPoint.X - line.FromPoint.X, line.ToPoint.Y - line.FromPoint.Y, line.ToPoint.Z - line.FromPoint.Z);
 
            IGeometry multiPatchGeometry = GetMultiPatchGeometry();
 
 
            IPointCollection intersectionPointCollection = new MultipointClass();
 
            ray.Intersect(multiPatchGeometry, intersectionPointCollection);
 
 
            //intersectionPointCollection[0] = (5, -2.13, 0.092)
 
            //intersectionPointCollection[1] = (1.612, -2.651, 7.012)
        }


Ora con ArcGIS 10.2 abbiamo anche la possibilità di esportare, tramite il tool Export To 3D Web Scene, i documenti di ArcScene nel formato 3ws (Esri CityEngine Web Scene) che possiamo poi pubblicare e visualizzare con arcgis.com o utilizzando il web viewer per la visualizzazione dei file 3ws sul proprio server.
Ad esempio potremo crearci delle multipatch al volo tramite una soe arcgis server e visualizzarla con il web viewer

            string pathfileNameSxd = System.IO.Path.Combine(newPath, "Multipatch.sxd");
            System.IO.File.Copy(System.IO.Path.Combine(pathRoot, "Multipatch.sxd"), pathfileNameSxd);
 
 
 
            IGeoProcessor2 gp = new GeoProcessorClass();
            IVariantArray parameters = new VarArrayClass();
            parameters.Add(pathfileNameSxd);
            parameters.Add(System.IO.Path.Combine(newPath, "Multipatch.3ws"));
            gp.Execute("ExportTo3DWebScene_3d", parameters, null);

Qui potete vedere un semplice esempio di multipatch create al volo da arcgis server.