ProConcepts Multipatches - Esri/arcgis-pro-sdk GitHub Wiki

This concepts document covers special considerations for working with multipatches. It augments the overall geometry concepts covered in ProConcepts Geometry

ArcGIS.Core.dll

Language:      C#
Subject:       Multipatches
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          10/06/2024
ArcGIS Pro:    3.4
Visual Studio: 2022

In this topic

Overview

A multipatch is a series of 3-dimensional surfaces used to represent a 3D object. It can be used to represent simple objects such as spheres and cubes or complex objects such as buildings and trees. It allows for the storing of texture image, color, transparency and lighting normal vector information within the geometry itself, making it the ideal data type for the representation of realistic-looking 3D features. Multipatches are useful in exchanging data with other non-GIS 3D software packages such as Collaborative Design Activity (COLLADA) and SketchUp®.

Patches

Each part in a multipatch is called a patch and has a list of vertices with 3D coordinates to represent it's shape. A patch can be of type Triangles, Triangle Strip, Triangle Fan, First Ring or Ring. The type of the patch determines how to interpret the list of vertices. The following image displays examples of the different patch types.

showPreview

As with all other geometry builders the general process of constructing a multipatch object is as follows.

  • Create an instance of the appropriate builder object; in this instance the MultipatchBuilderEx class.
  • Build the parts.
    • The MultipatchBuilderEx class has a MakePatch method that creates a new patch of the specified patchType. Add the new patch to the Patches collection.
    • Populate or modify patch properties. The patch has lists representing the x,y,z coordinates and M-values along with other properties to control it's visual appearance.
  • Use the ToGeometry method to obtain the geometry.

The MultipatchBuilderEx and Patch classes contain lists representing the patches, the coordinates, the M-values and other information. These lists can be manipulated directly or by using other methods provided. Note that if you modify a list you are not modifying a copy of the list, but modifying the list itself.

Here is a simple example of a multipatch built with patches of type Triangles. The process is as outlined above; the MultipatchBuilderEx class is created; a number of patches are created using the MakePatch method; each patch has coordinates assigned via the Coords property before being added to a list. This patch list is assigned to the MultipatchBuilderEx.Patches collection. Finally ToGeometry is called to obtain the multipatch geometry.

// coordinates
var coords_face1 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495461061000071,41.902603910000039,62.552700000000186),
  new Coordinate3D(12.495461061000071,41.902603910000039,59.504700000004959),
  new Coordinate3D(12.495461061000071,41.902576344000067,59.504700000004959),
  new Coordinate3D(12.495461061000071,41.902603910000039,62.552700000000186),
  new Coordinate3D(12.495461061000071,41.902576344000067,59.504700000004959),
  new Coordinate3D(12.495461061000071,41.902576344000067,62.552700000000186),
};

var coords_face2 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495461061000071, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495461061000071, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 62.552700000000186),
};

var coords_face3 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495488442000067, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 62.552700000000186),
};

var coords_face4 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495488442000067, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 59.504700000004959),
};

var coords_face5 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495488442000067, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 59.504700000004959),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 62.552700000000186),
};

var coords_face6 = new List<Coordinate3D>()
{
  new Coordinate3D(12.495488442000067, 41.902603910000039, 62.552700000000186),
  new Coordinate3D(12.495461061000071, 41.902603910000039, 62.552700000000186),
  new Coordinate3D(12.495461061000071, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902603910000039, 62.552700000000186),
  new Coordinate3D(12.495461061000071, 41.902576344000067, 62.552700000000186),
  new Coordinate3D(12.495488442000067, 41.902576344000067, 62.552700000000186),
};

// create the multipatchBuilderEx object
MultipatchBuilderEx builder = new MultipatchBuilderEx();

// create a list of patch objects
var patches = new List<Patch>();

// make each patch using the appropriate coordinates and add to the patch list
var patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face1;
patches.Add(patch);

patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face2;
patches.Add(patch);

patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face3;
patches.Add(patch);

patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face4;
patches.Add(patch);

patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face5;
patches.Add(patch);

patch = builder.MakePatch(PatchType.Triangles);
patch.Coords = coords_face6;
patches.Add(patch);

// assign the patches to the multipatchBuilder
builder.Patches = patches;

// call ToGeometry to get the multipatch
Multipatch multipatch = builder.ToGeometry() as Multipatch;

