collada 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ # Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'collada/parser/support'
22
+
23
+ module Collada
24
+ module Parser
25
+ class Animation
26
+ def initialize(id, sources, samples, channels)
27
+ @id = id
28
+ @sources = sources
29
+ @samples = samplers
30
+ @channels = channels
31
+ end
32
+
33
+ attr :id
34
+ attr :sources
35
+ attr :samplers
36
+ attr :channels
37
+
38
+ def self.parse(doc, element)
39
+ arrays = Source.parse_arrays(doc, element)
40
+
41
+ sources = OrderedMap.parse(element, 'source') do |source_element|
42
+ Source.parse(doc, source_element, arrays)
43
+ end
44
+
45
+ samplers = OrderedMap.parse(element, 'sampler') do |sampler_element|
46
+ Sampler.parse(doc, sampler_element, sources)
47
+ end
48
+
49
+ channels = OrderedMap.parse(element, 'channel', 'target') do |channel_element|
50
+ Channel.parse(doc, channel_element, samplers)
51
+ end
52
+
53
+ self.new(element.attributes['id'], sources, samplers, channels)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,152 @@
1
+ # Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'collada/parser/support'
22
+
23
+ module Collada
24
+ module Parser
25
+ class Controller
26
+ class Skin < Controller
27
+ class Joints < Sampler
28
+ end
29
+
30
+ class VertexWeights < Sampler
31
+ include Enumerable
32
+
33
+ def initialize(id, inputs, counts, vertices)
34
+ super id, inputs
35
+
36
+ @counts = counts
37
+ @vertices = vertices
38
+
39
+ # The number of indices per vertex:
40
+ @stride = @inputs.sort_by(&:offset).last.offset + 1
41
+ end
42
+
43
+ attr :counts
44
+ attr :vertices
45
+
46
+ def size
47
+ @counts.size
48
+ end
49
+
50
+ # Vertices by index:
51
+ def vertex(index)
52
+ offset = @stride * index
53
+
54
+ attributes = @inputs.collect do |input|
55
+ input[@vertices[offset + input.offset]]
56
+ end
57
+
58
+ return Attribute.flatten(attributes)
59
+ end
60
+
61
+ def each
62
+ vertex_offset = 0
63
+
64
+ @counts.each do |count|
65
+ # Grap all the vertices
66
+ weights = count.times.collect do |vertex_index|
67
+ vertex(vertex_offset + vertex_index)
68
+ end
69
+
70
+ yield weights
71
+
72
+ vertex_offset += count
73
+ end
74
+ end
75
+
76
+ def self.parse(doc, element, sources)
77
+ inputs = parse_inputs(doc, element, sources)
78
+
79
+ counts = element.elements['vcount'].text.split(/\s+/).collect &:to_i
80
+ vertices = element.elements['v'].text.split(/\s+/).collect &:to_i
81
+
82
+ self.new(element.attributes['id'], inputs, counts, vertices)
83
+ end
84
+ end
85
+
86
+ def initialize(id, name, source, bind_pose_transform, sources, joints, weights)
87
+ super id, name
88
+
89
+ @bind_pose_transform = bind_pose_transform
90
+
91
+ @source = source
92
+ @sources = sources
93
+ @joints = joints
94
+
95
+ @weights = weights
96
+ end
97
+
98
+ attr :source
99
+
100
+ attr :bind_pose_transform
101
+
102
+ attr :sources
103
+ attr :joints
104
+
105
+ # May contain multiple weights per vertex.
106
+ attr :weights
107
+
108
+ def self.parse(doc, controller_element, element)
109
+ id = controller_element.attributes['id']
110
+ name = controller_element.attributes['name']
111
+ source = Reference.new(:geometries, element.attributes['source'])
112
+
113
+ bind_pose_transform = extract_float4x4_matrix(element.elements['bind_shape_matrix'].text)
114
+
115
+ arrays = Source.parse_arrays(doc, element)
116
+
117
+ sources = OrderedMap.parse(element, 'source') do |source_element|
118
+ Source.parse(doc, source_element, arrays)
119
+ end
120
+
121
+ joints = Joints.parse(doc, element.elements['joints'], sources)
122
+
123
+ vertex_weights = VertexWeights.parse(doc, element.elements['vertex_weights'], sources)
124
+
125
+ self.new(id, name, source, bind_pose_transform, sources, joints, vertex_weights)
126
+ end
127
+
128
+ private
129
+
130
+ def self.extract_float4x4_matrix(text)
131
+ values = text.split(/\s+/).collect &:to_f
132
+
133
+ Matrix[*(values.each_slice(4).to_a)]
134
+ end
135
+ end
136
+
137
+ def initialize(id, name)
138
+ @id = id
139
+ @name = name
140
+ end
141
+
142
+ attr :id
143
+ attr :name
144
+
145
+ def self.parse(doc, element)
146
+ element.elements.each('skin') do |skin_element|
147
+ return Skin.parse(doc, element, skin_element)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -24,144 +24,61 @@ module Collada
24
24
  module Parser
