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
@@ -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
|
-
|
28
|
-
|
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(
|
45
|
-
@
|
46
|
-
@
|
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
|
-
|
68
|
-
|
69
|
-
end
|
36
|
+
attr :id
|
37
|
+
attr :inputs
|
70
38
|
|
71
39
|
def size
|
72
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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.
|
88
|
-
|
89
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
149
|
-
|
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
|
-
|
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
|
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
|
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,
|
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
|
-
@
|
95
|
+
@size = size
|
179
96
|
|
180
97
|
# The number of vertices per polygon:
|
181
|
-
@
|
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
|
-
#
|
191
|
-
attr :
|
107
|
+
# Number of polygons
|
108
|
+
attr :size
|
192
109
|
|
193
|
-
#
|
194
|
-
attr :
|
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
|
117
|
+
def vertex(index)
|
205
118
|
offset = @stride * index
|
206
119
|
|
207
|
-
@inputs.collect do |input|
|
208
|
-
input
|
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
|
213
|
-
|
127
|
+
def each_indices
|
128
|
+
return to_enum(:each_indices) unless block_given?
|
214
129
|
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
141
|
+
yield polygon_indices
|
220
142
|
|
221
|
-
|
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, '
|
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
|
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,
|
171
|
+
self.new(inputs, indices, count, Triangles.new)
|
242
172
|
elsif element.name == 'polylist'
|
243
|
-
self.new(inputs, indices, count,
|
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
|