geo2d 0.1.0 → 0.1.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.
- data/VERSION +1 -1
- data/geo2d.gemspec +1 -1
- data/lib/geo2d.rb +127 -103
- data/test/test_geo2d.rb +51 -0
- metadata +1 -1
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/geo2d.gemspec
CHANGED
data/lib/geo2d.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
1
|
# Planar geometry of points and line-strings
|
2
2
|
module Geo2D
|
3
|
-
|
3
|
+
|
4
4
|
# Planar vectors; used also to represent points of the plane
|
5
5
|
class Vector
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(x=0, y=0)
|
8
8
|
@x = x.to_f
|
9
9
|
@y = y.to_f
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
attr_accessor :x, :y
|
13
|
-
|
13
|
+
|
14
14
|
def modulus
|
15
15
|
Math.hypot(self.x, self.y)
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def length
|
19
19
|
modulus
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def argument
|
23
23
|
Math.atan2(self.y, self.x)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def +(other)
|
27
27
|
other = Geo2D.Vector(other)
|
28
28
|
Vector.new(self.x+other.x, self.y+other.y)
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def -(other)
|
32
32
|
other = Geo2D.Vector(other)
|
33
33
|
Vector.new(self.x-other.x, self.y-other.y)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def *(scalar_or_vector)
|
37
37
|
if Numeric===scalar_or_vector
|
38
38
|
# scalar product
|
@@ -43,70 +43,74 @@ module Geo2D
|
|
43
43
|
self.x*other.x + self.y*other.y
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def /(scalar)
|
48
48
|
# self * 1.0/scalar
|
49
49
|
Vector.new(self.x/scalar, self.y/scalar)
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
# z coordinate of cross product
|
53
53
|
def cross_z(other)
|
54
54
|
self.x*other.y - other.x*self.y
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def dot(other)
|
58
58
|
self.x*other.x + self.y*other.y
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
def ==(other)
|
62
62
|
self.x == other.x && self.y == other.y
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
def to_a
|
66
66
|
[self.x, self.y]
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
def to_s
|
70
70
|
"(#{self.x}, #{self.y})"
|
71
71
|
end
|
72
|
-
|
73
|
-
|
72
|
+
|
73
|
+
|
74
74
|
def split
|
75
75
|
to_a
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
# unitary vector in the direction of self
|
79
79
|
def unitary
|
80
80
|
self / self.modulus
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
# vector rotated 90 degrees counter-clockwise
|
84
84
|
def ortho
|
85
85
|
Vector.new(-self.y, self.x)
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
# angle between two vectors
|
89
89
|
def angle_to(other)
|
90
90
|
Math.atan2(cross_z(other), dot(other))
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def aligned_with?(other)
|
94
94
|
cross_z == 0
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
# multiply by matrix [[a11, a12], [a21, a22]]
|
98
98
|
def transform(*t)
|
99
99
|
a11, a12, a21, a22 = t.flatten
|
100
100
|
x, y = self.x, self.y
|
101
101
|
Vector.new(a11*x + a12*y, a21*x + a22*y)
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
# Apply arbitrary transformation (passed as a Proc or as a block)
|
105
105
|
def apply(prc, &blk)
|
106
106
|
prc ||= blk
|
107
107
|
prc[self]
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
|
+
def bounds
|
111
|
+
[x,y,x,y]
|
112
|
+
end
|
113
|
+
|
110
114
|
def coerce(scalar)
|
111
115
|
if scalar.kind_of?(Numeric)
|
112
116
|
[self, scalar]
|
@@ -114,16 +118,24 @@ module Geo2D
|
|
114
118
|
raise ArgumentError, "Vector: cannot coerce #{scalar.class}"
|
115
119
|
end
|
116
120
|
end
|
117
|
-
|
121
|
+
|
118
122
|
end
|
119
|
-
|
123
|
+
|
124
|
+
def distance_to(other)
|
125
|
+
if other.kind_of?(Vector)
|
126
|
+
(other-self).modulus
|
127
|
+
else
|
128
|
+
other.distance_to?(self)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
120
132
|
module_function
|
121
|
-
|
133
|
+
|
122
134
|
# Vector constructor
|
123
135
|
def Vector(*args)
|
124
136
|
case args.size
|
125
137
|
when 2
|
126
|
-
x, y = args
|
138
|
+
x, y = args
|
127
139
|
when 1
|
128
140
|
arg = args.first
|
129
141
|
if arg.is_a?(Vector)
|
@@ -138,54 +150,54 @@ module Geo2D
|
|
138
150
|
if arg.respond_to?(:x) && arg.respond_to?(:y)
|
139
151
|
x, y = arg.x, arg.y
|
140
152
|
else
|
141
|
-
raise ArgumentError,"Invalid point definition"
|
153
|
+
raise ArgumentError,"Invalid point definition"
|
142
154
|
end
|
143
155
|
end
|
144
156
|
else
|
145
|
-
raise ArgumentError,"Invalid number of parameters for a point"
|
157
|
+
raise ArgumentError,"Invalid number of parameters for a point"
|
146
158
|
end
|
147
159
|
Vector.new(x,y)
|
148
160
|
end
|
149
|
-
|
161
|
+
|
150
162
|
# Line segment between two points (defined by Vectors)
|
151
163
|
class LineSegment
|
152
|
-
|
164
|
+
|
153
165
|
def initialize(p1, p2)
|
154
166
|
@start = p1
|
155
167
|
@end = p2
|
156
168
|
raise ArgumentError,"Degenerate LineSegment" if p1==p2
|
157
169
|
end
|
158
|
-
|
170
|
+
|
159
171
|
attr_reader :start, :end
|
160
|
-
|
172
|
+
|
161
173
|
def points
|
162
174
|
[@start, @end]
|
163
175
|
end
|
164
|
-
|
176
|
+
|
165
177
|
def n_points
|
166
178
|
2
|
167
179
|
end
|
168
|
-
|
180
|
+
|
169
181
|
def vector
|
170
182
|
@vector ||= (@end-@start)
|
171
183
|
end
|
172
|
-
|
184
|
+
|
173
185
|
def length
|
174
186
|
@length ||= vector.modulus
|
175
187
|
end
|
176
|
-
|
188
|
+
|
177
189
|
def angle
|
178
190
|
vector.argument
|
179
191
|
end
|
180
|
-
|
192
|
+
|
181
193
|
def angle_at(parallel_distance)
|
182
194
|
angle
|
183
195
|
end
|
184
|
-
|
196
|
+
|
185
197
|
def aligned_with?(point)
|
186
198
|
vector.aligned_width?(point-@start)
|
187
199
|
end
|
188
|
-
|
200
|
+
|
189
201
|
def contains?(point)
|
190
202
|
if self.aligned_with?(point)
|
191
203
|
l,d = self.locate_point(point)
|
@@ -194,23 +206,23 @@ module Geo2D
|
|
194
206
|
false
|
195
207
|
end
|
196
208
|
end
|
197
|
-
|
209
|
+
|
198
210
|
def direction
|
199
211
|
@u ||= vector.unitary
|
200
212
|
end
|
201
|
-
|
213
|
+
|
202
214
|
# Returns the position in the segment (distance from the start node along the line) of the nearest line point
|
203
215
|
# to the point (point projected on the line) and the perpendicular separation of the point from the line (the
|
204
216
|
# distance from the point to the line).
|
205
217
|
# If the last parameter is true, the resulting point is forced to lie in the segment (so the distance along
|
206
218
|
# the line is between 0 and the segment's length) and the second result is the distance from the point to the
|
207
219
|
# segment (i.e. to the closest end of the segment if the projected point lies out of the segmen)
|
208
|
-
def locate_point(point, corrected=false)
|
209
|
-
point = Vector(point)
|
220
|
+
def locate_point(point, corrected=false)
|
221
|
+
point = Geo2D.Vector(point)
|
210
222
|
v = point - @start
|
211
223
|
l = v.dot(direction)
|
212
224
|
d = direction.cross_z(v) # == (v-l*direction).length == v.length*Math.sin(v.angle_to(direction))
|
213
|
-
|
225
|
+
|
214
226
|
if corrected
|
215
227
|
if l<0
|
216
228
|
l = 0
|
@@ -220,10 +232,10 @@ module Geo2D
|
|
220
232
|
d = (point-@end).length
|
221
233
|
end
|
222
234
|
end
|
223
|
-
|
235
|
+
|
224
236
|
[l, d]
|
225
237
|
end
|
226
|
-
|
238
|
+
|
227
239
|
# Computes the position of a point in the line given the distance along the line from the starting node.
|
228
240
|
# If a second parameter is passed it indicates the separation of the computed point in the direction
|
229
241
|
# perpendicular to the line; the point is on the left side of the line if the separation is > 0.
|
@@ -237,27 +249,27 @@ module Geo2D
|
|
237
249
|
def distance_to(point)
|
238
250
|
locate_point(point, true).last
|
239
251
|
end
|
240
|
-
|
252
|
+
|
241
253
|
# Distance from the line that contains the segment to the point
|
242
254
|
def line_distance_to(point)
|
243
255
|
locate_point(point, false).last
|
244
256
|
end
|
245
|
-
|
257
|
+
|
246
258
|
def length_to(point)
|
247
259
|
locate_point(point, true).first
|
248
260
|
end
|
249
|
-
|
261
|
+
|
250
262
|
# multiply by matrix [[a11, a12], [a21, a22]]
|
251
263
|
def transform(*t)
|
252
264
|
LineSegment.new(@start.transform(*t), @end = @end.transform(*t))
|
253
265
|
end
|
254
|
-
|
266
|
+
|
255
267
|
# Apply arbitrary transformation (passed as a Proc or as a block)
|
256
268
|
def apply(prc, &blk)
|
257
269
|
prc ||= blk
|
258
270
|
LineSegment.new(prc[@start], prc[@end])
|
259
271
|
end
|
260
|
-
|
272
|
+
|
261
273
|
# Returns the side of the line that contains the segment in which the point lies:
|
262
274
|
# * +1 the point is to the left of the line (as seen from the orientation of the segment)
|
263
275
|
# * -1 is in the right side
|
@@ -266,38 +278,44 @@ module Geo2D
|
|
266
278
|
v = vector.cross_z(point-@start)
|
267
279
|
v < 0 ? -1 : (v > 0 ? +1 : 0)
|
268
280
|
end
|
269
|
-
|
281
|
+
|
282
|
+
def bounds
|
283
|
+
xmn, xmx = [@start.x, @end.x].sort
|
284
|
+
ymn, ymx = [@start.y, @end.y].sort
|
285
|
+
[xmn, ymn, xmx, ymx]
|
286
|
+
end
|
287
|
+
|
270
288
|
end
|
271
|
-
|
289
|
+
|
272
290
|
class LineString
|
273
|
-
|
291
|
+
|
274
292
|
def initialize(*vertices)
|
275
293
|
@vertices = vertices
|
276
|
-
|
294
|
+
|
277
295
|
to_remove = []
|
278
296
|
prev = nil
|
279
297
|
@vertices.each_with_index do |v, i|
|
280
298
|
to_remove << i if prev && prev==v
|
281
299
|
prev = v
|
282
|
-
end
|
300
|
+
end
|
283
301
|
to_remove.each do |i|
|
284
302
|
@vertices.delete_at i
|
285
303
|
end
|
286
|
-
|
304
|
+
|
287
305
|
end
|
288
|
-
|
306
|
+
|
289
307
|
def start
|
290
308
|
@vertices.first
|
291
309
|
end
|
292
|
-
|
310
|
+
|
293
311
|
def end
|
294
312
|
@vertices.last
|
295
313
|
end
|
296
|
-
|
314
|
+
|
297
315
|
def length
|
298
316
|
@length ||= total_length
|
299
317
|
end
|
300
|
-
|
318
|
+
|
301
319
|
def n_points
|
302
320
|
@vertices.size
|
303
321
|
end
|
@@ -309,58 +327,58 @@ module Geo2D
|
|
309
327
|
yield v
|
310
328
|
end
|
311
329
|
end
|
312
|
-
|
330
|
+
|
313
331
|
def n_segments
|
314
332
|
[n_points - 1,0].max
|
315
333
|
end
|
316
|
-
|
334
|
+
|
317
335
|
def segments
|
318
336
|
(0...n_segments).to_a.map{|i| segment(i)}
|
319
337
|
end
|
320
|
-
|
338
|
+
|
321
339
|
def each_segment
|
322
340
|
(0...n_segments).each do |i|
|
323
341
|
yield segment(i)
|
324
342
|
end
|
325
343
|
end
|
326
|
-
|
344
|
+
|
327
345
|
def segment(i)
|
328
346
|
raise ArgumentError, "Invalid segment index #{i}" unless i>=0 && i<n_segments
|
329
347
|
LineSegment.new(@vertices[i],@vertices[i+1])
|
330
348
|
end
|
331
|
-
|
349
|
+
|
332
350
|
def distance_to(point)
|
333
351
|
locate_point(point, true).last
|
334
352
|
end
|
335
|
-
|
353
|
+
|
336
354
|
def length_to(point)
|
337
355
|
locate_point(point, true).first
|
338
356
|
end
|
339
|
-
|
340
|
-
# return parallalel distance and separation;
|
357
|
+
|
358
|
+
# return parallalel distance and separation;
|
341
359
|
# if corrected, then parallalel distance is in [0,length] (the point is inside the line)
|
342
360
|
# parallel distance in [0,length] , separation
|
343
|
-
def locate_point(point, corrected=false)
|
361
|
+
def locate_point(point, corrected=false)
|
344
362
|
best = nil
|
345
|
-
|
363
|
+
|
346
364
|
total_l = 0
|
347
365
|
(0...n_segments).each do |i|
|
348
|
-
|
366
|
+
|
349
367
|
seg = segment(i)
|
350
368
|
seg_l = seg.length
|
351
|
-
|
369
|
+
|
352
370
|
l,d = seg.locate_point(point, false)
|
353
371
|
max_i = n_segments-1
|
354
|
-
|
372
|
+
|
355
373
|
if (l>0 || i==0) && (l<=seg_l || i==max_i)
|
356
374
|
if best.nil? || d<best.last
|
357
375
|
best = [total_l+l, d]
|
358
376
|
end
|
359
|
-
end
|
360
|
-
|
377
|
+
end
|
378
|
+
|
361
379
|
total_l += seg_l
|
362
380
|
end
|
363
|
-
|
381
|
+
|
364
382
|
if best && corrected
|
365
383
|
l, d = best
|
366
384
|
if l<0
|
@@ -372,11 +390,11 @@ module Geo2D
|
|
372
390
|
end
|
373
391
|
best = [l, d]
|
374
392
|
end
|
375
|
-
|
393
|
+
|
376
394
|
best
|
377
|
-
|
395
|
+
|
378
396
|
end
|
379
|
-
|
397
|
+
|
380
398
|
def interpolate_point(parallel_distance, separation=0, sweep=nil)
|
381
399
|
# separation>0 => left side of line in direction of travel
|
382
400
|
i, l = segment_position_of(parallel_distance)
|
@@ -384,18 +402,18 @@ module Geo2D
|
|
384
402
|
sweep = 0.0 unless sweep.kind_of?(Numeric)
|
385
403
|
if i>0 && l<sweep
|
386
404
|
a = 0.5*(segment(i-1).angle+segment(i).angle) + Math::PI/2
|
387
|
-
@vertices[i] + separation*Vector(Math.cos(a), Math.sin(a))
|
405
|
+
@vertices[i] + separation*Geo2D.Vector(Math.cos(a), Math.sin(a))
|
388
406
|
elsif i<(n_segments-1) && l>=(segment_length(i)-sweep)
|
389
407
|
a = 0.5*(segment(i).angle+segment(i+1).angle) + Math::PI/2
|
390
|
-
@vertices[i+1] + separation*Vector(Math.cos(a), Math.sin(a))
|
408
|
+
@vertices[i+1] + separation*Geo2D.Vector(Math.cos(a), Math.sin(a))
|
391
409
|
else
|
392
410
|
segment(i).interpolate_point(l, separation)
|
393
|
-
end
|
394
|
-
else
|
411
|
+
end
|
412
|
+
else
|
395
413
|
segment(i).interpolate_point(l, separation)
|
396
414
|
end
|
397
415
|
end
|
398
|
-
|
416
|
+
|
399
417
|
def angle_at(parallel_distance, sweep=false)
|
400
418
|
i,l = segment_position_of(parallel_distance)
|
401
419
|
if sweep
|
@@ -406,34 +424,40 @@ module Geo2D
|
|
406
424
|
0.5*(segment(i).angle+segment(i+1).angle)
|
407
425
|
else
|
408
426
|
segment(i).angle
|
409
|
-
end
|
427
|
+
end
|
410
428
|
else
|
411
429
|
segment(i).angle
|
412
430
|
end
|
413
431
|
end
|
414
|
-
|
432
|
+
|
415
433
|
# multiply by matrix [[a11, a12], [a21, a22]]
|
416
434
|
def transform(*t)
|
417
435
|
LineString.new(*@vertices.map{|v| v.transforme(*t)})
|
418
436
|
end
|
419
|
-
|
437
|
+
|
420
438
|
def apply(prc=nil, &blk)
|
421
439
|
prc = prc || blk
|
422
440
|
LineString.new(*@vertices.map{|v| prc[v]})
|
423
441
|
end
|
424
|
-
|
442
|
+
|
425
443
|
def contains?(point)
|
426
444
|
self.locate_point(point, true).last == 0
|
427
445
|
end
|
428
|
-
|
446
|
+
|
447
|
+
def bounds
|
448
|
+
xs = @vertices.map{|v| v.x}
|
449
|
+
ys = @vertices.map{|v| v.y}
|
450
|
+
[xs.min, ys.min, xs.max, ys.max]
|
451
|
+
end
|
452
|
+
|
429
453
|
private
|
430
|
-
|
454
|
+
|
431
455
|
def segment_length(i)
|
432
456
|
raise ArgumentError, "Invalid segment index #{i}" unless i>=0 && i<n_segments
|
433
457
|
@segment_lengths ||= [nil]*n_segments
|
434
458
|
@segment_lengths[i] ||= (@vertices[i+1]-@vertices[i]).modulus
|
435
459
|
end
|
436
|
-
|
460
|
+
|
437
461
|
def total_length
|
438
462
|
l = 0
|
439
463
|
(0...n_segments).each do |i|
|
@@ -441,7 +465,7 @@ module Geo2D
|
|
441
465
|
end
|
442
466
|
l
|
443
467
|
end
|
444
|
-
|
468
|
+
|
445
469
|
# find segment and distance in segment corresponding to total parallel distance TODO: rename
|
446
470
|
def segment_position_of(l)
|
447
471
|
i = 0
|
@@ -452,27 +476,27 @@ module Geo2D
|
|
452
476
|
end
|
453
477
|
return i, l
|
454
478
|
end
|
455
|
-
|
479
|
+
|
456
480
|
# compute parallel distance of position in segment TODO: rename
|
457
|
-
def distance_along_line_of(segment_i, distance_in_segment)
|
481
|
+
def distance_along_line_of(segment_i, distance_in_segment)
|
458
482
|
l = 0
|
459
483
|
(0...segment_i).each do |i|
|
460
484
|
l += segment_length(i)
|
461
485
|
end
|
462
486
|
l + distance_in_segment
|
463
487
|
end
|
464
|
-
|
488
|
+
|
465
489
|
end
|
466
|
-
|
490
|
+
|
467
491
|
def Point(*args)
|
468
492
|
Vector(*args)
|
469
493
|
end
|
470
|
-
|
494
|
+
|
471
495
|
# Segment constructor
|
472
496
|
def LineSegment(start_point, end_point)
|
473
497
|
LineSegment.new(Vector(start_point), Vector(end_point))
|
474
498
|
end
|
475
|
-
|
499
|
+
|
476
500
|
# Line-string constructor
|
477
501
|
def Line(*args)
|
478
502
|
#if args.size<3
|
@@ -481,7 +505,7 @@ module Geo2D
|
|
481
505
|
LineString.new(*args.map{|arg| Vector(arg)})
|
482
506
|
#end
|
483
507
|
end
|
484
|
-
|
508
|
+
|
485
509
|
# Rotation transformation; given the center of rotation (a point, i.e. a Vector) and the angle
|
486
510
|
# this returns a procedure that can be used to apply the rotation to points.
|
487
511
|
def rotation(center, angle)
|
@@ -490,5 +514,5 @@ module Geo2D
|
|
490
514
|
cs = Math.cos(angle)
|
491
515
|
lambda{|p| center + (p-center).transform(cs, sn, -sn, cs)}
|
492
516
|
end
|
493
|
-
|
517
|
+
|
494
518
|
end
|
data/test/test_geo2d.rb
CHANGED
@@ -47,5 +47,56 @@ class TestGeo2d < Test::Unit::TestCase
|
|
47
47
|
assert [10,20] == @vector.to_a
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
context "Given a line segment" do
|
52
|
+
|
53
|
+
setup do
|
54
|
+
@seg = LineSegment([10,20],[78,57])
|
55
|
+
end
|
56
|
+
|
57
|
+
context "And some points" do
|
58
|
+
|
59
|
+
setup do
|
60
|
+
@pnts = [[30,40],[10,20],[78,57],[0,0],[-1,10],[20,30],[0.5*(10+20),0.5*(78+57)]].map{|x,y| Point(x,y)}
|
61
|
+
end
|
62
|
+
|
63
|
+
should "reference the points in the line consistently" do
|
64
|
+
seg_max = @seg.points.map{|p| p.length}.max
|
65
|
+
@pnts.each do |pnt|
|
66
|
+
l,d = @seg.locate_point(pnt)
|
67
|
+
pnt2 = @seg.interpolate_point(l,d)
|
68
|
+
tolerance = [pnt.modulus, seg_max].max * Float::EPSILON * 2
|
69
|
+
assert (pnt2-pnt).length < tolerance, "Point #{pnt} yields #{pnt2} [#{(pnt2-pnt).length} / #{tolerance}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "Given a line string" do
|
77
|
+
|
78
|
+
setup do
|
79
|
+
@seg = Line([10,20],[78,57],[30,50],[110,60],[100,30])
|
80
|
+
end
|
81
|
+
|
82
|
+
context "And some points" do
|
83
|
+
|
84
|
+
setup do
|
85
|
+
@pnts = [[30,40],[10,20],[78,57],[0,0],[-1,10],[20,30],[0.5*(10+20),0.5*(78+57)]].map{|x,y| Point(x,y)}
|
86
|
+
end
|
87
|
+
|
88
|
+
should "reference the points in the line consistently" do
|
89
|
+
seg_max = @seg.points.map{|p| p.length}.max
|
90
|
+
@pnts.each do |pnt|
|
91
|
+
l,d = @seg.locate_point(pnt)
|
92
|
+
pnt2 = @seg.interpolate_point(l,d)
|
93
|
+
tolerance = [pnt.modulus, seg_max].max * Float::EPSILON * 2
|
94
|
+
assert (pnt2-pnt).length < tolerance, "Point #{pnt} yields #{pnt2} [#{(pnt2-pnt).length} / #{tolerance}]"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
50
101
|
end
|
51
102
|
|