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 "titanic/star_control/camera.h"
24 #include "titanic/debugger.h"
25 #include "titanic/star_control/motion_control.h"
26 #include "titanic/star_control/fmatrix.h"
27 #include "titanic/star_control/fpoint.h"
28 #include "titanic/star_control/motion_control_marked.h"
29 #include "titanic/star_control/motion_control_unmarked.h"
30 #include "titanic/star_control/error_code.h"
31 #include "titanic/support/simple_file.h"
32 #include "titanic/titanic.h"
33 
34 namespace Titanic {
35 
36 const double rowScale1 = 100000.0;
37 const double rowScale2 = 1000000.0;
38 
39 FMatrix *CCamera::_priorOrientation;
40 FMatrix *CCamera::_newOrientation;
41 
CCamera(const CNavigationInfo * data)42 CCamera::CCamera(const CNavigationInfo *data) :
43 		_lockLevel(ZERO_LOCKED), _motion(nullptr), _isMoved(false), _isInLockingProcess(false) {
44 	createMotionControl(data);
45 }
46 
CCamera(CViewport * src)47 CCamera::CCamera(CViewport *src) :
48 		_lockLevel(ZERO_LOCKED), _motion(nullptr), _isMoved(false), _isInLockingProcess(false), _viewport(src) {
49 }
50 
init()51 void CCamera::init() {
52 	_priorOrientation = nullptr;
53 	_newOrientation = nullptr;
54 }
55 
deinit()56 void CCamera::deinit() {
57 	delete _priorOrientation;
58 	delete _newOrientation;
59 	_priorOrientation = nullptr;
60 	_newOrientation = nullptr;
61 }
62 
isLocked()63 bool CCamera::isLocked() {
64 	return _motion->isLocked();
65 }
66 
isNotInLockingProcess()67 bool CCamera::isNotInLockingProcess() {
68 	return !_isInLockingProcess;
69 }
70 
~CCamera()71 CCamera::~CCamera() {
72 	deleteMotionController();
73 }
74 
setViewport(const CViewport * src)75 void CCamera::setViewport(const CViewport *src) {
76 	_viewport.copyFrom(src);
77 }
78 
setMotion(const CNavigationInfo * src)79 void CCamera::setMotion(const CNavigationInfo *src) {
80 	_motion->setMotion(src);
81 }
82 
setPosition(const FVector & v)83 void CCamera::setPosition(const FVector &v) {
84 	if (!isLocked()) {
85 		_viewport.setPosition(v);
86 		setIsMoved();
87 	}
88 }
89 
setOrientation(const FVector & v)90 void CCamera::setOrientation(const FVector &v) {
91 	if (!isLocked())
92 		_viewport.setOrientation(v);
93 }
94 
95 // This never gets called
setRoleAngle(double angle)96 void CCamera::setRoleAngle(double angle) {
97 	if (!isLocked())
98 		_viewport.SetRoleAngle(angle);
99 }
100 
101 // This never gets called
setFrontClip(double n)102 void CCamera::setFrontClip(double n) {
103 	if (!isLocked())
104 		_viewport.setFrontClip(n);
105 }
106 
107 // This never gets called
SetBackClip(double f)108 void CCamera::SetBackClip(double f) {
109 	if (!isLocked())
110 		_viewport.setBackClip(f);
111 }
112 
113 // This never gets called
setCenterYAngle(int v)114 void CCamera::setCenterYAngle(int v) {
115 	if (!isLocked())
116 		_viewport.setCenterYAngle(v);
117 }
118 
119 // This never gets called
setCenterZAngle(int v)120 void CCamera::setCenterZAngle(int v) {
121 	if (!isLocked())
122 		_viewport.setCenterZAngle(v);
123 }
124 
randomizeOrientation()125 void CCamera::randomizeOrientation() {
126 	if (!isLocked())
127 		_viewport.randomizeOrientation();
128 }
129 
setFields(StarMode mode,double val)130 void CCamera::setFields(StarMode mode, double val) {
131 	if (!isLocked())
132 		_viewport.changeStarColorPixel(mode, val);
133 }
134 
setDestination(const FVector & v)135 void CCamera::setDestination(const FVector &v) {
136 	FMatrix orientation = _viewport.getOrientation();
137 	FVector oldPos = _viewport._position;
138 
139 	_motion->moveTo(oldPos, v, orientation);
140 }
141 
updatePosition(CErrorCode * errorCode)142 void CCamera::updatePosition(CErrorCode *errorCode) {
143 	if (!_priorOrientation)
144 		_priorOrientation = new FMatrix();
145 	if (!_newOrientation)
146 		_newOrientation = new FMatrix();
147 
148 	*_priorOrientation = _viewport.getOrientation();
149 	*_newOrientation = *_priorOrientation;
150 
151 	FVector priorPos = _viewport._position;
152 	FVector newPos = _viewport._position;
153 	_motion->updatePosition(*errorCode, newPos, *_newOrientation);
154 
155 	if (newPos != priorPos) {
156 		_viewport.setPosition(newPos);
157 		setIsMoved();
158 	}
159 
160 	if (*_priorOrientation != *_newOrientation) {
161 		_viewport.setOrientation(*_newOrientation);
162 	}
163 }
164 
accelerate()165 void CCamera::accelerate() {
166 	_motion->accelerate();
167 }
168 
deccelerate()169 void CCamera::deccelerate() {
170 	_motion->deccelerate();
171 }
172 
fullSpeed()173 void CCamera::fullSpeed() {
174 	_motion->fullSpeed();
175 }
176 
stop()177 void CCamera::stop() {
178 	_motion->stop();
179 }
180 
reposition(double factor)181 void CCamera::reposition(double factor) {
182 	if (!isLocked())
183 		_viewport.reposition(factor);
184 }
185 
setPosition(const FPose & pose)186 void CCamera::setPosition(const FPose &pose) {
187 	if (!isLocked()) {
188 		_viewport.setPosition(pose);
189 		setIsMoved();
190 	}
191 }
192 
changeOrientation(FMatrix & m)193 void CCamera::changeOrientation(FMatrix &m) {
194 	if (!isLocked())
195 		_viewport.changeOrientation(m);
196 }
197 
getPose()198 FPose CCamera::getPose() {
199 	return _viewport.getPose();
200 }
201 
getRawPose()202 FPose CCamera::getRawPose() {
203 	return _viewport.getRawPose();
204 }
205 
getFrontClip() const206 double CCamera::getFrontClip() const {
207 	return _viewport._frontClip;
208 }
209 
getBackClip() const210 double CCamera::getBackClip() const {
211 	return _viewport._backClip;
212 }
213 
getStarColor() const214 StarColor CCamera::getStarColor() const {
215 	return _viewport._starColor;
216 }
217 
getRelativePos(int index,const FVector & src)218 FVector CCamera::getRelativePos(int index, const FVector &src) {
219 	FVector dest;
220 
221 	double val;
222 	if (index == 2) {
223 		val = _viewport._isZero;
224 	} else {
225 		val = _viewport._valArray[index];
226 	}
227 
228 	dest._x = ((val + src._x) * _viewport._centerVector._x)
229 		/ (_viewport._centerVector._y * src._z);
230 	dest._y = src._y * _viewport._centerVector._x / (_viewport._centerVector._z * src._z);
231 	dest._z = src._z;
232 	return dest;
233 }
234 
getRelativePosNoCentering(int index,const FVector & src)235 FVector CCamera::getRelativePosNoCentering(int index, const FVector &src) {
236 	return _viewport.getRelativePosNoCentering(index, src);
237 }
238 
getRelativePosCentering(int index,const FVector & v)239 FVector CCamera::getRelativePosCentering(int index, const FVector &v) {
240 	return _viewport.getRelativePosCentering(index, v);
241 }
242 
getRelativePosCenteringRaw(int index,const FVector & v)243 FVector CCamera::getRelativePosCenteringRaw(int index, const FVector &v) {
244 	return _viewport.getRelativePosCenteringRaw(index, v);
245 }
246 
setViewportAngle(const FPoint & angles)247 void CCamera::setViewportAngle(const FPoint &angles) {
248 	debug(DEBUG_DETAILED, "setViewportAngle %f %f", angles._x, angles._y);
249 
250 	if (isLocked())
251 		return;
252 
253 	switch(_lockLevel) {
254 	case ZERO_LOCKED: {
255 		FPose subX(X_AXIS, angles._y);
256 		FPose subY(Y_AXIS, -angles._x); // needs to be negative or looking left will cause the view to go right
257 		FPose sub(subX, subY);
258 		changeOrientation(sub);
259 		break;
260 	}
261 
262 	case ONE_LOCKED: {
263 		FVector row1 = _lockedStarsPos._row1;
264 		FPose poseX(X_AXIS, angles._y);
265 		FPose poseY(Y_AXIS, -angles._x); // needs to be negative or looking left will cause the view to go right
266 		FPose pose(poseX, poseY);
267 
268 		FMatrix m1 = _viewport.getOrientation();
269 		FVector tempV1 = _viewport._position;
270 		FVector tempV2 = m1._row1 * rowScale1;
271 		FVector tempV3 = tempV2 + tempV1;
272 		FVector tempV4 = tempV3;
273 
274 		tempV2 = m1._row2 * rowScale1;
275 		FVector tempV5 = m1._row3 * rowScale1;
276 		FVector tempV6 = tempV2 + tempV1;
277 
278 		FVector tempV7 = tempV5 + tempV1;
279 		tempV5 = tempV6;
280 		tempV6 = tempV7;
281 
282 		tempV1 -= row1;
283 		tempV4 -= row1;
284 		tempV5 -= row1;
285 		tempV6 -= row1;
286 
287 		tempV1 = tempV1.matProdRowVect(pose);
288 		tempV4 = tempV4.matProdRowVect(pose);
289 		tempV5 = tempV5.matProdRowVect(pose);
290 		tempV6 = tempV6.matProdRowVect(pose);
291 
292 		tempV4 -= tempV1;
293 		tempV5 -= tempV1;
294 		tempV6 -= tempV1;
295 
296 		float unusedScale = 0.0;
297 		if (!tempV4.normalize(unusedScale) ||
298 				!tempV5.normalize(unusedScale) ||
299 				!tempV6.normalize(unusedScale)) {
300 			// Do the normalization, put the scale amount in unusedScale,
301 			// but if it is unsuccessful, crash
302 			assert(unusedScale);
303 		}
304 
305 		tempV1 += row1;
306 		m1.set(tempV4, tempV5, tempV6);
307 		_viewport.setOrientation(m1);
308 		_viewport.setPosition(tempV1);
309 		break;
310 	}
311 
312 	case TWO_LOCKED: {
313 		FVector tempV2;
314 		FPose m1;
315 		FVector mrow1, mrow2, mrow3;
316 		FVector tempV1, diffV, multV, multV2, tempV3, tempV7;
317 
318 		FPose subX(0, _lockedStarsPos._row1);
319 		FPose subY(Y_AXIS, angles._y);
320 
321 		tempV1 = _lockedStarsPos._row2 - _lockedStarsPos._row1;
322 		diffV = tempV1;
323 		m1 = diffV.formRotXY();
324 		FPose m11;
325 		fposeProd(m1, subX, m11);
326 
327 		subX = m11.inverseTransform();
328 		FPose m12;
329 		fposeProd(subX, subY, m12);
330 
331 		FMatrix m3 = _viewport.getOrientation();
332 		tempV2 = _viewport._position;
333 		multV._x = m3._row1._x * rowScale2;
334 		multV._y = m3._row1._y * rowScale2;
335 		multV._z = m3._row1._z * rowScale2;
336 		tempV3._x = tempV2._x;
337 		tempV3._y = tempV2._y;
338 		tempV3._z = tempV2._z;
339 		multV2._z = m3._row2._z * rowScale2;
340 
341 		tempV1._x = multV._x + tempV3._x;
342 		tempV1._y = multV._y + tempV3._y;
343 		tempV1._z = multV._z + tempV3._z;
344 		mrow3._z = 0.0;
345 		mrow3._y = 0.0;
346 		mrow3._x = 0.0;
347 		multV2._x = m3._row2._x * rowScale2;
348 		multV2._y = m3._row2._y * rowScale2;
349 		mrow1 = tempV1;
350 		multV = multV2 + tempV3;
351 		mrow2 = multV;
352 
353 		tempV7._z = m3._row3._z * rowScale2 + tempV3._z;
354 		tempV7._y = m3._row3._y * rowScale2 + tempV3._y;
355 		tempV7._x = m3._row3._x * rowScale2 + tempV3._x;
356 
357 		mrow3 = tempV7;
358 		tempV3 = tempV3.matProdRowVect(m12);
359 		mrow1 = mrow1.matProdRowVect(m12);
360 		mrow2 = mrow2.matProdRowVect(m12);
361 		mrow3 = mrow3.matProdRowVect(m12);
362 
363 		tempV3 = tempV3.matProdRowVect(m11);
364 		mrow1 = mrow1.matProdRowVect(m11);
365 		mrow2 = mrow2.matProdRowVect(m11);
366 		mrow3 = mrow3.matProdRowVect(m11);
367 
368 		mrow1 -= tempV3;
369 		mrow2 -= tempV3;
370 		mrow3 -= tempV3;
371 
372 		float unusedScale=0.0;
373 		if (!mrow1.normalize(unusedScale) ||
374 				!mrow2.normalize(unusedScale) ||
375 				!mrow3.normalize(unusedScale)) {
376 			// Do the normalization, put the scale amount in unusedScale,
377 			// but if it is unsuccessful, crash
378 			assert(unusedScale);
379 		}
380 
381 		m3.set(mrow1, mrow2, mrow3);
382 		_viewport.setOrientation(m3);
383 		_viewport.setPosition(tempV3);
384 		break;
385 	}
386 
387 	// All three stars are locked on in this case so the camera does not move
388 	// in response to the users mouse movements
389 	case THREE_LOCKED:
390 	default:
391 		break;
392 	}
393 }
394 
addLockedStar(const FVector v)395 bool CCamera::addLockedStar(const FVector v) {
396 	if (_lockLevel == THREE_LOCKED)
397 		return false;
398 
399 	CNavigationInfo data;
400 	_motion->getMotion(&data);
401 	deleteMotionController();
402 
403 	FVector &row = _lockedStarsPos[(int)_lockLevel];
404 	_lockLevel = (StarLockLevel)((int)_lockLevel + 1);
405 	row = v;
406 	createMotionControl(&data);
407 	return true;
408 }
409 
removeLockedStar()410 bool CCamera::removeLockedStar() {
411 	if (_lockLevel == ZERO_LOCKED)
412 		return false;
413 
414 	CNavigationInfo data;
415 	_motion->getMotion(&data);
416 	deleteMotionController();
417 
418 	_lockLevel = (StarLockLevel)((int)_lockLevel - 1);
419 	createMotionControl(&data);
420 	return true;
421 }
422 
getRelativeXCenterPixels(double * v1,double * v2,double * v3,double * v4)423 void CCamera::getRelativeXCenterPixels(double *v1, double *v2, double *v3, double *v4) {
424 	_viewport.getRelativeXCenterPixels(v1, v2, v3, v4);
425 }
426 
load(SimpleFile * file,int param)427 void CCamera::load(SimpleFile *file, int param) {
428 	_viewport.load(file, param);
429 }
430 
save(SimpleFile * file,int indent)431 void CCamera::save(SimpleFile *file, int indent) {
432 	_viewport.save(file, indent);
433 }
434 
createMotionControl(const CNavigationInfo * src)435 bool CCamera::createMotionControl(const CNavigationInfo *src) {
436 	CMotionControl *motion = nullptr;
437 
438 	switch (_lockLevel) {
439 	case ZERO_LOCKED:
440 		motion = new CMotionControlUnmarked(src);
441 		break;
442 
443 	case ONE_LOCKED:
444 	case TWO_LOCKED:
445 	case THREE_LOCKED:
446 		motion = new CMotionControlMarked(src);
447 		break;
448 
449 	default:
450 		break;
451 	}
452 
453 	if (motion) {
454 		assert(!_motion);
455 		_motion = motion;
456 		return true;
457 	} else {
458 		return false;
459 	}
460 }
461 
deleteMotionController()462 void CCamera::deleteMotionController() {
463 	if (_motion) {
464 		delete _motion;
465 		_motion = nullptr;
466 		_isInLockingProcess = false;
467 	}
468 }
469 
lockMarker1(FVector v1,FVector firstStarPosition,FVector v3)470 bool CCamera::lockMarker1(FVector v1, FVector firstStarPosition, FVector v3) {
471 	if (_lockLevel != ZERO_LOCKED)
472 		return true;
473 
474 	_isInLockingProcess = true;
475 	FVector tempV;
476 	double val1, val2, val3, val4, val5;
477 	double val6, val7, val8, val9;
478 
479 	val1 = _viewport._centerVector._y * v1._x;
480 	tempV._z = _viewport._frontClip;
481 	val2 = _viewport._centerVector._y * tempV._z * v3._x;
482 	val3 = _viewport._centerVector._z * v1._y;
483 	val4 = _viewport._centerVector._z * tempV._z;
484 	val5 = val1 * v1._z / _viewport._centerVector._x;
485 	v3._z = v1._z;
486 	val6 = val4 * v3._y;
487 	val7 = val3 * v1._z / _viewport._centerVector._x;
488 	val8 = val6 / _viewport._centerVector._x;
489 	val9 = val2 / _viewport._centerVector._x;
490 	v3._x = val5 - _viewport._isZero; // TODO: _viewport._isZero is always zero
491 	v3._y = val7;
492 	tempV._x = val9 - _viewport._isZero; // TODO: _viewport._isZero is always zero
493 	tempV._y = val8;
494 
495 	float unusedScale = 0.0;
496 	if (!v3.normalize(unusedScale) || !tempV.normalize(unusedScale)) {
497 		// Do the normalization, put the scale amount in unusedScale,
498 		// but if it is unsuccessful, crash
499 		assert(unusedScale);
500 	}
501 
502 	FMatrix matrix = _viewport.getOrientation();
503 	const FVector &pos = _viewport._position;
504 	_motion->transitionBetweenOrientations(v3, tempV, pos, matrix);
505 
506 	CCallbackHandler *callback = new CCallbackHandler(this, firstStarPosition);
507 	_motion->setCallback(callback);
508 
509 	return	true;
510 }
511 
lockMarker2(CViewport * viewport,const FVector & secondStarPosition)512 bool CCamera::lockMarker2(CViewport *viewport, const FVector &secondStarPosition) {
513 	if (_lockLevel != ONE_LOCKED)
514 		return true;
515 
516 	_isInLockingProcess = true;
517 	FVector firstStarPosition = _lockedStarsPos._row1;
518 	FPose m3(0, firstStarPosition); // Identity matrix and row4 as the 1st stars position
519 	FVector starDelta = secondStarPosition - firstStarPosition;
520 	FPose m10 = starDelta.formRotXY();
521 	FPose m11;
522 	fposeProd(m10, m3, m11);
523 
524 	m10 = m11.inverseTransform();
525 
526 	FVector oldPos = _viewport._position;
527 
528 	FPose m4;
529 	m4._row1 = viewport->_position;
530 	m4._row2 = FVector(0.0, 0.0, 0.0);
531 	m4._row3 = FVector(0.0, 0.0, 0.0);
532 	m4._vector = FVector(0.0, 0.0, 0.0);
533 
534 	FMatrix newOr = viewport->getOrientation();
535 	float yVal1 = newOr._row1._y * rowScale2;
536 	float zVal1 = newOr._row1._z * rowScale2;
537 	float xVal1 = newOr._row2._x * rowScale2;
538 	float yVal2 = newOr._row2._y * rowScale2;
539 	float zVal2 = newOr._row2._z * rowScale2;
540 	float zVal3 = zVal1 + m4._row1._z;
541 	float yVal3 = yVal1 + m4._row1._y;
542 	float xVal2 = newOr._row1._x * rowScale2 + m4._row1._x;
543 	float zVal4 = zVal2 + m4._row1._z;
544 	float yVal4 = yVal2 + m4._row1._y;
545 	float xVal3 = xVal1 + m4._row1._x;
546 
547 	FVector tempV4(xVal2, yVal3, zVal3);
548 	FVector tempV3(xVal3, yVal4, zVal4);
549 	m4._row3 = tempV4;
550 
551 	FVector tempV5;
552 	tempV5._x = newOr._row3._x * rowScale2;
553 	tempV5._y = newOr._row3._y * rowScale2;
554 	m4._row2 = tempV3;
555 
556 	tempV3._x = tempV5._x + m4._row1._x;
557 	tempV3._y = tempV5._y + m4._row1._y;
558 	tempV3._z = newOr._row3._z * rowScale2 + m4._row1._z;
559 	m4._vector = tempV3;
560 
561 
562 	FVector viewPosition2 = oldPos.matProdRowVect(m10);
563 	m3 = m4.compose2(m10);
564 
565 	float minDistance;
566 	FVector x1(viewPosition2);
567 	FVector x2(m3._row1);
568 	// Find the angle of rotation for m4._row1 that gives the minimum distance to viewPosition
569 	float minDegree = calcAngleForMinDist(x1, x2, minDistance);
570 
571 	m3.rotVectAxisY((double)minDegree);
572 	FPose m13;
573 	m13 = m3.compose2(m11);
574 
575 	m13._row3 -= m13._row1;
576 	m13._row2 -= m13._row1;
577 	m13._vector -= m13._row1;
578 
579 
580 
581 	float unusedScale=0.0;
582 	if (!m13._row2.normalize(unusedScale) ||
583 			!m13._row3.normalize(unusedScale) ||
584 			!m13._vector.normalize(unusedScale) ) {
585 		// Do the normalizations, put the scale amount in unusedScale,
586 		// but if any of the normalizations are unsuccessful, crash
587 		assert(unusedScale);
588 	}
589 
590 	newOr.set(m13._row3, m13._row2, m13._vector);
591 
592 	FVector newPos = m13._row1;
593 	FMatrix oldOr = _viewport.getOrientation();
594 
595 	// WORKAROUND: set old position to new position (1st argument), this prevents
596 	// locking issues when locking the 2nd star. Fixes #9961.
597 	_motion->transitionBetweenPosOrients(newPos, newPos, oldOr, newOr);
598 	CCallbackHandler *callback = new CCallbackHandler(this, secondStarPosition);
599 	_motion->setCallback(callback);
600 
601 	return	true;
602 }
603 
lockMarker3(CViewport * viewport,const FVector & thirdStarPosition)604 bool CCamera::lockMarker3(CViewport *viewport, const FVector &thirdStarPosition) {
605 	if (_lockLevel != TWO_LOCKED)
606 		return true;
607 
608 	_isInLockingProcess = true;
609 	FMatrix newOr = viewport->getOrientation();
610 	FMatrix oldOr = _viewport.getOrientation();
611 	FVector newPos = viewport->_position;
612 	//FVector oldPos = _viewport._position;
613 
614 	// WORKAROUND: set old position to new position (1st argument), this prevents
615 	// locking issues when locking the 3rd star. Fixes #9961.
616 	_motion->transitionBetweenPosOrients(newPos, newPos, oldOr, newOr);
617 
618 	CCallbackHandler *callback = new CCallbackHandler(this, thirdStarPosition);
619 	_motion->setCallback(callback);
620 
621 	return true;
622 }
623 
calcAngleForMinDist(FVector & x,FVector & y,float & minDistance)624 float CCamera::calcAngleForMinDist(FVector &x, FVector &y, float &minDistance) {
625 	FVector tempPos;
626 	minDistance = (float)1.0e20;
627 	float minDegree = 0.0;
628 	float degInc = 1.0; // one degree steps
629 	int nDegrees = floor(360.0/degInc);
630 	for (int i = 0; i < nDegrees; ++i) {
631 		tempPos = y;
632 		tempPos.rotVectAxisY((float)degInc*i);
633 		float distance = x.getDistance(tempPos);
634 
635 		if (distance < minDistance) {
636 			minDistance = distance;
637 			minDegree = (float) degInc*i;
638 		}
639 	}
640 	return minDegree;
641 }
642 
643 } // End of namespace Titanic
644