summaryrefslogtreecommitdiffhomepage
path: root/samples/99_genre_3d/02_wireframe/app/main.rb
diff options
context:
space:
mode:
Diffstat (limited to 'samples/99_genre_3d/02_wireframe/app/main.rb')
-rw-r--r--samples/99_genre_3d/02_wireframe/app/main.rb150
1 files changed, 150 insertions, 0 deletions
diff --git a/samples/99_genre_3d/02_wireframe/app/main.rb b/samples/99_genre_3d/02_wireframe/app/main.rb
new file mode 100644
index 0000000..8407d47
--- /dev/null
+++ b/samples/99_genre_3d/02_wireframe/app/main.rb
@@ -0,0 +1,150 @@
+def tick args
+ args.state.model ||= Object3D.new('data/shuttle.off')
+ args.state.mtx ||= rotate3D(0, 0, 0)
+ args.state.inv_mtx ||= rotate3D(0, 0, 0)
+ delta_mtx = rotate3D(args.inputs.up_down * 0.01, input_roll(args) * 0.01, args.inputs.left_right * 0.01)
+ args.outputs.lines << args.state.model.edges
+ args.state.model.fast_3x3_transform! args.state.inv_mtx
+ args.state.inv_mtx = mtx_mul(delta_mtx.transpose, args.state.inv_mtx)
+ args.state.mtx = mtx_mul(args.state.mtx, delta_mtx)
+ args.state.model.fast_3x3_transform! args.state.mtx
+ args.outputs.background_color = [0, 0, 0]
+ args.outputs.debug << args.gtk.framerate_diagnostics_primitives
+end
+
+def input_roll args
+ roll = 0
+ roll += 1 if args.inputs.keyboard.e
+ roll -= 1 if args.inputs.keyboard.q
+ roll
+end
+
+def rotate3D(theta_x = 0.1, theta_y = 0.1, theta_z = 0.1)
+ c_x, s_x = Math.cos(theta_x), Math.sin(theta_x)
+ c_y, s_y = Math.cos(theta_y), Math.sin(theta_y)
+ c_z, s_z = Math.cos(theta_z), Math.sin(theta_z)
+ rot_x = [[1, 0, 0], [0, c_x, -s_x], [0, s_x, c_x]]
+ rot_y = [[c_y, 0, s_y], [0, 1, 0], [-s_y, 0, c_y]]
+ rot_z = [[c_z, -s_z, 0], [s_z, c_z, 0], [0, 0, 1]]
+ mtx_mul(mtx_mul(rot_x, rot_y), rot_z)
+end
+
+def mtx_mul(a, b)
+ is = (0...a.length)
+ js = (0...b[0].length)
+ ks = (0...b.length)
+ is.map do |i|
+ js.map do |j|
+ ks.map do |k|
+ a[i][k] * b[k][j]
+ end.reduce(&:plus)
+ end
+ end
+end
+
+class Object3D
+ attr_reader :vert_count, :face_count, :edge_count, :verts, :faces, :edges
+
+ def initialize(path)
+ @vert_count = 0
+ @face_count = 0
+ @edge_count = 0
+ @verts = []
+ @faces = []
+ @edges = []
+ _init_from_file path
+ end
+
+ def _init_from_file path
+ file_lines = $gtk.read_file(path).split("\n")
+ .reject { |line| line.start_with?('#') || line.split(' ').length == 0 } # Strip out simple comments and blank lines
+ .map { |line| line.split('#')[0] } # Strip out end of line comments
+ .map { |line| line.split(' ') } # Tokenize by splitting on whitespace
+ raise "OFF file did not start with OFF." if file_lines.shift != ["OFF"] # OFF meshes are supposed to begin with "OFF" as the first line.
+ raise "<NVertices NFaces NEdges> line malformed" if file_lines[0].length != 3 # The second line needs to have 3 numbers. Raise an error if it doesn't.
+ @vert_count, @face_count, @edge_count = file_lines.shift&.map(&:to_i) # Update the counts
+ # Only the vertex and face counts need to be accurate. Raise an error if they are inaccurate.
+ raise "Incorrect number of vertices and/or faces (Parsed VFE header: #{@vert_count} #{@face_count} #{@edge_count})" if file_lines.length != @vert_count + @face_count
+ # Grab all the lines describing vertices.
+ vert_lines = file_lines[0, @vert_count]
+ # Grab all the lines describing faces.
+ face_lines = file_lines[@vert_count, @face_count]
+ # Create all the vertices
+ @verts = vert_lines.map_with_index { |line, id| Vertex.new(line, id) }
+ # Create all the faces
+ @faces = face_lines.map { |line| Face.new(line, @verts) }
+ # Create all the edges
+ @edges = @faces.flat_map(&:edges).uniq do |edge|
+ sorted = edge.sorted
+ [sorted.point_a, sorted.point_b]
+ end
+ end
+
+ def fast_3x3_transform! mtx
+ @verts.each { |vert| vert.fast_3x3_transform! mtx }
+ end
+end
+
+class Face
+
+ attr_reader :verts, :edges
+
+ def initialize(data, verts)
+ vert_count = data[0].to_i
+ vert_ids = data[1, vert_count].map(&:to_i)
+ @verts = vert_ids.map { |i| verts[i] }
+ @edges = []
+ (0...vert_count).each { |i| @edges[i] = Edge.new(verts[vert_ids[i - 1]], verts[vert_ids[i]]) }
+ @edges.rotate! 1
+ end
+end
+
+class Edge
+ attr_reader :point_a, :point_b
+
+ def initialize(point_a, point_b)
+ @point_a = point_a
+ @point_b = point_b
+ end
+
+ def sorted
+ @point_a.id < @point_b.id ? self : Edge.new(@point_b, @point_a)
+ end
+
+ def draw_override ffi
+ ffi.draw_line(@point_a.render_x, @point_a.render_y, @point_b.render_x, @point_b.render_y, 255, 0, 0, 128)
+ ffi.draw_line(@point_a.render_x+1, @point_a.render_y, @point_b.render_x+1, @point_b.render_y, 255, 0, 0, 128)
+ ffi.draw_line(@point_a.render_x, @point_a.render_y+1, @point_b.render_x, @point_b.render_y+1, 255, 0, 0, 128)
+ ffi.draw_line(@point_a.render_x+1, @point_a.render_y+1, @point_b.render_x+1, @point_b.render_y+1, 255, 0, 0, 128)
+ end
+
+ def primitive_marker
+ :line
+ end
+end
+
+class Vertex
+ attr_accessor :x, :y, :z, :id
+
+ def initialize(data, id)
+ @x = data[0].to_f
+ @y = data[1].to_f
+ @z = data[2].to_f
+ @id = id
+ end
+
+ def fast_3x3_transform! mtx
+ _x, _y, _z = @x, @y, @z
+ @x = mtx[0][0] * _x + mtx[0][1] * _y + mtx[0][2] * _z
+ @y = mtx[1][0] * _x + mtx[1][1] * _y + mtx[1][2] * _z
+ @z = mtx[2][0] * _x + mtx[2][1] * _y + mtx[2][2] * _z
+ end
+
+ def render_x
+ @x * (10 / (5 - @y)) * 170 + 640
+ end
+
+ def render_y
+ @z * (10 / (5 - @y)) * 170 + 360
+ end
+end \ No newline at end of file