collada 0.0.1 → 0.2.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.
- checksums.yaml +15 -0
- data/.travis.yml +0 -5
- data/README.md +53 -1
- data/bin/collada-convert +55 -0
- data/collada.gemspec +19 -15
- data/lib/collada/conversion/mesh.rb +144 -0
- data/lib/collada/conversion/skeleton.rb +84 -0
- data/lib/collada/conversion/tagged_format.rb +172 -0
- data/lib/collada/parser/animation.rb +57 -0
- data/lib/collada/parser/controller.rb +152 -0
- data/lib/collada/parser/geometry.rb +88 -145
- data/lib/collada/parser/library.rb +17 -12
- data/lib/collada/parser/scene.rb +1 -132
- data/lib/collada/parser/support.rb +353 -0
- data/lib/collada/parser/visual_scene.rb +159 -11
- data/lib/collada/transforms.rb +111 -0
- data/lib/collada/version.rb +1 -1
- data/test/animation.dae +165 -0
- data/test/sample.dae +211 -0
- data/test/test_parsing_animation.rb +96 -0
- data/test/test_parsing_geometry.rb +187 -0
- data/test/test_transforms.rb +56 -0
- metadata +70 -20
- data/test/test_parser.rb +0 -105
@@ -20,33 +20,38 @@
|
|
20
20
|
|
21
21
|
require 'collada/parser/visual_scene'
|
22
22
|
require 'collada/parser/geometry'
|
23
|
+
require 'collada/parser/animation'
|
24
|
+
require 'collada/parser/controller'
|
23
25
|
|
24
26
|
module Collada
|
25
27
|
module Parser
|
26
28
|
class Library
|
27
29
|
SECTIONS = {
|
28
30
|
:visual_scenes => ['COLLADA/library_visual_scenes/visual_scene', VisualScene],
|
29
|
-
:geometries => ['COLLADA/library_geometries/geometry', Geometry]
|
31
|
+
:geometries => ['COLLADA/library_geometries/geometry', Geometry],
|
32
|
+
:animations => ['COLLADA/library_animations/animation', Animation],
|
33
|
+
:controllers => ['COLLADA/library_controllers/controller', Controller],
|
30
34
|
}
|
31
35
|
|
32
|
-
def initialize(sections = {})
|
36
|
+
def initialize(doc, sections = {})
|
37
|
+
@doc = doc
|
33
38
|
@sections = sections
|
34
39
|
end
|
35
40
|
|
36
41
|
def [] key
|
37
|
-
@sections
|
42
|
+
@sections.fetch(key) do
|
43
|
+
path, klass = SECTIONS[key]
|
44
|
+
|
45
|
+
raise ArgumentError.new("Invalid section name #{key}!") unless klass
|
46
|
+
|
47
|
+
@sections[key] = OrderedMap.parse(@doc, path) do |element|
|
48
|
+
klass.parse(@doc, element)
|
49
|
+
end
|
50
|
+
end
|
38
51
|
end
|
39
52
|
|
40
53
|
def self.parse(doc)
|
41
|
-
|
42
|
-
|
43
|
-
SECTIONS.each do |key, (path, klass)|
|
44
|
-
sections[key] = OrderedMap.parse(doc, path) do |element|
|
45
|
-
klass.parse(doc, element)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
return Library.new(sections)
|
54
|
+
return Library.new(doc)
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
data/lib/collada/parser/scene.rb
CHANGED
@@ -67,131 +67,6 @@ module Collada
|
|
67
67
|
|
68
68
|
class Geometry
|
69
69
|
class Mesh
|
70
|
-
class Parameter
|
71
|
-
def initialize(name, type)
|
72
|
-
@name = name
|
73
|
-
@type = type
|
74
|
-
end
|
75
|
-
|
76
|
-
attr :name
|
77
|
-
attr :type
|
78
|
-
|
79
|
-
def self.parse(doc, element)
|
80
|
-
self.new(element.attributes['name'], element.attributes['type'].to_sym)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class Accessor
|
85
|
-
include Enumerable
|
86
|
-
|
87
|
-
def initialize(array, parameters, options = {})
|
88
|
-
@array = array
|
89
|
-
@parameters = parameters
|
90
|
-
@names = parameters.collect{|parameter| parameter.name}
|
91
|
-
|
92
|
-
@offset = (options[:offset] || 0).to_i
|
93
|
-
@stride = (options[:stride] || parameters.size).to_i
|
94
|
-
end
|
95
|
-
|
96
|
-
attr :array
|
97
|
-
attr :parameters
|
98
|
-
attr :names
|
99
|
-
|
100
|
-
attr :offset
|
101
|
-
attr :stride
|
102
|
-
|
103
|
-
def read index
|
104
|
-
base = @offset + (index * @stride)
|
105
|
-
values = @array[base, @parameters.size]
|
106
|
-
|
107
|
-
@names.zip(values)
|
108
|
-
end
|
109
|
-
|
110
|
-
def [] index
|
111
|
-
read(index).delete_if{|(name, value)| name == nil}
|
112
|
-
end
|
113
|
-
|
114
|
-
def size
|
115
|
-
(@array.size - @offset) / @stride
|
116
|
-
end
|
117
|
-
|
118
|
-
def each
|
119
|
-
size.times.each do |i|
|
120
|
-
yield self[i]
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def self.parse_parameters(doc, element)
|
125
|
-
OrderedMap.parse(element, 'param', 'name') do |param_element|
|
126
|
-
Parameter.parse(doc, param_element)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.parse(doc, element, arrays = {})
|
131
|
-
if (array_id = element.attributes['source'])
|
132
|
-
array_id.sub!(/^#/, '')
|
133
|
-
|
134
|
-
array = arrays[array_id]
|
135
|
-
else
|
136
|
-
array = Mesh.parse_arrays(doc, element).first
|
137
|
-
end
|
138
|
-
|
139
|
-
parameters = parse_parameters(doc, element)
|
140
|
-
|
141
|
-
options = {
|
142
|
-
:offset => element.attributes['offset'],
|
143
|
-
:stride => element.attributes['stride'],
|
144
|
-
}
|
145
|
-
|
146
|
-
self.new(array, parameters, options)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
class Source
|
151
|
-
def initialize(id, accessor)
|
152
|
-
@id = id
|
153
|
-
@accessor = accessor
|
154
|
-
end
|
155
|
-
|
156
|
-
attr :id
|
157
|
-
attr :accessor
|
158
|
-
|
159
|
-
def self.parse(doc, element, arrays = {})
|
160
|
-
accessor = Accessor.parse(doc, element.elements['technique_common/accessor'], arrays)
|
161
|
-
|
162
|
-
self.new(element.attributes['id'], accessor)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
class Input
|
167
|
-
def initialize(semantic, source, offset = 0)
|
168
|
-
@semantic = semantic
|
169
|
-
@source = source
|
170
|
-
@offset = offset
|
171
|
-
end
|
172
|
-
|
173
|
-
attr :semantic
|
174
|
-
attr :source
|
175
|
-
attr :offset
|
176
|
-
|
177
|
-
def inspect
|
178
|
-
"<Input: #{@semantic}>"
|
179
|
-
end
|
180
|
-
|
181
|
-
def self.parse(doc, element, sources = {})
|
182
|
-
semantic = element.attributes['semantic']
|
183
|
-
|
184
|
-
if (source_id = element.attributes['source'])
|
185
|
-
source_id.sub!(/^#/, '')
|
186
|
-
source = sources[source_id]
|
187
|
-
end
|
188
|
-
|
189
|
-
offset = element.attributes['offset'] || 0
|
190
|
-
|
191
|
-
self.new(semantic, source, offset.to_i)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
70
|
class TriangleVertices
|
196
71
|
def count(index)
|
197
72
|
3
|
@@ -302,14 +177,8 @@ module Collada
|
|
302
177
|
attr :sources
|
303
178
|
attr :polygons
|
304
179
|
|
305
|
-
def self.parse_arrays(doc, element)
|
306
|
-
OrderedMap.parse(element, '//float_array | //int_array', 'name') do |array_element|
|
307
|
-
array_element.text.strip.split(/\s+/).collect &:to_f
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
180
|
def self.parse(doc, element)
|
312
|
-
arrays = parse_arrays(doc, element)
|
181
|
+
arrays = Source.parse_arrays(doc, element)
|
313
182
|
|
314
183
|
sources = OrderedMap.parse(element, 'source') do |source_element|
|
315
184
|
Source.parse(doc, source_element, arrays)
|
@@ -37,6 +37,14 @@ module Collada
|
|
37
37
|
attr :ordered
|
38
38
|
attr :indexed
|
39
39
|
|
40
|
+
def keys
|
41
|
+
@indexed.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def values
|
45
|
+
@ordered
|
46
|
+
end
|
47
|
+
|
40
48
|
def [] key
|
41
49
|
@indexed[key]
|
42
50
|
end
|
@@ -49,6 +57,11 @@ module Collada
|
|
49
57
|
@ordered.size
|
50
58
|
end
|
51
59
|
|
60
|
+
def append(key, value)
|
61
|
+
@indexed[key] = value
|
62
|
+
@ordered << value
|
63
|
+
end
|
64
|
+
|
52
65
|
def self.parse(top, path, id_key = 'id')
|
53
66
|
ordered = []
|
54
67
|
indexed = {}
|
@@ -64,5 +77,345 @@ module Collada
|
|
64
77
|
return OrderedMap.new(ordered, indexed)
|
65
78
|
end
|
66
79
|
end
|
80
|
+
|
81
|
+
class Reference
|
82
|
+
def initialize(kind, url)
|
83
|
+
@kind = kind
|
84
|
+
@url = url
|
85
|
+
end
|
86
|
+
|
87
|
+
attr :kind
|
88
|
+
attr :url
|
89
|
+
|
90
|
+
def id
|
91
|
+
url.sub(/^.*\#/, '')
|
92
|
+
end
|
93
|
+
|
94
|
+
def lookup(library)
|
95
|
+
library[@kind].each do |item|
|
96
|
+
return item if item.id == id
|
97
|
+
end
|
98
|
+
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s
|
103
|
+
"\#<#{self.class} #{@url.dump} in #{@kind}>"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Attribute
|
108
|
+
def initialize(semantic, value)
|
109
|
+
@semantic = semantic
|
110
|
+
@value = value
|
111
|
+
end
|
112
|
+
|
113
|
+
attr :semantic
|
114
|
+
attr :value
|
115
|
+
|
116
|
+
def [] key
|
117
|
+
@value[key]
|
118
|
+
end
|
119
|
+
|
120
|
+
def == other
|
121
|
+
@value == other.value
|
122
|
+
end
|
123
|
+
|
124
|
+
def inspect
|
125
|
+
"Attribute.#{@semantic}(#{@value.inspect})"
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.method_missing(method, *args)
|
129
|
+
if args.size == 1 && Hash === args[0]
|
130
|
+
new(method, args[0])
|
131
|
+
else
|
132
|
+
new(method, args)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def flatten
|
137
|
+
if Array === @value
|
138
|
+
@value.collect{|attribute| attribute.flatten}
|
139
|
+
else
|
140
|
+
self
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.flatten(attributes)
|
145
|
+
attributes.collect{|attribute| attribute.flatten}.flatten
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.to_hash(attributes)
|
149
|
+
Hash[attributes.collect{|attribute| [attribute.semantic, attribute.value]}]
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.merge(attributes)
|
153
|
+
attributes.collect{|attribute| attribute.value}.inject(:merge)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Parameter
|
158
|
+
def initialize(name, type)
|
159
|
+
@name = name ? name.to_sym : nil
|
160
|
+
@type = type
|
161
|
+
end
|
162
|
+
|
163
|
+
attr :name
|
164
|
+
attr :type
|
165
|
+
|
166
|
+
def size
|
167
|
+
1
|
168
|
+
end
|
169
|
+
|
170
|
+
def read value
|
171
|
+
value.first
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.parse(doc, element)
|
175
|
+
name = element.attributes['name']
|
176
|
+
type = element.attributes['type']
|
177
|
+
|
178
|
+
case type
|
179
|
+
when /float(\d)x(\d)/
|
180
|
+
MatrixParameter.new(name, type, [$1.to_i, $2.to_i])
|
181
|
+
when /float(\d)/
|
182
|
+
VectorParameter.new(name, type, [$1.to_i, $2.to_i])
|
183
|
+
else
|
184
|
+
Parameter.new(name, type)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class MatrixParameter < Parameter
|
190
|
+
def initialize(name, type, dimensions)
|
191
|
+
super name, type
|
192
|
+
|
193
|
+
@rows = dimensions[1]
|
194
|
+
@size = dimensions[0] * dimensions[1]
|
195
|
+
end
|
196
|
+
|
197
|
+
attr :size
|
198
|
+
|
199
|
+
def read(value)
|
200
|
+
Matrix[*(value.each_slice(@rows).to_a)]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class VectorParameter < Parameter
|
205
|
+
def initialize(name, type, size)
|
206
|
+
super name, type
|
207
|
+
|
208
|
+
@size = size
|
209
|
+
end
|
210
|
+
|
211
|
+
attr :size
|
212
|
+
|
213
|
+
def read(value)
|
214
|
+
Vector[*value]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class Accessor
|
219
|
+
include Enumerable
|
220
|
+
|
221
|
+
def initialize(array, parameters, count, options = {})
|
222
|
+
@array = array
|
223
|
+
@parameters = parameters
|
224
|
+
@count = count
|
225
|
+
|
226
|
+
# Chunk size and stride are usually the same in many cases.
|
227
|
+
@chunk_size = @parameters.inject(0) {|sum, parameter| sum + parameter.size}
|
228
|
+
|
229
|
+
@offset = (options[:offset] || 0).to_i
|
230
|
+
@stride = (options[:stride] || @chunk_size).to_i
|
231
|
+
end
|
232
|
+
|
233
|
+
attr :array
|
234
|
+
attr :parameters
|
235
|
+
attr :names
|
236
|
+
|
237
|
+
attr :count
|
238
|
+
attr :offset
|
239
|
+
attr :stride
|
240
|
+
|
241
|
+
def read index
|
242
|
+
base = @offset + (index * @stride)
|
243
|
+
values = @array[base, @chunk_size]
|
244
|
+
|
245
|
+
@parameters.collect{|parameter| [parameter.name, parameter.read(values.shift(parameter.size))]}
|
246
|
+
end
|
247
|
+
|
248
|
+
def [] index
|
249
|
+
read(index).delete_if{|(name, value)| name == nil}
|
250
|
+
end
|
251
|
+
|
252
|
+
def size
|
253
|
+
(@array.size - @offset) / @stride
|
254
|
+
end
|
255
|
+
|
256
|
+
def each
|
257
|
+
size.times.each do |i|
|
258
|
+
yield self[i]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.parse_parameters(doc, element)
|
263
|
+
OrderedMap.parse(element, 'param', 'name') do |param_element|
|
264
|
+
Parameter.parse(doc, param_element)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.parse(doc, element, arrays = {})
|
269
|
+
if (array_id = element.attributes['source'])
|
270
|
+
array_id.sub!(/^#/, '')
|
271
|
+
|
272
|
+
array = arrays[array_id]
|
273
|
+
else
|
274
|
+
array = Mesh.parse_arrays(doc, element).first
|
275
|
+
end
|
276
|
+
|
277
|
+
raise UnsupportedFeature.new("Source array binding must be valid (id=#{array_id})") unless array
|
278
|
+
|
279
|
+
parameters = parse_parameters(doc, element)
|
280
|
+
count = element.attributes['count'].to_i
|
281
|
+
|
282
|
+
options = {
|
283
|
+
:offset => element.attributes['offset'],
|
284
|
+
:stride => element.attributes['stride'],
|
285
|
+
}
|
286
|
+
|
287
|
+
self.new(array, parameters, count, options)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# A source that reads directly from a data array:
|
292
|
+
class Source
|
293
|
+
def initialize(id, accessor)
|
294
|
+
@id = id
|
295
|
+
@accessor = accessor
|
296
|
+
end
|
297
|
+
|
298
|
+
attr :id
|
299
|
+
attr :accessor
|
300
|
+
|
301
|
+
def self.parse(doc, element, arrays = {})
|
302
|
+
accessor = Accessor.parse(doc, element.elements['technique_common/accessor'], arrays)
|
303
|
+
|
304
|
+
self.new(element.attributes['id'], accessor)
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.parse_arrays(doc, element)
|
308
|
+
OrderedMap.parse(element, '//float_array | //int_array | //Name_array') do |array_element|
|
309
|
+
case array_element.name
|
310
|
+
when 'Name_array'
|
311
|
+
array_element.text.strip.split(/\s+/)
|
312
|
+
else
|
313
|
+
array_element.text.strip.split(/\s+/).collect &:to_f
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def [] index
|
319
|
+
Hash[@accessor[index]]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class Input
|
324
|
+
# `Vertices` or `Source` are both okay for source.
|
325
|
+
def initialize(semantic, source, offset = 0)
|
326
|
+
@semantic = semantic
|
327
|
+
@source = source
|
328
|
+
@offset = offset
|
329
|
+
end
|
330
|
+
|
331
|
+
attr :semantic
|
332
|
+
attr :source
|
333
|
+
attr :offset
|
334
|
+
|
335
|
+
def size
|
336
|
+
@source.accessor.count
|
337
|
+
end
|
338
|
+
|
339
|
+
def [] index
|
340
|
+
Attribute.new(@semantic, @source[index])
|
341
|
+
end
|
342
|
+
|
343
|
+
def self.parse(doc, element, sources = {})
|
344
|
+
semantic = element.attributes['semantic']
|
345
|
+
|
346
|
+
if (source_id = element.attributes['source'])
|
347
|
+
source_id.sub!(/^#/, '')
|
348
|
+
source = sources[source_id]
|
349
|
+
end
|
350
|
+
|
351
|
+
raise UnsupportedFeature.new("Can't instantiate input with nil source (#{source_id})!") unless source
|
352
|
+
|
353
|
+
offset = element.attributes['offset'] || 0
|
354
|
+
|
355
|
+
self.new(semantic.downcase.to_sym, source, offset.to_i)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
class Sampler
|
360
|
+
include Enumerable
|
361
|
+
|
362
|
+
def initialize(id, inputs)
|
363
|
+
@id = id
|
364
|
+
@inputs = inputs
|
365
|
+
end
|
366
|
+
|
367
|
+
attr :id
|
368
|
+
attr :inputs
|
369
|
+
|
370
|
+
# Vertices by index, same interface as Input.
|
371
|
+
def [] index
|
372
|
+
@inputs.collect do |input|
|
373
|
+
input[index]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def self.parse_inputs(doc, element, sources = {})
|
378
|
+
OrderedMap.parse(element, 'input') do |input_element|
|
379
|
+
Input.parse(doc, input_element, sources)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def self.parse(doc, element, sources = {})
|
384
|
+
inputs = parse_inputs(doc, element, sources)
|
385
|
+
|
386
|
+
self.new(element.attributes['id'], inputs)
|
387
|
+
end
|
388
|
+
|
389
|
+
def size
|
390
|
+
@inputs.collect{|input| input.size}.max
|
391
|
+
end
|
392
|
+
|
393
|
+
def each
|
394
|
+
size.times do |i|
|
395
|
+
yield self[i]
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
class Channel
|
401
|
+
def initialize(source, target)
|
402
|
+
@source = source
|
403
|
+
@target = target
|
404
|
+
end
|
405
|
+
|
406
|
+
attr :source
|
407
|
+
attr :target
|
408
|
+
|
409
|
+
def self.parse(doc, element, sources = {})
|
410
|
+
source_id = element.attributes['source'].sub(/^#/, '')
|
411
|
+
target_id = element.attributes['target']
|
412
|
+
|
413
|
+
source = sources[source_id]
|
414
|
+
|
415
|
+
raise ArgumentError.new("Could not find #{source_id} in #{sources.keys.inspect}!") unless source
|
416
|
+
|
417
|
+
self.new(source, target_id)
|
418
|
+
end
|
419
|
+
end
|
67
420
|
end
|
68
421
|
end
|