Following the pattern of all other geometry builders, the MultipatchBuilderEx class also has properties for setting the attribute awareness for Ms and IDs for the resulting geometry - HasM, HasID. These properties work in conjunction with the Ms and IDs properties on each patch.

The MultipatchBuilderEx always recognizes Zs so the HasZ property always returns true.

Here is a snippet illustrating how to set M values on a multipatch. As before, The MultipatchBuilderEx class is created. The HasM flag is set. A single patch is created using the MakePatch method and added to the Patches collection. Coordinates and M values are assigned via the Coords and Ms properties on the patch. Finally the SynchronizeAttributeAwareness method is called before ToGeometry to retrieve the multipatch.

The SynchronizeAttributeAwareness method is important. It is used to ensure that the attribute awareness of the multipatch builder is applied to all of the patches. It also synchronizes the size of the Ms, IDs list to that of the Coords list for all patches.

MultipatchBuilderEx builder = new MultipatchBuilderEx();
// set the HasM flag
builder.HasM = true;

// make a new patch of type Triangles
builder.Patches.Add(builder.MakePatch(PatchType.Triangles));

// get the patch
Patch patch = builder.Patches.Last();
// add the coordinates and the M values
patch.Coords.Add(new Coordinate3D(0, 0, 0)); patch.Ms.Add(0);
patch.Coords.Add(new Coordinate3D(1, 0, 0)); patch.Ms.Add(1);
patch.Coords.Add(new Coordinate3D(1, 1, -10)); patch.Ms.Add(20);
patch.Coords.Add(new Coordinate3D(0, 0, 1)); patch.Ms.Add(3);
patch.Coords.Add(new Coordinate3D(1, 0, 1)); patch.Ms.Add(4);
patch.Coords.Add(new Coordinate3D(1, 1, 1)); patch.Ms.Add(5);

// synchronize attribute awareness
patch.SynchronizeAttributeAwarenessWithBuilder(builder);

// get the geometry
Multipatch multipatch = builder.ToGeometry();

// multipatch.HasM will be true

This second example illustrates the power of the SynchronizeAttributeAwareness method. When the patch is created it has 5 coordinates, 1 M value and 1 ID value. After setting the HasM and HasID properties on the builder and calling SyncronizeAttributeAwareness the Ms and IDs lists have the same size as the Coords. Default M and ID values have been assigned.

  // set up the coordinates
List<Coordinate3D> coords = new List<Coordinate3D>
{
  new Coordinate3D(1000, 1000, 0),
  new Coordinate3D(1000, 5000, 100),
  new Coordinate3D(5000, 5000, 100),
  new Coordinate3D(5000, 1000, 0),
  new Coordinate3D(1000, 1000, 0)
};

// make the patch and assign the coordinates
Patch patch = new Patch(PatchType.FirstRing);
patch.Coords = coords;

// set up the list of Ms and Ids on the patch 
// (note we only have 1 value in each list)
List<double> ms = new List<double> { 1.2 };
patch.Ms = ms;

List<int> ids = new List<int> { 1 };
patch.IDs = ids;

// create the builder and set attribute awareness
MultipatchBuilderEx builderEx = new MultipatchBuilderEx();
builderEx.HasM = true;
builderEx.HasID = true;

// add the patch
builderEx.Patches.Add(patch);

// synchronize
bool changed = builderEx.SynchronizeAttributeAwareness();

// The Ms, IDs now have the same count as the coordinates.  
// Default values have been assigned to the patch
// M default is double.Nan
// ID default is 0

// builderEx.Patches[0].Coords.Count = 5
// builderEx.Patches[0].Ms.Count = 5
// builderEx.Patches[0].IDs.Count = 5

// builderEx.Patches[0].Ms = (1.2, double.Nan, double.Nan, double.Nan, double.Nan)
// builderEx.Patches[0].Ids = (1, 0, 0, 0, 0)

// get the geometry
Multipatch multipatch = builderEx.ToGeometry();

In addition to the 3D coordinates, M and ID values, the Patch class has a number of other properties.

  • Patch Priority. When overlapping patches are defined within the same multipatch, priorities can be used to specify the order in which patches should be drawn. The larger the number assigned, the higher the patch priority or precedence the patch takes when rendered.
  • Normals. The normals of a 3D object are used for lighting. They define the direction that each face will reflect light. They are usually perpendicular to the patch but can also be defined in other directions. Use this property together with the HasNormals property on the MultipatchBuilderEx.
  • Material. The material of this patch. It controls the visual appearance.
  • Texture Coordinates 2D. The list of 2D coordinates for mapping a 2D texture to the patch.

