require 'stringio' module Mittsu class STLLoader include EventDispatcher FLOAT = /[\d|.|+|\-|e]+/ NORMAL_PATTERN = /normal\s+(#{FLOAT})\s+(#{FLOAT})\s+(#{FLOAT})/ VERTEX_PATTERN = /^\s*vertex\s+(#{FLOAT})\s+(#{FLOAT})\s+(#{FLOAT})/ def initialize(manager = DefaultLoadingManager) @manager = manager @_listeners = {} end def load(url) loader = FileLoader.new(@manager) data = loader.load(url) parse(data) end def parse(data) reset_loader_vars stream = StringIO.new(data, "rb") # Load STL header (first 80 bytes max) header = stream.read(80) if header.slice(0,5) === "solid" stream.rewind parse_ascii(stream) else parse_binary(stream) end @group end private def reset_loader_vars @vertex_hash = {} @vertex_count = 0 @line_num = 0 @group = Group.new end def parse_ascii(stream) while line = read_line(stream) case line when /^\s*solid/ parse_ascii_solid(stream) else raise_error end end end def parse_ascii_solid(stream) vertices = [] faces = [] while line = read_line(stream) case line when /^\s*facet/ facet_vertices, face = parse_ascii_facet(line, stream) vertices += facet_vertices faces << face when /^\s*endsolid/ break else raise_error end end add_mesh vertices, faces end def parse_ascii_facet(line, stream) vertices = [] normal = nil if line.match NORMAL_PATTERN normal = Vector3.new($1, $2, $3) end while line = read_line(stream) case line when /^\s*outer loop/ nil # Ignored when /^\s*endloop/ nil # Ignored when VERTEX_PATTERN vertices << Vector3.new($1, $2, $3) when /^\s*endfacet/ break else raise_error end end return nil if vertices.length != 3 # Merge with existing vertices face, new_vertices = face_with_merged_vertices(vertices) face.normal = normal return new_vertices, face end def parse_binary(stream) vertices = [] faces = [] num_faces = stream.read(4).unpack('L<').first num_faces.times do |i| # Face normal normal = read_binary_vector(stream) # Vertices face_vertices = [] face_vertices << read_binary_vector(stream) face_vertices << read_binary_vector(stream) face_vertices << read_binary_vector(stream) # Throw away the attribute bytes stream.read(2) # Store data face, new_vertices = face_with_merged_vertices(face_vertices) face.normal = normal faces << face vertices += new_vertices end add_mesh vertices, faces end def face_with_merged_vertices(vertices) new_vertices = [] indices = [] vertices.each do |v| index, is_new = vertex_index(v) indices << index if is_new new_vertices << v @vertex_count += 1 end end # Return face and new vertex list return Face3.new( indices[0], indices[1], indices[2] ), new_vertices end def vertex_index(vertex) key = vertex_key(vertex) if i = @vertex_hash[key] return i, false else return (@vertex_hash[key] = @vertex_count), true end end def vertex_key(vertex) vertex.elements.pack("D*") end def add_mesh(vertices, faces) geometry = Geometry.new geometry.vertices = vertices geometry.faces = faces geometry.compute_bounding_sphere @group.add Mesh.new(geometry) end def read_binary_vector(stream) Vector3.new( read_le_float(stream), read_le_float(stream), read_le_float(stream) ) end def read_le_float(stream) stream.read(4).unpack('e').first end def read_line(stream) @line_num += 1 stream.gets end def raise_error raise "Mittsu::STLLoader: Unhandled line #{@line_num}" end end end