1 /**
2 Copyright: Copyright (c) 2016-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.math.box;
7 
8 import std.algorithm : alg_min = min, alg_max = max;
9 import std.range : chain, only;
10 import voxelman.math;
11 
12 Box boxFromCorners(ivec3 a, ivec3 b)
13 {
14 	Box box;
15 	box.position = min(a, b);
16 	box.size = max(a, b) - box.position + ivec3(1,1,1);
17 	return box;
18 }
19 
20 struct Box
21 {
22 	ivec3 position;
23 	ivec3 size;
24 
25 	int volume() @property const @nogc
26 	{
27 		return size.x * size.y * size.z;
28 	}
29 
30 	ivec3 endPosition() @property const
31 	{
32 		return position + size - ivec3(1,1,1);
33 	}
34 
35 	bool empty() const @property
36 	{
37 		return size.x == 0 && size.y == 0 && size.z == 0;
38 	}
39 
40 	bool contains(ivec3 point) const
41 	{
42 		if (point.x < position.x || point.x >= position.x + size.x) return false;
43 		if (point.y < position.y || point.y >= position.y + size.y) return false;
44 		if (point.z < position.z || point.z >= position.z + size.z) return false;
45 		return true;
46 	}
47 
48 	import std.algorithm : cartesianProduct, map, joiner, equal, canFind;
49 	import std.range : iota, walkLength;
50 	import std.array : array;
51 
52 	// generates all positions within box.
53 	auto positions() const @property
54 	{
55 		return cartesianProduct(
56 			iota(position.x, position.x + size.x),
57 			iota(position.z, position.z + size.z),
58 			iota(position.y, position.y + size.y),)
59 			.map!((a)=>ivec3(a[0], a[2], a[1]));
60 	}
61 
62 	unittest
63 	{
64 		assert(Box(ivec3(0,0,0), ivec3(3,3,3)).positions.walkLength == 27);
65 	}
66 }
67 
68 struct TrisectResult
69 {
70 	Box[] aBoxes;
71 	Box intersection;
72 	Box[] bBoxes;
73 	import std.algorithm : map, joiner;
74 	auto aPositions() @property
75 	{
76 		return aBoxes.map!(a => a.positions).joiner;
77 	}
78 	auto bPositions() @property
79 	{
80 		return bBoxes.map!(a => a.positions).joiner;
81 	}
82 }
83 
84 // Finds intersection between two boxes
85 // TrisectResult[0] a - b  == a if no intersection
86 // TrisectResult[1] a intersects b
87 // TrisectResult[2] b - a  == b if no intersection
88 TrisectResult trisect(Box a, Box b)
89 {
90 	Box intersection = boxIntersection(a, b);
91 
92 	// no intersection
93 	if (intersection.empty)
94 	{
95 		return TrisectResult([a], Box(), [b]);
96 	}
97 
98 	auto result = trisectIntersecting(a, b);
99 	result.intersection = intersection;
100 	return result;
101 }
102 
103 /// Assumes that boxes have intersection. Does not write to intersection
104 TrisectResult trisectIntersecting(Box a, Box b)
105 {
106 	TrisectResult result;
107 
108 	auto xTrisect = trisectAxis(a.position.x, a.position.x + a.size.x, b.position.x, b.position.x + b.size.x);
109 	auto yTrisect = trisectAxis(a.position.y, a.position.y + a.size.y, b.position.y, b.position.y + b.size.y);
110 	auto zTrisect = trisectAxis(a.position.z, a.position.z + a.size.z, b.position.z, b.position.z + b.size.z);
111 
112 	foreach(xa; xTrisect.aranges[0..xTrisect.numRangesA].chain(only(xTrisect.irange)))
113 	foreach(ya; yTrisect.aranges[0..yTrisect.numRangesA].chain(only(yTrisect.irange)))
114 	foreach(za; zTrisect.aranges[0..zTrisect.numRangesA].chain(only(zTrisect.irange)))
115 	{
116 		if (!(xa.isIntersection && ya.isIntersection && za.isIntersection))
117 			result.aBoxes ~= Box(ivec3(xa.start, ya.start, za.start),
118 				ivec3(xa.length, ya.length, za.length));
119 	}
120 
121 	foreach(xb; xTrisect.branges[0..xTrisect.numRangesB].chain(only(xTrisect.irange)))
122 	foreach(yb; yTrisect.branges[0..yTrisect.numRangesB].chain(only(yTrisect.irange)))
123 	foreach(zb; zTrisect.branges[0..zTrisect.numRangesB].chain(only(zTrisect.irange)))
124 	{
125 		if (!(xb.isIntersection && yb.isIntersection && zb.isIntersection))
126 			result.bBoxes ~= Box(ivec3(xb.start, yb.start, zb.start),
127 				ivec3(xb.length, yb.length, zb.length));
128 	}
129 
130 	return result;
131 }
132 
133 struct AxisRange
134 {
135 	int start;
136 	int end;
137 	bool isIntersection;
138 	int length() @property
139 	{
140 		return end - start;
141 	}
142 }
143 
144 struct TrisectAxisResult
145 {
146 	AxisRange[2] aranges;
147 	ubyte numRangesA;
148 	AxisRange irange;
149 	AxisRange[2] branges;
150 	ubyte numRangesB;
151 }
152 
153 // a  aStart *----* aEnd
154 // b    bStart *----* bEnd
155 // does not handle situation when there is no intersection
156 TrisectAxisResult trisectAxis(int aStart, int aEnd, int bStart, int bEnd)
157 {
158 	TrisectAxisResult res;
159 
160 	if (aStart < bStart)
161 	{
162 		res.aranges[res.numRangesA++] = AxisRange(aStart, bStart);
163 		// bOnlyStart1 = 0
164 		// bOnlyEnd1 = 0
165 		res.irange.start = bStart;
166 	}
167 	else if (aStart > bStart)
168 	{
169 		// aOnlyStart1 = 0
170 		// aOnlyEnd1 = 0
171 		res.branges[res.numRangesB++] = AxisRange(bStart, aStart);
172 		res.irange.start = aStart;
173 	}
174 	else
175 	{
176 		// aOnlyStart1 = 0
177 		// aOnlyEnd1 = 0
178 		// bOnlyStart1 = 0
179 		// bOnlyEnd1 = 0
180 		res.irange.start = aStart;
181 	}
182 
183 	if (aEnd < bEnd)
184 	{
185 		res.irange.end = aEnd;
186 		// aOnlyStart2 = 0
187 		// aOnlyEnd2 = 0
188 		res.branges[res.numRangesB++] = AxisRange(aEnd, bEnd);
189 	}
190 	else if (aEnd > bEnd)
191 	{
192 		res.irange.end = bEnd;
193 		res.aranges[res.numRangesA++] = AxisRange(bEnd, aEnd);
194 		// bOnlyStart2 = 0
195 		// bOnlyEnd2 = 0
196 	}
197 	else
198 	{
199 		res.irange.end = bEnd;
200 		// aOnlyStart2 = 0
201 		// aOnlyEnd2 = 0
202 		// bOnlyStart2 = 0
203 		// bOnlyEnd2 = 0
204 	}
205 
206 	res.irange.isIntersection = true;
207 
208 	return res;
209 }
210 
211 Box boxIntersection(Box a, Box b)
212 {
213 	auto xTrisect = trisectAxis(a.position.x, a.position.x + a.size.x, b.position.x, b.position.x + b.size.x);
214 	auto yTrisect = trisectAxis(a.position.y, a.position.y + a.size.y, b.position.y, b.position.y + b.size.y);
215 	auto zTrisect = trisectAxis(a.position.z, a.position.z + a.size.z, b.position.z, b.position.z + b.size.z);
216 
217 	Box result = Box(
218 			ivec3(xTrisect.irange.start, yTrisect.irange.start, zTrisect.irange.start),
219 			ivec3(xTrisect.irange.length, yTrisect.irange.length, zTrisect.irange.length));
220 
221 	foreach(elem; result.size.arrayof) {
222 		if (elem <= 0)
223 			return Box();
224 	}
225 
226 	return result;
227 }
228 
229 unittest
230 {
231 	assert(boxIntersection(
232 		Box(ivec3(0,0,0), ivec3(2,2,2)),
233 		Box(ivec3(1,1,1), ivec3(2,2,2))) ==
234 		Box(ivec3(1,1,1), ivec3(1,1,1)));
235 	assert(boxIntersection(
236 		Box(ivec3(0,0,0), ivec3(2,2,2)),
237 		Box(ivec3(3,3,3), ivec3(4,4,4))) ==
238 		Box(ivec3()));
239 	assert(boxIntersection(
240 		Box(ivec3(1,1,1), ivec3(2,2,2)),
241 		Box(ivec3(0,0,0), ivec3(2,2,2))) ==
242 		Box(ivec3(1,1,1), ivec3(1,1,1)));
243 	assert(boxIntersection(
244 		Box(ivec3(1,1,1), ivec3(1,1,1)),
245 		Box(ivec3(1,1,1), ivec3(1,1,1))) ==
246 		Box(ivec3(1,1,1), ivec3(1,1,1)));
247 	assert(boxIntersection(
248 		Box(ivec3(0,0,0), ivec3(2,2,2)),
249 		Box(ivec3(0,0,-1), ivec3(2,2,2))) ==
250 		Box(ivec3(0,0,0), ivec3(2,2,1)));
251 	assert(boxIntersection(
252 		Box(ivec3(1,0,0), ivec3(1,1,1)),
253 		Box(ivec3(0,0,0), ivec3(32,32,32))) ==
254 		Box(ivec3(1,0,0), ivec3(1,1,1)));
255 }
256 
257 Box calcCommonBox(Box[] boxes ...)
258 {
259 	ivec3 start = boxes[0].position;
260 	ivec3 end = boxes[0].endPosition;
261 
262 	foreach(box; boxes[1..$])
263 	{
264 		start.x = alg_min(start.x, box.position.x);
265 		start.y = alg_min(start.y, box.position.y);
266 		start.z = alg_min(start.z, box.position.z);
267 
268 		end.x = alg_max(end.x, box.endPosition.x);
269 		end.y = alg_max(end.y, box.endPosition.y);
270 		end.z = alg_max(end.z, box.endPosition.z);
271 	}
272 
273 	return boxFromCorners(start, end);
274 }
275 
276 Vector!(T, size) min(T, int size)(Vector!(T, size) a, Vector!(T, size) b)
277 {
278 	Vector!(T, size) res;
279 	foreach(i; 0..size)
280 		res.arrayof[i] = alg_min(a.arrayof[i], b.arrayof[i]);
281 	return res;
282 }
283 
284 Vector!(T, size) max(T, int size)(Vector!(T, size) a, Vector!(T, size) b)
285 {
286 	Vector!(T, size) res;
287 	foreach(i; 0..size)
288 		res.arrayof[i] = alg_max(a.arrayof[i], b.arrayof[i]);
289 	return res;
290 }