-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
require_relative './example_helper' | ||
|
||
SCREEN_WIDTH = 800 | ||
SCREEN_HEIGHT = 600 | ||
ASPECT = SCREEN_WIDTH.to_f / SCREEN_HEIGHT.to_f | ||
|
||
scene = Mittsu::Scene.new | ||
camera = Mittsu::PerspectiveCamera.new(75.0, ASPECT, 0.1, 1000.0) | ||
|
||
renderer = Mittsu::OpenGLRenderer.new width: SCREEN_WIDTH, height: SCREEN_HEIGHT, title: '10 STL Loader Example' | ||
renderer.shadow_map_enabled = true | ||
renderer.shadow_map_type = Mittsu::PCFSoftShadowMap | ||
|
||
loader = Mittsu::STLLoader.new | ||
|
||
object = loader.load(File.expand_path('../male02.stl', __FILE__)) | ||
|
||
object.receive_shadow = true | ||
object.cast_shadow = true | ||
|
||
object.traverse do |child| | ||
child.receive_shadow = true | ||
child.cast_shadow = true | ||
end | ||
|
||
scene.add(object) | ||
|
||
scene.print_tree | ||
|
||
floor = Mittsu::Mesh.new( | ||
Mittsu::BoxGeometry.new(1000.0, 1.0, 1000.0), | ||
Mittsu::MeshPhongMaterial.new(color: 0xffffff) | ||
) | ||
floor.position.y = -1.0 | ||
floor.receive_shadow = true | ||
scene.add(floor) | ||
|
||
scene.add Mittsu::AmbientLight.new(0xffffff) | ||
|
||
light = Mittsu::SpotLight.new(0xffffff, 1.0) | ||
light.position.set(300.0, 200.0, 0.0) | ||
|
||
light.cast_shadow = true | ||
light.shadow_darkness = 0.5 | ||
|
||
light.shadow_map_width = 1024 | ||
light.shadow_map_height = 1024 | ||
|
||
light.shadow_camera_near = 1.0 | ||
light.shadow_camera_far = 2000.0 | ||
light.shadow_camera_fov = 60.0 | ||
|
||
light.shadow_camera_visible = false | ||
scene.add(light) | ||
|
||
camera.position.z = 200.0 | ||
camera.position.y = 100.0 | ||
|
||
renderer.window.on_resize do |width, height| | ||
renderer.set_viewport(0, 0, width, height) | ||
camera.aspect = width.to_f / height.to_f | ||
camera.update_projection_matrix | ||
end | ||
|
||
renderer.window.run do | ||
object.rotation.y += 0.1 | ||
renderer.render(scene, camera) | ||
end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
require 'minitest_helper' | ||
|
||
class TestSTLASCIILoader < Minitest::Test | ||
def test_parse | ||
loader = Mittsu::STLLoader.new | ||
|
||
object = loader.parse """solid | ||
facet normal 0 0 1 | ||
outer loop | ||
vertex -1 100.0e-2 0 | ||
vertex -1 -1e0 0 | ||
vertex 1 100.0e-2 0 | ||
endloop | ||
endfacet | ||
facet normal 0 0 1 | ||
outer loop | ||
vertex -1 -1e0 0 | ||
vertex 1 -1e0 0 | ||
vertex 1 100.0e-2 0 | ||
endloop | ||
endfacet | ||
endsolid | ||
""" | ||
|
||
assert_kind_of Mittsu::Group, object | ||
assert_equal 1, object.children.count | ||
|
||
square_mesh = object.children.first | ||
assert_kind_of Mittsu::Mesh, square_mesh | ||
assert_kind_of Mittsu::Geometry, square_mesh.geometry | ||
[ | ||
Mittsu::Vector3.new(-1.0, 1.0, 0), | ||
Mittsu::Vector3.new(-1.0, -1.0, 0), | ||
Mittsu::Vector3.new(1.0, 1.0, 0), | ||
Mittsu::Vector3.new(1.0, -1.0, 0) | ||
].each_with_index { |v, i| | ||
assert_equal v, square_mesh.geometry.vertices[i] | ||
} | ||
[ | ||
[0, 1, 2], | ||
[1, 3, 2] | ||
].each_with_index { |f, i| | ||
face = square_mesh.geometry.faces[i] | ||
a = f[0] | ||
b = f[1] | ||
c = f[2] | ||
assert_equal(a, face.a) | ||
assert_equal(b, face.b) | ||
assert_equal(c, face.c) | ||
assert_equal(Mittsu::Vector3.new(0, 0, 1), face.normal) | ||
} | ||
end | ||
|
||
def test_parse_with_error | ||
loader = Mittsu::STLLoader.new | ||
|
||
assert_raises('Mittsu::STLLoader: Unhandled line 3') { loader.parse """solid | ||
facet normal 0 0 1 | ||
broken | ||
outer loop | ||
vertex -1 1 -1 | ||
vertex -1 -1 -1 | ||
vertex 1 1 -1 | ||
endloop | ||
endfacet | ||
endsolid | ||
""" } | ||
end | ||
|
||
|
||
def test_parse_multiple_solids | ||
loader = Mittsu::STLLoader.new | ||
|
||
object = loader.parse """solid | ||
facet normal 0 0 1 | ||
outer loop | ||
vertex -1 100.0e-2 0 | ||
vertex -1 -1e0 0 | ||
vertex 1 100.0e-2 0 | ||
endloop | ||
endfacet | ||
endsolid | ||
solid | ||
facet normal 0 0 1 | ||
outer loop | ||
vertex -1 -1e0 0 | ||
vertex 1 -1e0 0 | ||
vertex 1 100.0e-2 0 | ||
endloop | ||
endfacet | ||
endsolid | ||
""" | ||
|
||
assert_kind_of Mittsu::Group, object | ||
assert_equal 2, object.children.count | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
require 'minitest_helper' | ||
|
||
class TestSTLBinaryLoader < Minitest::Test | ||
def test_parse_binary | ||
loader = Mittsu::STLLoader.new | ||
|
||
object = loader.load( | ||
File.expand_path('../../support/samples/square.binary.stl', __FILE__) | ||
) | ||
|
||
assert_kind_of Mittsu::Group, object | ||
assert_equal 1, object.children.count | ||
|
||
square_mesh = object.children.first | ||
assert_kind_of Mittsu::Mesh, square_mesh | ||
assert_kind_of Mittsu::Geometry, square_mesh.geometry | ||
[ | ||
Mittsu::Vector3.new(0.0, 2.0, 0.0), | ||
Mittsu::Vector3.new(0.0, 0.0, 0.0), | ||
Mittsu::Vector3.new(2.0, 2.0, 0.0), | ||
Mittsu::Vector3.new(2.2037353515625, 0.0, 0.0) # 2.2 in order to test EOL conversion - hex includes 0D0A | ||
].each_with_index { |v, i| | ||
assert_equal v, square_mesh.geometry.vertices[i] | ||
} | ||
[ | ||
[0, 1, 2], | ||
[1, 3, 2] | ||
].each_with_index { |f, i| | ||
face = square_mesh.geometry.faces[i] | ||
a = f[0] | ||
b = f[1] | ||
c = f[2] | ||
assert_equal(a, face.a) | ||
assert_equal(b, face.b) | ||
assert_equal(c, face.c) | ||
assert_equal(Mittsu::Vector3.new(0, 0, 1), face.normal) | ||
} | ||
end | ||
|
||
end |
Binary file not shown.