1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "sludge/fileset.h"
24 #include "sludge/floor.h"
25 #include "sludge/graphics.h"
26 #include "sludge/newfatal.h"
27 #include "sludge/people.h"
28 #include "sludge/sludge.h"
29 
30 #define ANGLEFIX (180.0 / 3.14157)
31 
32 namespace Sludge {
33 
FloorManager(SludgeEngine * vm)34 FloorManager::FloorManager(SludgeEngine *vm) {
35 	_vm = vm;
36 	_currentFloor = nullptr;
37 }
38 
~FloorManager()39 FloorManager::~FloorManager() {
40 	kill();
41 }
42 
pointInFloorPolygon(FloorPolygon & floorPoly,int x,int y)43 bool FloorManager::pointInFloorPolygon(FloorPolygon &floorPoly, int x, int y) {
44 	int i = 0, j, c = 0;
45 	float xp_i, yp_i;
46 	float xp_j, yp_j;
47 
48 	for (j = floorPoly.numVertices - 1; i < floorPoly.numVertices; j = i++) {
49 
50 		xp_i = _currentFloor->vertex[floorPoly.vertexID[i]].x;
51 		yp_i = _currentFloor->vertex[floorPoly.vertexID[i]].y;
52 		xp_j = _currentFloor->vertex[floorPoly.vertexID[j]].x;
53 		yp_j = _currentFloor->vertex[floorPoly.vertexID[j]].y;
54 
55 		if ((((yp_i <= y) && (y < yp_j)) || ((yp_j <= y) && (y < yp_i))) && (x < (xp_j - xp_i) * (y - yp_i) / (yp_j - yp_i) + xp_i)) {
56 			c = !c;
57 		}
58 	}
59 	return c;
60 }
61 
getMatchingCorners(FloorPolygon & a,FloorPolygon & b,int & cornerA,int & cornerB)62 bool FloorManager::getMatchingCorners(FloorPolygon &a, FloorPolygon &b, int &cornerA, int &cornerB) {
63 	int sharedVertices = 0;
64 	int i, j;
65 
66 	for (i = 0; i < a.numVertices; i++) {
67 		for (j = 0; j < b.numVertices; j++) {
68 			if (a.vertexID[i] == b.vertexID[j]) {
69 				if (sharedVertices++) {
70 					cornerB = a.vertexID[i];
71 					return true;
72 				} else {
73 					cornerA = a.vertexID[i];
74 				}
75 			}
76 		}
77 	}
78 
79 	return false;
80 }
81 
polysShareSide(FloorPolygon & a,FloorPolygon & b)82 bool FloorManager::polysShareSide(FloorPolygon &a, FloorPolygon &b) {
83 	int sharedVertices = 0;
84 	int i, j;
85 
86 	for (i = 0; i < a.numVertices; i++) {
87 		for (j = 0; j < b.numVertices; j++) {
88 			if (a.vertexID[i] == b.vertexID[j]) {
89 				if (sharedVertices++)
90 					return true;
91 			}
92 		}
93 	}
94 
95 	return false;
96 }
97 
init()98 bool FloorManager::init() {
99 	_currentFloor = new Floor;
100 	if (!checkNew(_currentFloor))
101 		return false;
102 	_currentFloor->numPolygons = 0;
103 	_currentFloor->polygon = nullptr;
104 	_currentFloor->vertex = nullptr;
105 	_currentFloor->matrix = nullptr;
106 	return true;
107 }
108 
setFloorNull()109 void FloorManager::setFloorNull() {
110 	if (_currentFloor) {
111 		for (int i = 0; i < _currentFloor->numPolygons; i++) {
112 			delete[] _currentFloor->polygon[i].vertexID;
113 			delete[] _currentFloor->matrix[i];
114 		}
115 		_currentFloor->numPolygons = 0;
116 		delete[] _currentFloor->polygon;
117 		_currentFloor->polygon = nullptr;
118 		delete[] _currentFloor->vertex;
119 		_currentFloor->vertex = nullptr;
120 		delete[] _currentFloor->matrix;
121 		_currentFloor->matrix = nullptr;
122 	}
123 }
124 
kill()125 void FloorManager::kill() {
126 	setFloorNull();
127 	if (_currentFloor) {
128 		delete _currentFloor;
129 		_currentFloor = nullptr;
130 	}
131 }
132 
setFloor(int fileNum)133 bool FloorManager::setFloor(int fileNum) {
134 
135 	int i, j;
136 
137 	setFloorNull();
138 
139 	setResourceForFatal(fileNum);
140 
141 	if (!g_sludge->_resMan->openFileFromNum(fileNum))
142 		return false;
143 
144 	g_sludge->_resMan->dumpFile(fileNum, "floor%04d.flo.comp");
145 
146 	// Find out how many polygons there are and reserve memory
147 
148 	_currentFloor->originalNum = fileNum;
149 	_currentFloor->numPolygons = g_sludge->_resMan->getData()->readByte();
150 	_currentFloor->polygon = new FloorPolygon[_currentFloor->numPolygons];
151 	if (!checkNew(_currentFloor->polygon))
152 		return false;
153 
154 	// Read in each polygon
155 
156 	for (i = 0; i < _currentFloor->numPolygons; i++) {
157 		// Find out how many vertex IDs there are and reserve memory
158 		_currentFloor->polygon[i].numVertices = g_sludge->_resMan->getData()->readByte();
159 		_currentFloor->polygon[i].vertexID = new int[_currentFloor->polygon[i].numVertices];
160 		if (!checkNew(_currentFloor->polygon[i].vertexID))
161 			return false;
162 
163 		// Read in each vertex ID
164 		for (j = 0; j < _currentFloor->polygon[i].numVertices; j++) {
165 			_currentFloor->polygon[i].vertexID[j] = g_sludge->_resMan->getData()->readUint16BE();
166 		}
167 	}
168 
169 	// Find out how many vertices there are and reserve memory
170 
171 	i = g_sludge->_resMan->getData()->readUint16BE();
172 	_currentFloor->vertex = new Common::Point[i];
173 	if (!checkNew(_currentFloor->vertex))
174 		return false;
175 
176 	for (j = 0; j < i; j++) {
177 		_currentFloor->vertex[j].x = g_sludge->_resMan->getData()->readUint16BE();
178 		_currentFloor->vertex[j].y = g_sludge->_resMan->getData()->readUint16BE();
179 	}
180 
181 	g_sludge->_resMan->finishAccess();
182 
183 	dumpFloor(fileNum);
184 
185 	// Now build the movement martix
186 
187 	_currentFloor->matrix = new int *[_currentFloor->numPolygons];
188 	int **distanceMatrix = new int *[_currentFloor->numPolygons];
189 
190 	if (!checkNew(_currentFloor->matrix))
191 		return false;
192 
193 	for (i = 0; i < _currentFloor->numPolygons; i++) {
194 		_currentFloor->matrix[i] = new int[_currentFloor->numPolygons];
195 		distanceMatrix[i] = new int[_currentFloor->numPolygons];
196 		if (!checkNew(_currentFloor->matrix[i]))
197 			return false;
198 		for (j = 0; j < _currentFloor->numPolygons; j++) {
199 			_currentFloor->matrix[i][j] = -1;
200 			distanceMatrix[i][j] = 10000;
201 		}
202 	}
203 
204 	for (i = 0; i < _currentFloor->numPolygons; i++) {
205 		for (j = 0; j < _currentFloor->numPolygons; j++) {
206 			if (i != j) {
207 				if (polysShareSide(_currentFloor->polygon[i], _currentFloor->polygon[j])) {
208 					_currentFloor->matrix[i][j] = j;
209 					distanceMatrix[i][j] = 1;
210 				}
211 			} else {
212 				_currentFloor->matrix[i][j] = -2;
213 				distanceMatrix[i][j] = 0;
214 			}
215 		}
216 	}
217 
218 	bool madeChange;
219 	int lookForDistance = 0;
220 
221 	do {
222 		lookForDistance++;
223 		madeChange = false;
224 		for (i = 0; i < _currentFloor->numPolygons; i++) {
225 			for (j = 0; j < _currentFloor->numPolygons; j++) {
226 				if (_currentFloor->matrix[i][j] == -1) {
227 
228 					// OK, so we don't know how to get from i to j...
229 					for (int d = 0; d < _currentFloor->numPolygons; d++) {
230 						if (d != i && d != j) {
231 							if (_currentFloor->matrix[i][d] == d && _currentFloor->matrix[d][j] >= 0 && distanceMatrix[d][j] <= lookForDistance) {
232 								_currentFloor->matrix[i][j] = d;
233 								distanceMatrix[i][j] = lookForDistance + 1;
234 								madeChange = true;
235 							}
236 						}
237 					}
238 				}
239 			}
240 		}
241 	} while (madeChange);
242 
243 	for (i = 0; i < _currentFloor->numPolygons; i++) {
244 		delete[] distanceMatrix[i];
245 	}
246 
247 	delete []distanceMatrix;
248 	distanceMatrix = nullptr;
249 
250 	setResourceForFatal(-1);
251 
252 	return true;
253 }
254 
dumpFloor(int fileNum)255 void FloorManager::dumpFloor(int fileNum) {
256 	if (!g_sludge->_dumpScripts)
257 		return;
258 
259 	Common::DumpFile dumpFile;
260 	dumpFile.open(Common::String::format("dumps/floor%04d.flo", fileNum));
261 
262 	for (int i = 0; i < _currentFloor->numPolygons; i++) {
263 		int nV = _currentFloor->polygon[i].numVertices;
264 		if (nV > 1) {
265 			int vert = _currentFloor->polygon[i].vertexID[0];
266 			dumpFile.writeString(Common::String::format("* %d, %d", _currentFloor->vertex[vert].x, _currentFloor->vertex[vert].y));
267 			for (int j = 1; j < nV; j++) {
268 				vert = _currentFloor->polygon[i].vertexID[j];
269 				dumpFile.writeString(Common::String::format("; %d, %d", _currentFloor->vertex[vert].x, _currentFloor->vertex[vert].y));
270 			}
271 			dumpFile.writeString("\n");
272 		}
273 	}
274 
275 	dumpFile.close();
276 }
277 
drawFloor()278 void FloorManager::drawFloor() {
279 	int i, j, nV;
280 	for (i = 0; i < _currentFloor->numPolygons; i++) {
281 		nV = _currentFloor->polygon[i].numVertices;
282 		if (nV > 1) {
283 			for (j = 1; j < nV; j++) {
284 				g_sludge->_gfxMan->drawLine(_currentFloor->vertex[_currentFloor->polygon[i].vertexID[j - 1]].x, _currentFloor->vertex[_currentFloor->polygon[i].vertexID[j - 1]].y,
285 						_currentFloor->vertex[_currentFloor->polygon[i].vertexID[j]].x, _currentFloor->vertex[_currentFloor->polygon[i].vertexID[j]].y);
286 			}
287 			g_sludge->_gfxMan->drawLine(_currentFloor->vertex[_currentFloor->polygon[i].vertexID[0]].x, _currentFloor->vertex[_currentFloor->polygon[i].vertexID[0]].y,
288 					_currentFloor->vertex[_currentFloor->polygon[i].vertexID[nV - 1]].x, _currentFloor->vertex[_currentFloor->polygon[i].vertexID[nV - 1]].y);
289 		}
290 	}
291 }
292 
inFloor(int x,int y)293 int FloorManager::inFloor(int x, int y) {
294 	int i, r = -1;
295 
296 	for (i = 0; i < _currentFloor->numPolygons; i++)
297 		if (pointInFloorPolygon(_currentFloor->polygon[i], x, y))
298 			r = i;
299 
300 	return r;
301 }
302 
closestPointOnLine(int & closestX,int & closestY,int x1,int y1,int x2,int y2,int xP,int yP)303 bool FloorManager::closestPointOnLine(int &closestX, int &closestY, int x1, int y1, int x2, int y2, int xP, int yP) {
304 	int xDiff = x2 - x1;
305 	int yDiff = y2 - y1;
306 
307 	double m = xDiff * (xP - x1) + yDiff * (yP - y1);
308 	m /= (xDiff * xDiff) + (yDiff * yDiff);
309 
310 	if (m < 0) {
311 		closestX = x1;
312 		closestY = y1;
313 	} else if (m > 1) {
314 		closestX = x2;
315 		closestY = y2;
316 	} else {
317 		closestX = x1 + m * xDiff;
318 		closestY = y1 + m * yDiff;
319 		return true;
320 	}
321 	return false;
322 }
323 
handleClosestPoint(int & setX,int & setY,int & setPoly)324 bool FloorManager::handleClosestPoint(int &setX, int &setY, int &setPoly) {
325 	int gotX = 320, gotY = 200, gotPoly = -1, i, j, xTest1, yTest1, xTest2, yTest2, closestX, closestY, oldJ, currentDistance = 0xFFFFF, thisDistance;
326 
327 	for (i = 0; i < _currentFloor->numPolygons; i++) {
328 		oldJ = _currentFloor->polygon[i].numVertices - 1;
329 		for (j = 0; j < _currentFloor->polygon[i].numVertices; j++) {
330 			xTest1 = _currentFloor->vertex[_currentFloor->polygon[i].vertexID[j]].x;
331 			yTest1 = _currentFloor->vertex[_currentFloor->polygon[i].vertexID[j]].y;
332 			xTest2 = _currentFloor->vertex[_currentFloor->polygon[i].vertexID[oldJ]].x;
333 			yTest2 = _currentFloor->vertex[_currentFloor->polygon[i].vertexID[oldJ]].y;
334 			closestPointOnLine(closestX, closestY, xTest1, yTest1, xTest2, yTest2, setX, setY);
335 			xTest1 = setX - closestX;
336 			yTest1 = setY - closestY;
337 			thisDistance = xTest1 * xTest1 + yTest1 * yTest1;
338 
339 			if (thisDistance < currentDistance) {
340 				currentDistance = thisDistance;
341 				gotX = closestX;
342 				gotY = closestY;
343 				gotPoly = i;
344 			}
345 			oldJ = j;
346 		}
347 	}
348 
349 	if (gotPoly == -1)
350 		return false;
351 	setX = gotX;
352 	setY = gotY;
353 	setPoly = gotPoly;
354 	return true;
355 }
356 
doBorderStuff(OnScreenPerson * moveMe)357 bool FloorManager::doBorderStuff(OnScreenPerson *moveMe) {
358 	if (moveMe->inPoly == moveMe->walkToPoly) {
359 		moveMe->inPoly = -1;
360 		moveMe->thisStepX = moveMe->walkToX;
361 		moveMe->thisStepY = moveMe->walkToY;
362 	} else {
363 		// The section in which we need to be next...
364 		int newPoly = _currentFloor->matrix[moveMe->inPoly][moveMe->walkToPoly];
365 		if (newPoly == -1)
366 			return false;
367 
368 		// Grab the index of the second matching corner...
369 		int ID, ID2;
370 		if (!getMatchingCorners(_currentFloor->polygon[moveMe->inPoly], _currentFloor->polygon[newPoly], ID, ID2))
371 			return fatal("Not a valid floor plan!");
372 
373 		// Remember that we're walking to the new polygon...
374 		moveMe->inPoly = newPoly;
375 
376 		// Calculate the destination position on the coincidantal line...
377 		int x1 = moveMe->x, y1 = moveMe->y;
378 		int x2 = moveMe->walkToX, y2 = moveMe->walkToY;
379 		int x3 = _currentFloor->vertex[ID].x, y3 = _currentFloor->vertex[ID].y;
380 		int x4 = _currentFloor->vertex[ID2].x, y4 = _currentFloor->vertex[ID2].y;
381 
382 		int xAB = x1 - x2;
383 		int yAB = y1 - y2;
384 		int xCD = x4 - x3;
385 		int yCD = y4 - y3;
386 
387 		double m = (yAB * (x3 - x1) - xAB * (y3 - y1));
388 		m /= ((xAB * yCD) - (yAB * xCD));
389 
390 		if (m > 0 && m < 1) {
391 			moveMe->thisStepX = x3 + m * xCD;
392 			moveMe->thisStepY = y3 + m * yCD;
393 		} else {
394 			int dx13 = x1 - x3, dx14 = x1 - x4, dx23 = x2 - x3, dx24 = x2 - x4;
395 			int dy13 = y1 - y3, dy14 = y1 - y4, dy23 = y2 - y3, dy24 = y2 - y4;
396 
397 			dx13 *= dx13;
398 			dx14 *= dx14;
399 			dx23 *= dx23;
400 			dx24 *= dx24;
401 			dy13 *= dy13;
402 			dy14 *= dy14;
403 			dy23 *= dy23;
404 			dy24 *= dy24;
405 
406 			if (sqrt((double)dx13 + dy13) + sqrt((double)dx23 + dy23) < sqrt((double)dx14 + dy14) + sqrt((double)dx24 + dy24)) {
407 				moveMe->thisStepX = x3;
408 				moveMe->thisStepY = y3;
409 			} else {
410 				moveMe->thisStepX = x4;
411 				moveMe->thisStepY = y4;
412 			}
413 		}
414 	}
415 
416 	float yDiff = moveMe->thisStepY - moveMe->y;
417 	float xDiff = moveMe->x - moveMe->thisStepX;
418 	if (xDiff || yDiff) {
419 		moveMe->wantAngle = 180 + ANGLEFIX * atan2(xDiff, yDiff * 2);
420 		moveMe->spinning = true;
421 	}
422 
423 	moveMe->makeTalker();
424 	return true;
425 }
426 
save(Common::WriteStream * stream)427 void FloorManager::save(Common::WriteStream *stream) {
428 	if (_currentFloor->numPolygons) {
429 		stream->writeByte(1);
430 		stream->writeUint16BE(_currentFloor->originalNum);
431 	} else {
432 		stream->writeByte(0);
433 	}
434 }
435 
load(Common::SeekableReadStream * stream)436 bool FloorManager::load(Common::SeekableReadStream *stream) {
437 	if (stream->readByte()) {
438 		if (!setFloor(stream->readUint16BE()))
439 			return false;
440 	} else {
441 		setFloorNull();
442 	}
443 	return true;
444 }
445 
446 } // End of namespace Sludge
447