-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBSPTree.cpp
More file actions
executable file
·315 lines (224 loc) · 6.84 KB
/
Copy pathBSPTree.cpp
File metadata and controls
executable file
·315 lines (224 loc) · 6.84 KB
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#include "BSPTree.h"
const segments_t* BSPTree::get_subsector(const Vector& point) const {
node* curr = root_.get();
while (!curr->leaf) {
if (curr->split.determine_pnt_side(point)) {
curr = curr->right.get();
}
else {
curr = curr->left.get();
}
}
return &curr->lines;
}
BSPTree::iterator BSPTree::begin(const Vector& position) const {
iterator new_it(position);
const node* curr = root_.get();
while (!curr->leaf) {
if (curr->split.determine_pnt_side(position)) {
new_it.s_.push(curr->left.get());
curr = curr->right.get();
}
else {
new_it.s_.push(curr->right.get());
curr = curr->left.get();
}
}
new_it.n_ = curr;
return new_it;
}
BSPTree::iterator BSPTree::iterator::operator++() {
if (s_.empty()) {
n_ = nullptr;
}
else {
const node* n = s_.top();
s_.pop();
while (!n->leaf) {
if (n->split.determine_pnt_side(pos_)) {
s_.push(n->left.get());
n = n->right.get();
}
else {
s_.push(n->right.get());
n = n->left.get();
}
}
n_ = n;
}
return *this;
}
BSPTree BSPTree::build(const lines_t & lines) {
BSPTree new_tree;
segments_t segs = new_tree.lines_to_segments(lines);
new_tree.root_ = new_tree.partition_segments(segs);
return new_tree;
}
void BSPTree::div_line_from_segment(StraightLine & d, const Segment & s) {
d.point = s.start;
d.diff.X = s.end.X - s.start.X;
d.diff.Y = s.end.Y - s.start.Y;
}
int BSPTree::determine_seg_side(const Segment & s, const StraightLine & d) {
// 0 - left
// 1 - right
// -1 - must be splitted
int start = d.determine_pnt_side(s.start);
int end = d.determine_pnt_side(s.end);
if (start == end) {
if (start == -1){
// colinear lines, we care about direction
float dx = s.end.X - s.start.X;
float dy = s.end.Y - s.start.Y;
// same direction is on left
return !((dx > 0) - (dx < 0) == (d.diff.X > 0) - (d.diff.X < 0) &&
(dy > 0) - (dy < 0) == (d.diff.Y > 0) - (d.diff.Y < 0));
}
return start;
}
// one point is on split line
if (start == -1) return end;
if (end == -1) return start;
// there exists an intersection point
return -1;
}
int BSPTree::split_evaluate(const segments_t & segments, Segment & split_segment, int best_value) {
int new_value;
int left_c = 0;
int right_c = 0;
// create splitting line from a segment
StraightLine split;
div_line_from_segment(split, split_segment);
int c = segments.size();
int side;
// iterate through all segments and put them into right half-plane
for (int i = 0; i != c; ++i) {
Segment curr_segment = segments[i];
side = (curr_segment == split_segment ? 0 : determine_seg_side(curr_segment, split));
switch (side) {
case 0: ++left_c; break;
case 1: ++right_c; break;
case -1: ++left_c; ++right_c; break;
}
// heuristic, less splitting is better
new_value = (left_c > right_c ? left_c : right_c) + (left_c + right_c - c) * 8;
// cannot be better, should interrupt
if (new_value > best_value) return new_value;
}
// splitting by boundary, we dont want this
if (left_c == 0 || right_c == 0)
return std::numeric_limits<int>::max();
return new_value;
}
segments_t BSPTree::lines_to_segments(const lines_t & lines) {
segments_t segs;
for (auto &line : lines) {
Segment s;
s.start = *line.start;
s.end = *line.end;
s.line = &line;
s.offset = 0;
s.direction = false;
segs.push_back(s);
// line is two sided
if (line.right != nullptr) {
s.start = *line.end;
s.end = *line.start;
s.direction = true;
segs.push_back(s);
}
}
return segs;
}
Segment BSPTree::split_segment(Segment & segment, const StraightLine& splitter) {
Segment new_segment;
StraightLine seg_line;
div_line_from_segment(seg_line, segment);
float t = StraightLine::line_intersection(splitter, seg_line);
Vector intersection = { seg_line.point.X + seg_line.diff.X * t,
seg_line.point.Y + seg_line.diff.Y * t };
new_segment = segment;
new_segment.offset = segment.offset + t * seg_line.diff.length();
int side = splitter.determine_pnt_side(segment.start);
// front
if (side == 0) {
segment.end = intersection;
new_segment.start = intersection;
}
// back
else {
segment.start = intersection;
new_segment.end = intersection;
}
return new_segment;
}
void BSPTree::split_segments(const segments_t & segments, const Segment& splitter, segments_t & front, segments_t & back) {
StraightLine line;
div_line_from_segment(line, splitter);
Segment segment;
for (size_t i = 0; i != segments.size(); ++i) {
segment = segments[i];
int side;
if (segment == splitter) side = 0;
else side = determine_seg_side(segment, line);
switch (side) {
case 0: front.push_back(segment); break;
case 1: back.push_back(segment); break;
case -1:
back.push_back(split_segment(segment, line));
front.push_back(segment);
break;
}
}
}
BSPTree::node_p BSPTree::partition_segments(segments_t & segments) {
// search for the best split candidate
int best_value = std::numeric_limits<int>::max();
Segment best_line, line;
node_p new_node = std::make_unique<node>();
int c = segments.size();
//int step = (c / 40) + 1;
int step = 1;
float min_x = std::numeric_limits<float>::max();
float min_y = std::numeric_limits<float>::max();
float max_x = std::numeric_limits<float>::lowest();
float max_y = std::numeric_limits<float>::lowest();
for (int i = 0; i < c; i += step) {
line = segments[i];
if (line.end.X < min_x) min_x = line.end.X;
if (line.end.Y < min_y) min_y = line.end.Y;
if (line.start.X < min_x) min_x = line.start.X;
if (line.start.Y < min_y) min_y = line.start.Y;
if (line.end.X > max_x) max_x = line.end.X;
if (line.end.Y > max_y) max_y = line.end.Y;
if (line.start.X > max_x) max_x = line.start.X;
if (line.start.Y > max_y) max_y = line.start.Y;
int v = split_evaluate(segments, line, best_value);
if (v < best_value) {
best_value = v;
best_line = line;
}
}
// make bounding box for better search
new_node->min_bound = Vector{ min_x, min_y };
new_node->max_bound = Vector{ max_x, max_y };
//
// remaining lines are convex, and form a leaf node
//
if (best_value == std::numeric_limits<int>::max()) {
new_node->lines = move(segments);
new_node->leaf = true;
return new_node;
}
//
// divide the line list into two nodes along the best split line
//
div_line_from_segment(new_node->split, best_line);
segments_t front;
segments_t back;
split_segments(segments, best_line, front, back);
new_node->left = move(partition_segments(front));
new_node->right = move(partition_segments(back));
new_node->leaf = false;
return new_node;
}