fml 0.2.1

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.
Files changed (44) hide show
  1. data/.gitignore +8 -0
  2. data/README +1 -0
  3. data/Rakefile +3 -0
  4. data/bin/fml2dae.rb +15 -0
  5. data/bin/fml2obj.rb +10 -0
  6. data/fml.gemspec +36 -0
  7. data/lib/collada/document.rb +101 -0
  8. data/lib/collada/geometry.rb +110 -0
  9. data/lib/config.yml +17 -0
  10. data/lib/floorplanner/area_builder.rb +42 -0
  11. data/lib/floorplanner/asset.rb +185 -0
  12. data/lib/floorplanner/collada_export.rb +118 -0
  13. data/lib/floorplanner/design.rb +108 -0
  14. data/lib/floorplanner/document.rb +68 -0
  15. data/lib/floorplanner/obj_export.rb +24 -0
  16. data/lib/floorplanner/opening3d.rb +140 -0
  17. data/lib/floorplanner/rib_export.rb +24 -0
  18. data/lib/floorplanner/svg_export.rb +25 -0
  19. data/lib/floorplanner/wall3d.rb +97 -0
  20. data/lib/floorplanner/wall_builder.rb +165 -0
  21. data/lib/floorplanner.rb +52 -0
  22. data/lib/geom/connection.rb +14 -0
  23. data/lib/geom/ear_trim.rb +52 -0
  24. data/lib/geom/edge.rb +89 -0
  25. data/lib/geom/glu_tess.rb +34 -0
  26. data/lib/geom/intersection.rb +38 -0
  27. data/lib/geom/matrix3d.rb +141 -0
  28. data/lib/geom/number.rb +104 -0
  29. data/lib/geom/plane.rb +36 -0
  30. data/lib/geom/polygon.rb +264 -0
  31. data/lib/geom/triangle.rb +38 -0
  32. data/lib/geom/triangle_mesh.rb +94 -0
  33. data/lib/geom/vertex.rb +33 -0
  34. data/lib/geom.rb +13 -0
  35. data/lib/keyhole/archive.rb +36 -0
  36. data/tasks/github-gem.rake +315 -0
  37. data/views/design.dae.erb +439 -0
  38. data/views/design.obj.erb +17 -0
  39. data/views/design.rib.erb +17 -0
  40. data/views/design.svg.erb +42 -0
  41. data/xml/collada_schema_1_4.xsd +11046 -0
  42. data/xml/fml.rng +268 -0
  43. data/xml/fml2kml.xsl +59 -0
  44. metadata +117 -0