We will look at materials next.

Materials

When defining the patches of your multipatch, you can assign graphic properties to each patch to enhance the visual appearance of the geometry. This is achieved using a Material. The BasicMaterial class has a number of properties to support this.

  • Color. Color of the material specified using RGB values.
  • Transparency Percent. Transparency of the material. It is useful for creating materials such as glass.
  • Shininess. Controls how the light is reflected from the surface.
  • Cull back faces. Back-face culling determines whether the material is visible when projected onto the screen.
  • Edge Color. Color of the edges specified using RGB values.
  • Edge Width. The width of the edges.

Here is a snippet showing how to create a material with a number of properties set.

BasicMaterial faceMaterial = new BasicMaterial();
faceMaterial.Color = System.Windows.Media.Color.FromRgb(203, 65, 84);
faceMaterial.Shininess = 150;
faceMaterial.TransparencyPercent = 50;
faceMaterial.EdgeWidth = 20;

And assigning this to the patch

patch.Material = faceMaterial;

You can share materials between patches. Here is a snippet illustrating this. The multipatch being built has 2 patches but shares one material.

  patch1.Material = faceMaterial;
  patch2.Material = faceMaterial;

  Multipatch multipatch = builderEx.ToGeometry() as Multipatch;

  // multipatch.PartCount = 2  (parts is number of patches)
  // multipatch.HasMaterials = true
  // multipatch.MaterialCount = 1

If your multipatch has already been created you can retrieve information about the material on a particular patch via methods on the Multipatch object.

bool hasMaterials = multipatch.HasMaterials;
int materialCount = multipatch.MaterialCount;

// if the multipatch has materials 
if (hasMaterials)
{
  // does the patch have a material?  
  // materialIndex = -1 if the patch does not have a material; 
  // 0 <= materialIndex < materialCount if the patch does have materials
  int patchIndex = 0;
  int materialIndex = multipatch.GetPatchMaterialIndex(patchIndex);

  // properties for an individual material (if multipatch.MaterialCount > 0)
  var color = multipatch.GetMaterialColor(materialIndex);
  var edgeColor = multipatch.GetMaterialEdgeColor(materialIndex);
  var edgeWidth = multipatch.GetMaterialEdgeWidth(materialIndex);
  var shininess = multipatch.GetMaterialShininess(materialIndex);
  var transparency = multipatch.GetMaterialTransparencyPercent(materialIndex);
  var cullBackFace = multipatch.IsMaterialCullBackFace(materialIndex);
}

Textures

In order to make the 3D geometries look more realistic you can define textures in a material and map these textures to the surface of the geometry. Textures are bitmap images stored in some file formats. Two types of textures are currently supported

The BasicMaterial class contains a reference to a texture resource via the TextureResource property. The TextureResource class wraps an instance of the Texture class. Multiple materials can point to the same texture resource, allowing the sharing of textures between materials. This extra level of indirection allows textures to be replaced or updated for all the materials that point to the same texture resource.

You can use the other BasicMaterial class properties such as Transparency combined with the TextureResource property to configure the material.

//At 3.0 you need https://www.nuget.org/packages/Microsoft.Windows.Compatibility
System.Drawing.Image image = System.Drawing.Image.FromFile("OrangeSquare.jpg");
MemoryStream memoryStream = new MemoryStream();
System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Jpeg;
image.Save(memoryStream, format);
byte[] imageBuffer = memoryStream.ToArray();

// create the texture
var texture = new JPEGTexture(imageBuffer);

// create a texture resource
var textureResource = new TextureResource(texture);

// create the material and assign the texture resource
BasicMaterial material = new BasicMaterial();
material.TextureResource = textureResource;

// assign the material to the patch
Patch patch = new Patch(PatchType.FirstRing);
patch.Material = material;

In addition to specifying the texture resource as part of the material on the patch, the TextureCoords2D property on the patch object is needed to specify the mapping of the image to the 2D face. There should be the same number of vertices in this list as there are in the Coords property. Normally textures will map to a (0,0), (1,1) pattern mapping the entire image to the face, however there may be instances where you want to map a subset of the texture to the face. For example, the following image displays a flat image of a Rubik's cube mapped to a grid. Rubiks Cube Coordinates

If only the part of the image containing the nine white squares is to be mapped to a patch, this can be achieved by specifying which [s,t] image coordinates map to each vertex of the patch. Here is a snippet illustrating this.

