md2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. data/.document +5 -0
  2. data/.gitignore +26 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +174 -0
  5. data/Rakefile +51 -0
  6. data/VERSION +1 -0
  7. data/bin/md2_to_json +33 -0
  8. data/ext/md2/extconf.rb +3 -0
  9. data/ext/md2/md2.c +95 -0
  10. data/lib/md2.rb +97 -0
  11. data/lib/md2/command.rb +63 -0
  12. data/lib/md2/errors.rb +13 -0
  13. data/lib/md2/frame.rb +61 -0
  14. data/lib/md2/header.rb +37 -0
  15. data/lib/md2/normals.rb +81 -0
  16. data/lib/md2/triangle.rb +9 -0
  17. data/lib/md2/vertex.rb +33 -0
  18. data/md2.gemspec +117 -0
  19. data/spec/lib/md2/frame_spec.rb +13 -0
  20. data/spec/lib/md2/header_spec.rb +21 -0
  21. data/spec/lib/md2_spec.rb +66 -0
  22. data/spec/spec_helper.rb +40 -0
  23. data/spec/support/crafty/Crafty.BMP +0 -0
  24. data/spec/support/crafty/Crafty.pcx +0 -0
  25. data/spec/support/crafty/Crafty.txt +113 -0
  26. data/spec/support/crafty/Weapon.md2 +0 -0
  27. data/spec/support/crafty/Weapon.pcx +0 -0
  28. data/spec/support/crafty/crafty.md2 +0 -0
  29. data/spec/support/laalaa/laalaa.md2 +0 -0
  30. data/spec/support/laalaa/laalaa24.BMP +0 -0
  31. data/spec/support/laalaa/laalaa8.BMP +0 -0
  32. data/spec/support/ogro/Ogro.txt +132 -0
  33. data/spec/support/ogro/Ogrobase.pcx +0 -0
  34. data/spec/support/ogro/Weapon.md2 +0 -0
  35. data/spec/support/ogro/Weapon.pcx +0 -0
  36. data/spec/support/ogro/ogro.md2 +0 -0
  37. data/spec/support/ogro/ogro.png +0 -0
  38. data/spec/support/pilot/CC_attribution_licence.txt +68 -0
  39. data/spec/support/pilot/GNU_licence.txt +340 -0
  40. data/spec/support/pilot/Readme.txt +30 -0
  41. data/spec/support/pilot/body.md2 +0 -0
  42. data/spec/support/pilot/body.obj +9331 -0
  43. data/spec/support/pilot/head.md2 +0 -0
  44. data/spec/support/pilot/head.obj +6145 -0
  45. data/spec/support/pilot/pilot.md2 +0 -0
  46. data/spec/support/pilot/pilot_body.3ds +0 -0
  47. data/spec/support/pilot/pilot_body.jpg +0 -0
  48. data/spec/support/pilot/pilot_body.obj +2264 -0
  49. data/spec/support/pilot/pilot_head.3ds +0 -0
  50. data/spec/support/pilot/pilot_head.jpg +0 -0
  51. data/spec/support/pilot/pilot_head.obj +1970 -0
  52. data/spec/support/pknight/pknight.BMP +0 -0
  53. data/spec/support/pknight/pknight.PCX +0 -0
  54. data/spec/support/pknight/pknight.md2 +0 -0
  55. data/spec/support/pknight/pknight_weapon.PCX +0 -0
  56. data/spec/support/pknight/pknight_weapon.bmp +0 -0
  57. data/spec/support/pknight/pknight_weapon.md2 +0 -0
  58. data/spec/support/sodf8/Abarlith.pcx +0 -0
  59. data/spec/support/sodf8/SFOD8.txt +108 -0
  60. data/spec/support/sodf8/Weapon.PCX +0 -0
  61. data/spec/support/sodf8/Weapon.md2 +0 -0
  62. data/spec/support/sodf8/sodf8.md2 +0 -0
  63. metadata +167 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,26 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .idea