@@ -0,0 +1,264 @@
1
+ module Geom
2
+ class Polygon < TriangleMesh
3
+
4
+ WINDING_CW = 0
5
+ WINDING_CCW = 1
6
+
7
+ AXIS_X = 1
8
+ AXIS_Y = 2
9
+ AXIS_Z = 3
10
+
11
+ CAP_TOP = 4
12
+ CAP_BASE = 5
13
+ CAP_BOTH = 6
14
+
15
+ def clone
16
+ vertices = @vertices.collect{|v| v.clone}
17
+ texcoord = @texcoord ? @texcoord.collect{|uv| uv.clone} : nil
18
+ faces = @faces.collect do |f|
19
+ if texcoord
20
+ Triangle.new([
21
+ vertices[ @vertices.index(f.vertices[0]) ],
22
+ vertices[ @vertices.index(f.vertices[1]) ],
23
+ vertices[ @vertices.index(f.vertices[2]) ]
24
+ ],[
25
+ texcoord[ @texcoord.index(f.texcoord[0]) ],
26
+ texcoord[ @texcoord.index(f.texcoord[1]) ],
27
+ texcoord[ @texcoord.index(f.texcoord[2]) ]
28
+ ])
29
+ else
30
+ Triangle.new([
31
+ vertices[ @vertices.index(f.vertices[0]) ],
32
+ vertices[ @vertices.index(f.vertices[1]) ],
33
+ vertices[ @vertices.index(f.vertices[2]) ]
34
+ ])
35
+ end
36
+ end
37
+ result = Polygon.new(vertices,faces,@data.dup)
38
+ result.texcoord = texcoord
39
+ result
40
+ end
41
+
42
+ def update
43
+ return false if @vertices.length < 3
44
+ triangles = @tess.triangulate(self)
45
+ unless triangles
46
+ @vertices.reverse!
47
+ triangles = @tess.triangulate(self)
48
+ return false unless triangles
49
+ end
50
+
51
+ @texcoord = calc_uv
52
+ triangles.each do |t|
53
+ v0 = @vertices[t[0]]
54
+ v1 = @vertices[t[1]]
55
+ v2 = @vertices[t[2]]
56
+
57
+ t0 = @texcoord[t[0]]
58
+ t1 = @texcoord[t[1]]
59
+ t2 = @texcoord[t[2]]
60
+ @faces.push(Triangle.new([v2,v1,v0],[t0,t1,t2]))
61
+ end
62
+ true
63
+ end
64
+
65
+ def area
66
+ # remove duplicates and invisibles
67
+ return nil if @vertices.length < 3
68
+ @vertices.each{|v| return 0 if @vertices.grep(v).length>1}
69
+
70
+ result = 0
71
+ points = @vertices.dup
72
+ plane = self.plane
73
+
74
+ ax = plane.normal.x > 0 ? plane.normal.x : -plane.normal.x
75
+ ay = plane.normal.y > 0 ? plane.normal.y : -plane.normal.y
76
+ az = plane.normal.z > 0 ? plane.normal.z : -plane.normal.z
77
+
78
+ coord = AXIS_Z
79
+
80
+ if ax > ay
81
+ if ax > az
82
+ coord = AXIS_X
83
+ end
84
+ elsif ay > az
85
+ coord = AXIS_Y
86
+ end
87
+
88
+ points.push(points[0],points[1])
89
+
90
+ # compute area of the 2D projection
91
+ points.each_with_index do |point,i|
92
+ next if i.zero?
93
+ j = (i+1) % points.length
94
+ k = (i-1) % points.length
95
+
96
+ case coord
97
+ when AXIS_X
98
+ result += (point.y * (points[j].z - points[k].z))
99
+ when AXIS_Y
100
+ result += (point.x * (points[j].z - points[k].z))
101
+ else
102
+ result += (point.x * (points[j].y - points[k].y))
103
+ end
104
+ end
105
+
106
+ # scale to get area before projection
107
+ an = Math.sqrt(ax**2 + ay**2 + az**2) # length of normal vector
108
+ case coord
109
+ when AXIS_X
110
+ result *= (an / (2*ax))
111
+ when AXIS_Y
112
+ result *= (an / (2*ay))
113
+ when AXIS_Z
114
+ result *= (an / (2*az))
115
+ end
116
+ 2.times {points.pop}
117
+ result
118
+ end
119
+
120
+ # TODO real plane, not just from first 3 vertices
121
+ def plane
122
+ return nil if @vertices.length < 3
123
+ Plane.three_points(*@vertices[0..2])
124
+ end
125
+
126
+ def point_inside(pt)
127
+ x = "x"
128
+ y = "y"
129
+ n = @vertices.length
130
+ dominant = dominant_axis
131
+ dist = self.plane.distance(pt)
132
+ result = false
133
+
134
+ return false if dist.abs > 0.01
135
+ case dominant
136
+ when AXIS_X
137
+ x = "y"
138
+ y = "z"
139
+ when AXIS_Y
140
+ y = "z"
141
+ end
142
+
143
+ @vertices.each_with_index do |v,i|
144
+ vn = @vertices[(i+1)%n] # next
145
+ if (((v.send(y) <= pt.send(y)) && (pt.send(y) < vn.send(y))) || ((vn.send(y) <= pt.send(y)) && (pt.send(y) < v.send(y)))) &&
146
+ (pt.send(x) < (vn.send(x) - v.send(x)) * (pt.send(y) - v.send(y)) / (vn.send(y) - v.send(y)) + v.send(x))
147
+ result = !result
148
+ end
149
+ end
150
+ result
151
+ end
152
+
153
+ def winding
154
+ area < 0 ? WINDING_CW : WINDING_CCW
155
+ end
156
+
157
+ def extrude(distance,direction,cap=CAP_BOTH,update=true)
158
+ direction.normalize
159
+ top_cap = clone
160
+ top_cap.vertices.each do |v|
161
+ v.x += distance*direction.x
162
+ v.y += distance*direction.y
163
+ v.z += distance*direction.z
164
+ end
165
+ top_cap.faces.each {|f| f.normal = direction }
166
+ num = @vertices.length
167
+
168
+ sides = Array.new(@vertices.length).map!{ Polygon.new }
169
+ sides.each_with_index do |side,i|
170
+ j = (i+1) % num
171
+ side.vertices.push(top_cap.vertices[i],top_cap.vertices[j])
172
+ side.vertices.push(@vertices[j],@vertices[i])
173
+ side.data[:side] = true
174
+ side.update if update
175
+ end
176
+
177
+ case cap
178
+ when CAP_BASE
179
+ top_cap.faces.clear
180
+ when CAP_TOP
181
+ self.faces.clear
182
+ when CAP_BOTH
183
+ self.faces.each do |f|
184
+ f.flip_normal
185
+ end
186
+ end
187
+
188
+ sides + [top_cap]
189
+ end
190
+
191
+ def dominant_axis
192
+ plane = self.plane
193
+ return 0 unless plane
194
+
195
+ ax = (plane.normal.x > 0 ? plane.normal.x : -plane.normal.x)
196
+ ay = (plane.normal.y > 0 ? plane.normal.y : -plane.normal.y)
197
+ az = (plane.normal.z > 0 ? plane.normal.z : -plane.normal.z)
198
+
199
+ axis = AXIS_Z
200
+ if ax > ay
201
+ if ax > az
202
+ axis = AXIS_X
203
+ end
204
+ elsif ay > az
205
+ axis = AXIS_Y
206
+ end
207
+ axis
208
+ end
209
+
210
+ def calc_uv
211
+ result = []
212
+ plane = self.plane
213
+ up = Number3D.new( 0, 1, 0 )
214
+
215
+ # get side vector
216
+ side = Number3D.cross(up, plane.normal)
217
+ side.normalize
218
+
219
+ # adjust up vector
220
+ up = Number3D.cross(self.plane.normal, side)
221
+ up.normalize
222
+
223
+ matrix = Matrix3D[
224
+ [side.x, up.x, plane.normal.x, 0],
225
+ [side.y, up.y, plane.normal.y, 0],
226
+ [side.z, up.z, plane.normal.z, 0],
227
+ [0, 0, 0, 1]]
228
+
229
+ v, n, t = nil, nil, nil
230
+ min = Number3D.new(1000,1000,1000)
231
+ max = Number3D.new(-min.x, -min.y, -min.z)
232
+ pts = []
233
+
234
+ @vertices.each do |v|
235
+ n = v.position
236
+
237
+ # Matrix3D.multiplyVector3x3( matrix, n );
238
+
239
+ min.x = n.x if n.x < min.x
240
+ min.y = n.y if n.y < min.y
241
+ max.x = n.x if n.x > max.x
242
+ max.y = n.y if n.y > max.y
243
+
244
+ pts << n
245
+ result << NumberUV.new
246
+ end
247
+
248
+ w = max.x - min.x
249
+ h = max.y - min.y
250
+ size = w < h ? h : w
251
+
252
+ @vertices.each_with_index do |v,i|
253
+ n = pts[i]
254
+ t = result[i]
255
+
256
+ t.u = ((n.x - min.x) / size) * size
257
+ t.v = ((n.y - min.y) / size) * size
258
+ end
259
+
260
+ result
261
+ end
262
+
263
+ end
264
+ end
@@ -0,0 +1,38 @@
1
+ module Geom
2
+ class Triangle
3
+
4
+ attr_accessor :vertices, :texcoord, :normal
5
+
6
+ def initialize(vertices,texcoord=nil,normal=nil)
7
+ unless vertices.length == 3
8
+ raise "Triangle must consist of exactly 3 vertices"
9
+ end
10
+ @normal = normal || Number3D.new
11
+ @vertices = vertices
12
+ @texcoord = texcoord
13
+ create_normal
14
+ end
15
+
16
+ def flip_normal
17
+ @normal.x = -@normal.x
18
+ @normal.y = -@normal.y
19
+ @normal.z = -@normal.z
20
+ end
21
+
22
+ def clone
23
+ Triangle.new(@vertices.collect{|v| v.clone},@texcoord.collect{|uv| uv.clone})
24
+ end
25
+
26
+ private
27
+ def create_normal
28
+ vn0 = @vertices[0].position.clone
29
+ vn1 = @vertices[1].position.clone
30
+ vn2 = @vertices[2].position.clone
31
+ vn1.minus_eq(vn0)
32
+ vn2.minus_eq(vn0)
33
+ @normal = Number3D.cross(vn1,vn2,@normal)
34
+ @normal.normalize
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,94 @@
1
+ module Geom
2
+ class TriangleMesh
3
+
4
+ attr_accessor(:vertices,:texcoord,:faces,:meshes,:data,:tess)
5
+
6
+ def initialize( vertices=nil, faces=nil, user_data=nil )
7
+ @data = user_data || Hash.new
8
+ @faces = faces || Array.new
9
+ @vertices = vertices || Array.new
10
+ @meshes = Array.new
11
+
12
+ @tess = EarTrim
13
+ end
14
+
15
+ def << (mesh)
16
+ @meshes << mesh
17
+ end
18
+
19
+ def vertices
20
+ result = @meshes.collect {|m| m.vertices}.flatten
21
+ result.empty? ? @vertices : result
22
+ end
23
+
24
+ def faces
25
+ result = @meshes.collect {|m| m.faces}.flatten
26
+ result.empty? ? @faces : result
27
+ end
28
+
29
+ def texcoord
30
+ result = @meshes.collect {|m| m.texcoord}.flatten
31
+ result.empty? ? @texcoord : result
32
+ end
33
+
34
+ def update
35
+ @meshes.each {|m| m.update}
36
+ end
37
+
38
+ def reverse
39
+ faces.each {|f| f.vertices.reverse! }
40
+ end
41
+
42
+ def transform_vertices(transformation)
43
+ ta = transformation.to_a
44
+ m11 = ta[0][0]
45
+ m12 = ta[0][1]
46
+ m13 = ta[0][2]
47
+ m21 = ta[1][0]
48
+ m22 = ta[1][1]
49
+ m23 = ta[1][2]
50
+ m31 = ta[2][0]
51
+ m32 = ta[2][1]
52
+ m33 = ta[2][2]
53
+
54
+ m14 = ta[0][3]
55
+ m24 = ta[1][3]
56
+ m34 = ta[2][3]
57
+
58
+ @vertices.each do |v|
59
+ vx = v.x
60
+ vy = v.y
61
+ vz = v.z
62
+
63
+ tx = vx * m11 + vy * m12 + vz * m13 + m14
64
+ ty = vx * m21 + vy * m22 + vz * m23 + m24
65
+ tz = vx * m31 + vy * m32 + vz * m33 + m34
66
+
67
+ v.x = tx
68
+ v.y = ty
69
+ v.z = tz
70
+ end
71
+ end
72
+
73
+ def bounding_box
74
+ min = Geom::Number3D.new( 1000, 1000, 1000)
75
+ max = Geom::Number3D.new(-1000,-1000,-1000)
76
+
77
+ vertices.each do |v|
78
+ min.x = v.x if v.x < min.x
79
+ min.y = v.y if v.y < min.y
80
+ min.z = v.z if v.z < min.z
81
+
82
+ max.x = v.x if v.x > max.x
83
+ max.y = v.y if v.y > max.y
84
+ max.z = v.z if v.z > max.z
85
+ end
86
+
87
+ { :max => max , :min => min }
88
+ end
89
+
90
+ def merge(other)
91
+ @vertices.concat other.vertices
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,33 @@
1
+ require 'forwardable'
2
+
3
+ module Geom
4
+ class Vertex
5
+ attr_accessor(:position,:normal)
6
+ extend Forwardable
7
+ def_delegators(:@position,
8
+ :x, :y, :z, :x=, :y=, :z=,
9
+ :distance_x, :distance_y, :distance_z, :distance, :to_floats)
10
+ def initialize(x=0.0,y=0.0,z=0.0,normal=nil)
11
+ @position = Number3D.new(x,y,z)
12
+ @normal = normal
13
+ end
14
+
15
+ def == (other)
16
+ @position == other.position
17
+ end
18
+
19
+ def equal?(other,snap)
20
+ @position.x-snap < other.x && @position.x+snap > other.x &&
21
+ @position.y-snap < other.y && @position.y+snap > other.y &&
22
+ @position.z-snap < other.z && @position.z+snap > other.z
23
+ end
24
+
25
+ def clone
26
+ Vertex.new(x,y,z)
27
+ end
28
+
29
+ def to_s
30
+ "#<Geom::Vertex:#{@position.to_s}>"
31
+ end
32
+ end
33
+ end
data/lib/geom.rb ADDED
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.push(File.dirname(__FILE__))
2
+ require 'matrix'
3
+ require 'geom/number'
4
+ require 'geom/vertex'
5
+ require 'geom/triangle_mesh'
6
+ require 'geom/polygon'
7
+ require 'geom/triangle'
8
+ require 'geom/plane'
9
+ require 'geom/matrix3d'
10
+ require 'geom/intersection'
11
+ require 'geom/connection'
12
+ require 'geom/edge'
13
+ require 'geom/ear_trim'
@@ -0,0 +1,36 @@
1
+ module Keyhole
2
+ class Archive < Zip::ZipFile
3
+ DAE_CACHE_PATH = File.join(Floorplanner.config['dae_cache_path'],'dae')
4
+ IMG_CACHE_PATH = File.join(Floorplanner.config['dae_cache_path'],'textures')
5
+
6
+ def dae_path(asset_id)
7
+ FileUtils.mkdir_p DAE_CACHE_PATH
8
+ dae = entries.select{|e| e.name.match(/\.dae$/)}.first
9
+ @relative_dae_path = dae.name
10
+ @dae_path = File.join(
11
+ DAE_CACHE_PATH,
12
+ "#{asset_id}_#{File.basename(dae.name)}"
13
+ )
14
+ dae.extract(@dae_path) unless File.exists?(@dae_path)
15
+ @dae_path
16
+ end
17
+
18
+ def image_path(asset_id,relative_to_dae,relative_out=false)
19
+ FileUtils.mkdir_p IMG_CACHE_PATH
20
+ img_path = File.join(File.dirname(@relative_dae_path),relative_to_dae)
21
+ target_path = File.join(IMG_CACHE_PATH,asset_id)
22
+ tex_path = File.join(target_path,File.basename(img_path))
23
+
24
+ unless File.exists?(tex_path)
25
+ FileUtils.mkdir_p target_path
26
+ zip_image = entries.select{|e| e.name.match(File.basename(img_path)) }.first
27
+ extract( zip_image , tex_path )
28
+ end
29
+ relative_out ? '../textures'+tex_path.gsub(IMG_CACHE_PATH,'') : tex_path
30
+ end
31
+
32
+ def destroy
33
+ File.unlink(@dae_path)
34
+ end
35
+ end
36
+ end