collada 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.8.7"
4
+ - "1.9.2"
5
+ - "1.9.3"
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in trenni.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "rake"
8
+ end
@@ -0,0 +1,53 @@
1
+ # Collada
2
+
3
+ This library provides support for loading and processing data from Collada Digital Asset Exchange files. These files are typically used for sharing geometry and scenes.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'collada'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install collada
18
+
19
+ ## Usage
20
+
21
+ The main loader is incomplete can can only load geometry data at this point in time.
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
30
+
31
+ ## License
32
+
33
+ Released under the MIT license.
34
+
35
+ Copyright, 2012, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ of this software and associated documentation files (the "Software"), to deal
39
+ in the Software without restriction, including without limitation the rights
40
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ copies of the Software, and to permit persons to whom the Software is
42
+ furnished to do so, subject to the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be included in
45
+ all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
53
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'collada/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "collada"
8
+ gem.version = Collada::VERSION
9
+ gem.authors = ["Samuel Williams"]
10
+ gem.email = ["samuel.williams@oriontransfer.co.nz"]
11
+ gem.description = <<-EOF
12
+ This library provides support for loading and processing data from Collada
13
+ Digital Asset Exchange files. These files are typically used for sharing
14
+ geometry and scenes.
15
+ EOF
16
+ gem.summary = %q{A library for loading and manipulating Collada .dae files.}
17
+ gem.homepage = ""
18
+
19
+ gem.files = `git ls-files`.split($/)
20
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright, 2012, 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/version"
22
+
23
+ module Collada
24
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright, 2012, 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/library'
22
+
23
+ module Collada
24
+ end
@@ -0,0 +1,292 @@
1
+ # Copyright, 2012, 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 Geometry
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
42
+ include Enumerable
43
+
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)
65
+ end
66
+
67
+ def [] index
68
+ read(index).delete_if{|(name, value)| name == nil}
69
+ end
70
+
71
+ 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
79
+ end
80
+
81
+ def self.parse_parameters(doc, element)
82
+ OrderedMap.parse(element, 'param', 'name') do |param_element|
83
+ Parameter.parse(doc, param_element)
84
+ end
85
+ end
86
+
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
94
+ 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
+ end
112
+
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
+ 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)
145
+ end
146
+ end
147
+
148
+ class TriangleVertices
149
+ def count(index)
150
+ 3
151
+ end
152
+ end
153
+
154
+ class PolygonVertices
155
+ def initialize(counts)
156
+ @counts = counts
157
+ end
158
+
159
+ def count(index)
160
+ @counts[index]
161
+ end
162
+
163
+ def self.parse(doc, element)
164
+ counts = element.elements('vcount').text.strip.split(/\s+/).collect &:to_i
165
+
166
+ self.new(counts)
167
+ end
168
+ end
169
+
170
+ class Polygons
171
+ include Enumerable
172
+
173
+ def initialize(inputs, indices, count, vertices)
174
+ @inputs = inputs
175
+ @indices = indices
176
+
177
+ # The total number of polygons:
178
+ @count = count
179
+
180
+ # The number of vertices per polygon:
181
+ @vertices = vertices
182
+
183
+ # The number of indices per vertex:
184
+ @stride = @inputs.sort_by(&:offset).last.offset + 1
185
+ end
186
+
187
+ attr :inputs
188
+ attr :indices
189
+
190
+ # Element count:
191
+ attr :count
192
+
193
+ # Number of vertices per element:
194
+ attr :vertices
195
+
196
+ # Number of indices consumed per vertex:
197
+ attr :stride
198
+
199
+ def size
200
+ @count
201
+ end
202
+
203
+ # Vertices by index:
204
+ def [] index
205
+ offset = @stride * index
206
+
207
+ @inputs.collect do |input|
208
+ input.source.accessor[@indices[offset + input.offset]]
209
+ end
210
+ end
211
+
212
+ def each
213
+ consumed = 0
214
+
215
+ @count.times do |index|
216
+ elements = @vertices.count(index)
217
+ polygon = elements.times.collect{|edge| self[consumed + edge]}
218
+
219
+ yield polygon
220
+
221
+ consumed += elements
222
+ end
223
+ end
224
+
225
+ def self.parse_inputs(doc, element, sources = {})
226
+ OrderedMap.parse(element, '//input') do |input_element|
227
+ Input.parse(doc, input_element, sources)
228
+ end
229
+ end
230
+
231
+ def self.parse_indices(doc, element)
232
+ element.elements['p'].text.strip.split(/\s+/).collect{|index| index.to_i - 1}
233
+ end
234
+
235
+ def self.parse(doc, element, sources = {})
236
+ inputs = parse_inputs(doc, element, sources)
237
+ indices = parse_indices(doc, element)
238
+ count = element.attributes['count'].to_i
239
+
240
+ if element.name == 'triangles'
241
+ self.new(inputs, indices, count, TriangleVertices.new)
242
+ elsif element.name == 'polylist'
243
+ self.new(inputs, indices, count, PolygonVertices.parse(doc, element))
244
+ else
245
+ raise UnsupportedFeature.new(element)
246
+ end
247
+ end
248
+ end
249
+
250
+ def initialize(sources, polygons)
251
+ @sources = sources
252
+ @polygons = polygons
253
+ end
254
+
255
+ attr :sources
256
+ attr :polygons
257
+
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
+ def self.parse(doc, element)
265
+ arrays = parse_arrays(doc, element)
266
+
267
+ sources = OrderedMap.parse(element, 'source') do |source_element|
268
+ Source.parse(doc, source_element, arrays)
269
+ end
270
+
271
+ if (polygons_element = element.elements['triangles | polylist'])
272
+ polygons = Polygons.parse(doc, polygons_element, sources)
273
+ end
274
+
275
+ self.new(sources, polygons)
276
+ end
277
+ end
278
+
279
+ def initialize(mesh)
280
+ @mesh = mesh
281
+ end
282
+
283
+ attr :mesh
284
+
285
+ def self.parse(doc, element)
286
+ mesh = Mesh.parse(doc, element.elements['mesh'])
287
+
288
+ self.new(mesh)
289
+ end
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright, 2012, 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/visual_scene'
22
+ require 'collada/parser/geometry'
23
+
24
+ module Collada
25
+ module Parser
26
+ class Library
27
+ SECTIONS = {
28
+ :visual_scenes => ['COLLADA/library_visual_scenes/visual_scene', VisualScene],
29
+ :geometries => ['COLLADA/library_geometries/geometry', Geometry]
30
+ }
31
+
32
+ def initialize(sections = {})
33
+ @sections = sections
34
+ end
35
+
36
+ def [] key
37
+ @sections[key]
38
+ end
39
+
40
+ def self.parse(doc)
41
+ sections = {}
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)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,410 @@
1
+ # Copyright, 2012, 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 'rexml/document'
22
+ require 'yaml'
23
+
24
+ module Collada
25
+ module Parser
26
+ class UnsupportedFeature < StandardError
27
+ end
28
+
29
+ class OrderedMap
30
+ include Enumerable
31
+
32
+ def initialize(ordered, indexed)
33
+ @ordered = ordered
34
+ @indexed = indexed
35
+ end
36
+
37
+ attr :ordered
38
+ attr :indexed
39
+
40
+ def [] key
41
+ @indexed[key]
42
+ end
43
+
44
+ def each(&block)
45
+ @ordered.each(&block)
46
+ end
47
+
48
+ def size
49
+ @ordered.size
50
+ end
51
+
52
+ def self.parse(top, path, id_key = 'id')
53
+ ordered = []
54
+ indexed = {}
55
+
56
+ top.elements.each(path) do |element|
57
+ id = element.attributes[id_key]
58
+ value = (yield element)
59
+
60
+ indexed[id] = value if id
61
+ ordered << value
62
+ end
63
+
64
+ return OrderedMap.new(ordered, indexed)
65
+ end
66
+ end
67
+
68
+ class Geometry
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
+ class TriangleVertices
196
+ def count(index)
197
+ 3
198
+ end
199
+ end
200
+
201
+ class PolygonVertices
202
+ def initialize(counts)
203
+ @counts = counts
204
+ end
205
+
206
+ def count(index)
207
+ @counts[index]
208
+ end
209
+
210
+ def self.parse(doc, element)
211
+ counts = element.elements('vcount').text.strip.split(/\s+/).collect &:to_i
212
+
213
+ self.new(counts)
214
+ end
215
+ end
216
+
217
+ class Polygons
218
+ include Enumerable
219
+
220
+ def initialize(inputs, indices, count, vertices)
221
+ @inputs = inputs
222
+ @indices = indices
223
+
224
+ # The total number of polygons:
225
+ @count = count
226
+
227
+ # The number of vertices per polygon:
228
+ @vertices = vertices
229
+
230
+ # The number of indices per vertex:
231
+ @stride = @inputs.sort_by(&:offset).last.offset + 1
232
+ end
233
+
234
+ attr :inputs
235
+ attr :indices
236
+
237
+ # Element count:
238
+ attr :count
239
+
240
+ # Number of vertices per element:
241
+ attr :vertices
242
+
243
+ # Number of indices consumed per vertex:
244
+ attr :stride
245
+
246
+ def size
247
+ @count
248
+ end
249
+
250
+ # Vertices by index:
251
+ def [] index
252
+ offset = @stride * index
253
+
254
+ @inputs.collect do |input|
255
+ input.source.accessor[@indices[offset + input.offset]]
256
+ end
257
+ end
258
+
259
+ def each
260
+ consumed = 0
261
+
262
+ @count.times do |index|
263
+ elements = @vertices.count(index)
264
+ polygon = elements.times.collect{|edge| self[consumed + edge]}
265
+
266
+ yield polygon
267
+
268
+ consumed += elements
269
+ end
270
+ end
271
+
272
+ def self.parse_inputs(doc, element, sources = {})
273
+ OrderedMap.parse(element, '//input') do |input_element|
274
+ Input.parse(doc, input_element, sources)
275
+ end
276
+ end
277
+
278
+ def self.parse_indices(doc, element)
279
+ element.elements['p'].text.strip.split(/\s+/).collect{|index| index.to_i - 1}
280
+ end
281
+
282
+ def self.parse(doc, element, sources = {})
283
+ inputs = parse_inputs(doc, element, sources)
284
+ indices = parse_indices(doc, element)
285
+ count = element.attributes['count'].to_i
286
+
287
+ if element.name == 'triangles'
288
+ self.new(inputs, indices, count, TriangleVertices.new)
289
+ elsif element.name == 'polylist'
290
+ self.new(inputs, indices, count, PolygonVertices.parse(doc, element))
291
+ else
292
+ raise UnsupportedFeature.new(element)
293
+ end
294
+ end
295
+ end
296
+
297
+ def initialize(sources, polygons)
298
+ @sources = sources
299
+ @polygons = polygons
300
+ end
301
+
302
+ attr :sources
303
+ attr :polygons
304
+
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
+ def self.parse(doc, element)
312
+ arrays = parse_arrays(doc, element)
313
+
314
+ sources = OrderedMap.parse(element, 'source') do |source_element|
315
+ Source.parse(doc, source_element, arrays)
316
+ end
317
+
318
+ if (polygons_element = element.elements['triangles | polylist'])
319
+ polygons = Polygons.parse(doc, polygons_element, sources)
320
+ end
321
+
322
+ self.new(sources, polygons)
323
+ end
324
+ end
325
+
326
+ def initialize(mesh)
327
+ @mesh = mesh
328
+ end
329
+
330
+ attr :mesh
331
+
332
+ def self.parse(doc, element)
333
+ mesh = Mesh.parse(doc, element.elements['mesh'])
334
+
335
+ self.new(mesh)
336
+ end
337
+ end
338
+
339
+ class VisualScene
340
+ class Node
341
+ def initialize(structure = {})
342
+ @structure = structure
343
+ end
344
+
345
+ attr :structure
346
+
347
+ def self.parse(doc, element)
348
+ self.new(element)
349
+ end
350
+ end
351
+
352
+ def initialize(nodes = [])
353
+ @nodes = nodes
354
+ end
355
+
356
+ attr :nodes
357
+
358
+ def self.parse(doc, element)
359
+ nodes = []
360
+
361
+ element.elements.each('node') do |element|
362
+ nodes << Node.parse(doc, element)
363
+ end
364
+
365
+ self.new(nodes)
366
+ end
367
+ end
368
+
369
+ class Library
370
+ SECTIONS = {
371
+ :visual_scenes => ['COLLADA/library_visual_scenes/visual_scene', VisualScene],
372
+ :geometries => ['COLLADA/library_geometries/geometry', Geometry]
373
+ }
374
+
375
+ def initialize(sections = {})
376
+ @sections = sections
377
+ end
378
+
379
+ def [] key
380
+ @sections[key]
381
+ end
382
+
383
+ def self.parse(doc)
384
+ sections = {}
385
+
386
+ SECTIONS.each do |key, (path, klass)|
387
+ sections[key] = OrderedMap.parse(doc, path) do |element|
388
+ klass.parse(doc, element)
389
+ end
390
+ end
391
+
392
+ return Library.new(sections)
393
+ end
394
+ end
395
+
396
+ class Scene
397
+ def initialize(library)
398
+ @library = library
399
+ end
400
+
401
+ attr :library
402
+
403
+ def self.parse(doc)
404
+ scene = doc.elements['COLLADA/scene']
405
+
406
+ library = Library.parse(doc)
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,68 @@
1
+ # Copyright, 2012, 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 'rexml/document'
22
+ require 'yaml'
23
+
24
+ module Collada
25
+ module Parser
26
+ class UnsupportedFeature < StandardError
27
+ end
28
+
29
+ class OrderedMap
30
+ include Enumerable
31
+
32
+ def initialize(ordered, indexed)
33
+ @ordered = ordered
34
+ @indexed = indexed
35
+ end
36
+
37
+ attr :ordered
38
+ attr :indexed
39
+
40
+ def [] key
41
+ @indexed[key]
42
+ end
43
+
44
+ def each(&block)
45
+ @ordered.each(&block)
46
+ end
47
+
48
+ def size
49
+ @ordered.size
50
+ end
51
+
52
+ def self.parse(top, path, id_key = 'id')
53
+ ordered = []
54
+ indexed = {}
55
+
56
+ top.elements.each(path) do |element|
57
+ id = element.attributes[id_key]
58
+ value = (yield element)
59
+
60
+ indexed[id] = value if id
61
+ ordered << value
62
+ end
63
+
64
+ return OrderedMap.new(ordered, indexed)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,56 @@
1
+ # Copyright, 2012, 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 VisualScene
26
+ class Node
27
+ def initialize(structure = {})
28
+ @structure = structure
29
+ end
30
+
31
+ attr :structure
32
+
33
+ def self.parse(doc, element)
34
+ self.new(element)
35
+ end
36
+ end
37
+
38
+ def initialize(nodes = [])
39
+ @nodes = nodes
40
+ end
41
+
42
+ attr :nodes
43
+
44
+ def self.parse(doc, element)
45
+ nodes = []
46
+
47
+ element.elements.each('node') do |element|
48
+ nodes << Node.parse(doc, element)
49
+ end
50
+
51
+ self.new(nodes)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright, 2012, 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
+ module Collada
22
+ VERSION = "0.0.1"
23
+ end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'pathname'
24
+ require 'test/unit'
25
+ require 'stringio'
26
+
27
+ require 'collada/parser'
28
+
29
+ class TestParser < Test::Unit::TestCase
30
+ def test_accessors
31
+ parameters = [
32
+ Collada::Parser::Geometry::Mesh::Parameter.new('X', :float),
33
+ Collada::Parser::Geometry::Mesh::Parameter.new(nil, :float),
34
+ Collada::Parser::Geometry::Mesh::Parameter.new('Z', :float),
35
+ ]
36
+
37
+ accessor = Collada::Parser::Geometry::Mesh::Accessor.new(
38
+ [1, 2, 3, 4, 5, 6],
39
+ parameters
40
+ )
41
+
42
+ assert_equal [['X', 1], ['Z', 3]], accessor[0]
43
+ assert_equal [['X', 4], ['Z', 6]], accessor[1]
44
+
45
+ assert_equal 2, accessor.size
46
+ assert_equal [[["X", 1], ["Z", 3]], [["X", 4], ["Z", 6]]], accessor.to_a
47
+ end
48
+
49
+ def test_sources
50
+ chunk = <<-EOF
51
+ <?xml version="1.0" encoding="utf-8"?>
52
+ <mesh>
53
+ <source id="position">
54
+ <float_array name="values" count="30">
55
+ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
56
+ </float_array>
57
+ <technique_common>
58
+ <accessor source="#values" count="3" stride="10">
59
+ <param name="PX" type="float"/>
60
+ <param name="PY" type="float"/>
61
+ <param name="PZ" type="float"/>
62
+ </accessor>
63
+ </technique_common>
64
+ </source>
65
+ <source id="normal">
66
+ <technique_common>
67
+ <accessor source="#values" offset="3" count="3" stride="10">
68
+ <param name="NX" type="float"/>
69
+ <param name="NY" type="float"/>
70
+ <param name="NZ" type="float"/>
71
+ </accessor>
72
+ </technique_common>
73
+ </source>
74
+ <source id="mapping">
75
+ <technique_common>
76
+ <accessor source="#values" offset="6" count="3" stride="10">
77
+ <param name="TU" type="float"/>
78
+ <param name="TV" type="float"/>
79
+ </accessor>
80
+ </technique_common>
81
+ </source>
82
+
83
+ <triangles count="1">
84
+ <input semantic="POSITION" source="#position" offset="0"/>
85
+ <input semantic="NORMAL" source="#normal" offset="0"/>
86
+ <input semantic="TEXCOORD" source="#mapping" offset="0"/>
87
+ <p>1 2 3</p>
88
+ </triangles>
89
+ </mesh>
90
+ EOF
91
+
92
+ doc = REXML::Document.new(chunk)
93
+ mesh = Collada::Parser::Geometry::Mesh.parse(doc, doc.elements['mesh'])
94
+
95
+ expected = [
96
+ [[["PX", 1.0], ["PY", 2.0], ["PZ", 3.0]], [["NX", 4.0], ["NY", 5.0], ["NZ", 6.0]], [["TU", 7.0], ["TV", 8.0]]],
97
+ [[["PX", 11.0], ["PY", 12.0], ["PZ", 13.0]], [["NX", 14.0], ["NY", 15.0], ["NZ", 16.0]], [["TU", 17.0], ["TV", 18.0]]],
98
+ [[["PX", 21.0], ["PY", 22.0], ["PZ", 23.0]], [["NX", 24.0], ["NY", 25.0], ["NZ", 26.0]], [["TU", 27.0], ["TV", 28.0]]],
99
+ ]
100
+
101
+ assert_equal expected[0], mesh.polygons[0]
102
+ assert_equal expected[1], mesh.polygons[1]
103
+ assert_equal expected[2], mesh.polygons[2]
104
+ end
105
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: collada
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Samuel Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! " This library provides support for loading and processing data from
15
+ Collada \n Digital Asset Exchange files. These files are typically used for sharing\n
16
+ \ geometry and scenes.\n"
17
+ email:
18
+ - samuel.williams@oriontransfer.co.nz
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - .travis.yml
25
+ - Gemfile
26
+ - README.md
27
+ - Rakefile
28
+ - collada.gemspec
29
+ - lib/collada.rb
30
+ - lib/collada/parser.rb
31
+ - lib/collada/parser/geometry.rb
32
+ - lib/collada/parser/library.rb
33
+ - lib/collada/parser/scene.rb
34
+ - lib/collada/parser/support.rb
35
+ - lib/collada/parser/visual_scene.rb
36
+ - lib/collada/version.rb
37
+ - test/test_parser.rb
38
+ homepage: ''
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ segments:
51
+ - 0
52
+ hash: 2074081511210776165
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ segments:
60
+ - 0
61
+ hash: 2074081511210776165
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 1.8.24
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A library for loading and manipulating Collada .dae files.
68
+ test_files:
69
+ - test/test_parser.rb