25
25
  class Geometry
26
26
  class Mesh
27
- class Parameter
28
- def initialize(name, type)
29
- @name = name
30
- @type = type
31
- end
32
-
33
- attr :name
34
- attr :type
35
-
36
- def self.parse(doc, element)
37
- self.new(element.attributes['name'], element.attributes['type'].to_sym)
38
- end
39
- end
40
-
41
- class Accessor
27
+ # A source that provides individual vertices:
28
+ class Vertices
42
29
  include Enumerable
43
30
 
44
- def initialize(array, parameters, options = {})
45
- @array = array
46
- @parameters = parameters
47
- @names = parameters.collect{|parameter| parameter.name}
48
-
49
- @offset = (options[:offset] || 0).to_i
50
- @stride = (options[:stride] || parameters.size).to_i
51
- end
52
-
53
- attr :array
54
- attr :parameters
55
- attr :names
56
-
57
- attr :offset
58
- attr :stride
59
-
60
- def read index
61
- base = @offset + (index * @stride)
62
- values = @array[base, @parameters.size]
63
-
64
- @names.zip(values)
31
+ def initialize(id, inputs)
32
+ @id = id
33
+ @inputs = inputs
65
34
  end
66
35
 
67
- def [] index
68
- read(index).delete_if{|(name, value)| name == nil}
69
- end
36
+ attr :id
37
+ attr :inputs
70
38
 
71
39
  def size
72
- (@array.size - @offset) / @stride
73
- end
74
-
75
- def each
76
- size.times.each do |i|
77
- yield self[i]
78
- end
40
+ @inputs.collect{|input| input.size}.max
79
41
  end
80
42
 
81
- def self.parse_parameters(doc, element)
82
- OrderedMap.parse(element, 'param', 'name') do |param_element|
83
- Parameter.parse(doc, param_element)
84
- end
43
+ # Vertices by index, same interface as Input.
44
+ def [] index
45
+ @inputs.collect do |input|
46
+ input[index]
47
+ end + [Attribute.new(:vertex, :index => index)]
85
48
  end
86
49
 
