md2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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