lightwave 0.0.3

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.
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2006 Alex Young (alex@shaxam.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,46 @@
1
+ = Read Me
2
+
3
+ by Alex Young
4
+
5
+ == Description
6
+
7
+ A simple Lightwave object file reader.
8
+
9
+ This library provides functions for reading Lightwave object files (.lwo), and
10
+ converting them to either a Yaml (http://www.yaml.org) or Xaml (http://msdn.microsoft.com/windowsvista/about/) representation.
11
+
12
+ Specifically, it generates a Xaml ResourceDictionary file with the following
13
+ contents:
14
+
15
+ - A Model3DGroup for each Lightwave object layer. Child layers are
16
+ included as StaticResources which point to Model3DGroups.
17
+ - A GeometryModel3D tag within each Model3DGroup for each surface. The
18
+ Geometry attribute is a StaticResource that points to a MeshGeometry3D,
19
+ and the Material attribute is a StaticResource that points to a
20
+ MaterialGroup.[1]
21
+ - A Model3DGroup.Transform tag to shift the contained geometry to the
22
+ pivot point of the Lightwave layer. This allows child hierarchies to be
23
+ transported from Lightwave through to Xaml.
24
+
25
+ Currently only colour textures are supported, and it's an either/or situation:
26
+ either you have a single image texture, or a single colour texture. This is
27
+ planned to change imminently.
28
+
29
+ [1] Lightwave supports surfaces per polygon. Xaml supports materialgroups
30
+ per GeometryModel3D. Splitting the Lightwave geometry along surface
31
+ boundaries is the simplest way to emulate the former with the latter.
32
+
33
+ == Documentation
34
+
35
+ == Examples
36
+
37
+ Simple:
38
+
39
+ $ ruby lw2xaml.rb MyLightwaveObject > MyLightwaveObject.xaml
40
+
41
+ See the test/ directory of this project for more examples of code using the
42
+ internals.
43
+
44
+ == Questions and/or Comments
45
+
46
+ Feel free to email {Alex Young}[mailto:alex@shaxam.com] with any questions.
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require "rake/rdoctask"
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ task :default => [:test]
6
+
7
+ Rake::TestTask.new do |test|
8
+ test.libs << 'test'
9
+ test.test_files = ['test/test_lightwave.rb']
10
+ test.verbose = true
11
+ end
12
+
13
+ Rake::RDocTask.new do |rdoc|
14
+ rdoc.rdoc_files.include( "README",
15
+ "TODO", "LICENSE", "lib/" )
16
+ rdoc.main = "README"
17
+ rdoc.rdoc_dir = "doc/html"
18
+ rdoc.title = "Lightwave Documentation"
19
+ end
20
+
21
+ spec = Gem::Specification.new do |spec|
22
+ spec.name = "lightwave"
23
+ spec.version = "0.0.3"
24
+ spec.platform = Gem::Platform::RUBY
25
+ spec.summary = "Lightwave is a package to read and manipulate Lightwave object files."
26
+ spec.files = ["{lib,test}/**/*.rb", "test/**/*.lwo", "test/**/*.yaml"].collect{|s| Dir.glob(s)}.flatten + ["Rakefile"]
27
+
28
+ spec.test_suite_file = "test/test_lightwave.rb"
29
+ spec.has_rdoc = true
30
+ spec.extra_rdoc_files = %w{README TODO LICENSE}
31
+ spec.rdoc_options << '--title' << 'Lightwave Documentation' <<
32
+ '--main' << 'README'
33
+
34
+ spec.add_dependency("activesupport")
35
+
36
+ spec.require_path = 'lib'
37
+ spec.autorequire = "lightwave"
38
+ spec.author = "Alex Young"
39
+ spec.email = "alex@shaxam.com"
40
+ spec.homepage = "http://www.shaxam.com"
41
+ spec.description = <<END_DESC
42
+ A set of libraries that handle reading Lightwave object files and (for the moment)
43
+ turning them into a set of Xaml objects.
44
+ END_DESC
45
+ end
46
+
47
+ Rake::GemPackageTask.new(spec) do |pkg|
48
+ pkg.need_zip = true
49
+ pkg.need_tar = true
50
+ end
51
+
data/TODO ADDED
@@ -0,0 +1,15 @@
1
+ The test coverage isn't as good as I'd like it, so anybody who thinks they've
2
+ found a bug should send me a failing model with as complete a description of
3
+ what *should* be happening as possible.
4
+
5
+ The tests need to be factored out more.
6
+
7
+ The code structure stinks at the moment, and needs to be simplified. Don't
8
+ assume that the module structure that you see will last for very long.
9
+
10
+ SPEEDUPS: There is currently a big bottleneck in the polygon reader, according
11
+ to Ruby's profiler. I'll look at converting various parts of the reader to C
12
+ to speed things up. There are also several places where the algorithms I'm
13
+ using are almost certainly not the fastest, but without more runtime data (and
14
+ more accurate profiling information than the built-in profiler gives me) it's
15
+ not been worthwhile hammering them out.
@@ -0,0 +1,8 @@
1
+ %w{matrices lightwave}.each do |f|
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), f)
3
+ end
4
+ require 'yaml'
5
+
6
+ include Lightwave
7
+
8
+ puts YAML::dump(LightwaveObject::read(File.read(ARGV[0]), ARGV[0]))
data/lib/lightwave.rb ADDED
@@ -0,0 +1,534 @@
1
+ require 'stringio'
2
+ require 'delegate'
3
+ require 'rubygems'
4
+ require 'active_support'
5
+
6
+ DEBUG = false
7
+
8
+ module Lightwave
9
+ class LightwaveStringIO < StringIO
10
+ def read_vector
11
+ read(12).unpack('g3')
12
+ end
13
+ def read_type
14
+ read(4)
15
+ end
16
+ def read_vx
17
+ # Reads a variable-length index.
18
+ marker_str = read(2, " ")
19
+ #if !marker_str then raise "Null marker." end # These tests *seriously* slow things down
20
+ if marker_str[0] != 255
21
+ return marker_str.unpack('n')[0]
22
+ else
23
+ #if !low_byte then raise "Null low byte." end
24
+ return ("\0#{marker_str[1]}" + read(2, " ")).unpack('N')[0]
25
+ end
26
+ end
27
+ def read_poly_set
28
+ spec = self.read(2).unpack('n')[0]
29
+ vert_count = spec & 0x03FF
30
+ flags = spec & 0xFC00
31
+ return Array.new(vert_count){self.read_vx}
32
+ end
33
+ def read_string
34
+ result = readline("\0")
35
+ if result.length % 2 == 1
36
+ read(1)
37
+ end
38
+ return result.strip
39
+ end
40
+ def read_string_list
41
+ # used in the TAGS block
42
+ result = []
43
+ while !self.eof?
44
+ result << read_string
45
+ end
46
+ return result
47
+ end
48
+ def read_float
49
+ read(4).unpack('g')[0]
50
+ end
51
+ def read_u2
52
+ read(2).unpack('n')[0]
53
+ end
54
+ def read_u4
55
+ read(4).unpack('N')[0]
56
+ end
57
+ def read_chunk(size=4)
58
+ label = read_type
59
+ size_in = read(size)
60
+ if size_in.length != size
61
+ raise 'String was shorter than expected.'
62
+ end
63
+ size_str = (["\0"]*(4-size)).join + size_in
64
+ chunk_size = size_str.unpack('N')[0]
65
+ data = read(chunk_size)
66
+ if data.length % 2 == 1
67
+ read(1)
68
+ end
69
+ if data.length != chunk_size
70
+ raise 'Chunk was shorter than expected. Size string was ' + size_in.inspect + ', expected ' + chunk_size.to_s + ', read ' + data.length.to_s
71
+ end
72
+ return [label, LightwaveStringIO.new(data), size_str]
73
+ end
74
+ def read_colour
75
+ result = [read_float, read_float, read_float]
76
+ end
77
+ end
78
+
79
+ class VCR
80
+ def initialize
81
+ @messages = []
82
+ end
83
+ def method_missing(method, *args, &block)
84
+ @messages << [method, args, block]
85
+ end
86
+ def play_back_to(obj)
87
+ @messages.each do |method, args, block|
88
+ obj.send(method, *args, &block)
89
+ end
90
+ end
91
+ end
92
+
93
+ module LightwaveObject
94
+ AXES=[:x,:y,:z]
95
+ PROJECTION_MODES = [:planar, :cylindrical, :spherical, :cubic, :front, :uv]
96
+ WRAP_TYPES = [:reset, :repeat, :mirror, :edge]
97
+
98
+
99
+ class ChunkError
100
+ attr_accessor :message
101
+ def initialize(msg)
102
+ @message = msg
103
+ end
104
+ end
105
+ class ChunkReader
106
+ attr_writer :errors
107
+ class_inheritable_hash :matched_chunks
108
+ class_inheritable_accessor :chunk_length
109
+ def self.chunk_map(sym, method=nil, &var)
110
+ if method and method.respond_to? :to_sym
111
+ pr = Proc.new{|s,c| s.send(method.to_sym, c) }
112
+ write_inheritable_hash(:matched_chunks, {sym => pr})
113
+ else
114
+ write_inheritable_hash(:matched_chunks, {sym => var})
115
+ end
116
+ end
117
+ def self.set_chunk_length(len)
118
+ write_inheritable_attribute(:chunk_length,len)
119
+ end
120
+
121
+ def process_tags(strio)
122
+ while !strio.eof?
123
+ label, chunk, length = strio.read_chunk(chunk_length || 2)
124
+ #p self.class.to_s + ' reading ' + label
125
+ #p "Contents: " + chunk.string
126
+ if matched_chunks and matched_chunks.has_key?(label) and matched_chunks[label].respond_to?(:call)
127
+ begin
128
+ matched_chunks[label].call(self, chunk)
129
+ rescue
130
+ error_message = "#{label} Tag reading broke, length was " + length.inspect + "\n" + chunk.string.inspect
131
+ errors << ChunkError.new(error_message)
132
+ end
133
+ else
134
+ error_message = self.class.to_s + ' Unhandled chunk:' + label
135
+ errors << ChunkError.new(error_message)
136
+ end
137
+ end
138
+ end
139
+ def errors
140
+ @errors ||= []
141
+ end
142
+ def make_defaults
143
+ end
144
+ def initialize(strio)
145
+ process_tags(strio)
146
+ make_defaults
147
+ end
148
+ end
149
+
150
+ class SurfaceProperty
151
+ attr_accessor :intensity
152
+ def initialize(strio)
153
+ @intensity = strio.read_float
154
+ end
155
+ end
156
+
157
+ class Diffuse < SurfaceProperty; end
158
+ class Specular < SurfaceProperty; end
159
+ class Luminosity < SurfaceProperty; end
160
+ class Glossiness < SurfaceProperty; end
161
+
162
+ class Colour < SurfaceProperty
163
+ attr_accessor :rgb
164
+ def initialize(strio)
165
+ @rgb = strio.read_vector
166
+ end
167
+ end
168
+
169
+ class Opacity
170
+ attr_accessor :type
171
+ attr_accessor :opacity
172
+ TYPEMAP = [:normal,:subtractive,:difference,:multiply,:divide,:alpha,:texture_displacement,:additive]
173
+ def initialize(strio)
174
+ @type = TYPEMAP[strio.read_u2]
175
+ @opacity = strio.read_float
176
+ end
177
+ end
178
+
179
+ class TextureHeader < ChunkReader
180
+ attr_accessor :channel, :opacity, :enabled, :negative, :displacement_axis
181
+ alias :enabled? :enabled
182
+ chunk_map('CHAN'){|s,c| s.channel = c.read_type}
183
+ chunk_map('OPAC'){|s,c| s.opacity = Opacity.new(c)}
184
+ chunk_map('ENAB'){|s,c| s.enabled = c.read_u2 != 0}
185
+ chunk_map('NEGA'){|s,c| s.negative = c.read_u2 != 0}
186
+ chunk_map('AXIS'){|s,c| s.displacement_axis = AXES[c.read_u2]}
187
+ def make_defaults
188
+ if !@opacity
189
+ @opacity = Opacity.new
190
+ @opacity.type=:additive
191
+ @opacity.opacity = 1.0
192
+ end
193
+ if @enabled.nil? then @enabled = true end
194
+ if @negative.nil? then @negative = false end
195
+ end
196
+ end
197
+
198
+ class TextureFalloff
199
+ FALLOFF_TYPES = [:cubic, :spherical, :linear_x, :linear_y, :linear_z]
200
+ attr_accessor :type, :vector, :envelope
201
+ def initialize(strio)
202
+ @type, @vector = FALLOFF_TYPES[strio.read_u2], strio.read_vector
203
+ end
204
+ end
205
+
206
+ class TextureMapping < ChunkReader
207
+ attr_accessor :center, :size, :rotation, :falloff, :object_name, :coordinate_system
208
+ COORDINATE_SYSTEMS = [:object, :world]
209
+ chunk_map('CNTR'){|s,c| s.center = c.read_vector}
210
+ chunk_map('SIZE'){|s,c| s.size = c.read_vector}
211
+ chunk_map('ROTA'){|s,c| s.rotation = c.read_vector}
212
+ chunk_map('FALL'){|s,c| s.falloff = TextureFalloff.new(c)}
213
+ chunk_map('OREF'){|s,c| s.object_name = c.read_string}
214
+ chunk_map('CSYS'){|s,c| s.coordinate_system = COORDINATE_SYSTEMS[c.read_u2]}
215
+ end
216
+
217
+ class TextureError
218
+ attr_accessor :message
219
+ def initialize(msg)
220
+ @message = msg
221
+ end
222
+ end
223
+
224
+ class TextureLayer < ChunkReader
225
+ # Bodgetastic. Assumes that we're only ever an IMAP texture block. Must change.
226
+ # The UV file has a NEGA as part of the header. I don't think that can be right,
227
+ # according to the docs, but it does make sense in context.
228
+ attr_accessor :type , :ordinal , :header , :mapping, :projection_mode, :axis
229
+ attr_accessor :image_index, :width_wrap, :height_wrap
230
+ attr_accessor :width_repeat, :height_repeat
231
+ attr_accessor :vertex_map_name
232
+ attr_accessor :aa, :aa_strength
233
+ attr_accessor :pixel_blending
234
+ attr_accessor :sticky, :sticky_time
235
+ attr_accessor :amplitude
236
+
237
+ alias :aa? :aa
238
+ alias :pixel_blending? :pixel_blending
239
+ alias :sticky? :sticky
240
+
241
+
242
+ chunk_map('TMAP'){|s,c| s.mapping = TextureMapping.new(c)}
243
+ chunk_map('PROJ'){|s,c| s.projection_mode = PROJECTION_MODES[c.read_u2]}
244
+ chunk_map('AXIS'){|s,c| s.axis = AXES[c.read_u2]}
245
+ chunk_map('IMAG'){|s,c| s.image_index = c.read_u2}
246
+ chunk_map 'WRAP', :read_wraps
247
+ chunk_map('WRPW'){|s,c| s.width_repeat = c.read_float}
248
+ chunk_map('WRPH'){|s,c| s.height_repeat = c.read_float}
249
+ chunk_map('VMAP'){|s,c| s.vertex_map_name = c.read_string}
250
+ chunk_map 'AAST', :read_aa
251
+ chunk_map('PIXB'){|s,c| s.pixel_blending = (c.read_u2 % 2) == 1}
252
+ chunk_map 'STCK', :read_sticky
253
+ chunk_map('TAMP'){|s,c| s.amplitude = c.read_float}
254
+
255
+ def read_sticky(c)
256
+ self.sticky = c.read_u2 != 0
257
+ self.sticky_time = c.read_float
258
+ end
259
+ def read_wraps(c)
260
+ self.width_wrap, self.height_wrap = [c.read_u2, c.read_u2].collect{|i| WRAP_TYPES[i]}
261
+ end
262
+ def read_aa(c)
263
+ self.aa = c.read_u2 != 0
264
+ self.aa_strength = c.read_float
265
+ end
266
+ def initialize(strio)
267
+ @type, header_chunk = strio.read_chunk(2)
268
+ @errors = []
269
+ if @type != 'IMAP'
270
+ @errors << TextureError.new("#{@type} (non image-mapped) texture layer found.")
271
+ end
272
+ @ordinal = header_chunk.read_string
273
+
274
+ @header = TextureHeader.new(header_chunk)
275
+ super(strio)
276
+ end
277
+ end
278
+
279
+ class Surface < ChunkReader
280
+ attr_accessor :name # Matched up to polygons by this name
281
+ # and the index of this string in the
282
+ # Form#tags attribute.
283
+ attr_accessor :parent_name
284
+ attr_accessor :colour
285
+ attr_accessor :diffuse
286
+ attr_accessor :specular
287
+ attr_accessor :luminosity
288
+ attr_accessor :glossiness
289
+ attr_accessor :textures
290
+ attr_accessor :comment
291
+
292
+
293
+ chunk_map('COLR'){|s,c| s.colour = Colour.new(c)}
294
+ chunk_map('DIFF'){|s,c| s.diffuse = Diffuse.new(c)}
295
+ chunk_map('SPEC'){|s,c| s.specular = Specular.new(c)}
296
+ chunk_map('LUMI'){|s,c| s.luminosity = Luminosity.new(c)}
297
+ chunk_map('GLOS'){|s,c| s.glossiness = Glossiness.new(c)}
298
+ chunk_map('BLOK'){|s,c| s.textures << TextureLayer.new(c)}
299
+ chunk_map('CMNT'){|s,c| s.comment = c.read_string}
300
+ chunk_map('TRAN'){}
301
+ chunk_map('BUMP'){}
302
+ chunk_map('SMAN'){}
303
+
304
+
305
+
306
+ def initialize(strio)
307
+ @errors = []
308
+ @name = strio.read_string
309
+ @parent_name = strio.read_string
310
+ @textures = []
311
+ process_tags(strio)
312
+ @textures.each {|t| @errors += t.errors}
313
+ end
314
+ end
315
+
316
+ class Polygon < Array
317
+ attr_accessor :flags
318
+ attr_accessor :points
319
+ attr_accessor :surface_id
320
+ def new(poly_set, flags=nil)
321
+ @points = poly_set
322
+ @flags = flags
323
+ end
324
+ end
325
+
326
+ class Polygons
327
+ attr_accessor :faces
328
+ attr_accessor :curves
329
+ attr_accessor :patches
330
+ attr_accessor :metaballs
331
+ attr_accessor :bones
332
+ attr_accessor :latest
333
+ attr_accessor :surface_groups
334
+ def initialize
335
+ @surface_groups = Hash.new(){|h,k| h[k] = []}
336
+ @faces = []
337
+ end
338
+ def <<(strio)
339
+ label = strio.read_type
340
+ lookup = {'FACE' => @faces, 'PTCH' => @faces}
341
+ target = lookup[label]
342
+ points = []
343
+ while !strio.eof?
344
+ poly_set = strio.read_poly_set
345
+ points << Polygon.new(poly_set)
346
+ end
347
+ target.concat points
348
+ @latest = points # Need to keep this hanging around for the surface assignments
349
+ end
350
+ end
351
+
352
+ class GeomLayer
353
+ attr_accessor :number # an ID
354
+ attr_accessor :hidden # boolean
355
+ attr_accessor :pivot # Vector of the layer origin
356
+ attr_accessor :name # Label
357
+ attr_accessor :parent # Another layer number or nil.
358
+ attr_accessor :points
359
+ attr_accessor :max, :min # Bounding box points.
360
+ attr_accessor :polygons
361
+ attr_accessor :uvmaps
362
+ attr_accessor :morphs
363
+ attr_accessor :children #Not actually used here. Used by the lw2xaml code.
364
+ def read_ptag(strio)
365
+ type = strio.read_type
366
+ while !strio.eof?
367
+ vertex_id = strio.read_vx
368
+ tag_id = strio.read_u2
369
+ @polygons.latest[vertex_id].surface_id = tag_id
370
+ @polygons.surface_groups[tag_id] << @polygons.latest[vertex_id]
371
+ end
372
+ end
373
+ def read_polygons(strio)
374
+ @polygons ||= Polygons.new
375
+ @polygons << strio
376
+ end
377
+ def read_bbox(strio)
378
+ @min = strio.read_vector
379
+ @max = strio.read_vector
380
+ end
381
+ def read_points(strio)
382
+ @zero_offset = @points.length
383
+ while !strio.eof?
384
+ @points << strio.read_vector
385
+ end
386
+ end
387
+ def read_uvmap(strio)
388
+ @uvmaps ||= {}
389
+ dimension = strio.read_u2
390
+ name = strio.read_string
391
+ map = {}
392
+ while !strio.eof?
393
+ index = strio.read_vx
394
+ vector = []
395
+ dimension.times do vector << strio.read_float end
396
+ map[@zero_offset + index] = vector
397
+ end
398
+ @uvmaps[name] = map
399
+ end
400
+ def read_morph(strio)
401
+ @morphs ||= {}
402
+ dimension = strio.read_u2
403
+ name = strio.read_string
404
+ map = {}
405
+ while !strio.eof?
406
+ index = strio.read_vx
407
+ vector = []
408
+ dimension.times do vector << strio.read_float end
409
+ map[@zero_offset + index] = vector
410
+ end
411
+ @morphs[name] = map
412
+ end
413
+ def read_vmap(strio)
414
+ type = strio.read_type
415
+ case type
416
+ when 'TXUV'
417
+ read_uvmap(strio)
418
+ when 'MORF'
419
+ read_morph(strio)
420
+
421
+ end
422
+ end
423
+ def initialize(scene, strio)
424
+ strio.read(4) # discard number and flags for now
425
+ @pivot = strio.read_vector
426
+ @name = strio.read_string
427
+ @parent = strio.eof? ? nil : strio.read_u2
428
+ @points = []
429
+ @uvmaps = {}
430
+ end
431
+ end
432
+
433
+ class Clip < ChunkReader
434
+ attr_accessor :still
435
+ chunk_map('STIL'){|s,c| s.still = c.read_string}
436
+ chunk_map('FLAG'){}
437
+ end
438
+ class Scene < ChunkReader
439
+ attr_accessor :filename
440
+ attr_accessor :type # Version
441
+ attr_accessor :tags # list of strings
442
+ attr_accessor :layers # list of layers
443
+ attr_accessor :surfaces
444
+ attr_accessor :icon
445
+ attr_accessor :current_layer
446
+ attr_accessor :clips
447
+ attr_accessor :text
448
+ attr_accessor :description
449
+ attr_accessor :min, :max
450
+ set_chunk_length 4
451
+
452
+ chunk_map('ICON'){|s,c| s.icon = c.read}
453
+ chunk_map('TEXT'){|s,c| s.text = c.read_string}
454
+ chunk_map('DESC'){|s,c| s.description = c.read_string}
455
+ chunk_map('TAGS'){|s,c| s.tags = c.read_string_list}
456
+ chunk_map 'LAYR', :read_layer
457
+ chunk_map 'SURF', :read_surface
458
+ chunk_map 'PNTS', :read_points
459
+ chunk_map 'BBOX', :read_bbox
460
+ chunk_map 'POLS', :read_polygons
461
+ chunk_map 'PTAG', :read_ptag
462
+ chunk_map 'VMAP', :read_vmap
463
+ chunk_map 'CLIP', :read_clip
464
+ chunk_map('VMPA'){}
465
+
466
+ def method_missing(sym, *args, &block)
467
+ @current_layer.send(sym, *args, &block)
468
+ end
469
+ def init_lists
470
+ @errors = []
471
+ @layers = []
472
+ @surfaces = {}
473
+ @tags = []
474
+ @clips = {}
475
+ end
476
+ def initialize(string)
477
+ init_lists
478
+
479
+ ext_reader = LightwaveStringIO.new(string)
480
+ discard, reader = ext_reader.read_chunk
481
+ @type = reader.read_type
482
+
483
+ @current_layer = VCR.new
484
+
485
+ super(reader)
486
+ read_scene_bbox
487
+ end
488
+ def read_scene_bbox
489
+ @min = @layers[0].min.dup
490
+ @max = @layers[0].max.dup
491
+ @layers.each do |layer|
492
+ (0..2).each do |i|
493
+ @min[i] = layer.min[i] < @min[i] ? layer.min[i] : @min[i]
494
+ @max[i] = layer.max[i] > @max[i] ? layer.max[i] : @max[i]
495
+ end
496
+ end
497
+ end
498
+ def read_clip(strio)
499
+ @clips[strio.read_u4] = Clip.new(strio)
500
+ end
501
+ def read_layer(strio)
502
+ new_layer = GeomLayer.new(self,strio)
503
+ if @current_layer.respond_to? :play_back_to
504
+ @current_layer.play_back_to new_layer
505
+ end
506
+ @layers << new_layer
507
+ @current_layer = new_layer
508
+ end
509
+ def read_surface(strio)
510
+ s = Surface.new(strio)
511
+ @surfaces[s.name] = s
512
+ # @errors += s.errors
513
+ end
514
+ end
515
+
516
+ def self.read_file(file, object_name = nil)
517
+ result = Scene.new(file)
518
+ result.filename = object_name
519
+ return result
520
+ end
521
+ def self.read(filename, object_name = nil)
522
+ self.read_file(filename, object_name)
523
+ end
524
+ end
525
+ def scene_data(file, object_name)
526
+ LightwaveObject.read(file, object_name)
527
+ end
528
+ end
529
+
530
+ if __FILE__==$0
531
+ filename = ARGV[0]
532
+ scene = Lightwave::LightwaveObject::read(File.read(filename), filename)
533
+ puts "Done!"
534
+ end
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Lightwave::LightwaveObject::Scene
2
+ clips: {}
3
+ current_layer: &id003 !ruby/object:Lightwave::LightwaveObject::GeomLayer
4
+ max:
5
+ - -1.0
6
+ - 1.0
7
+ - 0.0
8
+ min:
9
+ - -1.5
10
+ - 0.0
11
+ - 0.0
12
+ name: Small -X tri
13
+ parent: 0
14
+ pivot:
15
+ - -1.0
16
+ - 0.0
17
+ - 0.0
18
+ points:
19
+ -
20
+ - -1.0
21
+ - 0.0
22
+ - 0.0
23
+ -
24
+ - -1.5
25
+ - 1.0
26
+ - 0.0
27
+ -
28
+ - -1.5
29
+ - 0.0
30
+ - 0.0
31
+ polygons: !ruby/object:Lightwave::LightwaveObject::Polygons
32
+ faces:
33
+ - &id001 !ruby/array:Lightwave::LightwaveObject::Polygon
34
+ - 0
35
+ - 1
36
+ - 2
37
+ latest:
38
+ - *id001
39
+ surface_groups:
40
+ 0:
41
+ - *id001
42
+ 1:
43
+ - *id001
44
+ uvmaps: {}
45
+ zero_offset: 0
46
+ filename: triangles.lwo
47
+ layers:
48
+ - !ruby/object:Lightwave::LightwaveObject::GeomLayer
49
+ max:
50
+ - 1.5
51
+ - 2.0
52
+ - 0.0
53
+ min:
54
+ - 0.5
55
+ - 0.0
56
+ - 0.0
57
+ name: Large +X tri
58
+ parent:
59
+ pivot:
60
+ - 0.5
61
+ - 0.0
62
+ - 0.0
63
+ points:
64
+ -
65
+ - 0.5
66
+ - 0.0
67
+ - 0.0
68
+ -
69
+ - 0.5
70
+ - 2.0
71
+ - 0.0
72
+ -
73
+ - 1.5
74
+ - 0.0
75
+ - 0.0
76
+ polygons: !ruby/object:Lightwave::LightwaveObject::Polygons
77
+ faces:
78
+ - &id002 !ruby/array:Lightwave::LightwaveObject::Polygon
79
+ - 0
80
+ - 1
81
+ - 2
82
+ latest:
83
+ - *id002
84
+ surface_groups:
85
+ 0:
86
+ - *id002
87
+ 1:
88
+ - *id002
89
+ uvmaps: {}
90
+ zero_offset: 0
91
+ - *id003
92
+ max:
93
+ - 1.5
94
+ - 2.0
95
+ - 0.0
96
+ min:
97
+ - -1.5
98
+ - 0.0
99
+ - 0.0
100
+ surfaces:
101
+ Default: !ruby/object:Lightwave::LightwaveObject::Surface
102
+ colour: !ruby/object:Lightwave::LightwaveObject::Colour
103
+ rgb:
104
+ - 0.7843137383461
105
+ - 0.7843137383461
106
+ - 0.7843137383461
107
+ diffuse: !ruby/object:Lightwave::LightwaveObject::Diffuse
108
+ intensity: 1.0
109
+ name: Default
110
+ parent_name: ''
111
+ textures: []
112
+ tags:
113
+ - DkBlu
114
+ - Default
115
+ type: LWO2
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Lightwave::LightwaveObject::Scene
2
+ clips:
3
+ 1: !ruby/object:Lightwave::LightwaveObject::Clip
4
+ still: Images/testbars.iff
5
+ filename: uvmap.lwo
6
+ current_layer: &id007 !ruby/object:Lightwave::LightwaveObject::GeomLayer
7
+ max:
8
+ - 0.5
9
+ - 0.5
10
+ - 0.5
11
+ min:
12
+ - -0.5
13
+ - -0.5
14
+ - -0.5
15
+ name: ''
16
+ parent:
17
+ pivot:
18
+ - 0.0
19
+ - 0.0
20
+ - 0.0
21
+ points:
22
+ -
23
+ - -0.5
24
+ - -0.5
25
+ - -0.5
26
+ -
27
+ - 0.5
28
+ - -0.5
29
+ - -0.5
30
+ -
31
+ - 0.5
32
+ - -0.5
33
+ - 0.5
34
+ -
35
+ - -0.5
36
+ - -0.5
37
+ - 0.5
38
+ -
39
+ - -0.5
40
+ - 0.5
41
+ - -0.5
42
+ -
43
+ - 0.5
44
+ - 0.5
45
+ - -0.5
46
+ -
47
+ - 0.5
48
+ - 0.5
49
+ - 0.5
50
+ -
51
+ - -0.5
52
+ - 0.5
53
+ - 0.5
54
+ polygons: !ruby/object:Lightwave::LightwaveObject::Polygons
55
+ faces:
56
+ - &id001 !ruby/array:Lightwave::LightwaveObject::Polygon
57
+ - 0
58
+ - 1
59
+ - 2
60
+ - 3
61
+ - &id002 !ruby/array:Lightwave::LightwaveObject::Polygon
62
+ - 0
63
+ - 4
64
+ - 5
65
+ - 1
66
+ - &id003 !ruby/array:Lightwave::LightwaveObject::Polygon
67
+ - 1
68
+ - 5
69
+ - 6
70
+ - 2
71
+ - &id004 !ruby/array:Lightwave::LightwaveObject::Polygon
72
+ - 3
73
+ - 2
74
+ - 6
75
+ - 7
76
+ - &id005 !ruby/array:Lightwave::LightwaveObject::Polygon
77
+ - 0
78
+ - 3
79
+ - 7
80
+ - 4
81
+ - &id006 !ruby/array:Lightwave::LightwaveObject::Polygon
82
+ - 4
83
+ - 7
84
+ - 6
85
+ - 5
86
+ latest:
87
+ - *id001
88
+ - *id002
89
+ - *id003
90
+ - *id004
91
+ - *id005
92
+ - *id006
93
+ surface_groups:
94
+ 0:
95
+ - *id001
96
+ - *id002
97
+ - *id003
98
+ - *id005
99
+ - *id006
100
+ 1:
101
+ - *id004
102
+ uvmaps:
103
+ UV Texture:
104
+ 6:
105
+ - 0.0
106
+ - 1.0
107
+ 7:
108
+ - 1.0
109
+ - 1.0
110
+ 2:
111
+ - 0.5
112
+ - 0.0
113
+ 3:
114
+ - 1.0
115
+ - 0.0
116
+ zero_offset: 0
117
+ layers:
118
+ - *id007
119
+ max:
120
+ - 0.5
121
+ - 0.5
122
+ - 0.5
123
+ min:
124
+ - -0.5
125
+ - -0.5
126
+ - -0.5
127
+ surfaces:
128
+ UVExample: !ruby/object:Lightwave::LightwaveObject::Surface
129
+ colour: !ruby/object:Lightwave::LightwaveObject::Colour
130
+ rgb:
131
+ - 0.7843137383461
132
+ - 0.7843137383461
133
+ - 0.7843137383461
134
+ diffuse: !ruby/object:Lightwave::LightwaveObject::Diffuse
135
+ intensity: 1.0
136
+ name: UVExample
137
+ parent_name: ''
138
+ specular: !ruby/object:Lightwave::LightwaveObject::Specular
139
+ intensity: 0.0
140
+ textures:
141
+ - !ruby/object:Lightwave::LightwaveObject::TextureLayer
142
+ aa: true
143
+ aa_strength: 1.0
144
+ amplitude: 1.0
145
+ axis: :z
146
+ header: !ruby/object:Lightwave::LightwaveObject::TextureHeader
147
+ channel: COLR
148
+ displacement_axis: :y
149
+ enabled: true
150
+ negative: false
151
+ opacity: !ruby/object:Lightwave::LightwaveObject::Opacity
152
+ opacity: 1.0
153
+ type: :normal
154
+ height_repeat: 1.0
155
+ height_wrap: :repeat
156
+ image_index: 1
157
+ mapping: !ruby/object:Lightwave::LightwaveObject::TextureMapping
158
+ center:
159
+ - 0.0
160
+ - 0.0
161
+ - 0.0
162
+ coordinate_system: :object
163
+ falloff: !ruby/object:Lightwave::LightwaveObject::TextureFalloff
164
+ type: :cubic
165
+ vector:
166
+ - 0.0
167
+ - 0.0
168
+ - 0.0
169
+ object_name: "(none)"
170
+ rotation:
171
+ - 0.0
172
+ - 0.0
173
+ - 0.0
174
+ size:
175
+ - 1.0
176
+ - 1.0
177
+ - 1.0
178
+ ordinal: "�"
179
+ pixel_blending: true
180
+ projection_mode: :uv
181
+ sticky: false
182
+ sticky_time: 0.0
183
+ type: IMAP
184
+ vertex_map_name: UV Texture
185
+ width_repeat: 1.0
186
+ width_wrap: :repeat
187
+ Default: !ruby/object:Lightwave::LightwaveObject::Surface
188
+ colour: !ruby/object:Lightwave::LightwaveObject::Colour
189
+ rgb:
190
+ - 0.7843137383461
191
+ - 0.7843137383461
192
+ - 0.7843137383461
193
+ diffuse: !ruby/object:Lightwave::LightwaveObject::Diffuse
194
+ intensity: 1.0
195
+ name: Default
196
+ parent_name: ''
197
+ textures: []
198
+ tags:
199
+ - Default
200
+ - UVExample
201
+ type: LWO2
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ require 'test/unit'
4
+ file = File.expand_path(File.join(File.dirname(__FILE__), '..','lib','lightwave'))
5
+ require file
6
+
7
+ fixtures_base = File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures')
8
+
9
+ LWO_DIR = File.join(fixtures_base, 'lwo')
10
+ YAML_DIR = File.join(fixtures_base, 'yaml')
11
+ def read_file(dir, filename)
12
+ File.open(File.join(dir,filename), 'rb'){|f|
13
+ f.read
14
+ }
15
+ end
16
+ def lwo_file(filename)
17
+ read_file(LWO_DIR,filename)
18
+ end
19
+
20
+ def yaml_file(filename)
21
+ read_file(YAML_DIR, filename)
22
+ end
23
+
24
+ module TestHelperFuncs
25
+ def get_scene_data(filename)
26
+ case filename
27
+ when /\.lwo\Z/
28
+ file = lwo_file(filename)
29
+ Lightwave::LightwaveObject.read(file, filename)
30
+ when /\.yaml\Z/
31
+ YAML::load(yaml_file(filename))
32
+ end
33
+ end
34
+ end
35
+
36
+ class LightwaveStringIOTest < Test::Unit::TestCase
37
+ def lwstrio(string)
38
+ Lightwave::LightwaveStringIO.new(string)
39
+ end
40
+ def pair_equal(a,bstring,method)
41
+ assert_equal a, lwstrio(bstring).send(method)
42
+ end
43
+ def test_basic_read_methods
44
+ [ [1, "\000\001", :read_u2],
45
+ [1, "\000\000\000\001", :read_u4],
46
+ [1.0, "?\200\000\000", :read_float],
47
+ [[1.0, 1.0, 1.0], "?\200\000\000" * 3, :read_vector],
48
+ ["foo", "foo\000", :read_string],
49
+ ["foo", "foo\000\000", :read_string] ].each do |a, bstring, method|
50
+ pair_equal(a, bstring, method)
51
+ end
52
+ end
53
+ end
54
+
55
+ class VCRTest < Test::Unit::TestCase
56
+ def test_vcr
57
+ my_vcr = Lightwave::VCR.new
58
+ my_vcr.concat('foo')
59
+ my_vcr.gsub!(/o/, 'e')
60
+ a = ''
61
+ my_vcr.play_back_to(a)
62
+ assert_equal 'fee', a
63
+ end
64
+ end
65
+
66
+ class BBoxTest < Test::Unit::TestCase
67
+ include TestHelperFuncs
68
+ def setup
69
+ @cube_scene = self.get_scene_data('parented_cubes.lwo')
70
+ @triangles_scene = self.get_scene_data('test_triangles.yaml')
71
+ end
72
+ def test_layers_bbox
73
+ assert_equal [-0.5, -0.5, -0.5], @cube_scene.layers[0].min
74
+ assert_equal [ 0.5, 0.5, 0.5], @cube_scene.layers[0].max
75
+ assert_equal [ 0.5, -0.5, -0.5], @cube_scene.layers[1].min
76
+ assert_equal [ 1.5, 0.5, 0.5], @cube_scene.layers[1].max
77
+ end
78
+ def test_scene_bbox
79
+ assert_equal [-0.5, -0.5, -0.5], @cube_scene.min
80
+ assert_equal [ 1.5, 0.5, 0.5], @cube_scene.max
81
+ end
82
+ def test_tri_layers_bbox
83
+ assert_equal [ 0.5, 0.0, 0.0], @triangles_scene.layers[0].min
84
+ assert_equal [ 1.5, 2.0, 0.0], @triangles_scene.layers[0].max
85
+ assert_equal [-1.5, 0.0, 0.0], @triangles_scene.layers[1].min
86
+ assert_equal [-1.0, 1.0, 0.0], @triangles_scene.layers[1].max
87
+ end
88
+ def test_tri_scene_bbox
89
+ assert_equal [-1.5, 0.0, 0.0], @triangles_scene.min
90
+ assert_equal [ 1.5, 2.0, 0.0], @triangles_scene.max
91
+ end
92
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: lightwave
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.3
7
+ date: 2006-06-13 00:00:00 +01:00
8
+ summary: Lightwave is a package to read and manipulate Lightwave object files.
9
+ require_paths:
10
+ - lib
11
+ email: alex@shaxam.com
12
+ homepage: http://www.shaxam.com
13
+ rubyforge_project:
14
+ description: A set of libraries that handle reading Lightwave object files and (for the moment) turning them into a set of Xaml objects.
15
+ autorequire: lightwave
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Alex Young
30
+ files:
31
+ - lib/lightwave.rb
32
+ - lib/lightwave/lw2yaml.rb
33
+ - test/test_lightwave.rb
34
+ - test/fixtures/lwo/parented_cubes.lwo
35
+ - test/fixtures/yaml/uvmap.yaml
36
+ - test/fixtures/yaml/test_triangles.yaml
37
+ - Rakefile
38
+ - README
39
+ - TODO
40
+ - LICENSE
41
+ test_files:
42
+ - test/test_lightwave.rb
43
+ rdoc_options:
44
+ - --title
45
+ - Lightwave Documentation
46
+ - --main
47
+ - README
48
+ extra_rdoc_files:
49
+ - README
50
+ - TODO
51
+ - LICENSE
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ requirements: []
57
+
58
+ dependencies:
59
+ - !ruby/object:Gem::Dependency
60
+ name: activesupport
61
+ version_requirement:
62
+ version_requirements: !ruby/object:Gem::Version::Requirement
63
+ requirements:
64
+ - - ">"
65
+ - !ruby/object:Gem::Version
66
+ version: 0.0.0
67
+ version: