rubyvor 0.1.3
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/History.rdoc +60 -0
- data/Manifest.txt +30 -0
- data/README.rdoc +76 -0
- data/Rakefile +41 -0
- data/ext/Doc +30 -0
- data/ext/edgelist.c +204 -0
- data/ext/extconf.rb +3 -0
- data/ext/geometry.c +219 -0
- data/ext/heap.c +118 -0
- data/ext/memory.c +118 -0
- data/ext/output.c +251 -0
- data/ext/rb_cComputation.c +369 -0
- data/ext/rb_cPoint.c +35 -0
- data/ext/rb_cPriorityQueue.c +120 -0
- data/ext/ruby_vor_c.c +115 -0
- data/ext/ruby_vor_c.h +40 -0
- data/ext/vdefs.h +150 -0
- data/ext/voronoi.c +271 -0
- data/lib/ruby_vor.rb +16 -0
- data/lib/ruby_vor/computation.rb +136 -0
- data/lib/ruby_vor/geo_ruby_extensions.rb +15 -0
- data/lib/ruby_vor/point.rb +32 -0
- data/lib/ruby_vor/priority_queue.rb +87 -0
- data/lib/ruby_vor/version.rb +3 -0
- data/lib/ruby_vor/visualizer.rb +218 -0
- data/rubyvor.gemspec +35 -0
- data/test/test_computation.rb +354 -0
- data/test/test_point.rb +100 -0
- data/test/test_priority_queue.rb +129 -0
- data/test/test_voronoi_interface.rb +161 -0
- metadata +99 -0
data/ext/rb_cPoint.c
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <vdefs.h>
|
3
|
+
#include <ruby_vor_c.h>
|
4
|
+
#include <math.h>
|
5
|
+
|
6
|
+
/*
|
7
|
+
* Euclidean distance between two Points
|
8
|
+
*/
|
9
|
+
VALUE
|
10
|
+
RubyVor_distance_from(VALUE self, VALUE other)
|
11
|
+
{
|
12
|
+
return rb_float_new(sqrt(pow(NUM2DBL(rb_iv_get(self, "@x")) - NUM2DBL(rb_iv_get(other, "@x")), 2.0) +
|
13
|
+
pow(NUM2DBL(rb_iv_get(self, "@y")) - NUM2DBL(rb_iv_get(other, "@y")), 2.0)));
|
14
|
+
}
|
15
|
+
|
16
|
+
VALUE
|
17
|
+
RubyVor_point_hash(VALUE self)
|
18
|
+
{
|
19
|
+
double x, y;
|
20
|
+
char *c;
|
21
|
+
long i, xHash, yHash;
|
22
|
+
|
23
|
+
x = NUM2DBL(rb_iv_get(self, "@x"));
|
24
|
+
y = NUM2DBL(rb_iv_get(self, "@y"));
|
25
|
+
|
26
|
+
/* Bastardized from Ruby's numeric.c */
|
27
|
+
|
28
|
+
for (c = (char*)&x, xHash = 0, i = 0; i < sizeof(double); i++) xHash += c[i] * 971;
|
29
|
+
for (c = (char*)&y, yHash = 0, i = 0; i < sizeof(double); i++) yHash += c[i] * 971;
|
30
|
+
|
31
|
+
xHash = xHash & RB_HASH_FILTER;
|
32
|
+
yHash = yHash & RB_HASH_FILTER;
|
33
|
+
|
34
|
+
return LONG2FIX((xHash << (RB_LONG_BITS / 2)) | yHash);
|
35
|
+
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <vdefs.h>
|
3
|
+
#include <ruby_vor_c.h>
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Instance methods for RubyVor::VDDT::PriorityQueue
|
7
|
+
*/
|
8
|
+
|
9
|
+
|
10
|
+
static VALUE
|
11
|
+
compare(VALUE a, VALUE b)
|
12
|
+
{
|
13
|
+
double aD, bD;
|
14
|
+
ID minDistance;
|
15
|
+
minDistance = ID2SYM(rb_intern("min_distance"));
|
16
|
+
|
17
|
+
if (CLASS_OF(a) == rb_path2class("RubyVor::PriorityQueue::QueueItem"))
|
18
|
+
aD = NUM2DBL(rb_funcall(a, rb_intern("priority"), 0));
|
19
|
+
else
|
20
|
+
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", rb_obj_classname(a), rb_obj_classname(rb_path2class("RubyVor::PriorityQueue::QueueItem")));
|
21
|
+
|
22
|
+
if (CLASS_OF(a) == rb_path2class("RubyVor::PriorityQueue::QueueItem"))
|
23
|
+
bD = NUM2DBL(rb_funcall(b, rb_intern("priority"), 0));
|
24
|
+
else
|
25
|
+
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", rb_obj_classname(a), rb_obj_classname(rb_path2class("RubyVor::PriorityQueue::QueueItem")));
|
26
|
+
|
27
|
+
return RTEST(aD < bD);
|
28
|
+
}
|
29
|
+
|
30
|
+
VALUE
|
31
|
+
RubyVor_percolate_up(VALUE self, VALUE index)
|
32
|
+
{
|
33
|
+
VALUE item, data;
|
34
|
+
long i, j, size;
|
35
|
+
|
36
|
+
Check_Type(index, T_FIXNUM);
|
37
|
+
|
38
|
+
data = rb_iv_get(self, "@data");
|
39
|
+
Check_Type(data, T_ARRAY);
|
40
|
+
|
41
|
+
size = FIX2INT(rb_iv_get(self, "@size"));
|
42
|
+
i = FIX2INT(index) + 1;
|
43
|
+
|
44
|
+
if (i < 1 || i > size)
|
45
|
+
rb_raise(rb_eIndexError, "index %li out of range", i-1);
|
46
|
+
|
47
|
+
j = i / 2;
|
48
|
+
|
49
|
+
item = RARRAY_PTR(data)[i - 1];
|
50
|
+
|
51
|
+
while(j > 0 && compare(item, RARRAY_PTR(data)[j - 1]))
|
52
|
+
{
|
53
|
+
rb_ary_store(data, i-1, RARRAY_PTR(data)[j - 1]);
|
54
|
+
rb_funcall(RARRAY_PTR(data)[i-1], rb_intern("index="), 1, INT2FIX(i-1));
|
55
|
+
i = j;
|
56
|
+
j = j / 2;
|
57
|
+
}
|
58
|
+
|
59
|
+
rb_ary_store(data, i-1, item);
|
60
|
+
rb_funcall(RARRAY_PTR(data)[i-1], rb_intern("index="), 1, INT2FIX(i-1));
|
61
|
+
|
62
|
+
return Qnil;
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
VALUE
|
67
|
+
RubyVor_percolate_down(VALUE self, VALUE index)
|
68
|
+
{
|
69
|
+
VALUE item, data;
|
70
|
+
long i, j, k, size;
|
71
|
+
|
72
|
+
Check_Type(index, T_FIXNUM);
|
73
|
+
|
74
|
+
data = rb_iv_get(self, "@data");
|
75
|
+
Check_Type(data, T_ARRAY);
|
76
|
+
|
77
|
+
size = FIX2INT(rb_iv_get(self, "@size"));
|
78
|
+
i = FIX2INT(index) + 1;
|
79
|
+
|
80
|
+
if (i < 1 || i > size)
|
81
|
+
rb_raise(rb_eIndexError, "index %li out of range", i-1);
|
82
|
+
|
83
|
+
j = size / 2;
|
84
|
+
|
85
|
+
item = RARRAY_PTR(data)[i - 1];
|
86
|
+
|
87
|
+
while (!(i > j))
|
88
|
+
{
|
89
|
+
k = i * 2;
|
90
|
+
if (k < size && compare(RARRAY_PTR(data)[k], RARRAY_PTR(data)[k - 1]))
|
91
|
+
k++;
|
92
|
+
|
93
|
+
if (compare(item, RARRAY_PTR(data)[k - 1]))
|
94
|
+
j = -1;
|
95
|
+
else
|
96
|
+
{
|
97
|
+
rb_ary_store(data, i-1, RARRAY_PTR(data)[k - 1]);
|
98
|
+
rb_funcall(RARRAY_PTR(data)[i-1], rb_intern("index="), 1, INT2FIX(i-1));
|
99
|
+
i = k;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
rb_ary_store(data, i-1, item);
|
104
|
+
rb_funcall(RARRAY_PTR(data)[i-1], rb_intern("index="), 1, INT2FIX(i-1));
|
105
|
+
|
106
|
+
return Qnil;
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
VALUE
|
111
|
+
RubyVor_heapify(VALUE self)
|
112
|
+
{
|
113
|
+
long i, size;
|
114
|
+
size = FIX2INT(rb_iv_get(self, "@size"));
|
115
|
+
|
116
|
+
for(i = size / 2; i >= 1; i--)
|
117
|
+
RubyVor_percolate_down(self, INT2FIX(i-1));
|
118
|
+
|
119
|
+
return Qnil;
|
120
|
+
}
|
data/ext/ruby_vor_c.c
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <vdefs.h>
|
3
|
+
#include <ruby_vor_c.h>
|
4
|
+
|
5
|
+
/* Classes & Modules */
|
6
|
+
static VALUE RubyVor_rb_mRubyVor;
|
7
|
+
static VALUE RubyVor_rb_mVDDT;
|
8
|
+
|
9
|
+
static VALUE RubyVor_rb_cComputation;
|
10
|
+
static VALUE RubyVor_rb_cPriorityQueue;
|
11
|
+
static VALUE RubyVor_rb_cQueueItem;
|
12
|
+
static VALUE RubyVor_rb_cPoint;
|
13
|
+
|
14
|
+
/*
|
15
|
+
* Extension initialization
|
16
|
+
*/
|
17
|
+
void
|
18
|
+
Init_ruby_vor_c(void)
|
19
|
+
{
|
20
|
+
/*
|
21
|
+
* Set up our Modules and Class.
|
22
|
+
*/
|
23
|
+
|
24
|
+
/*
|
25
|
+
* Main RubyVor namespace.
|
26
|
+
*/
|
27
|
+
RubyVor_rb_mRubyVor = rb_define_module("RubyVor");
|
28
|
+
|
29
|
+
|
30
|
+
/*
|
31
|
+
* Voronoi Digram and Delaunay Triangulation namespace.
|
32
|
+
*/
|
33
|
+
RubyVor_rb_mVDDT = rb_define_module_under(RubyVor_rb_mRubyVor, "VDDT");
|
34
|
+
|
35
|
+
|
36
|
+
/*
|
37
|
+
* Class representing a VD/DT computation based on a set of 2-dimensional points
|
38
|
+
*/
|
39
|
+
RubyVor_rb_cComputation = rb_define_class_under(RubyVor_rb_mVDDT, "Computation", rb_cObject);
|
40
|
+
rb_define_singleton_method(RubyVor_rb_cComputation, "from_points", RubyVor_from_points, 1);
|
41
|
+
rb_define_method(RubyVor_rb_cComputation, "nn_graph", RubyVor_nn_graph, 0);
|
42
|
+
rb_define_method(RubyVor_rb_cComputation, "minimum_spanning_tree", RubyVor_minimum_spanning_tree, -1);
|
43
|
+
|
44
|
+
|
45
|
+
/*
|
46
|
+
* A priority queue with a customizable heap-order property.
|
47
|
+
*/
|
48
|
+
RubyVor_rb_cPriorityQueue = rb_define_class_under(RubyVor_rb_mRubyVor, "PriorityQueue", rb_cObject);
|
49
|
+
RubyVor_rb_cQueueItem = rb_define_class_under(RubyVor_rb_cPriorityQueue, "QueueItem", rb_cObject);
|
50
|
+
rb_define_method(RubyVor_rb_cPriorityQueue, "percolate_up", RubyVor_percolate_up, 1);
|
51
|
+
rb_define_method(RubyVor_rb_cPriorityQueue, "percolate_down", RubyVor_percolate_down, 1);
|
52
|
+
rb_define_method(RubyVor_rb_cPriorityQueue, "heapify", RubyVor_heapify, 0);
|
53
|
+
|
54
|
+
|
55
|
+
/*
|
56
|
+
* A simple Point class
|
57
|
+
*/
|
58
|
+
RubyVor_rb_cPoint = rb_define_class_under(RubyVor_rb_mRubyVor, "Point", rb_cObject);
|
59
|
+
rb_define_method(RubyVor_rb_cPoint, "distance_from", RubyVor_distance_from, 1);
|
60
|
+
rb_define_method(RubyVor_rb_cPoint, "hash", RubyVor_point_hash, 0);
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
/*
|
67
|
+
* Method declarations duplicated here for RDOC
|
68
|
+
*/
|
69
|
+
|
70
|
+
/*
|
71
|
+
* Compute the voronoi diagram and delaunay triangulation from a set of points.
|
72
|
+
*
|
73
|
+
* This implementation uses Steven Fortune's sweepline algorithm, which runs in O(n log n) time and O(n) space.
|
74
|
+
* It is limited to 2-dimensional space, therefore it expects to receive an array of objects that respond to 'x' and 'y' methods.
|
75
|
+
*/
|
76
|
+
VALUE RubyVor_from_points(VALUE, VALUE);
|
77
|
+
|
78
|
+
/*
|
79
|
+
* Compute the nearest-neighbor graph using the existing Delaunay triangulation.
|
80
|
+
*/
|
81
|
+
VALUE RubyVor_nn_graph(VALUE);
|
82
|
+
|
83
|
+
/*
|
84
|
+
* Computes the minimum spanning tree for given points, using the Delaunay triangulation as a seed.
|
85
|
+
*
|
86
|
+
* For points on a Euclidean plane, the MST is always comprised of a subset of the edges in a Delaunay triangulation. This makes computation of the tree very efficient: simply compute the Delaunay triangulation, and then run Prim's algorithm on the resulting edges.
|
87
|
+
*/
|
88
|
+
VALUE RubyVor_minimum_spanning_tree(int, VALUE*, VALUE);
|
89
|
+
|
90
|
+
/*
|
91
|
+
* Move from the given index up, restoring the heap-order property.
|
92
|
+
*/
|
93
|
+
VALUE RubyVor_percolate_up(VALUE, VALUE);
|
94
|
+
|
95
|
+
/*
|
96
|
+
* Move from the index down, restoring the heap-order property.
|
97
|
+
*/
|
98
|
+
VALUE RubyVor_percolate_down(VALUE, VALUE);
|
99
|
+
|
100
|
+
/*
|
101
|
+
* Restore the heap-order property for a randomly ordered array of entries.
|
102
|
+
*/
|
103
|
+
VALUE RubyVor_heapify(VALUE);
|
104
|
+
|
105
|
+
/*
|
106
|
+
* Compute the Euclidean distance between two points.
|
107
|
+
*/
|
108
|
+
VALUE RubyVor_distance_from(VALUE, VALUE);
|
109
|
+
|
110
|
+
/*
|
111
|
+
* Hash value for a point.
|
112
|
+
*/
|
113
|
+
VALUE RubyVor_point_hash(VALUE);
|
114
|
+
|
115
|
+
/* keep comment so RDOC will find the last method definition */
|
data/ext/ruby_vor_c.h
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#ifndef __RUBY_VOR_H
|
2
|
+
#define __RUBY_VOR_H
|
3
|
+
|
4
|
+
#ifndef RB_LONG_BITS
|
5
|
+
#define RB_LONG_BITS sizeof(long)*8
|
6
|
+
#endif
|
7
|
+
|
8
|
+
#ifndef RB_HASH_FILTER
|
9
|
+
#define RB_HASH_FILTER ((2 << (RB_LONG_BITS / 2 - 1)) - 1)
|
10
|
+
#endif
|
11
|
+
|
12
|
+
#ifndef RUBY_19
|
13
|
+
#ifndef RFLOAT_VALUE
|
14
|
+
#define RFLOAT_VALUE(v) (RFLOAT(v)->value)
|
15
|
+
#endif
|
16
|
+
#ifndef RARRAY_LEN
|
17
|
+
#define RARRAY_LEN(v) (RARRAY(v)->len)
|
18
|
+
#endif
|
19
|
+
#ifndef RARRAY_PTR
|
20
|
+
#define RARRAY_PTR(v) (RARRAY(v)->ptr)
|
21
|
+
#endif
|
22
|
+
#endif
|
23
|
+
|
24
|
+
extern VoronoiState rubyvorState;
|
25
|
+
|
26
|
+
/* Computation */
|
27
|
+
VALUE RubyVor_from_points(VALUE, VALUE);
|
28
|
+
VALUE RubyVor_nn_graph(VALUE);
|
29
|
+
VALUE RubyVor_minimum_spanning_tree(int, VALUE*, VALUE);
|
30
|
+
|
31
|
+
/* PriorityQueue */
|
32
|
+
VALUE RubyVor_percolate_up(VALUE, VALUE);
|
33
|
+
VALUE RubyVor_percolate_down(VALUE, VALUE);
|
34
|
+
VALUE RubyVor_heapify(VALUE);
|
35
|
+
|
36
|
+
/* Point */
|
37
|
+
VALUE RubyVor_distance_from(VALUE, VALUE);
|
38
|
+
VALUE RubyVor_point_hash(VALUE);
|
39
|
+
|
40
|
+
#endif
|
data/ext/vdefs.h
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
#ifndef __VDEFS_H
|
2
|
+
#define __VDEFS_H
|
3
|
+
|
4
|
+
#ifndef NULL
|
5
|
+
#define NULL 0
|
6
|
+
#endif
|
7
|
+
|
8
|
+
#define DELETED -2
|
9
|
+
|
10
|
+
typedef struct tagFreenode
|
11
|
+
{
|
12
|
+
struct tagFreenode * nextfree;
|
13
|
+
} Freenode ;
|
14
|
+
|
15
|
+
|
16
|
+
typedef struct tagFreelist
|
17
|
+
{
|
18
|
+
Freenode * head;
|
19
|
+
int nodesize;
|
20
|
+
} Freelist ;
|
21
|
+
|
22
|
+
typedef struct tagPoint
|
23
|
+
{
|
24
|
+
float x ;
|
25
|
+
float y ;
|
26
|
+
} Point ;
|
27
|
+
|
28
|
+
/* structure used both for sites and for vertices */
|
29
|
+
|
30
|
+
typedef struct tagSite
|
31
|
+
{
|
32
|
+
Point coord ;
|
33
|
+
int sitenbr ;
|
34
|
+
int refcnt ;
|
35
|
+
} Site ;
|
36
|
+
|
37
|
+
|
38
|
+
typedef struct tagEdge
|
39
|
+
{
|
40
|
+
float a, b, c ;
|
41
|
+
Site * ep[2] ;
|
42
|
+
Site * reg[2] ;
|
43
|
+
int edgenbr ;
|
44
|
+
} Edge ;
|
45
|
+
|
46
|
+
#define le 0
|
47
|
+
#define re 1
|
48
|
+
|
49
|
+
typedef struct tagHalfedge
|
50
|
+
{
|
51
|
+
struct tagHalfedge * ELleft ;
|
52
|
+
struct tagHalfedge * ELright ;
|
53
|
+
Edge * ELedge ;
|
54
|
+
int ELrefcnt ;
|
55
|
+
char ELpm ;
|
56
|
+
Site * vertex ;
|
57
|
+
float ystar ;
|
58
|
+
struct tagHalfedge * PQnext ;
|
59
|
+
} Halfedge ;
|
60
|
+
|
61
|
+
typedef struct tagVoronoiState
|
62
|
+
{
|
63
|
+
/* voronoi.c */
|
64
|
+
int sorted, plot, debug, siteidx;
|
65
|
+
float xmin, xmax, ymin, ymax;
|
66
|
+
Site * sites;
|
67
|
+
void * comp;
|
68
|
+
void (* storeT)(int, int, int);
|
69
|
+
void (* storeL)(float, float, float);
|
70
|
+
void (* storeE)(int, int, int);
|
71
|
+
void (* storeV)(float, float);
|
72
|
+
void (* storeS)(float, float);
|
73
|
+
|
74
|
+
/* geometry.c */
|
75
|
+
float deltax, deltay;
|
76
|
+
int nsites, nedges, sqrt_nsites, nvertices;
|
77
|
+
Freelist sfl;
|
78
|
+
|
79
|
+
/* edgelist.c */
|
80
|
+
int ELhashsize;
|
81
|
+
Site * bottomsite;
|
82
|
+
} VoronoiState;
|
83
|
+
|
84
|
+
extern VoronoiState rubyvorState;
|
85
|
+
|
86
|
+
/* edgelist.c */
|
87
|
+
void ELinitialize(void) ;
|
88
|
+
Halfedge * HEcreate(Edge *, int) ;
|
89
|
+
void ELinsert(Halfedge *, Halfedge *) ;
|
90
|
+
Halfedge * ELgethash(int) ;
|
91
|
+
Halfedge * ELleftbnd(Point *) ;
|
92
|
+
void ELdelete(Halfedge *) ;
|
93
|
+
Halfedge * ELright(Halfedge *) ;
|
94
|
+
Halfedge * ELleft(Halfedge *) ;
|
95
|
+
Site * leftreg(Halfedge *) ;
|
96
|
+
Site * rightreg(Halfedge *) ;
|
97
|
+
Halfedge * getELleftend(void) ;
|
98
|
+
Halfedge * getELrightend(void) ;
|
99
|
+
|
100
|
+
/* geometry.c */
|
101
|
+
void geominit(void) ;
|
102
|
+
Edge * bisect(Site *, Site *) ;
|
103
|
+
Site * intersect(Halfedge *, Halfedge *) ;
|
104
|
+
int right_of(Halfedge *, Point *) ;
|
105
|
+
void endpoint(Edge *, int, Site *) ;
|
106
|
+
float dist(Site *, Site *) ;
|
107
|
+
void makevertex(Site *) ;
|
108
|
+
void deref(Site *) ;
|
109
|
+
void ref(Site *) ;
|
110
|
+
|
111
|
+
/* heap.c */
|
112
|
+
void PQinsert(Halfedge *, Site *, float) ;
|
113
|
+
void PQdelete(Halfedge *) ;
|
114
|
+
int PQbucket(Halfedge *) ;
|
115
|
+
int PQempty(void) ;
|
116
|
+
Point PQ_min(void) ;
|
117
|
+
Halfedge * PQextractmin(void) ;
|
118
|
+
void PQinitialize(void) ;
|
119
|
+
|
120
|
+
/* getopt.c */
|
121
|
+
extern int getopt(int, char *const *, const char *);
|
122
|
+
|
123
|
+
/* memory.c */
|
124
|
+
void freeinit(Freelist *, int) ;
|
125
|
+
char *getfree(Freelist *) ;
|
126
|
+
void makefree(Freenode *, Freelist *) ;
|
127
|
+
char *myalloc(unsigned) ;
|
128
|
+
char *myrealloc(void *, unsigned, unsigned);
|
129
|
+
void free_all(void);
|
130
|
+
|
131
|
+
/* output.c */
|
132
|
+
void openpl(void) ;
|
133
|
+
void line(float, float, float, float) ;
|
134
|
+
void circle(float, float, float) ;
|
135
|
+
void range(float, float, float, float) ;
|
136
|
+
void out_bisector(Edge *) ;
|
137
|
+
void out_ep(Edge *) ;
|
138
|
+
void out_vertex(Site *) ;
|
139
|
+
void out_site(Site *) ;
|
140
|
+
void out_triple(Site *, Site *, Site *) ;
|
141
|
+
void plotinit(void) ;
|
142
|
+
void clip_line(Edge *) ;
|
143
|
+
void debug_memory(void);
|
144
|
+
|
145
|
+
/* voronoi.c */
|
146
|
+
void voronoi(Site *(*)()) ;
|
147
|
+
void initialize_state(int);
|
148
|
+
#endif
|
149
|
+
|
150
|
+
|