1 /*
2  * @file
3  * @brief Based on the BSP_tool from botman's Half-Life BSP utilities
4  */
5 #ifndef ANDROID
6 #include "bspslicer.h"
7 #include "../ports/system.h"
8 #include "../common/common.h"
9 #include "../shared/images.h"
10 
11 #define DEPTH 3
12 
SL_CreatePNGFile(const char * filename,unsigned char * buffer,int width,int height)13 static bool SL_CreatePNGFile (const char* filename, unsigned char* buffer, int width, int height)
14 {
15 	ScopedFile f;
16 
17 	/* create the .bmp file... */
18 	if (FS_OpenFile(filename, &f, FILE_WRITE) == -1)
19 		return false;
20 
21 	R_WritePNG(&f, buffer, width, height);
22 
23 	return true;
24 }
25 
26 #define IMAGE_BUFFER_SET_BLACK(buffer, offset) do {int i; for (i = 0; i < DEPTH; i++) {buffer[offset + i] = 0xFF; } } while(0);
27 
SL_Bresenham(int x0,int y0,int x1,int y1,int width,int height,bool rotated,unsigned char * outputBuffer)28 static void SL_Bresenham (int x0, int y0, int x1, int y1, int width, int height, bool rotated, unsigned char* outputBuffer)
29 {
30 	int dy = y1 - y0;
31 	int dx = x1 - x0;
32 	int stepx, stepy;
33 	int offset;
34 	const int maxOffset = width * height * DEPTH;
35 
36 	if (dy < 0) {
37 		dy = -dy;
38 		stepy = -1;
39 	} else {
40 		stepy = 1;
41 	}
42 	if (dx < 0) {
43 		dx = -dx;
44 		stepx = -1;
45 	} else {
46 		stepx = 1;
47 	}
48 	dy *= 2;
49 	dx *= 2;
50 
51 	if (rotated)
52 		offset = x0 * DEPTH * height + y0;
53 	else
54 		offset = y0 * width * DEPTH + x0 * DEPTH;
55 
56 	if (offset >= 0 && offset < maxOffset) {
57 		IMAGE_BUFFER_SET_BLACK(outputBuffer, offset);
58 	} else
59 		return;
60 
61 	if (dx > dy) {
62 		int fraction = 2 * dy - (dx >> 1); /* same as 2*dy - dx */
63 		while (x0 != x1) {
64 			if (fraction >= 0) {
65 				y0 += stepy;
66 				fraction -= dx; /* same as fraction -= 2*dx */
67 			}
68 			x0 += stepx;
69 			fraction += dy; /* same as fraction -= 2*dy */
70 
71 			if (rotated)
72 				offset = x0 * DEPTH * height + y0;
73 			else
74 				offset = y0 * width * DEPTH + x0 * DEPTH;
75 
76 			if (offset >= 0 && offset < maxOffset) {
77 				IMAGE_BUFFER_SET_BLACK(outputBuffer, offset);
78 			} else
79 				return;
80 		}
81 	} else {
82 		int fraction = dx - (dy >> 1);
83 		while (y0 != y1) {
84 			if (fraction >= 0) {
85 				x0 += stepx;
86 				fraction -= dy;
87 			}
88 			y0 += stepy;
89 			fraction += dx;
90 
91 			if (rotated)
92 				offset = x0 * DEPTH * height + y0;
93 			else
94 				offset = y0 * width * DEPTH + x0 * DEPTH;
95 
96 			if (offset >= 0 && offset < maxOffset) {
97 				IMAGE_BUFFER_SET_BLACK(outputBuffer, offset);
98 			} else
99 				return;
100 		}
101 	}
102 }
103 
104 /**
105  * @note the "vector" parameter should be NORMALIZED!!!
106  */
SL_DistanceToIntersection(const vec3_t origin,const vec3_t vector,const vec3_t planeOrigin,const vec3_t planeNormal)107 static float SL_DistanceToIntersection (const vec3_t origin, const vec3_t vector, const vec3_t planeOrigin, const vec3_t planeNormal)
108 {
109 	const float d = -(DotProduct(planeNormal, planeOrigin));
110 	const float numerator = DotProduct(planeNormal, origin) + d;
111 	const float denominator = DotProduct(planeNormal, vector);
112 
113 	if (fabs(denominator) < 0.00001)
114 		/* normal is orthogonal to vector, no intersection */
115 		return -1.0f;
116 
117 	return -(numerator / denominator);
118 }
119 
120 /**
121  * @return @c true or false if vector intersects a plane...
122  */
SL_VectorIntersectPlane(const vec3_t origin,const vec3_t vector,const vec3_t planeOrigin,const vec3_t planeNormal,vec3_t intersectPoint)123 static bool SL_VectorIntersectPlane (const vec3_t origin, const vec3_t vector, const vec3_t planeOrigin, const vec3_t planeNormal, vec3_t intersectPoint)
124 {
125 	vec3_t v_temp;
126 	const float dist = SL_DistanceToIntersection(origin, vector, planeOrigin, planeNormal);
127 	if (dist < 0)
128 		return false;
129 
130 	VectorScale(vector, dist, v_temp);
131 	VectorAdd(origin, v_temp, intersectPoint);
132 
133 	return true;
134 }
135 
136 /**
137  * @brief slice the world in Z planes going from min_z to max_z by stepsize...
138  */
SL_SliceTheWorld(const TR_TILE_TYPE * tile,const vec3_t mins,const vec3_t maxs,float stepsize,int scale,bool singleContour,bool multipleContour)139 static void SL_SliceTheWorld (const TR_TILE_TYPE *tile, const vec3_t mins, const vec3_t maxs, float stepsize, int scale, bool singleContour, bool multipleContour)
140 {
141 	int sliceIndex;
142 	vec3_t zDistance = { 0.0f, 0.0f, 0.0f };
143 	const vec3_t zNormal = { 0.0f, 0.0f, 1.0f };
144 	vec3_t v[2];
145 	vec3_t vTemp, intersectPoint;
146 	float lineX1, lineY1, lineX2, lineY2;
147 	bool first, both;
148 	char filename[MAX_QPATH];
149 	const float minX = mins[0];
150 	const float maxX = maxs[0];
151 	const float minY = mins[1];
152 	const float maxY = maxs[1];
153 	const float minZ = mins[2];
154 	const float maxZ = maxs[2];
155 
156 	const int numberOfSlices = (int) ((maxZ - minZ) / stepsize);
157 	int width;
158 	int height;
159 	bool rotated = false;
160 	unsigned char* pictureBuffer;
161 
162 	lineX1 = lineY1 = lineX2 = lineY2 = 0.0f;
163 
164 	width = (int) (maxX - minX);
165 	height = (int) (maxY - minY);
166 
167 	/* is the map higher than it is wide? */
168 	if (height > width) {
169 		rotated = true; /* rotate the map 90 degrees */
170 		width = (int) (maxY - minY); /* swap the width and height */
171 		height = (int) (maxX - minX);
172 	}
173 
174 	height /= scale;
175 	width /= scale;
176 
177 	pictureBuffer = Mem_AllocTypeN(unsigned char, width * height * DEPTH);
178 
179 	for (sliceIndex = 0; sliceIndex < numberOfSlices; sliceIndex++) {
180 		int j;
181 		const float zHeight = minZ + (sliceIndex * stepsize);
182 		zDistance[2] = zHeight;
183 
184 		/* if not doing a contour map then clear the buffer each pass... */
185 		if (!singleContour && !multipleContour)
186 			memset(pictureBuffer, 0, width * height * DEPTH);
187 
188 		/* loop through all the faces of the BSP file... */
189 		for (j = 0; j < tile->nummodels; j++) {
190 			const dBspModel_t* model = &tile->models[j];
191 			for (int faceIndex = 0; faceIndex < model->numfaces; faceIndex++) {
192 				const dBspSurface_t* face = &tile->faces[model->firstface + faceIndex];
193 				const dBspTexinfo_t* texinfo = &tile->texinfo[face->texinfo];
194 				if (texinfo->surfaceFlags & (SURF_NODRAW|SURF_HINT|SURF_SKIP|SURF_BLEND33|SURF_BLEND66))
195 					continue;
196 				/** @todo handle content flags */
197 
198 				first = true;
199 				both = false;
200 
201 				/* for each face, loop though all of the edges, getting the vertexes... */
202 				for (int edgeIndex = 0; edgeIndex < face->numedges; edgeIndex++) {
203 					/* get the coordinates of the vertex of this edge... */
204 					int edge = tile->surfedges[face->firstedge + edgeIndex];
205 
206 					if (edge < 0) {
207 						edge = -edge;
208 						const dBspEdge_t* e = &tile->edges[edge];
209 						v[0][0] = tile->vertexes[e->v[1]].point[0];
210 						v[0][1] = tile->vertexes[e->v[1]].point[1];
211 						v[0][2] = tile->vertexes[e->v[1]].point[2];
212 					} else {
213 						const dBspEdge_t* e = &tile->edges[edge];
214 						v[0][0] = tile->vertexes[e->v[0]].point[0];
215 						v[0][1] = tile->vertexes[e->v[0]].point[1];
216 						v[0][2] = tile->vertexes[e->v[0]].point[2];
217 					}
218 
219 					/* get the coordinates of the vertex of the next edge... */
220 					edge = tile->surfedges[face->firstedge
221 							+ ((edgeIndex + 1) % face->numedges)];
222 
223 					if (edge < 0) {
224 						edge = -edge;
225 						const dBspEdge_t* e = &tile->edges[edge];
226 						v[1][0] = tile->vertexes[e->v[1]].point[0];
227 						v[1][1] = tile->vertexes[e->v[1]].point[1];
228 						v[1][2] = tile->vertexes[e->v[1]].point[2];
229 					} else {
230 						const dBspEdge_t* e = &tile->edges[edge];
231 						v[1][0] = tile->vertexes[e->v[0]].point[0];
232 						v[1][1] = tile->vertexes[e->v[0]].point[1];
233 						v[1][2] = tile->vertexes[e->v[0]].point[2];
234 					}
235 
236 					/* vector from v[0] to v[1] intersect the Z plane? */
237 					if (((v[0][2] < zHeight) && (v[1][2] > zHeight))
238 							|| ((v[1][2] < zHeight) && (v[0][2] > zHeight))) {
239 						/* create a normalized vector between the 2 vertexes... */
240 						VectorSubtract(v[1], v[0], vTemp);
241 						VectorNormalize(vTemp);
242 
243 						/* find the point where the vector intersects the Z plane... */
244 						SL_VectorIntersectPlane(v[0], vTemp, zDistance,
245 								zNormal, intersectPoint);
246 
247 						/* is this the first or second Z plane intersection point? */
248 						if (first) {
249 							first = false;
250 							lineX1 = intersectPoint[0];
251 							lineY1 = intersectPoint[1];
252 						} else {
253 							both = true;
254 							lineX2 = intersectPoint[0];
255 							lineY2 = intersectPoint[1];
256 						}
257 					}
258 
259 					/* move v[1] to v[0] */
260 					VectorCopy(v[1], v[0]);
261 				}
262 
263 				/* did we find 2 points that intersected the Z plane? */
264 				if (both) {
265 					/* offset by min_x to start at 0 */
266 					lineX1 -= minX;
267 					lineX2 -= minX;
268 					/* offset by min_y to start at 0 */
269 					lineY1 -= minY;
270 					lineY2 -= minY;
271 
272 					lineX1 = lineX1 / scale;
273 					lineY1 = lineY1 / scale;
274 					lineX2 = lineX2 / scale;
275 					lineY2 = lineY2 / scale;
276 
277 					/* are these points within the bounding box of the world? */
278 					if (lineX1 >= 0 && lineX1 < width && lineY1 >= 0 && lineY1 < height
279 							&& lineX2 >= 0 && lineX2 < width && lineY2 >= 0 && lineY2 < height) {
280 						const int x1 = (int) lineX1;
281 						const int y1 = (int) lineY1;
282 						const int x2 = (int) lineX2;
283 						const int y2 = (int) lineY2;
284 
285 						if (rotated)
286 							SL_Bresenham(x1, height - y1, x2, height - y2, width, height, rotated, pictureBuffer);
287 						else
288 							SL_Bresenham(x1, y1, x2, y2, width, height, rotated, pictureBuffer);
289 					}
290 				}
291 			}
292 		}
293 
294 		if (!singleContour) {
295 			Com_sprintf(filename, sizeof(filename), "out%d.png", sliceIndex + 1);
296 			Com_Printf("creating %s...\n", filename);
297 			SL_CreatePNGFile(filename, pictureBuffer, width, height);
298 		}
299 	}
300 
301 	if (singleContour) {
302 		Q_strncpyz(filename, "out.png", sizeof(filename));
303 		Com_Printf("creating %s...\n", filename);
304 		SL_CreatePNGFile(filename, pictureBuffer, width, height);
305 	}
306 
307 	Mem_Free(pictureBuffer);
308 }
309 
310 /**
311  * @param[in] thickness the thickness of the brushes to render to the 2d map
312  */
SL_BSPSlice(const TR_TILE_TYPE * model,float thickness,int scale,bool singleContour,bool multipleContour)313 void SL_BSPSlice (const TR_TILE_TYPE *model, float thickness, int scale, bool singleContour, bool multipleContour)
314 {
315 	int i;
316 	/** @todo remove these values once the mins/maxs calculation works */
317 	vec3_t mins = {-MAX_WORLD_WIDTH, -MAX_WORLD_WIDTH, 0};
318 	vec3_t maxs = {MAX_WORLD_WIDTH, MAX_WORLD_WIDTH, (PATHFINDING_HEIGHT - 1) * UNIT_HEIGHT};
319 
320 	if (model == nullptr)
321 		return;
322 
323 	for (i = 0; i < model->nummodels; i++) {
324 		const dBspModel_t* d = &model->models[i];
325 		AddPointToBounds(d->mins, mins, maxs);
326 		AddPointToBounds(d->maxs, mins, maxs);
327 	}
328 
329 	/* don't start or end on exact multiples of the Z slice thickness
330 	 * (if you do, it causes "weirdness" in the plane intersect function) */
331 
332 	/* offset a tiny bit */
333 	mins[2] = (float) floor(mins[2]) + 0.01f;
334 	maxs[2] = (float) floor(maxs[2]) + 0.01f;
335 
336 	SL_SliceTheWorld(model, mins, maxs, thickness, scale, singleContour, multipleContour);
337 }
338 
339 #endif
340