fml 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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