1 #include "pch.h"
2 #include "../vdrift/par.h"
3 #include "common/Def_Str.h"
4 #include "FollowCamera.h"
5 #include "../vdrift/settings.h"
6 
7 #include "../tinyxml/tinyxml2.h"
8 #include "../vdrift/pathmanager.h"
9 #include "../vdrift/mathvector.h"
10 // ray cast
11 #include "../vdrift/collision_contact.h"
12 #include "../vdrift/collision_world.h"
13 #include "../btOgre/BtOgreDebug.h"
14 #include "btBulletCollisionCommon.h"
15 #include "CarPosInfo.h"
16 
17 #include <OgreCamera.h>
18 #include <OgreSceneNode.h>
19 #include <OgreTerrainGroup.h>
20 
21 #include <MyGUI.h>
22 using namespace Ogre;
23 using namespace tinyxml2;
24 
25 
GetAngle(float x,float y)26 static float GetAngle(float x, float y)
27 {
28 	if (x == 0.f && y == 0.f)
29 		return 0.f;
30 
31 	if (y == 0.f)
32 		return (x < 0.f) ? PI_d : 0.f;
33 	else
34 		return (y < 0.f) ? atan2f(-y, x) : (-atan2f(y, x));
35 }
36 
37 ///  Update
38 //-----------------------------------------------------------------------------------------------------
39 
update(Real time,const PosInfo & posIn,PosInfo * posOut,COLLISION_WORLD * world,bool bounce)40 void FollowCamera::update(Real time, const PosInfo& posIn, PosInfo* posOut, COLLISION_WORLD* world, bool bounce)
41 {
42 	if (!ca || !posOut)  return;
43 
44 	///  input from car posInfoIn
45 	Vector3 posGoal = posIn.pos;
46 	Quaternion orientGoal = posIn.rot;
47 	///  output saved back to car posInfoOut
48 	Quaternion camRotFinal;
49 
50 	const static Quaternion
51 		qO = Quaternion(Degree(180),Vector3::UNIT_Z) * Quaternion(Degree(-90),Vector3::UNIT_Y),
52 		qR = Quaternion(Degree(90),Vector3(0,1,0));
53 
54 	Quaternion  orient = orientGoal * qO;
55 	Vector3  ofs = orient * ca->mOffset,  goalLook = posGoal + ofs;
56 
57 	first = iFirst < 2;  ///par few first frames after reset
58 	if (iFirst < 10)  // after reset
59 	{	++iFirst;	mDistReduce = 0.f;  mATilt = 0.f;  }
60 
61 	///  Camera Tilt from terrain/road slope under car
62 	//-------------------------------------------------------------------------------------------
63 	const float			//  params  . . .
64 		Rdist = 1.f,     // dist from car to ray (front or back)
65 		HupCar = 1.5f,	  // car up dir dist - for pipes - so pos stays inside pipe
66 		Habove = 1.5f,    // up axis dist, above car - for very high terrain angles
67 		HMaxDepth = 12.f;  // how far down can the ray goes
68 	const static Radian r0(0.f),
69 		angMin = Degree(10.f),   // below this angle, tilt has no effect - terrain bumps
70 		maxDiff = Degree(1.4f);  // max diff of tilt - no sudden jumps
71 	const float smoothSpeed = 14.f;  // how fast to apply tilt change
72 
73 	bool bUseTilt = ca->mType == CAM_ExtAng || ca->mType == CAM_Follow;
74 	Radian tilt(0.f);
75 	if (pSet->cam_tilt && bUseTilt)
76 	{
77 		//  car pos
78 		Vector3 carUp = posIn.pos - HupCar * posIn.carY;
79 		MATHVECTOR<float,3> pos(carUp.x, -carUp.z, carUp.y + Habove);  // to vdr/blt
80 		const static MATHVECTOR<float,3> dir(0,0,-1);  // cast rays down
81 
82 		//  car rot, yaw angle
83 		Quaternion q = posIn.rot * Quaternion(Degree(90),Vector3(0,1,0));
84 		float angCarY = q.getYaw().valueRadians() + PI_d/2.f;
85 		float ax = cosf(angCarY)*Rdist, ay = sinf(angCarY)*Rdist;
86 		//LogO("pos: "+fToStr(pos[0],2,4)+" "+fToStr(pos[1],2,4)+"  a: "+fToStr(angCarY,2,4)+"  dir: "+fToStr(ax,2,4)+" "+fToStr(ay,2,4));
87 
88 		//  cast 2 rays - 2 times, average 2 angles
89 		COLLISION_CONTACT ct0,ct1,ct20,ct21;
90 		MATHVECTOR<float,3> ofs(ax*0.5f,ay*0.5f,0),ofs2(ax,ay,0);
91 		world->CastRay(pos+ofs, dir, HMaxDepth,chassis, ct0,  0,0, true, true);
92 		world->CastRay(pos-ofs, dir, HMaxDepth,chassis, ct1,  0,0, true, true);
93 		world->CastRay(pos+ofs2,dir, HMaxDepth,chassis, ct20, 0,0, true, true);
94 		world->CastRay(pos-ofs2,dir, HMaxDepth,chassis, ct21, 0,0, true, true);
95 
96 		#ifdef CAM_TILT_DBG
97 			MATHVECTOR<float,3> v;
98 			v = pos+ofs;  posHit[0] = Vector3(v[0],v[2]- ct0.GetDepth(), -v[1]);
99 			v = pos-ofs;  posHit[1] = Vector3(v[0],v[2]- ct1.GetDepth(), -v[1]);
100 			v = pos+ofs2; posHit[2] = Vector3(v[0],v[2]- ct20.GetDepth(),-v[1]);
101 			v = pos-ofs2; posHit[3] = Vector3(v[0],v[2]- ct21.GetDepth(),-v[1]);
102 		#endif
103 
104 		if (ct0.GetColObj() && ct1.GetColObj() && ct20.GetColObj() && ct21.GetColObj() )
105 			tilt = (GetAngle(Rdist, ct1.GetDepth() - ct0.GetDepth()) +
106 				GetAngle(2.f*Rdist, ct21.GetDepth() - ct20.GetDepth())) * 0.5f;
107 		//else  LogO(String("no hit: ")+(ct0.col?"1":"0")+(ct1.col?" 1":" 0"));
108 
109 		//if (tilt < angMin && tilt > -angMin)  tilt = 0.f;
110 		if (tilt < r0 && tilt >-angMin) {  Radian d = tilt-angMin;  tilt = std::min(r0, tilt + d*d*5.f);  }
111 		if (tilt > r0 && tilt < angMin) {  Radian d =-angMin-tilt;  tilt = std::max(r0, tilt - d*d*5.f);  }
112 
113 		//LogO("a "+fToStr(angCarY,3,5)+" d  "+fToStr(ct0.GetDepth(),3,5)+" "+fToStr(ct1.GetDepth(),3,5)+"  t "+fToStr(tilt.valueDegrees(),3,5));
114 	}
115 	//  smooth tilt angle
116 	mATilt += std::min(maxDiff, std::max(-maxDiff, tilt - mATilt)) * time * smoothSpeed;
117 
118 
119 	//-------------------------------------------------------------------------------------------
120     if (ca->mType == CAM_Car)	/* 3 Car - car pos & rot full */
121     {
122 		camPosFinal = goalLook;
123 		camRotFinal = orient;
124 
125 		posOut->camPos = camPosFinal;  // save result in out posInfo
126 		posOut->camRot = camRotFinal;
127 		return;
128 	}
129 
130     if (ca->mType == CAM_Follow)  ofs = ca->mOffset;
131 
132 	Vector3  pos,goalPos;
133 	pos     = camPosFinal - ofs;
134 	goalPos = posGoal;
135 
136 	Vector3  xyz;
137 	if (ca->mType != CAM_Arena)
138 	{
139 		Real x,y,z,xz;   // pitch & yaw to direction vector
140 		Real ap = bUseTilt ? (ca->mPitch.valueRadians() + mATilt.valueRadians()) : ca->mPitch.valueRadians(),
141 			 ay = ca->mYaw.valueRadians();
142 		y = sin(ap), xz = cos(ap);
143 		x = sin(ay) * xz, z = cos(ay) * xz;
144 		xyz = Vector3(x,y,z);  xyz *= ca->mDist;
145 	}
146 
147 	bool manualOrient = false;
148 	switch (ca->mType)
149 	{
150 		case CAM_Arena:		/* 2 Arena - free pos & rot */
151 		    goalPos = ca->mOffset - ofs;
152 		    break;
153 
154 		case CAM_Free:		/* 1 Free - free rot, pos from car */
155 			goalPos += xyz;
156 			break;
157 
158 		case CAM_Follow:	/* 0 Follow - car rotY & pos from behind car, smooth */
159 		{	Quaternion  orient = orientGoal * qR;
160 			orient.FromAngleAxis(orient.getYaw(), Vector3::UNIT_Y);
161 			goalPos += orient * xyz;
162 		}	break;
163 
164 		case CAM_ExtAng:    /* 4 Extended Angle - car in center, angle smooth */
165 		{	Quaternion  orient = orientGoal * qR;
166 			Quaternion  ory;  ory.FromAngleAxis(orient.getYaw(), Vector3::UNIT_Y);
167 
168 			if (first)  {  qq = ory;  }
169 			else  qq = orient.Slerp(ca->mSpeed * time, qq, ory, true);
170 
171 			//  smooth dist from vel
172 			#if 0
173 			{
174 				if (first)  {  mPosNodeOld = posGoal;  }
175 				Real vel = (posGoal - mPosNodeOld).length() / std::max(0.002f, std::min(0.1f, time));
176 				mPosNodeOld = posGoal;
177 				if (first)  mVel = 0.f;  else
178 					mVel += (vel - mVel) * time * 8.f;  //par-  vel smooth speed
179 				if (!first)
180 					xyz *= 1.f + std::min(100.f, mVel) * 0.01f;  //par-  vel dist factor
181 			}
182 			#endif
183 
184 			Quaternion  qy = Quaternion(ca->mYaw,Vector3(0,1,0));
185 			goalPos += qq * (xyz + ca->mOffset);
186 
187 			camPosFinal = goalPos;
188 			camRotFinal = qq * qy * Quaternion(Degree(-ca->mPitch - mATilt), Vector3(1,0,0));
189 			manualOrient = true;
190 		}	break;
191 	}
192 
193 	if (!manualOrient)  // if !CAM_ExtAng
194 	{
195 		float dtmul = ca->mSpeed == 0 ? 1.0f : ca->mSpeed * time;
196 
197 		if (ca->mType ==  CAM_Arena)
198 		{
199 			Vector3  Pos(0,0,0), goalPos = ca->mOffset;
200 			Pos = camPosFinal;  //read last state (smooth)
201 			Pos += (goalPos - Pos) * dtmul;
202 
203 			mAPitch += (ca->mPitch - mAPitch) * dtmul;
204 			mAYaw += (ca->mYaw - mAYaw) * dtmul;
205 
206 			if (first)  {  Pos = goalPos;  mAPitch = ca->mPitch;  mAYaw = ca->mYaw;  }
207 			camPosFinal = Pos;
208 			camRotFinal = Quaternion(Degree(mAYaw),Vector3(0,1,0)) * Quaternion(Degree(mAPitch),Vector3(1,0,0));
209 			manualOrient = true;
210 		}
211 		else
212 		{
213 			if (first)  pos = goalPos;
214 			Vector3  addPos,addLook;
215 			addPos = (goalPos - pos).normalisedCopy() * (goalPos - pos).length() * dtmul;
216 			if (addPos.squaredLength() > (goalPos - pos).squaredLength())  addPos = goalPos - pos;
217 			pos += addPos;
218 			camPosFinal = pos + ofs;
219 
220 			goalLook = posGoal + ofs;
221 			if (first)	{	mLook = goalLook;  }
222 
223 			addLook = (goalLook - mLook) * dtmul;//Rot;
224 			mLook += addLook;
225 		}
226 	}
227 
228 	//camLookFinal = mLook;
229 	if (!manualOrient)  // CAM_Free or CAM_Follow
230 	{
231 		Vector3 zdir = camPosFinal - mLook;  zdir.normalise();
232         Vector3 xVec = Vector3::UNIT_Y.crossProduct(zdir);  xVec.normalise();
233         Vector3 yVec = zdir.crossProduct(xVec);  yVec.normalise();
234         Quaternion q;  q.FromAxes(xVec, yVec, zdir);
235 		camRotFinal = q;
236 	}
237 
238 	//  cast ray from car to camera, reduce dist if hit
239 	//-------------------------------------------------------------------------------------------
240 	Vector3 pp = camPosFinal;
241 	if (bounce)
242 		pp += posIn.camOfs * ca->mOfsMul
243 			* gPar.camBncScale * pSet->cam_bnc_mul;
244 
245 	Vector3 p = posGoal;  p.y += 1.f;  //up
246 	//Vector3 d = camRotFinal * Vector3::UNIT_Z;  d.normalise();
247 	Vector3 d = pp - p;  d.normalise();
248 
249 	if (!first && ca->mType != CAM_Arena)
250 	{
251 		MATHVECTOR<float,3> pos1(p.x,-p.z,p.y), dir(d.x,-d.z,d.y);  //dir = dir.Normalize();
252 		COLLISION_CONTACT ct;
253 		float maxLen = (p - pp).length();  //cam near
254 		world->CastRay(pos1, dir, maxLen,chassis, ct,  0,0, true, true, true/*+*/);
255 		//dbgLen = -maxLen;
256 
257 		if (ct.GetColObj())
258 		{
259 			float len = ct.GetDepth();  //dbgLen = len;
260 			len -= 0.2f + ct.GetNormal()[2];  ///par  normal up, flat terrain, move closer
261 			if (len < maxLen)
262 			{
263 				Real dist = maxLen - len;
264 				if (dist > mDistReduce)
265 					mDistReduce = dist;
266 		}	}
267 	}
268 
269 	//  save result in out posInfo
270 	posOut->camPos = mDistReduce > 0.0001f ? (pp - d * mDistReduce) : pp;
271 	posOut->camRot = camRotFinal;
272 
273 	//  smooth, back to normal dist
274 	if (mDistReduce > 0.f)
275 		mDistReduce -= time * 10.f;
276 }
277 
278 
Apply(const PosInfo & posIn)279 void FollowCamera::Apply(const PosInfo& posIn)
280 {
281 	//boost::this_thread::sleep(boost::posix_time::milliseconds(rand()%20));
282 	if (!mCamera)  return;
283 
284 	mCamera->setPosition(posIn.camPos);
285 	mCamera->setOrientation(posIn.camRot);
286 }
287 
288 
289 ///  mouse Move
290 //-----------------------------------------------------------------------------------------------------
291 
Move(bool mbLeft,bool mbRight,bool mbMiddle,bool shift,Real mx,Real my,Real mz)292 void FollowCamera::Move( bool mbLeft, bool mbRight, bool mbMiddle, bool shift, Real mx, Real my, Real mz )
293 {
294 	if (!ca)  return;
295 	fMoveTime = 0;
296 	bool arena = ca->mType == CAM_Arena;
297 	Real myH = my * -0.01, mzH = mz/120.f;
298 	mx *= 0.005;  my *= 0.005;
299 
300 	if (shift && mbMiddle)
301 	{
302 		ca->mSpeed += my*5;
303 		if (ca->mSpeed < 0.f)  ca->mSpeed = 0.f;
304 		return;
305 	}
306 
307 	//----------------------------------------------
308 	if (arena)  // Arena - free camera
309 	{
310 		Real a = ca->mYaw.valueRadians(), sx = cosf(a), sy = sinf(a);
311 		Vector3 vx(sx,0,-sy), vy(sy,0,sx);
312 
313 		if (mbMiddle)
314 		{
315 			const Real s = -20;
316 			ca->mOffset += s*mx *vy + Vector3(0, s*my, 0);
317 		}
318 		if (mbRight)
319 		{
320 			const Real s = 20;
321 			if (shift)
322 				ca->mOffset += Vector3(0, s*myH, 0);
323 			else
324 				ca->mOffset += s*mx*vx + s*my*vy;
325 		}
326 		if (mbLeft)
327 		{
328 			const Real s = 0.5;
329 			ca->mPitch -= Radian(s*my);
330 			if (!shift)
331 				ca->mYaw -= Radian(s*mx);
332 		}
333 		//  wheel
334 		ca->mPitch  += Radian(mzH * 3.f*PI_d/180.f);
335 		return;
336 	}
337 	//----------------------------------------------
338 	if (ca->mType == CAM_ExtAng)
339 	{
340 		if (mbMiddle)
341 		{	ca->mOffset.x = 0;  ca->mOffset.z = 0;  ca->mYaw = 0.f;  }
342 		if (mbLeft)
343 		{
344 			ca->mPitch -= Radian(my);
345 			if (shift)
346 				ca->mYaw += Radian(mx);
347 			else
348 				ca->mDist  *= 1.0 - mx * 0.4;
349 		}
350 		if (mbRight)
351 		if (shift)	ca->mOffset += Vector3(mx, 0, my);
352 		else		ca->mOffset += Vector3(0, -my, 0);
353 
354 		ca->mDist  *= 1.0 - mzH * 0.1;
355 		return;
356 	}
357 	if (ca->mType == CAM_Car)
358 	{
359 		if (mbMiddle)
360 			ca->mOffset.x = 0;
361 		if (mbLeft)
362 		{
363 			ca->mOffset += Vector3(0, -my, 0);
364 			ca->mDist   *= 1.0 - mx * 0.4;
365 		}
366 		if (mbRight)
367 			ca->mOffset += Vector3(mx, 0, my);
368 
369 		ca->mOffset += Vector3(0, mzH * 0.004, 0);
370 		return;
371 	}
372 	//----------------------------------------------
373 	if (mbMiddle)
374 	{
375 		if (ca->mType == CAM_Follow)
376 			ca->mYaw = Degree(0);
377 		else
378 			ca->mOffset = Vector3(0, 0.0, 0);
379 	}
380 	if (mbRight)
381 	{
382 		if (!shift)
383 			ca->mOffset += Vector3(0, -my, 0);
384 		else
385 		{	ca->mOffset += Vector3(0, myH, 0);
386 			ca->mDist   *= 1.0 - mx * 0.3;
387 		}
388 	}
389 	if (mbLeft)
390 	{
391 		if (!shift)
392 		{	ca->mPitch -= Radian(my);
393 			//ca->mDist *= 1.0 - mzH * 0.1;
394 		} else {
395 			ca->mYaw   += Radian(mx);
396 			ca->mPitch -= Radian(my);
397 		}
398 		if (ca->mDist < 1.5)
399 			ca->mDist = 1.5;
400 	}
401 	ca->mDist  *= 1.0 - mzH * 0.1;
402 }
403 
404 
405 ///  upd Info
406 //-----------------------------------------------------------------------------------------------------
updInfo(Real time)407 bool FollowCamera::updInfo(Real time)
408 {
409 	if (!ca)  return false;
410 
411 	if (fMoveTime >= 1.0)	// hide after 1sec
412 		return false;
413 	else
414 		fMoveTime += time;
415 
416     switch (ca->mType)
417     {
418 	case CAM_Follow: sprintf(ss, sFmt_Follow.c_str()
419 		,ca->mType, CAM_Str[ca->mType], ca->mYaw.valueDegrees(), ca->mPitch.valueDegrees(), ca->mDist
420 		,ca->mOffset.y, ca->mSpeed);	break;
421 	case CAM_Free:   sprintf(ss, sFmt_Free.c_str()
422 		,ca->mType, CAM_Str[ca->mType], ca->mYaw.valueDegrees(), ca->mPitch.valueDegrees(), ca->mDist
423 		,ca->mOffset.y, ca->mSpeed);	break;
424 	case CAM_ExtAng:   sprintf(ss, sFmt_ExtAng.c_str()
425 		,ca->mType, CAM_Str[ca->mType], ca->mPitch.valueDegrees(), ca->mDist
426 		,ca->mOffset.y, ca->mOffset.x, ca->mOffset.z, ca->mSpeed);	break;
427 	case CAM_Arena:  sprintf(ss, sFmt_Arena.c_str()
428 		,ca->mType, CAM_Str[ca->mType], ca->mYaw.valueDegrees(), ca->mPitch.valueDegrees(), ca->mDist
429 		,ca->mOffset.x, ca->mOffset.y, ca->mOffset.z, ca->mSpeed);	break;
430 	case CAM_Car:    sprintf(ss, sFmt_Car.c_str()
431 		,ca->mType, CAM_Str[ca->mType], ca->mOffset.z, ca->mOffset.x, ca->mOffset.y);	break;
432 	}
433 	return true;
434 }
435 
436 
437 
438 ///  Cameras
439 ///-----------------------------------------------------------------------------------------------------
440 
updAngle()441 void FollowCamera::updAngle()
442 {
443 	if (miCount <= 0)  return;
444 	miCurrent = std::max(0, std::min(miCount-1, miCurrent));
445 
446 	CameraAngle* c = mCameraAngles[miCurrent];
447 	if (ca->mType != c->mType)	First();  // changed type, reset
448     *ca = *c;  // copy
449     mDistReduce = 0.f;  //reset
450 
451 	sName = toStr(miCurrent+1) + "/" + toStr(miCount)
452 		+ (ca->mMain > 0 ? ". " : "  ") + ca->mName;
453 	updName = true;
454 }
455 
saveCamera()456 void FollowCamera::saveCamera()
457 {
458 	CameraAngle* c = mCameraAngles[miCurrent];
459 	c->mName = ca->mName;	c->mType = ca->mType;  c->mSpeed = ca->mSpeed;
460 	c->mYaw = ca->mYaw;		c->mPitch = ca->mPitch;
461 	c->mDist = ca->mDist;	c->mOffset = ca->mOffset;
462 }
463 
464 
465 //  change next,prev
466 
incCur(int dir)467 void FollowCamera::incCur(int dir)
468 {
469 	miCurrent += dir;
470 	if (miCurrent >= miCount)	miCurrent = 0;  // -= miCount;
471 	if (miCurrent < 0)			miCurrent = miCount-1;
472 }
473 
Next(bool bPrev,bool bMainOnly)474 void FollowCamera::Next(bool bPrev, bool bMainOnly)
475 {
476 	int dir = bPrev ? -1 : 1;
477 	if (!bMainOnly)  // all
478 	{
479 		incCur(dir);  updAngle();  return;
480 	}else
481 	{	int cnt = 0, old = miCurrent;
482 		while (cnt < miCount)
483 		{
484 			cnt++;  incCur(dir);
485 			CameraAngle* c = mCameraAngles[miCurrent];
486 			if (c->mMain > 0)
487 			{	updAngle();  return;  }
488 		}
489 		miCurrent = old;
490 	}
491 }
492 
setCamera(int ang)493 void FollowCamera::setCamera(int ang)
494 {
495 	miCurrent = ang;  updAngle();
496 }
497 
498 
499 //  ctors
500 
FollowCamera(Camera * cam,SETTINGS * pSet1)501 FollowCamera::FollowCamera(Camera* cam,	SETTINGS* pSet1) :
502 	first(true), iFirst(0), ca(0), updName(0),
503     mCamera(cam), mTerrain(0), chassis(0), pSet(pSet1),
504     mLook(Vector3::ZERO), mPosNodeOld(Vector3::ZERO), mVel(0),
505     mAPitch(0.f),mAYaw(0.f), mATilt(0.f), mDistReduce(0.f)
506 {
507 	ca = new CameraAngle();
508 	ss[0]=0;
509 }
510 
First()511 void FollowCamera::First()
512 {
513 	first = true;  iFirst = 0;
514 }
515 
~FollowCamera()516 FollowCamera::~FollowCamera()
517 {
518 	delete ca;  ca = 0;
519 	Destroy();
520 }
521 
Destroy()522 void FollowCamera::Destroy()
523 {
524 	for (std::vector<CameraAngle*>::iterator it = mCameraAngles.begin(); it != mCameraAngles.end(); ++it)
525 		delete *it;
526 	mCameraAngles.clear();
527 }
528 
CameraAngle()529 CameraAngle::CameraAngle()
530 	:mType(CAM_Follow), mName("Follow Default"), mMain(0)
531 	,mDist(7), mSpeed(10), mSpeedRot(10), mOfsMul(1)
532 	,mYaw(0), mPitch(7),  mOffset(0,1.2,0), mHideGlass(0)
533 {	}
534 
535 
536 
537 ///  Load from xml
538 //-----------------------------------------------------------------------------------------------------
loadCameras()539 bool FollowCamera::loadCameras()
540 {
541 	fMoveTime = 5.f;  // hide hint on start
542 
543 	miCount = 0;  miCurrent = 0;
544 	Destroy();
545 
546 	XMLDocument doc;
547 	XMLError e = doc.LoadFile((PATHMANAGER::Cars() + "/cameras.xml").c_str());
548 	if (e == XML_SUCCESS)
549 	{
550 		XMLElement* root = doc.RootElement();
551 		if (!root) {  /*mErrorDialog->show(String("Error loading Cameras !!"), false );  return false;*/  }
552 		XMLElement* cam = root->FirstChildElement("Camera");
553 		if (!cam) {  /*mErrorDialog->show(String("Error loading Camera !!"), false );  return false;*/  }
554 
555 		while (cam)
556 		{
557 			CameraAngle* c = new CameraAngle();  const char* a = 0;
558 			c->mName = cam->Attribute("name");
559 			c->mType = (CamTypes)s2i(cam->Attribute("type"));
560 			c->mYaw = Degree(0);  c->mPitch = Degree(0);  c->mDist = 10;  c->mSpeed = 10;
561 
562 			a = cam->Attribute("default");	if (a)  if (s2i(a)==1)  miCurrent = miCount;
563 			a = cam->Attribute("on");		if (a)  c->mMain = s2i(a)-1;
564 			a = cam->Attribute("hideGlass");	if (a)  c->mHideGlass = s2i(a);
565 
566 			a = cam->Attribute("yaw");		if (a)  c->mYaw += Degree(s2r(a));
567 			a = cam->Attribute("pitch");	if (a)  c->mPitch = Degree(s2r(a));
568 			a = cam->Attribute("dist");		if (a)  c->mDist = s2r(a);
569 			a = cam->Attribute("offset");	if (a)  c->mOffset = s2v(a);
570 			a = cam->Attribute("speed");	if (a)  c->mSpeed = s2r(a);
571 			a = cam->Attribute("spRot");	if (a)  c->mSpeedRot = s2r(a);  else  c->mSpeedRot = c->mSpeed;
572 			a = cam->Attribute("bounce");	if (a)  c->mOfsMul = s2r(a);
573 
574 			if (c->mMain >= 0)  {
575 				mCameraAngles.push_back(c);  miCount++;  }
576 			else
577 				delete c;
578 			cam = cam->NextSiblingElement("Camera");
579 		}
580 	}
581 
582 	miCount = mCameraAngles.size();
583 	if (miCount == 0)
584 	{
585 		CameraAngle* c = new CameraAngle();  c->mName = "Follow Default";
586 		c->mType = CAM_Follow;  c->mYaw = Degree(0);  c->mPitch = Degree(14);
587 		c->mDist = 9;  c->mOffset = Vector3(0,2,0);  c->mSpeed = 15;
588 		mCameraAngles.push_back(c);
589 		miCount++;
590 	}
591 
592 	updAngle();  updFmtTxt();
593 	return true;
594 }
595 
596 
597 //  update format texts, from translations
598 //-----------------------------------------------------------------------------------------------------
updFmtTxt()599 void FollowCamera::updFmtTxt()
600 {
601 	String sTR = TR("#{CamInfoStrings}");
602 	vector<String>::type vs = StringUtil::split(sTR,",");
603 
604 	if (vs.size() < 17)
605 	{	LogO("==== Error in camera info translate string. Need 17 strings, have "+toStr(vs.size())+", using default English. " + sTR);
606 		sTR="Type,Yaw,Pitch,Dist,Height,Speed,Offset,LEFT,RIGHT,Middle,Wheel,shift,Rotate,reset,move,H,Pos";
607 		vs = StringUtil::split(sTR,",");  }
608 
609 	String sType  =vs[0],
610 		sYaw   =vs[1],  sPitch =vs[2],  sDist  =vs[3],  sHeight=vs[4],  sSpeed =vs[5],  sOffset=vs[6],
611 		sLEFT  =vs[7],  sRIGHT =vs[8],  sMiddle=vs[9],  sWheel =vs[10], sshift =vs[11],
612 		sRotate=vs[12], sreset =vs[13], smove  =vs[14], sH     =vs[15], sPos   =vs[16];
613 
614 	sFmt_Follow =
615 		sType+": %d %s  "+sYaw+":%5.1f "+sPitch+":%5.1f  "+sDist+":%5.1f  "+sHeight+": %3.1f  "+sSpeed+": %2.0f\n"+
616 		sLEFT+": "+sPitch+"  "+sshift+": "+sRotate+" | "+sRIGHT+": "+sHeight+"  "+sshift+": "+sDist+","+sH+"\n"+
617 		sMiddle+": "+sreset+" "+sYaw+"  "+sshift+": "+sSpeed+" | "+sWheel+": "+sDist;  // | S: save"
618 	sFmt_Free =
619 		sType+": %d %s  "+sYaw+":%5.1f "+sPitch+":%5.1f  "+sDist+":%5.1f  "+sHeight+": %3.1f  "+sSpeed+": %2.0f\n"+
620 		sLEFT+": "+sPitch+"  "+sshift+": "+sRotate+" | "+sRIGHT+": "+sHeight+"  "+sshift+": "+sDist+","+sH+"\n"+
621 		sMiddle+": "+sreset+" "+sHeight+"  "+sshift+": "+sSpeed+" | "+sWheel+": "+sDist;
622 	sFmt_ExtAng =
623 		sType+": %d %s  "+sPitch+":%5.1f  "+sDist+":%5.1f  "+sHeight+": %3.1f  "+sOffset+": %3.1f %3.1f  "+sSpeed+": %3.1f\n"+
624 		sLEFT+": "+sPitch+", "+sDist+"  "+sshift+": "+sRotate+" | "+sRIGHT+": "+sHeight+"  "+sshift+": "+sOffset+"\n"+
625 		sMiddle+": "+sreset+" "+sOffset+"  "+sshift+": "+sSpeed+" | "+sWheel+": "+sDist;
626 	sFmt_Arena =
627 		sType+": %d %s  "+sYaw+":%5.1f "+sPitch+":%5.1f  "+sDist+":%5.1f  "+sPos+": %3.1f %3.1f %3.1f  "+sSpeed+": %2.0f\n"+
628 		sLEFT+": "+sRotate+"  "+sshift+": "+sPitch+" | "+sRIGHT+": "+smove+"  "+sshift+": "+sHeight+"\n"+
629 		sMiddle+": "+smove+","+sH+" | "+sWheel+": "+sPitch;
630 	sFmt_Car =
631 		sType+": %d %s  "+sOffset+": %4.2f %4.2f  "+sHeight+": %4.2f\n"+
632 		sLEFT+": "+sHeight+" | "+sRIGHT+": "+sOffset+" | "+sMiddle+": "+sreset+" "+sOffset+"X";
633 }
634