// assign texture coordinates to the top face patch
Patch topFacePatch = cubeMultipatchBuilderExWithTextures.Patches[0];
topFacePatch.TextureCoords2D = new List<Coordinate2D>
{
  new Coordinate2D(0.25, 0.33),
  new Coordinate2D(0.25, 0),
  new Coordinate2D(0.5, 0),
  new Coordinate2D(0.5, 0.33),
  new Coordinate2D(0.25, 0.33)
};

As with the materials, you can retrieve information about the textures on a multipatch via methods on the Multipatch object.

bool hasMaterials = multipatch.HasMaterials;

// if the multipatch has materials 
if (hasMaterials)
{
  // does the patch have a material?  
  // materialIndex = -1 if the patch does not have a material; 
  // 0 <= materialIndex < materialCount if the patch does have materials
  int patchIndex = 0;
  int materialIndex = multipatch.GetPatchMaterialIndex(patchIndex);

  // texture properties
  bool isTextured = multipatch.IsMaterialTextured(materialIndex);
  if (isTextured)
  {
    TextureCompressionType compressionType = multipatch.GetMaterialTextureCompressionType(materialIndex);
    var texture = multipatch.GetMaterialTexture(materialIndex);
    int columnCount = multipatch.GetMaterialTextureColumnCount(materialIndex);
    int rowCount = multipatch.GetMaterialTextureRowCount(materialIndex);
    int bpp = multipatch.GetMaterialTextureBytesPerPixel(materialIndex);
  }
}

3D Model Files

As previously mentioned, multipatches are useful in exchanging data with other non-GIS 3D software packages such as Collaborative Design Activity (COLLADA) and SketchUp®. The MultipatchBuilderEx.From3DModelFile method allows a multipatch to be imported from a supported 3D model file. The 3D model files that are supported are .3DS, .DAE, .FLT, .OBJ, .gITF, .GLB. If the file cannot be read or doesn't contain any geometry an empty multipatch will be returned.

string modelFileName = @"D:\Data\Multipatch\Collada\BrooksidePark\PicnicTable.dae";
try
{
  var modelGeom = MultipatchBuilderEx.From3DModelFile(modelFileName);
  if (!modelGeom.IsEmpty)
  {
    // do something with the multipatch geometry
  }
}
catch (ArgumentException)
{
  // filetype not supported
}
catch (Exception)
{
  // other exceptions
}

Predefined 3D Shapes

Special 3D shapes such as cubes, tetrahedrons, diamonds, sphere frames, spheres, cylinders, hexagons, and cones can be created using the MultipatchBuilderEx object.

Predefined Multipatches

Here is an example of creating a cube of size 10.

 MapPoint centerPt = MapPointBuilderEx.CreateMapPoint(1, 1, 1);

 // cube
 Multipatch cube = MultipatchBuilderEx.CreateMultipatch(MultipatchConstructType.Cube, centerPt, 10);

Or a sphere of size 10 with quality 0.8. The quality is a value between 0 and 1 inclusive. A higher quality number means more vertices for the shape resulting in a smoother shape.

 MapPoint centerPt = MapPointBuilderEx.CreateMapPoint(1, 1, 1);

 // sphere
 Multipatch sphere = MultipatchBuilderEx.CreateMultipatch(MultipatchConstructType.Sphere, centerPt, 10, 0.8);

In addition to using a static convenience method, you can also use the builder object and add materials or textures to the patches before creating the multipatch. Here is a snippet illustrating how to create a cone with each patch pointing to a material.

SpatialReference sr = SpatialReferenceBuilder.CreateSpatialReference(54004);
MapPoint centerZ = MapPointBuilderEx.CreateMapPoint(300, 400, 500, sr);

// use the builder constructor
MultipatchBuilderEx builder = new MultipatchBuilderEx(MultipatchConstructType.Cone, centerZ, 200, 0.8, sr);

// create the material
BasicMaterial faceMaterial = new BasicMaterial();
faceMaterial.Color = System.Windows.Media.Color.FromRgb(255, 0, 0);
faceMaterial.Shininess = 150;
faceMaterial.TransparencyPercent = 50;
faceMaterial.EdgeWidth = 20;

// assign the material to each of the patches
var patches = builder.Patches;
foreach (var patch in patches)
  patch.Material = faceMaterial;

// create the multipatch
Multipatch multipatch = builder.ToGeometry();
⚠️ **GitHub.com Fallback** ⚠️