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 "bladerunner/scene_objects.h"
24 
25 #include "bladerunner/bladerunner.h"
26 
27 #include "bladerunner/obstacles.h"
28 #include "bladerunner/savefile.h"
29 #include "bladerunner/view.h"
30 
31 namespace BladeRunner {
32 
SceneObjects(BladeRunnerEngine * vm,View * view)33 SceneObjects::SceneObjects(BladeRunnerEngine *vm, View *view) {
34 	_vm = vm;
35 	_view = view;
36 
37 	_count = 0;
38 
39 	clear();
40 }
41 
~SceneObjects()42 SceneObjects::~SceneObjects() {
43 	_vm = nullptr;
44 	_view = nullptr;
45 	_count = 0;
46 }
47 
clear()48 void SceneObjects::clear() {
49 	for (int i = 0; i < kSceneObjectCount; ++i) {
50 		_sceneObjects[i].id               = -1;
51 		_sceneObjects[i].type             = kSceneObjectTypeUnknown;
52 		_sceneObjects[i].distanceToCamera = 0.0f;
53 		_sceneObjects[i].isPresent        = false;
54 		_sceneObjects[i].isClickable      = false;
55 		_sceneObjects[i].isObstacle       = false;
56 		_sceneObjects[i].unknown1         = 0;
57 		_sceneObjects[i].isTarget         = false;
58 		_sceneObjects[i].isMoving         = false;
59 		_sceneObjects[i].isRetired        = false;
60 
61 		_sceneObjectsSortedByDistance[i]  = -1;
62 	}
63 	_count = 0;
64 }
65 
addActor(int sceneObjectId,const BoundingBox & boundingBox,const Common::Rect & screenRectangle,bool isClickable,bool isMoving,bool isTarget,bool isRetired)66 bool SceneObjects::addActor(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isMoving, bool isTarget, bool isRetired) {
67 	return addSceneObject(sceneObjectId, kSceneObjectTypeActor, boundingBox, screenRectangle, isClickable, false, 0, isTarget, isMoving, isRetired);
68 }
69 
addObject(int sceneObjectId,const BoundingBox & boundingBox,bool isClickable,bool isObstacle,uint8 unknown1,bool isTarget)70 bool SceneObjects::addObject(int sceneObjectId, const BoundingBox &boundingBox, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget) {
71 	Common::Rect rect(-1, -1, -1, -1);
72 	return addSceneObject(sceneObjectId, kSceneObjectTypeObject, boundingBox, rect, isClickable, isObstacle, unknown1, isTarget, false, false);
73 }
74 
addItem(int sceneObjectId,const BoundingBox & boundingBox,const Common::Rect & screenRectangle,bool isTarget,bool isObstacle)75 bool SceneObjects::addItem(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isTarget, bool isObstacle) {
76 	return addSceneObject(sceneObjectId, kSceneObjectTypeItem, boundingBox, screenRectangle, isObstacle, 0, 0, isTarget, 0, 0);
77 }
78 
remove(int sceneObjectId)79 bool SceneObjects::remove(int sceneObjectId) {
80 	int i = findById(sceneObjectId);
81 	if (i == -1) {
82 		return false;
83 	}
84 	_sceneObjects[i].isPresent = false;
85 	int j;
86 	for (j = 0; j < _count; ++j) {
87 		if (_sceneObjectsSortedByDistance[j] == i) {
88 			break;
89 		}
90 	}
91 	for (int k = j; k < _count - 1; ++k) {
92 		_sceneObjectsSortedByDistance[k] = _sceneObjectsSortedByDistance[k + 1];
93 	}
94 
95 	--_count;
96 	return true;
97 }
98 
findByXYZ(bool * isClickable,bool * isObstacle,bool * isTarget,Vector3 & position,bool findClickables,bool findObstacles,bool findTargets) const99 int SceneObjects::findByXYZ(bool *isClickable, bool *isObstacle, bool *isTarget, Vector3 &position, bool findClickables, bool findObstacles, bool findTargets) const {
100 	*isClickable = false;
101 	*isObstacle  = false;
102 	*isTarget    = false;
103 
104 	for (int i = 0; i < _count; ++i) {
105 		assert(_sceneObjectsSortedByDistance[i] < kSceneObjectCount);
106 
107 		const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
108 
109 		if ((findClickables && sceneObject->isClickable) ||
110 			(findObstacles  && sceneObject->isObstacle) ||
111 			(findTargets    && sceneObject->isTarget)) {
112 			BoundingBox boundingBox = sceneObject->boundingBox;
113 
114 			if (sceneObject->type == kSceneObjectTypeActor) {
115 				boundingBox.expand(-4.0, 0.0, -4.0, 4.0, 0.0, 4.0);
116 			}
117 
118 			if (boundingBox.inside(position)) {
119 				*isClickable = sceneObject->isClickable;
120 				*isObstacle  = sceneObject->isObstacle;
121 				*isTarget    = sceneObject->isTarget;
122 
123 				return sceneObject->id;
124 			}
125 		}
126 	}
127 
128 	return -1;
129 }
130 
existsOnXZ(int exceptSceneObjectId,float x,float z,bool movingActorIsObstacle,bool standingActorIsObstacle) const131 bool SceneObjects::existsOnXZ(int exceptSceneObjectId, float x, float z, bool movingActorIsObstacle, bool standingActorIsObstacle) const {
132 	float xMin = x - 12.5f;
133 	float xMax = x + 12.5f;
134 	float zMin = z - 12.5f;
135 	float zMax = z + 12.5f;
136 
137 	int count = _count;
138 
139 	if (count > 0) {
140 		for (int i = 0; i < count; ++i) {
141 			const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
142 			bool isObstacle = false;
143 			if (sceneObject->type == kSceneObjectTypeActor) {
144 				if (sceneObject->isRetired) {
145 					isObstacle = false;
146 				} else if (sceneObject->isMoving) {
147 					isObstacle = movingActorIsObstacle;
148 				} else {
149 					isObstacle = standingActorIsObstacle;
150 				}
151 			} else {
152 				isObstacle = sceneObject->isObstacle;
153 			}
154 
155 			if (isObstacle && sceneObject->id != exceptSceneObjectId) {
156 				float x1, y1, z1, x2, y2, z2;
157 				sceneObject->boundingBox.getXYZ(&x1, &y1, &z1, &x2, &y2, &z2);
158 				if (z1 <= zMax && z2 >= zMin && x1 <= xMax && x2 >= xMin) {
159 //					if (sceneObject->type == kSceneObjectTypeObject) {
160 //						Vector3 a(x1,y1,z1);
161 //						Vector3 b(x2,y2,z2);
162 //						Vector3 pos = _vm->_view->calculateScreenPosition(0.5 * (a + b));
163 //						debug("%d: %s (Clk: %s, Trg: %s, Prs: %s, Obs: %s, Mvg: %s), Pos(%02.2f,%02.2f,%02.2f)\n     Bbox(%02.2f,%02.2f,%02.2f) ~ (%02.2f,%02.2f,%02.2f)\n",
164 //								 sceneObject->id - kSceneObjectOffsetObjects,
165 //								 _vm->_scene->objectGetName(sceneObject->id - kSceneObjectOffsetObjects).c_str(),
166 //								 sceneObject->isClickable? "T" : "F",
167 //								 sceneObject->isTarget?    "T" : "F",
168 //								 sceneObject->isPresent?   "T" : "F",
169 //								 sceneObject->isObstacle?  "T" : "F",
170 //								 sceneObject->isMoving?    "T" : "F",
171 //								 pos.x, pos.y, pos.z,
172 //								 a.x, a.y, a.z, b.x, b.y, b.z);
173 //					}
174 					return true;
175 				}
176 			}
177 		}
178 	}
179 	return false;
180 }
181 
findById(int sceneObjectId) const182 int SceneObjects::findById(int sceneObjectId) const {
183 	for (int i = 0; i < _count; ++i) {
184 		int j = this->_sceneObjectsSortedByDistance[i];
185 
186 		if (_sceneObjects[j].isPresent && _sceneObjects[j].id == sceneObjectId) {
187 			return j;
188 		}
189 	}
190 	return -1;
191 }
192 
addSceneObject(int sceneObjectId,SceneObjectType sceneObjectType,const BoundingBox & boundingBox,const Common::Rect & screenRectangle,bool isClickable,bool isObstacle,uint8 unknown1,bool isTarget,bool isMoving,bool isRetired)193 bool SceneObjects::addSceneObject(int sceneObjectId, SceneObjectType sceneObjectType, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget, bool isMoving, bool isRetired) {
194 	int index = findEmpty();
195 	if (index == -1) {
196 		return false;
197 	}
198 
199 	_sceneObjects[index].id              = sceneObjectId;
200 	_sceneObjects[index].type            = sceneObjectType;
201 	_sceneObjects[index].isPresent       = true;
202 	_sceneObjects[index].boundingBox     = boundingBox;
203 	_sceneObjects[index].screenRectangle = screenRectangle;
204 	_sceneObjects[index].isClickable     = isClickable;
205 	_sceneObjects[index].isObstacle      = isObstacle;
206 	_sceneObjects[index].unknown1        = unknown1;
207 	_sceneObjects[index].isTarget        = isTarget;
208 	_sceneObjects[index].isMoving        = isMoving;
209 	_sceneObjects[index].isRetired       = isRetired;
210 
211 	float centerZ = (_sceneObjects[index].boundingBox.getZ0() + _sceneObjects[index].boundingBox.getZ1()) / 2.0f;
212 
213 	float distanceToCamera = fabs(-centerZ - _view->_cameraPosition.y); // y<->z is intentional, not a bug
214 	_sceneObjects[index].distanceToCamera = distanceToCamera;
215 
216 	// insert according to distance from camera
217 	int i;
218 	for (i = 0; i < _count; ++i) {
219 		if (distanceToCamera < _sceneObjects[_sceneObjectsSortedByDistance[i]].distanceToCamera) {
220 			break;
221 		}
222 	}
223 	for (int j = CLIP(_count - 1, 0, kSceneObjectCount - 2); j >= i; --j) {
224 		_sceneObjectsSortedByDistance[j + 1] = _sceneObjectsSortedByDistance[j];
225 	}
226 
227 	_sceneObjectsSortedByDistance[i] = index;
228 	++_count;
229 	return true;
230 }
231 
findEmpty() const232 int SceneObjects::findEmpty() const {
233 	for (int i = 0; i < kSceneObjectCount; ++i) {
234 		if (!_sceneObjects[i].isPresent)
235 			return i;
236 	}
237 	return -1;
238 }
239 
setMoving(int sceneObjectId,bool isMoving)240 void SceneObjects::setMoving(int sceneObjectId, bool isMoving) {
241 	int i = findById(sceneObjectId);
242 	if (i == -1) {
243 		return;
244 	}
245 	_sceneObjects[i].isMoving = isMoving;
246 }
247 
setRetired(int sceneObjectId,bool isRetired)248 void SceneObjects::setRetired(int sceneObjectId, bool isRetired) {
249 	int i = findById(sceneObjectId);
250 	if (i == -1) {
251 		return;
252 	}
253 	_sceneObjects[i].isRetired = isRetired;
254 }
255 
isBetween(float sourceX,float sourceZ,float targetX,float targetZ,int sceneObjectId) const256 bool SceneObjects::isBetween(float sourceX, float sourceZ, float targetX, float targetZ, int sceneObjectId) const {
257 	int i = findById(sceneObjectId);
258 	if (i == -1) {
259 		return false;
260 	}
261 
262 	float objectX1, objectY1, objectZ1, objectX2, objectY2, objectZ2;
263 	_sceneObjects[i].boundingBox.getXYZ(&objectX1, &objectY1, &objectZ1, &objectX2, &objectY2, &objectZ2);
264 
265 	Vector2 intersection;
266 	return lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX1, objectZ1), Vector2(objectX2, objectZ1), &intersection)
267 	    || lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX2, objectZ1), Vector2(objectX2, objectZ2), &intersection)
268 	    || lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX2, objectZ2), Vector2(objectX1, objectZ2), &intersection)
269 	    || lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX1, objectZ2), Vector2(objectX1, objectZ1), &intersection);
270 }
271 
isObstacleBetween(const Vector3 & source,const Vector3 & target,int exceptSceneObjectId) const272 bool SceneObjects::isObstacleBetween(const Vector3 &source, const Vector3 &target, int exceptSceneObjectId) const {
273 	for (int i = 0; i < _count; ++i) {
274 		const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
275 
276 		if (sceneObject->type == kSceneObjectTypeActor || !sceneObject->isObstacle || sceneObject->id == exceptSceneObjectId) {
277 			continue;
278 		}
279 
280 		float objectX1, objectY1, objectZ1, objectX2, objectY2, objectZ2;
281 		sceneObject->boundingBox.getXYZ(&objectX1, &objectY1, &objectZ1, &objectX2, &objectY2, &objectZ2);
282 
283 		if (84.0f <= objectY1 - source.y || 72.0f >= objectY2 - source.y) {
284 			continue;
285 		}
286 
287 		float xAdjustement = (objectX2 - objectX1) * 0.1f;
288 		float zAdjustement = (objectZ2 - objectZ1) * 0.1f;
289 
290 		objectX1 = objectX1 + xAdjustement;
291 		objectZ1 = objectZ1 + zAdjustement;
292 		objectX2 = objectX2 - xAdjustement;
293 		objectZ2 = objectZ2 - zAdjustement;
294 
295 		Vector2 intersection;
296 		if (lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX1, objectZ1), Vector2(objectX2, objectZ1), &intersection)
297 		 || lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX2, objectZ1), Vector2(objectX2, objectZ2), &intersection)
298 		 || lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX2, objectZ2), Vector2(objectX1, objectZ2), &intersection)
299 		 || lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX1, objectZ2), Vector2(objectX1, objectZ1), &intersection)) {
300 			return true;
301 		}
302 	}
303 	return false;
304 }
305 
setIsClickable(int sceneObjectId,bool isClickable)306 void SceneObjects::setIsClickable(int sceneObjectId, bool isClickable) {
307 	int i = findById(sceneObjectId);
308 	if (i == -1) {
309 		return;
310 	}
311 	_sceneObjects[i].isClickable = isClickable;
312 }
313 
setIsObstacle(int sceneObjectId,bool isObstacle)314 void SceneObjects::setIsObstacle(int sceneObjectId, bool isObstacle) {
315 	int i = findById(sceneObjectId);
316 	if (i == -1) {
317 		return;
318 	}
319 	_sceneObjects[i].isObstacle = isObstacle;
320 }
321 
setIsTarget(int sceneObjectId,bool isTarget)322 void SceneObjects::setIsTarget(int sceneObjectId, bool isTarget) {
323 	int i = findById(sceneObjectId);
324 	if (i == -1) {
325 		return;
326 	}
327 	_sceneObjects[i].isTarget = isTarget;
328 }
329 
updateObstacles()330 void SceneObjects::updateObstacles() {
331 	_vm->_obstacles->clear();
332 	for (int i = 0; i < _count; ++i) {
333 		int index = _sceneObjectsSortedByDistance[i];
334 		const SceneObject *sceneObject = &_sceneObjects[index];
335 		if (sceneObject->isObstacle) {
336 			float x0, y0, z0, x1, y1, z1;
337 			sceneObject->boundingBox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
338 			_vm->_obstacles->add(x0, z0, x1, z1);
339 		}
340 	}
341 	_vm->_obstacles->backup();
342 }
343 
isEmptyScreenRectangle(int sceneObjectId)344 bool SceneObjects::isEmptyScreenRectangle(int sceneObjectId) {
345 	int index = findById(sceneObjectId);
346 	if (index != -1) {
347 		const SceneObject *sceneObject = &_sceneObjects[index];
348 		return sceneObject->screenRectangle.isEmpty();
349 	}
350 	return true;
351 }
352 
compareScreenRectangle(int sceneObjectId,const Common::Rect & rectangle)353 int SceneObjects::compareScreenRectangle(int sceneObjectId, const Common::Rect &rectangle) {
354 	int index = findById(sceneObjectId);
355 	if (index != -1) {
356 		const SceneObject *sceneObject = &_sceneObjects[index];
357 		if (sceneObject->screenRectangle == rectangle) {
358 			return 0;
359 		}
360 	}
361 	return -1;
362 }
363 
resetScreenRectangleAndBbox(int sceneObjectId)364 void SceneObjects::resetScreenRectangleAndBbox(int sceneObjectId) {
365 	int index = findById(sceneObjectId);
366 	if (index != -1) {
367 		SceneObject *sceneObject = &_sceneObjects[index];
368 		sceneObject->screenRectangle.left   = -1;
369 		sceneObject->screenRectangle.top    = -1;
370 		sceneObject->screenRectangle.right  = -1;
371 		sceneObject->screenRectangle.bottom = -1;
372 		sceneObject->boundingBox.setXYZ(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
373 	}
374 }
375 
synchScreenRectangle(int sceneObjectId,const Common::Rect & targetScreenRect)376 void SceneObjects::synchScreenRectangle(int sceneObjectId, const Common::Rect &targetScreenRect) {
377 	int index = findById(sceneObjectId);
378 	if (index != -1) {
379 		SceneObject *sceneObject = &_sceneObjects[index];
380 		sceneObject->screenRectangle.left   = targetScreenRect.left;
381 		sceneObject->screenRectangle.top    = targetScreenRect.top;
382 		sceneObject->screenRectangle.right  = targetScreenRect.right;
383 		sceneObject->screenRectangle.bottom = targetScreenRect.bottom;
384 	}
385 }
386 
save(SaveFileWriteStream & f)387 void SceneObjects::save(SaveFileWriteStream &f) {
388 	f.writeInt(_count);
389 	for (int i = 0; i < kSceneObjectCount; ++i) {
390 		f.writeInt(_sceneObjects[i].id);
391 		f.writeInt(_sceneObjects[i].type);
392 		f.writeBoundingBox(_sceneObjects[i].boundingBox, true);
393 		f.writeRect(_sceneObjects[i].screenRectangle);
394 		f.writeFloat(_sceneObjects[i].distanceToCamera);
395 		f.writeBool(_sceneObjects[i].isPresent);
396 		f.writeBool(_sceneObjects[i].isClickable);
397 		f.writeBool(_sceneObjects[i].isObstacle);
398 		f.writeInt(_sceneObjects[i].unknown1);
399 		f.writeBool(_sceneObjects[i].isTarget);
400 		f.writeBool(_sceneObjects[i].isMoving);
401 		f.writeBool(_sceneObjects[i].isRetired);
402 	}
403 	for (int i = 0; i < kSceneObjectCount; ++i) {
404 		f.writeInt(_sceneObjectsSortedByDistance[i]);
405 	}
406 }
407 
load(SaveFileReadStream & f)408 void SceneObjects::load(SaveFileReadStream &f) {
409 	_count = f.readInt();
410 	for (int i = 0; i < kSceneObjectCount; ++i) {
411 		_sceneObjects[i].id = f.readInt();
412 		_sceneObjects[i].type = (SceneObjectType)f.readInt();
413 		_sceneObjects[i].boundingBox = f.readBoundingBox(true);
414 		_sceneObjects[i].screenRectangle = f.readRect();
415 		_sceneObjects[i].distanceToCamera = f.readFloat();
416 		_sceneObjects[i].isPresent = f.readBool();
417 		_sceneObjects[i].isClickable = f.readBool();
418 		_sceneObjects[i].isObstacle = f.readBool();
419 		_sceneObjects[i].unknown1 = f.readInt();
420 		_sceneObjects[i].isTarget = f.readBool();
421 		_sceneObjects[i].isMoving = f.readBool();
422 		_sceneObjects[i].isRetired = f.readBool();
423 	}
424 	for (int i = 0; i < kSceneObjectCount; ++i) {
425 		_sceneObjectsSortedByDistance[i] = f.readInt();
426 	}
427 }
428 
429 } // End of namespace BladeRunner
430