2.5D Topography with Color - Export to File

I have topographic data that is gridded with an image overlay. I need to export this to a file (GLB, PLY, etc). that retains height and color/texture.
image.jpg (72.6 KB)

The data structure is:
int nPtsAlongX = 1024; // width in pixels
int nPtsAlongY = 768; // height in pixels
double dSpacingAlongXMM = 1.5;
double dSpacingAlongYMM = 2.5;
double dXOriginMM = 10.0;
double dYOriginMM = 5.0;

// INVALID POINTS are float.MinValue
float fSurfaceHeightsMM = new float[nPtsAlongY, nPtsAlongX];
Bitmap bmpOverlay = new Bitmap(nPtsAlongX, nPtsAlongY);

Individual points and colors can be extracted via:

for (int nY=0; nY<nPtsAlongY; nY++)
{
for (int nX=0; nX<nPtsAlongX; nX++)
{
if (fSurfaceHeightsMM[nY, nX] != float.MinValue)
{
double dXCurrentMM = dXOriginMM + dSpacingAlongXMMnX;
double dYCurrentMM = dYOriginMM + dSpacingAlongYMM
nY;
double dZCurrentMM = fSurfaceHeightsMM[nY, nX];
Color colorCurrent = bmpOverlay.GetPixel(nX, nPtsAlongY-1-nY);
// do something with X, Y, Z and color… like put them in a list for saving to a file! :blush:
} // end if valid
} // end for nX
} // end for nY

What is the most efficient way to convert these points to a scene and export to a 3D file?

Thanks!

@mcmalburg

We have logged an investigation ticket as THREEDNET-838 in our issue tracking system for your requirements. We will look into its details and keep you posted with the status of ticket resolution. Please be patient and spare us some time.

We are sorry for the inconvenience.

The issues you have found earlier (filed as THREEDNET-838) have been fixed in Aspose.3D for .NET 21.3.

I’m glad to hear that this is in 21.3… however I do not see anything regarding how to use this functionality. Is the API documented somewhere for converting topography to a scene?

Thanks.

@mcmalburg

Please check the following code snippet in order to achieve the purpose:

int nPtsAlongX = 1024; // width in pixels
            int nPtsAlongY = 768; // height in pixels
            double dSpacingAlongXMM = 1.5;
            double dSpacingAlongYMM = 2.5;
            double dXOriginMM = 10.0;
            double dYOriginMM = 5.0;

            float[,] fSurfaceHeightsMM = new float[nPtsAlongY, nPtsAlongX];
            Bitmap bmpOverlay = (Bitmap)Image.FromFile(@"test.png");

            //Individual points and colors can be extracted via:
            var mesh = new Mesh();
            var vertexColor = (VertexElementVertexColor)mesh.CreateElement(VertexElementType.VertexColor, MappingMode.ControlPoint, ReferenceMode.Direct);

            var numVertices = 0;
            //use an 2d-array to store the mapping of sparse vertices mapping
            //0 means no vertex, value > 0 means there is a vertex in (value-1) position
            int[,] verticesMapping = new int[nPtsAlongY, nPtsAlongX];

            Color white = Color.FromArgb(255, 255, 255, 255);

            for (int nY = 0; nY < nPtsAlongY; nY++)
            {
                for (int nX = 0; nX < nPtsAlongX; nX++)
                {
                    if (fSurfaceHeightsMM[nY, nX] != float.MinValue)
                    {
                        double dXCurrentMM = dXOriginMM + dSpacingAlongXMM * nX;
                        double dYCurrentMM = dYOriginMM + dSpacingAlongYMM * nY;
                        double dZCurrentMM = fSurfaceHeightsMM[nY, nX];
                        Color colorCurrent = bmpOverlay.GetPixel(nX, nPtsAlongY - 1 - nY);
                        // do something with X, Y, Z and color… like put them in a list for saving to a file! :blush:

                        //if (colorCurrent != white) //filter condition for my testing
                        {
                            mesh.ControlPoints.Add(new Vector4(dXCurrentMM, dYCurrentMM, dZCurrentMM, 1));
                            vertexColor.Data.Add(new Vector4(colorCurrent));
                            verticesMapping[nY, nX] = ++numVertices;
                        }
                    } // end if valid
                } // end for nX
            }
            //create quads to connect the vertices.
            for (int nY = 1; nY < nPtsAlongY; nY++)
            {
                for (int nX = 1; nX < nPtsAlongX; nX++)
                {
                    int a = verticesMapping[nY - 1, nX - 1];
                    int b = verticesMapping[nY , nX - 1 ];
                    int c = verticesMapping[nY, nX ];
                    int d = verticesMapping[nY - 1, nX];
                    //check if any corner is invalid in the first stage.
                    if (a != 0 && b != 0 && c != 0 && d != 0)
                        mesh.CreatePolygon(a - 1, b - 1, c - 1, d - 1);
                }
            }
            var mat = new LambertMaterial();
            mat.SetTexture(Material.MapDiffuse, new Texture() { FileName = @"test.png" });
            var scene = new Scene();
            scene.RootNode.CreateChildNode(mesh).Material = mat;
            FileFormat.PLY.Encode(mesh, "test.ply");
            scene.Save("test.glb", FileFormat.GLTF2_Binary);

For the line:
mat.SetTexture(Material.MapDiffuse, new Texture() { FileName = @“test.png” });

Can this SetTexture use the bmpOverlay directly? In this example the image comes from a file. In my application, it will already reside in memory… not in a file.

Thanks for this great example!

@mcmalburg

A feature request as THREEDNET-864 has been logged in our issue tracking system in order to implement the required property for Texture Class. We will let you know as soon as the ticket is resolved. Please give us some time.

We apologize for the inconvenience.

The issues you have found earlier (filed as THREEDNET-864) have been fixed in Aspose.3D for .NET 21.4.

This sounds great!

How do I call the function with a memory bitmap?
Where do I find API documentation regarding the new function call?

@mcmalburg

We have investigated the earlier logged ticket and found that Aspose.3D already supported this feature by implementing a FileSystem subclass to provide “virtual” file access:

        class MyFileSystem : FileSystem
        {
            private Dictionary<string, byte[]> data;
            public MyFileSystem(Dictionary<string, byte[]> data)
            {
                this.data = data;
            }

            public override Stream ReadFile(string fileName, IOConfig options)
            {
                byte[] bytes;
                if (!data.TryGetValue(fileName, out bytes))
                    throw new FileNotFoundException();
                return new MemoryStream(bytes);
            }

            public override Stream WriteFile(string fileName, IOConfig options)
            {
                throw new NotImplementedException();
            }

        }

Aspose.3D will close the stream returned by ReadFile/WriteFile. Here is the code of how to use this custom file system:

            var opt = new GLTFSaveOptions(FileFormat.GLTF2_Binary);
            //All file read/write are done through this FileSystem property, here we implemented a "MyFileSystem" 
            opt.FileSystem = new MyFileSystem(new Dictionary<string, byte[]>
            {
                { "test.png", File.ReadAllBytes("test.png")}//here's the bytes you want the exporter to use
            });

            scene.Save("test.glb", opt);