// vim: shiftwidth=4 tabstop=4 noexpandtab /* Wikipedia about STL file format: https://en.wikipedia.org/wiki/STL_(file_format) */ package main import ( "io/ioutil" "log" "encoding/binary" "math" ) // StlModel is the Go-internal representation of a STL model. type StlModel struct { header []byte numberOfTriangles uint32 surface Surface } // Vector3 is a three-dimensional vector based on 32 bit floats. type Vector3 [3]float32 // ReadBinaryStlFile reads and parses a given binary STL file. func ReadBinaryStlFile(filePath string) (StlModel, error) { fileContent, err := ioutil.ReadFile(filePath) if err != nil { return StlModel{}, err } model := StlModel{} model.surface = Surface{} model.header = fileContent[0:80] log.Printf("STL header: '%s'\n", string(model.header)) model.numberOfTriangles = binary.LittleEndian.Uint32(fileContent[80:84]) log.Printf("STL model has %d triangles\n", model.numberOfTriangles) var i uint32 for i = 0; i < model.numberOfTriangles; i++ { // for each expected triangle start := 84 + i*50 // 50 bytes is length of one triangle end := 84 + (i+1)*50 model.surface.triangles = append(model.surface.triangles, ParseBinaryStlTriangle(fileContent[start:end])) } return model,nil } // ParseBinaryStlTriangle parses the 50 bytes of the STL file representing a // triangle (surface normal is ignored). func ParseBinaryStlTriangle(data []byte) *Triangle { // FIXME: This function should only accept 50 byte slices/arrays // allocate a new triangle and three corner points on the heap triangle := new(Triangle) triangle.points[0] = new(Point) triangle.points[1] = new(Point) triangle.points[2] = new(Point) // parse x, y and z coordinate for corner point a triangle.points[0].scalars[0] = math.Float32frombits( binary.LittleEndian.Uint32(data[12:16])) triangle.points[0].scalars[1] = math.Float32frombits( binary.LittleEndian.Uint32(data[16:20])) triangle.points[0].scalars[2] = math.Float32frombits( binary.LittleEndian.Uint32(data[20:24])) // parse x, y and z coordinate for corner point b triangle.points[1].scalars[0] = math.Float32frombits( binary.LittleEndian.Uint32(data[24:28])) triangle.points[1].scalars[1] = math.Float32frombits( binary.LittleEndian.Uint32(data[28:32])) triangle.points[1].scalars[2] = math.Float32frombits( binary.LittleEndian.Uint32(data[32:36])) // parse x, y and z coordinate for corner point c triangle.points[2].scalars[0] = math.Float32frombits( binary.LittleEndian.Uint32(data[36:40])) triangle.points[2].scalars[1] = math.Float32frombits( binary.LittleEndian.Uint32(data[40:44])) triangle.points[2].scalars[2] = math.Float32frombits( binary.LittleEndian.Uint32(data[44:48])) return triangle } // toVertices converts a STL model into vertices suitable for OpenGL rendering. func (stl StlModel) toVertices() (vertex_position []float32, vertex_normal []float32) { vertex_position = make([]float32, stl.numberOfTriangles * 9) vertex_normal = make([]float32, stl.numberOfTriangles * 9) var point0,point1,point2,edge0,edge1,normal Vector3; for triangleIndex,triangle := range(stl.surface.triangles) { for pointIndex,point := range(triangle.points) { for scalarIndex,scalar := range(point.scalars) { i := triangleIndex * 9 + pointIndex * 3 + scalarIndex vertex_position[i] = scalar } } // calculate normal point0 = Vector3{triangle.points[0].scalars[0], triangle.points[0].scalars[1], triangle.points[0].scalars[2]} point1 = Vector3{triangle.points[1].scalars[0], triangle.points[1].scalars[1], triangle.points[1].scalars[2]} point2 = Vector3{triangle.points[2].scalars[0], triangle.points[2].scalars[1], triangle.points[2].scalars[2]} edge0 = point1.subtract(point0) edge1 = point2.subtract(point1) normal = edge0.crossProduct(edge1) normal.divideScalar(2.0) // length of normal vector corresponds to // triangle area // save normal to each vertex of the triangle for i := 0; i<3; i++ { vertex_normal[triangleIndex*9+i*3+0] = normal[0] vertex_normal[triangleIndex*9+i*3+1] = normal[1] vertex_normal[triangleIndex*9+i*3+2] = normal[2] } } return vertex_position, vertex_normal } // divideScalar divides a three-dimensional vector by a scalar. func (vector *Vector3) divideScalar(scalar float32) { vector[0] = vector[0] / scalar vector[1] = vector[1] / scalar vector[2] = vector[2] / scalar } // subtract subtracts vectorB from vectorA. func (vectorA Vector3) subtract(vectorB Vector3) (vectorC Vector3) { vectorC[0] = vectorA[0] - vectorB[0] vectorC[1] = vectorA[1] - vectorB[1] vectorC[2] = vectorA[2] - vectorB[2] return vectorC } // crossProduct returns the cross product vectorA x vectorB. func (vectorA Vector3) crossProduct(vectorB Vector3) (vectorC Vector3) { vectorC[0] = vectorA[1] * vectorB[2] - vectorA[2] * vectorB[1] vectorC[1] = vectorA[2] * vectorB[0] - vectorA[0] * vectorB[2] vectorC[2] = vectorA[0] * vectorB[1] - vectorA[1] * vectorB[0] return vectorC }