@James_Plotts wrote:
Hi all.
Thought I'd share some code for loading *.STL object definition files into a vertex array for use in rendering them as a 3D object. I developed this for an image generation program that created previews from different perspectives of the object. I wrote everything in VB, but I am sure it can be converted to C# (for you poor souls).
The first thing that was needed was a custom Vertex definition:
' Copyright 2017 by James Plotts. ' Licensed under Gnu GPL 3.0. Imports Microsoft.Xna.Framework Imports Microsoft.Xna.Framework.Graphics Namespace OpenForge.Development ''' <summary> ''' Custom Vertex that allows the use of Position, Color and a Normal. ''' </summary> ''' <remarks> Thanks to Riemer's XNA Tutorials for this format. ''' http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series1/Lighting_basics.php ''' ''' </remarks> Public Structure VertexPositionColorNormal ''' <summary> ''' Vector3 describing the Vertex Location in 3D space. ''' </summary> ''' <remarks></remarks> Public Position As Vector3 ''' <summary> ''' Color at the Vertex positon. ''' </summary> ''' <remarks></remarks> Public Color As Color ''' <summary> ''' Normal Coordinates for the Vertex ''' </summary> ''' <remarks></remarks> Public Normal As Vector3 Public Sub New(ByVal vPos As Vector3, ByVal vCol As Color, ByVal vNor As Vector3) Position = vPos Color = vCol Normal = vNor End Sub ''' <summary> ''' Necessary VertexDeclaration information to use this Vertex with GraphicsDevice.DrawUserPrimitives. ''' </summary> ''' <value></value> ''' <returns>Valid VertexDeclaration defining the elements of this structure.</returns> ''' <remarks> ''' Example use: ''' GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, NumTriangles, VertexPositionColorNormal.VertexDeclaration) ''' ^^^^^^^^^^^^^^^^^ ''' </remarks> Public Shared ReadOnly Property VertexDeclaration() As VertexDeclaration Get Return New VertexDeclaration({ _ New VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), _ New VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), _ New VertexElement(16, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)}) End Get End Property End Structure End Namespace
The next was a structure to load the STL object data from a stream and convert it to a vertex array:
' Copyright 2017 by James Plotts. ' Licensed under Gnu GPL 3.0. ' Thanks to thjerman for the Stereolithography File Formats post at: ' http://forums.codeguru.com/showthread.php?148668-loading-a-stl-3d-model-file Imports System Imports System.IO Namespace OpenForge.Development Public Class STLDefinition Public Class STLObject Public STLHeader As New STLHeader Public Vertices() As VertexPositionColorNormal Public Facets() As Facet End Class Public Class STLHeader 'id' is a null-terminated string of the form "filename.stl", where filename is the name of the converted ".bin" file. Public Id(22) As Char 'date' is the date stamp in UNIX ctime() format. Public DateCreated(26) As Char 'xmin' - 'zmax' are the geometric bounds on the data Public xmin As Single Public xmax As Single Public ymin As Single Public ymax As Single Public zmin As Single Public zmax As Single Public xpixelsize As Single ' Dimensions of grid for this model Public ypixelsize As Single ' in user units. Public nfacets As UInt32 Public ReadOnly Property XCenter As Single Get Return (xmax - xmin) / 2 + xmin End Get End Property Public ReadOnly Property YCenter As Single Get Return (ymax - ymin) / 2 + ymin End Get End Property Public ReadOnly Property ZCenter As Single Get Return (zmax - zmin) / 2 + zmin End Get End Property End Class Public Class Vertex Public x As Single Public y As Single Public z As Single End Class Public Class Facet Public normal As New Vertex ' facet surface normal Public v1 As New Vertex ' vertex 1 Public v2 As New Vertex ' vertex 2 Public v3 As New Vertex ' vertex 3 End Class ''' <summary> ''' LoadSTL reads a stream and converts the data to an STLObject. ''' Stream must contain an STL formatted file. ''' </summary> ''' <param name="stream"></param> ''' <returns></returns> ''' <remarks></remarks> Public Shared Function LoadSTL(ByVal stream As Stream) As STLDefinition.STLObject Dim vStl As New STLDefinition.STLObject Try Dim index As Int32 = 0 Dim tb(4) As Byte Dim tb2(30) As Byte Dim sr As Stream = stream Dim x1 As Single, x2 As Single Dim y1 As Single, y2 As Single Dim z1 As Single, z2 As Single stream.Read(tb2, 0, 22) stream.Read(tb2, 0, 26) With vStl With .STLHeader .xmin = rc(sr) .xmax = rc(sr) .ymin = rc(sr) .ymax = rc(sr) .xmin = rc(sr) .xmax = rc(sr) .xpixelsize = rc(sr) .ypixelsize = rc(sr) sr.Read(tb, 0, 4) .nfacets = BitConverter.ToUInt32(tb, 0) End With Dim retval As Facet ReDim .Facets(CInt(.STLHeader.nfacets)) For i As Int32 = 0 To CInt(.STLHeader.nfacets) - 1 retval = New Facet With retval With .normal .x = rc(sr) .y = rc(sr) .z = -rc(sr) ' Negative Z value Flips to our coordinate system End With With .v1 .x = rc(sr) If .x < x1 Then x1 = .x If .x > x2 Then x2 = .x .y = rc(sr) If .y < y1 Then y1 = .y If .y > y2 Then y2 = .y .z = -rc(sr) If .z < z1 Then z1 = .z If .z > z2 Then z2 = .z End With With .v2 .x = rc(sr) If .x < x1 Then x1 = .x If .x > x2 Then x2 = .x .y = rc(sr) If .y < y1 Then y1 = .y If .y > y2 Then y2 = .y .z = -rc(sr) If .z < z1 Then z1 = .z If .z > z2 Then z2 = .z End With With .v3 .x = rc(sr) If .x < x1 Then x1 = .x If .x > x2 Then x2 = .x .y = rc(sr) If .y < y1 Then y1 = .y If .y > y2 Then y2 = .y .z = -rc(sr) If .z < z1 Then z1 = .z If .z > z2 Then z2 = .z End With End With sr.Read(tb, 0, 2) ' just padding bytes not used. .Facets(i) = retval Next End With With vStl.STLHeader If x1 > x2 Then .xmin = x2 .xmax = x1 Else .xmin = x1 .xmax = x2 End If If y1 > y2 Then .ymin = y2 .ymax = y1 Else .ymin = y1 .ymax = y2 End If If z1 > z2 Then .zmin = z2 .zmax = z1 Else .zmin = z1 .zmax = z2 End If End With Catch vStl = Nothing End Try Return vStl End Function ''' <summary> ''' Reads 4 bytes from the supplied Stream, ''' converts them to a Single and returns the result. ''' </summary> ''' <param name="sr">A valid Stream object</param> ''' <returns>A Single value read from the stream.</returns> ''' <remarks></remarks> Private Shared Function rc(ByVal sr As Stream) As Single Dim tb(4) As Byte sr.Read(tb, 0, 4) Return BitConverter.ToSingle(tb, 0) End Function End Class End Namespace
Then, the function that loads the file:
Private pvtUS1 As Single, pvtUS2 As Single ''' <summary> ''' Displays an open file dialog and then loads the chosen STL object. ''' </summary> ''' <remarks></remarks> <STAThreadAttribute> _ Sub BackgroundLoader() Dim fd As New OpenFileDialog Dim dlgres As DialogResult fd.ShowReadOnly = True fd.Title = "Open *.STL File" fd.Multiselect = False fd.Filter = "STL Files (*.stl)|*.stl" fd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) dlgres = fd.ShowDialog() If dlgres = DialogResult.OK Then Dim stl As STLDefinition.STLObject SavePath = fd.FileName stl = STLDefinition.LoadSTL(fd.OpenFile()) NumFacets = CInt(stl.STLHeader.nfacets) pvtUS1 = stl.STLHeader.xpixelsize pvtUS2 = stl.STLHeader.ypixelsize Dim vn As Vector3 Dim lVertices(NumFacets * 3) As VertexPositionColorNormal With stl For i As Int32 = 0 To NumFacets - 1 With .Facets(i) With .normal vn = New Vector3(.x, .y, .z) End With With .v1 lVertices(i * 3) = New VertexPositionColorNormal(New Vector3(.x, .y, .z), ObjectColor, vn) End With End With With .Facets(i) With .normal vn = New Vector3(.x, .y, .z) End With With .v2 lVertices(i * 3 + 1) = New VertexPositionColorNormal(New Vector3(.x, .y, .z), ObjectColor, vn) End With End With With .Facets(i) With .normal vn = New Vector3(.x, .y, .z) End With With .v3 lVertices(i * 3 + 2) = New VertexPositionColorNormal(New Vector3(.x, .y, .z), ObjectColor, vn) End With End With Next With .STLHeader ObjectCenter = Matrix.CreateTranslation(-.XCenter, -.YCenter, .ZCenter) xMin = .xmin xMax = .xmax yMin = .ymin yMax = .ymax zMin = .zmin zMax = .zmax End With End With verticesloaded = False 'ReDim vertices(NumFacets * 3) vertices = lVertices verticesloaded = True For i As Int32 = 0 To 4 OutputGenerated(i) = False Next bolRotateToggle = True CurDir = eDir.North RotateY = Matrix.Identity FocusPoint.Z = 0 FocusPoint.X = 0 CameraOffset.X = 1000 CameraOffset.Z = 1000 End If loadthreadrunning = False End Sub
And lastly, a function that illustrates how to render the verticies and also save the screenshot:
Private Function GrabScreenshot() As Bitmap Dim ss As RenderTarget2D Dim b As System.Drawing.Bitmap = Nothing Static bolGeneratingCurrently As Boolean If Not bolGeneratingCurrently Then bolGeneratingCurrently = True Scales = New Vector3(ScaleValue, ScaleValue, ScaleValue) worldMatrix = ObjectCenter * Matrix.CreateScale(Scales) * Matrix.CreateRotationX(MathHelper.ToRadians(90.0F)) * RotateY * RotateTop BasicEffect.Projection = projectionMatrix BasicEffect.View = ViewMatrix BasicEffect.World = worldMatrix ' Turn off culling so we see both sides of our rendered triangle Dim RasterizerState As New RasterizerState() RasterizerState.CullMode = CullMode.None ' Prepare GraphicsDevice 'GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue) GraphicsDevice.RasterizerState = RasterizerState ss = New RenderTarget2D(GraphicsDevice, width, height, False, SurfaceFormat.Color, DepthFormat.Depth24) GraphicsDevice.SetRenderTarget(ss) Dim bolRunOnce As Boolean = True Do While bolRunOnce GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue) For Each pass As EffectPass In BasicEffect.CurrentTechnique.Passes pass.Apply() If verticesloaded = True Then GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, NumFacets, VertexPositionColorNormal.VertexDeclaration) End If Next bolRunOnce = False Loop GraphicsDevice.SetRenderTarget(Nothing) ' finished with render target Dim fs As New MemoryStream Try ' save intermediate PNG image to stream ss.SaveAsPng(fs, width, height) ' read image from stream to a bitmap object b = New Bitmap(fs) Catch End Try fs.Close() ss = Nothing fs = Nothing bolGeneratingCurrently = False End If Return b End Function
There are a bajillion STL 3D object files available on the internet, which makes this appealing. What might be interesting to game developers is a website called Thingiverse, which has 3D STL files for miniature tabletop gaming (for 3D printing). Since the STL files are freely usable there, then, hey, you have a big asset library.
Posts: 1
Participants: 1