23
+ *.bundle
24
+ *.o
25
+ *.out
26
+ Makefile
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Colin MacKenzie IV
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ = md2
2
+
3
+ A Ruby library for loading MD2 (Quake II) 3D model files. It doesn't actually render them; that part is up to you.
4
+
5
+ == Installation
6
+
7
+ gem install md2
8
+
9
+ == Usage
10
+
11
+ require 'md2'
12
+
13
+ md2 = MD2.new("/path/to/md2")
14
+ md2.frames.each_with_index do |frame, index|
15
+ puts frame.name
16
+ puts frame.vertices.length
17
+ puts frame.normal_indices
18
+ end
19
+ puts md2.triangles.length
20
+ puts md2.texcoords.length
21
+ # and so on.
22
+
23
+ See the MD2 class documentation for more, or scroll down for an example of how to render the model.
24
+
25
+ == Converting to JSON
26
+
27
+ The MD2 library works great with the 'json' gem. Just load an MD2 and then call #to_json:
28
+
29
+ require 'md2'
30
+ require 'json'
31
+
32
+ md2 = MD2.new("/path/to/md2")
33
+ json = md2.to_json
34
+
35
+ I think this will be pretty cool as WebGL and friends begin to take off. I've also added a generator for this. You can
36
+ convert an MD2 file into its JSON counterpart at the command line with:
37
+
38
+ $ md2_to_json /path/to/md2 >/path/to/json
39
+
40
+ The generator just prints the JSON to stdout (which made it easier for me to test), so you have to redirect the output
41
+ into a file if you want to capture it.
42
+
43
+ The ">" in the command above is used to redirect stdout. Check Wikipedia for
44
+ Redirection (computing) for more details if you've never seen this before:
45
+ http://en.wikipedia.org/wiki/Redirection_(computing)
46
+
47
+ === About the JSON output
48
+
49
+ First, here's the general structure of a JSON string created by this library:
50
+
51
+ {
52
+ "header": {
53
+ "skin_width":256, "skin_height":256, "skin_count":0, "frame_count":199,
54
+ (other header data that was previously used for loading the model)
55
+ },
56
+ "frames": {[
57
+ {"name" : "Frame1",
58
+ "translation" :[ (XYZ float array) ],
59
+ "scale" :[ (XYZ float array) ],
60
+ "vertices" :[ (array of vertex data: these are INTEGERS! - see below) ],
61
+ "normal_indices":[array of normal indices]
62
+ },
63
+ ...
64
+ ]},
65
+ "triangles":[
66
+ {"vertex_indices" :[ (XYZ int array) ],
67
+ "texcoord_indices":[ (ST int array) ]},
68
+ ...
69
+ ],
70
+ "texcoords":[
71
+ [ (ST float array) ],
72
+ [ (ST float array) ],
73
+ [ (ST float array) ],
74
+ ...
75
+ ],
76
+ "skins": [ (string array of filenames) ],
77
+ "gl_commands": [
78
+ { "texture_s": float,
79
+ "texture_t": float,
80
+ "vertex_index": int },
81
+ ...
82
+ ]
83
+ }
84
+
85
+ Now that you can see the general flow of the JSON, here's a bit more info:
86
+
87
+ The header info is generally not useful to you and consists mostly of information that was read from the MD2 file when
88
+ it was initially loaded.
89
+
90
+ Frames are one potential gotcha: the vertex information stored in the frame is an integer between 0 and 255. This
91
+ integer is useless on its own; you need to expand the vertex data from it by first multiplying by scale, and then
92
+ adding translation. The vertex count is divisible by 3 because -- you guessed it -- every first vertex is X, every
93
+ second is Y and every third is Z. The reason for all of this? Because when I tried to precalculate it all for you,
94
+ the JSON result was 4 times bigger! A 250KB MD2 file resulted in over 4MB of JSON. By letting you do this on your
95
+ end, I'm saving you a ton of bandwidth. If you go another step and gzip compress the current result, it actually ends
96
+ up smaller than the original MD2 file!
97
+
98
+ * Note that the vertex data is already unpacked for you if you're using just the Ruby library; the vertex information
99
+ is only packaged this way in JSON, to reduce bandwidth requirements.
100
+
101
+ So here's a code snippet of how to extract the vertex information in JavaScript. Copy and paste to your heart's content!
102
+ /* assume we have a JSON object called 'model' */
103
+ for (var frame_index = 0; frame_index < model.frames.length; frame_index++)
104
+ {
105
+ var vertices = [];
106
+ var frame = model.frames[frame_index];
107
+ for (var vert_index = 0; vert_index < frame.vertices.length; vert_index += 3)
108
+ for (var k = 0; k < 3; k++)
109
+ vertices[vert_index+k] = (frame.vertices[vert_index+k] * frame.scale[k]) + frame.translation[k];
110
+ /* we have the unpacked vertex data; may as well just replace the now-useless packed data */
111
+ frame.vertices = vertices;
112
+ }
113
+
114
+ Nothing else in the JSON output is packed in this way. The texture coordinate indices point to the "texcoords"
115
+ array, and the normal indices point to the precomputed MD2 normals. If you need to implement those in JavaScript,
116
+ take a look at the MD2::Normals constant for the data.
117
+
118
+ Triangles are made up of 3 vertices (each with an X, Y, Z coordinate) and 3 texture coordinates (each with an S, T
119
+ coordinate); therefore, they consist of 3 vertex indices (which reference the frame vertices you just unpacked) and
120
+ 3 texture coordinate indices.
121
+
122
+ The texcoords array consists of a number of nested arrays. Each nested array has 2 elements, the S and T value for
123
+ that texture coordinate. The nesting is designed to make it easier for you to access via coordinate indices (see
124
+ Triangles, above).
125
+
126
+ Skins are simply a list of filenames (Strings) referenced within the MD2. Do what you will with them.
127
+
128
+ The final component, gl_commands, is discussed below. The code is in Ruby, but the approach is the same.
129
+
130
+ == Note on Rendering
131
+
132
+ The preferred way to render an MD2 file is to make use of its GL Commands, which produce an optimized approach to
133
+ rendering by switching between triangle strips and triangle fans (as opposed to just rendering the entire model with
134
+ only triangles). This library parses the command information into an intuitive structure so you don't have to deal
135
+ with the nuances of the format. Instead, here's how you would go about rendering an MD2 using its GL commands:
136
+
137
+ frame = md2.frames[current_frame]
138
+
139
+ md2.gl_commands.each do |command|
140
+ case command.type
141
+ when :triangle_strip then glBegin(GL_TRIANGLE_STRIP)
142
+ when :triangle_fan then glBegin(GL_TRIANGLE_FAN)
143
+ end
144
+
145
+ command.segments.each do |segment|
146
+ index = segment.vertex_index
147
+
148
+ glTexCoord2f(segment.texture_s, segment.texture_t)
149
+ glNormal3f(frame.normals[index].x, frame.normals[index].y, frame.normals[index].z)
150
+ glVertex3f(frame.vertices[index].x, frame.vertices[index].y, frame.vertices[index].z)
151
+ end
152
+
153
+ glEnd
154
+ end
155
+
156
+ == Limitations
157
+
158
+ Modifying and saving the MD2 file is not currently supported, although this may be implemented sometime in the future.
159
+
160
+ == Note on Patches/Pull Requests
161
+
162
+ * Fork the project.
163
+ * Make your feature addition or bug fix.
164
+ * Add tests for it. This is important so I don't break it in a
165
+ future version unintentionally.
166
+ * Commit, do not mess with rakefile, version, or history.
167
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
168
+ * Send me a pull request. Bonus points for topic branches.
169
+
170
+ == Copyright
171
+
172
+ Copyright (c) 2010 Colin MacKenzie IV. See LICENSE for details.
173
+
174
+ http://thoughtsincomputation.com
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "md2"
8
+ gem.summary = %Q{A Ruby library for loading MD2 3D model files.}
9
+ gem.description = %Q{A Ruby library for loading MD2 3D model files.}
10
+ gem.email = "sinisterchipmunk@gmail.com"
11
+ gem.homepage = "http://thoughtsincomputation.com"
12
+ gem.authors = ["Colin MacKenzie IV"]
13
+ gem.add_dependency "sizes", ">= 1.0"
14
+ gem.add_dependency "activesupport", ">= 2.3.5"
15
+ gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ begin
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :spec => :check_dependencies
37
+
38
+ task :default => :spec
39
+ rescue LoadError
40
+ puts "It seems you don't have rspec 1.x. You won't be able to run the test suite."
41
+ end
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "md2 #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ def banner
4
+ puts "Usage:"
5
+ puts "\truby md2_to_json.rb path/to/input.md2"
6
+ puts
7
+ end
8
+
9
+ unless ARGV.length == 1
10
+ banner
11
+ puts "Output is printed to stdout. To capture json output, redirect"
12
+ puts "to file:"
13
+ puts
14
+ puts "\truby md2_to_json.rb path/to/input.md2 >path/to/output.md2"
15
+ puts
16
+ exit
17
+ end
18
+
19
+ if File.file?(filename = ARGV.first)
20
+ require 'rubygems'
21
+ gem 'json'
22
+
23
+ require File.join(File.dirname(__FILE__), '../lib/md2')
24
+ require 'json'
25
+ md2 = MD2.new(filename)
26
+ puts md2.to_json
27
+ else
28
+ puts
29
+ puts "!! File '#{filename}' does not exist !!"
30
+ puts
31
+ banner
32
+ exit
33
+ end
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile 'md2'
@@ -0,0 +1,95 @@
1
+ #include "ruby.h"
2
+
3
+ static VALUE read_gl_commands_ext(VALUE self, VALUE gl_command_count, VALUE gl_command_offset, VALUE gl_command_data);
4
+ static VALUE construct_command(VALUE args);
5
+ static VALUE cMD2 = Qnil;
6
+
7
+ void Init_md2()
8
+ {
9
+ cMD2 = rb_define_class("MD2", rb_cObject);
10
+ rb_define_method(cMD2, "read_gl_commands_ext", read_gl_commands_ext, 3);
11
+ }
12
+
13
+ /*
14
+ Accepts a command count, command offset and filename. The file is opened and the data is read;
15
+ an argument error is raised if the file could not be opened or if the exact command count could
16
+ not be read from the file. Otherwise, the data is deserialized according to the MD2::Command
17
+ documentation and then it is returned as an array of MD2::Command instances.
18
+
19
+ See the documentation for MD2::Command for the exact algoritm; I couldn't figure out how to
20
+ implement it using pure Ruby.
21
+ */
22
+ static VALUE read_gl_commands_ext(VALUE self, VALUE gl_command_count, VALUE gl_command_offset, VALUE file_path)
23
+ {
24
+ char *cpath = StringValueCStr(file_path);
25
+ FILE *inf = fopen(cpath, "r");
26
+ if (!inf) rb_raise(rb_eArgError, "Could not open file %s to read GL command data!", cpath);
27
+
28
+ unsigned int command_count = FIX2INT(gl_command_count);
29
+ int command_offset = FIX2INT(gl_command_offset);
30
+ int *cdata = (int *)malloc(sizeof(int) * command_count);
31
+
32
+ fseek(inf, command_offset, SEEK_SET);
33
+ if (fread(cdata, sizeof(int), command_count, inf) != command_count)
34
+ {
35
+ fclose(inf);
36
+ free(cdata);
37
+ rb_raise(rb_eArgError, "Could not read %d GL commands from file", command_count);
38
+ }
39
+ fclose(inf);
40
+
41
+ VALUE ary = rb_ary_new();
42
+ int i = 0, error;
43
+ int val;
44
+ while ((val = cdata[i++]) != 0)
45
+ {
46
+ int count;
47
+ if (val > 0) count = val;
48
+ else count = -val;
49
+
50
+ VALUE sym;
51
+ const char *symcstr;
52
+ if (val > 0) sym = ID2SYM(rb_intern("triangle_strip"));
53
+ else sym = ID2SYM(rb_intern("triangle_fan"));
54
+
55
+ // construct the packet which will contain the args to be sent to MD2::Command
56
+ VALUE packet = rb_ary_new();
57
+ // add the render type to the packet
58
+ rb_ary_push(packet, sym);
59
+
60
+ while (count--)
61
+ {
62
+ float s = *(float *)&cdata[i++];
63
+ float t = *(float *)&cdata[i++];
64
+ int index = cdata[i++];
65
+
66
+ // add s, t, index to the packet
67
+ rb_ary_push(packet, rb_float_new(s));
68
+ rb_ary_push(packet, rb_float_new(t));
69
+ rb_ary_push(packet, INT2FIX(index));
70
+ }
71
+
72
+ // construct the MD2::Command from the packet data. If error,
73
+ // intercept it and then break so that we can free cdata properly.
74
+ VALUE command = rb_protect(construct_command, packet, &error);
75
+ if (error) break;
76
+
77
+ // store the command in the resultant array
78
+ rb_ary_push(ary, command);
79
+ }
80
+
81
+ free(cdata);
82
+
83
+ // now that cdata is taken care of, re-raise the error if there was one
84
+ if (error)
85
+ rb_exc_raise(rb_gv_get("$!"));
86
+
87
+ return ary;
88
+ }
89
+
90
+ static VALUE construct_command(VALUE args)
91
+ {
92
+ VALUE cMD2Command = rb_const_get(cMD2, rb_intern("Command"));
93
+ VALUE command = rb_funcall2(cMD2Command, rb_intern("new"), 1, &args);
94
+ return command;
95
+ }
@@ -0,0 +1,97 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require File.join(File.dirname(__FILE__), "../ext/md2/md2")
3
+ require 'sizes'
4
+ require 'active_support/core_ext'
5
+
6
+ class MD2
7
+ include Sizes
8
+
9
+ autoload :Header, "md2/header"
10
+ autoload :Errors, "md2/errors"
11
+ autoload :Frame, "md2/frame"
12
+ autoload :Vertex, "md2/vertex"
13
+ autoload :Triangle, "md2/triangle"
14
+ autoload :Command, "md2/command"
15
+ autoload :Normals, "md2/normals"
16
+
17
+ attr_reader :header
18
+ attr_reader :base_path, :frames, :triangles, :skins, :texcoords, :gl_commands
19
+
20
+ delegate :skin_width, :skin_height, :frame_size, :skin_count, :vertex_count,
21
+ :texture_coord_count, :triangle_count, :gl_command_count, :frame_count,
22
+ :skin_name_offset, :texture_coord_offset, :triangle_offset, :frame_data_offset,
23
+ :gl_command_offset, :eof_offset, :to => :header
24
+
25
+ def initialize(path)
26
+ load(path)
27
+ end
28
+
29
+ def to_json
30
+ raise "Can't convert to json unless you've already activated the 'json' gem!" if !defined?(JSON)
31
+
32
+ {
33
+ :header => @header,
34
+ :frames => @frames.collect { |f| f.reduce },
35
+ :triangles => @triangles,
36
+ :texcoords => @texcoords,
37
+ :skins => @skins,
38
+ :gl_commands => @gl_commands,
39
+ :base_path => @base_path
40
+ }.to_json
41
+ end
42
+
43
+ private
44
+ def load(path)
45
+ @base_path = File.dirname(path)
46
+ File.open(path, "rb") do |file|
47
+ read_header(file)
48
+ read_frames(file)
49
+ read_triangles(file)
50
+ read_skins(file)
51
+ read_texcoords(file)
52
+ end
53
+ @gl_commands = read_gl_commands_ext(gl_command_count, gl_command_offset, path)
54
+ end
55
+
56
+ def read_header(file)
57
+ @header = MD2::Header.new(file)
58
+ end
59
+
60
+ def read_frames(file)
61
+ @frames = []
62
+ read_data(file, frame_data_offset, frame_count, frame_size) do |chunk|
63
+ @frames << MD2::Frame.new(chunk)
64
+ end
65
+ end
66
+
67
+ def read_triangles(file)
68
+ @triangles = []
69
+ read_data(file, triangle_offset, triangle_count, 6 * sizeof(:short)) do |chunk|
70
+ @triangles << MD2::Triangle.new(chunk)
71
+ end
72
+ end
73
+
74
+ def read_skins(file)
75
+ @skins = []
76
+ read_data(file, skin_name_offset, skin_count, sizeof(:char)*64) do |chunk|
77
+ @skins << chunk.strip
78
+ end
79
+ end
80
+
81
+ def read_texcoords(file)
82
+ @texcoords = []
83
+ read_data(file, texture_coord_offset, texture_coord_count, sizeof(:short)*2) do |chunk|
84
+ @texcoords << chunk.unpack("s2")
85
+ @texcoords.last[0] = @texcoords.last[0] / skin_width.to_f
86
+ @texcoords.last[1] = @texcoords.last[1] / skin_height.to_f
87
+ end
88
+ end
89
+
90
+ def read_data(file, offset, count, chunk_size)
91
+ file.sysseek(offset)
92
+ data = file.sysread(count * chunk_size)
93
+ count.times do |num|
94
+ yield data[(num*chunk_size)...(num*chunk_size+chunk_size)]
95
+ end
96
+ end
97
+ end