87
- def self.parse(doc, element, arrays = {})
88
- if (array_id = element.attributes['source'])
89
- array_id.sub!(/^#/, '')
90
-
91
- array = arrays[array_id]
92
- else
93
- array = Mesh.parse_arrays(doc, element).first
50
+ def self.parse_inputs(doc, element, sources = {})
51
+ OrderedMap.parse(element, 'input') do |input_element|
52
+ Input.parse(doc, input_element, sources)
94
53
  end
95
-
96
- parameters = parse_parameters(doc, element)
97
-
98
- options = {
99
- :offset => element.attributes['offset'],
100
- :stride => element.attributes['stride'],
101
- }
102
-
103
- self.new(array, parameters, options)
104
- end
105
- end
106
-
107
- class Source
108
- def initialize(id, accessor)
109
- @id = id
110
- @accessor = accessor
111
54
  end
112
55
 
113
- attr :id
114
- attr :accessor
115
-
116
- def self.parse(doc, element, arrays = {})
117
- accessor = Accessor.parse(doc, element.elements['technique_common/accessor'], arrays)
118
-
119
- self.new(element.attributes['id'], accessor)
120
- end
121
- end
122
-
123
- class Input
124
- def initialize(semantic, source, offset = 0)
125
- @semantic = semantic
126
- @source = source
127
- @offset = offset
128
- end
129
-
130
- attr :semantic
131
- attr :source
132
- attr :offset
133
-
134
56
  def self.parse(doc, element, sources = {})
135
- semantic = element.attributes['semantic']
136
-
137
- if (source_id = element.attributes['source'])
138
- source_id.sub!(/^#/, '')
139
- source = sources[source_id]
140
- end
141
-
142
- offset = element.attributes['offset'] || 0
143
-
144
- self.new(semantic, source, offset.to_i)
57
+ inputs = parse_inputs(doc, element, sources)
58
+
59
+ self.new(element.attributes['id'], inputs)
145
60
  end
146
61
  end
147
62
 
148
- class TriangleVertices
149
- def count(index)
63
+ # Vertices are organised as triangles
64
+ class Triangles
65
+ def vertex_count index
150
66
  3
151
67
  end
152
68
  end
153
69
 
154
- class PolygonVertices
70
+ # Vertices are organised as arbitrary ngons.
71
+ class PolyList
155
72
  def initialize(counts)
156
73
  @counts = counts
157
74
  end
158
75
 
159
- def count(index)
76
+ def vertex_count index
160
77
  @counts[index]
161
78
  end
162
79
 
163
80
  def self.parse(doc, element)
164
- counts = element.elements('vcount').text.strip.split(/\s+/).collect &:to_i
81
+ counts = element.elements['vcount'].text.strip.split(/\s+/).collect &:to_i
165
82
 
166
83
  self.new(counts)
167
84
  end
@@ -170,15 +87,15 @@ module Collada
170
87
  class Polygons
171
88
  include Enumerable
172
89
 
173
- def initialize(inputs, indices, count, vertices)
90
+ def initialize(inputs, indices, size, elements)
174
91
  @inputs = inputs
175
92
  @indices = indices
176
93
 
177
94
  # The total number of polygons:
178
- @count = count
95
+ @size = size
179
96
 
180
97
  # The number of vertices per polygon:
181
- @vertices = vertices
98
+ @elements = elements
182
99
 
183
100
  # The number of indices per vertex:
184
101
  @stride = @inputs.sort_by(&:offset).last.offset + 1
@@ -187,49 +104,62 @@ module Collada
187
104
  attr :inputs
188
105
  attr :indices
189
106
 
190
- # Element count:
191
- attr :count
107
+ # Number of polygons
108
+ attr :size
192
109
 
193
- # Number of vertices per element:
194
- attr :vertices
110
+ # Per-element data:
111
+ attr :elements
195
112
 
196
113
  # Number of indices consumed per vertex:
197
114
  attr :stride
198
115
 
199
- def size
200
- @count
201
- end
202
-
203
116
  # Vertices by index:
204
- def [] index
117
+ def vertex(index)
205
118
  offset = @stride * index
206
119
 
207
- @inputs.collect do |input|
208
- input.source.accessor[@indices[offset + input.offset]]
120
+ attributes = @inputs.collect do |input|
121
+ input[@indices[offset + input.offset]]
209
122
  end
123
+
124
+ return Attribute.flatten(attributes)
210
125
  end
211
126
 
212
- def each
213
- consumed = 0
127
+ def each_indices
128
+ return to_enum(:each_indices) unless block_given?
214
129
 
215
- @count.times do |index|
216
- elements = @vertices.count(index)
217
- polygon = elements.times.collect{|edge| self[consumed + edge]}
130
+ vertex_offset = 0
131
+
132
+ @size.times do |index|
133
+ # There are n vertices per face:
134
+ vertex_count = @elements.vertex_count(index)
135
+
136
+ # Grap all the vertices
137
+ polygon_indices = vertex_count.times.collect do |vertex_index|
138
+ vertex_offset + vertex_index
139
+ end
218
140
 
219
- yield polygon
141
+ yield polygon_indices
220
142
 
221
- consumed += elements
143
+ vertex_offset += vertex_count
144
+ end
145
+ end
146
+
147
+ def each
148
+ return to_enum(:each) unless block_given?
149
+
150
+ each_indices.each do |indices|
151
+ yield indices.collect {|index| vertex(index)}
222
152
  end
223
153
  end
224
154
 
225
155
  def self.parse_inputs(doc, element, sources = {})
226
- OrderedMap.parse(element, '//input') do |input_element|
156
+ OrderedMap.parse(element, 'input') do |input_element|
227
157
  Input.parse(doc, input_element, sources)
228
158
  end
229
159
  end
230
160
 
231
161
  def self.parse_indices(doc, element)
232
- element.elements['p'].text.strip.split(/\s+/).collect{|index| index.to_i - 1}
162
+ element.elements['p'].text.strip.split(/\s+/).collect &:to_i
233
163
  end
234
164
 
235
165
  def self.parse(doc, element, sources = {})
@@ -238,54 +168,67 @@ module Collada
238
168
  count = element.attributes['count'].to_i
239
169
 
240
170
  if element.name == 'triangles'
241
- self.new(inputs, indices, count, TriangleVertices.new)
171
+ self.new(inputs, indices, count, Triangles.new)
242
172
  elsif element.name == 'polylist'
243
- self.new(inputs, indices, count, PolygonVertices.parse(doc, element))
173
+ self.new(inputs, indices, count, PolyList.parse(doc, element))
244
174
  else
245
175
  raise UnsupportedFeature.new(element)
246
176
  end
247
177
  end
248
178
  end
249
179
 
250
- def initialize(sources, polygons)
180
+ def initialize(sources, vertices, polygons)
251
181
  @sources = sources
182
+ @vertices = vertices
252
183
  @polygons = polygons
253
184
  end
254
185
 
255
186
  attr :sources
187
+ attr :vertices
256
188
  attr :polygons
257
189
 
258
- def self.parse_arrays(doc, element)
259
- OrderedMap.parse(element, '//float_array | //int_array', 'name') do |array_element|
260
- array_element.text.strip.split(/\s+/).collect &:to_f
261
- end
262
- end
263
-
264
190
  def self.parse(doc, element)
265
- arrays = parse_arrays(doc, element)
191
+ arrays = Source.parse_arrays(doc, element)
266
192
 
267
193
  sources = OrderedMap.parse(element, 'source') do |source_element|
268
194
  Source.parse(doc, source_element, arrays)
269
195
  end
270
196
 
197
+ if (vertices_element = element.elements['vertices'])
198
+ vertices = Vertices.parse(doc, vertices_element, sources)
199
+ sources.append(vertices.id, vertices)
200
+ end
201
+
271
202
  if (polygons_element = element.elements['triangles | polylist'])
272
203
  polygons = Polygons.parse(doc, polygons_element, sources)
273
204
  end
274
205
 
275
- self.new(sources, polygons)
206
+ self.new(sources, vertices, polygons)
276
207
  end
277
208
  end
278
209
 
279
- def initialize(mesh)
210
+ def initialize(id, name, mesh, attributes)
211
+ @id = id
212
+ @name = name
213
+
280
214
  @mesh = mesh
215
+
216
+ @attributes = attributes
281
217
  end
282
218
 
219
+ attr :id
220
+ attr :name
221
+
283
222
  attr :mesh
223
+ attr :attributes
284
224
 
285
225
  def self.parse(doc, element)
226
+ id = element.attributes['id']
227
+ name = element.attributes['name']
228
+
286
229
  mesh = Mesh.parse(doc, element.elements['mesh'])
287
230
 
288
- self.new(mesh)
231
+ self.new(id, name, mesh, element.attributes)
289
232
  end
290
233
  end
291
234
  end