|
@@ -35,6 +35,12 @@
|
|
#include "math/mathUtils.h"
|
|
#include "math/mathUtils.h"
|
|
#include "math/mTransform.h"
|
|
#include "math/mTransform.h"
|
|
|
|
|
|
|
|
+#ifdef TORQUE_EXTENDED_MOVE
|
|
|
|
+ #include "T3D/gameBase/extended/extendedMove.h"
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+S32 Camera::smExtendedMovePosRotIndex = 0; // The ExtendedMove position/rotation index used for camera movements
|
|
|
|
+
|
|
#define MaxPitch 1.5706f
|
|
#define MaxPitch 1.5706f
|
|
#define CameraRadius 0.05f;
|
|
#define CameraRadius 0.05f;
|
|
|
|
|
|
@@ -254,6 +260,7 @@ Camera::Camera()
|
|
{
|
|
{
|
|
mNetFlags.clear(Ghostable);
|
|
mNetFlags.clear(Ghostable);
|
|
mTypeMask |= CameraObjectType;
|
|
mTypeMask |= CameraObjectType;
|
|
|
|
+ mDataBlock = 0;
|
|
mDelta.pos = Point3F(0.0f, 0.0f, 100.0f);
|
|
mDelta.pos = Point3F(0.0f, 0.0f, 100.0f);
|
|
mDelta.rot = Point3F(0.0f, 0.0f, 0.0f);
|
|
mDelta.rot = Point3F(0.0f, 0.0f, 0.0f);
|
|
mDelta.posVec = mDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f);
|
|
mDelta.posVec = mDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f);
|
|
@@ -270,6 +277,9 @@ Camera::Camera()
|
|
mObservingClientObject = false;
|
|
mObservingClientObject = false;
|
|
mMode = FlyMode;
|
|
mMode = FlyMode;
|
|
|
|
|
|
|
|
+ mLastAbsoluteYaw = 0.0f;
|
|
|
|
+ mLastAbsolutePitch = 0.0f;
|
|
|
|
+
|
|
// For NewtonFlyMode
|
|
// For NewtonFlyMode
|
|
mNewtonRotation = false;
|
|
mNewtonRotation = false;
|
|
mAngularVelocity.set(0.0f, 0.0f, 0.0f);
|
|
mAngularVelocity.set(0.0f, 0.0f, 0.0f);
|
|
@@ -301,7 +311,7 @@ Camera::~Camera()
|
|
|
|
|
|
bool Camera::onAdd()
|
|
bool Camera::onAdd()
|
|
{
|
|
{
|
|
- if(!Parent::onAdd())
|
|
|
|
|
|
+ if(!Parent::onAdd() || !mDataBlock)
|
|
return false;
|
|
return false;
|
|
|
|
|
|
mObjBox.maxExtents = mObjScale;
|
|
mObjBox.maxExtents = mObjScale;
|
|
@@ -310,29 +320,46 @@ bool Camera::onAdd()
|
|
resetWorldBox();
|
|
resetWorldBox();
|
|
|
|
|
|
addToScene();
|
|
addToScene();
|
|
|
|
+
|
|
|
|
+ scriptOnAdd();
|
|
|
|
+
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
-void Camera::onEditorEnable()
|
|
|
|
|
|
+void Camera::onRemove()
|
|
{
|
|
{
|
|
- mNetFlags.set(Ghostable);
|
|
|
|
|
|
+ scriptOnRemove();
|
|
|
|
+ removeFromScene();
|
|
|
|
+ Parent::onRemove();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
-void Camera::onEditorDisable()
|
|
|
|
|
|
+bool Camera::onNewDataBlock( GameBaseData *dptr, bool reload )
|
|
{
|
|
{
|
|
- mNetFlags.clear(Ghostable);
|
|
|
|
|
|
+ mDataBlock = dynamic_cast<CameraData*>(dptr);
|
|
|
|
+ if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ scriptOnNewDataBlock();
|
|
|
|
+
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
-void Camera::onRemove()
|
|
|
|
|
|
+void Camera::onEditorEnable()
|
|
{
|
|
{
|
|
- removeFromScene();
|
|
|
|
- Parent::onRemove();
|
|
|
|
|
|
+ mNetFlags.set(Ghostable);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------------------
|
|
|
|
+
|
|
|
|
+void Camera::onEditorDisable()
|
|
|
|
+{
|
|
|
|
+ mNetFlags.clear(Ghostable);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
@@ -460,79 +487,154 @@ void Camera::processTick(const Move* move)
|
|
|
|
|
|
VectorF rotVec(0, 0, 0);
|
|
VectorF rotVec(0, 0, 0);
|
|
|
|
|
|
- // process input/determine rotation vector
|
|
|
|
- if(virtualMode != StationaryMode &&
|
|
|
|
- virtualMode != TrackObjectMode &&
|
|
|
|
- (!mLocked || virtualMode != OrbitObjectMode && virtualMode != OrbitPointMode))
|
|
|
|
|
|
+ bool doStandardMove = true;
|
|
|
|
+
|
|
|
|
+#ifdef TORQUE_EXTENDED_MOVE
|
|
|
|
+ GameConnection* con = getControllingClient();
|
|
|
|
+
|
|
|
|
+ // Work with an absolute rotation from the ExtendedMove class?
|
|
|
|
+ if(con && con->getControlSchemeAbsoluteRotation())
|
|
{
|
|
{
|
|
- if(!strafeMode)
|
|
|
|
|
|
+ doStandardMove = false;
|
|
|
|
+ const ExtendedMove* emove = dynamic_cast<const ExtendedMove*>(move);
|
|
|
|
+ U32 emoveIndex = smExtendedMovePosRotIndex;
|
|
|
|
+ if(emoveIndex >= ExtendedMove::MaxPositionsRotations)
|
|
|
|
+ emoveIndex = 0;
|
|
|
|
+
|
|
|
|
+ if(emove->EulerBasedRotation[emoveIndex])
|
|
{
|
|
{
|
|
- rotVec.x = move->pitch;
|
|
|
|
- rotVec.z = move->yaw;
|
|
|
|
|
|
+ if(virtualMode != StationaryMode &&
|
|
|
|
+ virtualMode != TrackObjectMode &&
|
|
|
|
+ (!mLocked || virtualMode != OrbitObjectMode && virtualMode != OrbitPointMode))
|
|
|
|
+ {
|
|
|
|
+ // Pitch
|
|
|
|
+ mRot.x += (emove->rotX[emoveIndex] - mLastAbsolutePitch);
|
|
|
|
+
|
|
|
|
+ // Do we also include the relative pitch value?
|
|
|
|
+ if(con->getControlSchemeAddPitchToAbsRot() && !strafeMode)
|
|
|
|
+ {
|
|
|
|
+ F32 x = move->pitch;
|
|
|
|
+ if (x > M_PI_F)
|
|
|
|
+ x -= M_2PI_F;
|
|
|
|
+
|
|
|
|
+ mRot.x += x;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Constrain the range of mRot.x
|
|
|
|
+ while (mRot.x < -M_PI_F)
|
|
|
|
+ mRot.x += M_2PI_F;
|
|
|
|
+ while (mRot.x > M_PI_F)
|
|
|
|
+ mRot.x -= M_2PI_F;
|
|
|
|
+
|
|
|
|
+ // Yaw
|
|
|
|
+ mRot.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw);
|
|
|
|
+
|
|
|
|
+ // Do we also include the relative yaw value?
|
|
|
|
+ if(con->getControlSchemeAddYawToAbsRot() && !strafeMode)
|
|
|
|
+ {
|
|
|
|
+ F32 z = move->yaw;
|
|
|
|
+ if (z > M_PI_F)
|
|
|
|
+ z -= M_2PI_F;
|
|
|
|
+
|
|
|
|
+ mRot.z += z;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Constrain the range of mRot.z
|
|
|
|
+ while (mRot.z < -M_PI_F)
|
|
|
|
+ mRot.z += M_2PI_F;
|
|
|
|
+ while (mRot.z > M_PI_F)
|
|
|
|
+ mRot.z -= M_2PI_F;
|
|
|
|
+
|
|
|
|
+ mLastAbsoluteYaw = emove->rotZ[emoveIndex];
|
|
|
|
+ mLastAbsolutePitch = emove->rotX[emoveIndex];
|
|
|
|
+
|
|
|
|
+ // Bank
|
|
|
|
+ mRot.y = emove->rotY[emoveIndex];
|
|
|
|
+
|
|
|
|
+ // Constrain the range of mRot.y
|
|
|
|
+ while (mRot.y > M_PI_F)
|
|
|
|
+ mRot.y -= M_2PI_F;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else if(virtualMode == TrackObjectMode && bool(mOrbitObject))
|
|
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+ if(doStandardMove)
|
|
{
|
|
{
|
|
- // orient the camera to face the object
|
|
|
|
- Point3F objPos;
|
|
|
|
- // If this is a shapebase, use its render eye transform
|
|
|
|
- // to avoid jittering.
|
|
|
|
- ShapeBase *shape = dynamic_cast<ShapeBase*>((GameBase*)mOrbitObject);
|
|
|
|
- if( shape != NULL )
|
|
|
|
|
|
+ // process input/determine rotation vector
|
|
|
|
+ if(virtualMode != StationaryMode &&
|
|
|
|
+ virtualMode != TrackObjectMode &&
|
|
|
|
+ (!mLocked || virtualMode != OrbitObjectMode && virtualMode != OrbitPointMode))
|
|
{
|
|
{
|
|
- MatrixF ret;
|
|
|
|
- shape->getRenderEyeTransform( &ret );
|
|
|
|
- objPos = ret.getPosition();
|
|
|
|
|
|
+ if(!strafeMode)
|
|
|
|
+ {
|
|
|
|
+ rotVec.x = move->pitch;
|
|
|
|
+ rotVec.z = move->yaw;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+ else if(virtualMode == TrackObjectMode && bool(mOrbitObject))
|
|
{
|
|
{
|
|
- mOrbitObject->getWorldBox().getCenter(&objPos);
|
|
|
|
|
|
+ // orient the camera to face the object
|
|
|
|
+ Point3F objPos;
|
|
|
|
+ // If this is a shapebase, use its render eye transform
|
|
|
|
+ // to avoid jittering.
|
|
|
|
+ ShapeBase *shape = dynamic_cast<ShapeBase*>((GameBase*)mOrbitObject);
|
|
|
|
+ if( shape != NULL )
|
|
|
|
+ {
|
|
|
|
+ MatrixF ret;
|
|
|
|
+ shape->getRenderEyeTransform( &ret );
|
|
|
|
+ objPos = ret.getPosition();
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ mOrbitObject->getWorldBox().getCenter(&objPos);
|
|
|
|
+ }
|
|
|
|
+ mObjToWorld.getColumn(3,&pos);
|
|
|
|
+ vec = objPos - pos;
|
|
|
|
+ vec.normalizeSafe();
|
|
|
|
+ F32 pitch, yaw;
|
|
|
|
+ MathUtils::getAnglesFromVector(vec, yaw, pitch);
|
|
|
|
+ rotVec.x = -pitch - mRot.x;
|
|
|
|
+ rotVec.z = yaw - mRot.z;
|
|
|
|
+ if(rotVec.z > M_PI_F)
|
|
|
|
+ rotVec.z -= M_2PI_F;
|
|
|
|
+ else if(rotVec.z < -M_PI_F)
|
|
|
|
+ rotVec.z += M_2PI_F;
|
|
}
|
|
}
|
|
- mObjToWorld.getColumn(3,&pos);
|
|
|
|
- vec = objPos - pos;
|
|
|
|
- vec.normalizeSafe();
|
|
|
|
- F32 pitch, yaw;
|
|
|
|
- MathUtils::getAnglesFromVector(vec, yaw, pitch);
|
|
|
|
- rotVec.x = -pitch - mRot.x;
|
|
|
|
- rotVec.z = yaw - mRot.z;
|
|
|
|
- if(rotVec.z > M_PI_F)
|
|
|
|
- rotVec.z -= M_2PI_F;
|
|
|
|
- else if(rotVec.z < -M_PI_F)
|
|
|
|
- rotVec.z += M_2PI_F;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- // apply rotation vector according to physics rules
|
|
|
|
- if(mNewtonRotation)
|
|
|
|
- {
|
|
|
|
- const F32 force = mAngularForce;
|
|
|
|
- const F32 drag = mAngularDrag;
|
|
|
|
|
|
+ // apply rotation vector according to physics rules
|
|
|
|
+ if(mNewtonRotation)
|
|
|
|
+ {
|
|
|
|
+ const F32 force = mAngularForce;
|
|
|
|
+ const F32 drag = mAngularDrag;
|
|
|
|
|
|
- VectorF acc(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
+ VectorF acc(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
- rotVec.x *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script
|
|
|
|
- rotVec.z *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script
|
|
|
|
|
|
+ rotVec.x *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script
|
|
|
|
+ rotVec.z *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script
|
|
|
|
|
|
- F32 rotVecL = rotVec.len();
|
|
|
|
- if(rotVecL > 0)
|
|
|
|
- {
|
|
|
|
- acc = (rotVec * force / mMass) * TickSec;
|
|
|
|
- }
|
|
|
|
|
|
+ F32 rotVecL = rotVec.len();
|
|
|
|
+ if(rotVecL > 0)
|
|
|
|
+ {
|
|
|
|
+ acc = (rotVec * force / mMass) * TickSec;
|
|
|
|
+ }
|
|
|
|
|
|
- // Accelerate
|
|
|
|
- mAngularVelocity += acc;
|
|
|
|
|
|
+ // Accelerate
|
|
|
|
+ mAngularVelocity += acc;
|
|
|
|
|
|
- // Drag
|
|
|
|
- mAngularVelocity -= mAngularVelocity * drag * TickSec;
|
|
|
|
|
|
+ // Drag
|
|
|
|
+ mAngularVelocity -= mAngularVelocity * drag * TickSec;
|
|
|
|
|
|
- // Rotate
|
|
|
|
- mRot += mAngularVelocity * TickSec;
|
|
|
|
- clampPitchAngle(mRot.x);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- mRot.x += rotVec.x;
|
|
|
|
- mRot.z += rotVec.z;
|
|
|
|
- clampPitchAngle(mRot.x);
|
|
|
|
|
|
+ // Rotate
|
|
|
|
+ mRot += mAngularVelocity * TickSec;
|
|
|
|
+ clampPitchAngle(mRot.x);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ mRot.x += rotVec.x;
|
|
|
|
+ mRot.z += rotVec.z;
|
|
|
|
+ clampPitchAngle(mRot.x);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// Update position
|
|
// Update position
|
|
@@ -667,6 +769,13 @@ void Camera::processTick(const Move* move)
|
|
mDelta.rot = mRot;
|
|
mDelta.rot = mRot;
|
|
mDelta.posVec = mDelta.posVec - mDelta.pos;
|
|
mDelta.posVec = mDelta.posVec - mDelta.pos;
|
|
mDelta.rotVec = mDelta.rotVec - mDelta.rot;
|
|
mDelta.rotVec = mDelta.rotVec - mDelta.rot;
|
|
|
|
+ for(U32 i=0; i<3; ++i)
|
|
|
|
+ {
|
|
|
|
+ if (mDelta.rotVec[i] > M_PI_F)
|
|
|
|
+ mDelta.rotVec[i] -= M_2PI_F;
|
|
|
|
+ else if (mDelta.rotVec[i] < -M_PI_F)
|
|
|
|
+ mDelta.rotVec[i] += M_2PI_F;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if(mustValidateEyePoint)
|
|
if(mustValidateEyePoint)
|
|
@@ -793,7 +902,21 @@ void Camera::_setPosition(const Point3F& pos, const Point3F& rot)
|
|
zRot.set(EulerF(0.0f, 0.0f, rot.z));
|
|
zRot.set(EulerF(0.0f, 0.0f, rot.z));
|
|
|
|
|
|
MatrixF temp;
|
|
MatrixF temp;
|
|
- temp.mul(zRot, xRot);
|
|
|
|
|
|
+
|
|
|
|
+ if(mDataBlock->cameraCanBank)
|
|
|
|
+ {
|
|
|
|
+ // Take rot.y into account to bank the camera
|
|
|
|
+ MatrixF imat;
|
|
|
|
+ imat.mul(zRot, xRot);
|
|
|
|
+ MatrixF ymat;
|
|
|
|
+ ymat.set(EulerF(0.0f, rot.y, 0.0f));
|
|
|
|
+ temp.mul(imat, ymat);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ temp.mul(zRot, xRot);
|
|
|
|
+ }
|
|
|
|
+
|
|
temp.setColumn(3, pos);
|
|
temp.setColumn(3, pos);
|
|
Parent::setTransform(temp);
|
|
Parent::setTransform(temp);
|
|
mRot = rot;
|
|
mRot = rot;
|
|
@@ -808,7 +931,21 @@ void Camera::setRotation(const Point3F& rot)
|
|
zRot.set(EulerF(0.0f, 0.0f, rot.z));
|
|
zRot.set(EulerF(0.0f, 0.0f, rot.z));
|
|
|
|
|
|
MatrixF temp;
|
|
MatrixF temp;
|
|
- temp.mul(zRot, xRot);
|
|
|
|
|
|
+
|
|
|
|
+ if(mDataBlock->cameraCanBank)
|
|
|
|
+ {
|
|
|
|
+ // Take rot.y into account to bank the camera
|
|
|
|
+ MatrixF imat;
|
|
|
|
+ imat.mul(zRot, xRot);
|
|
|
|
+ MatrixF ymat;
|
|
|
|
+ ymat.set(EulerF(0.0f, rot.y, 0.0f));
|
|
|
|
+ temp.mul(imat, ymat);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ temp.mul(zRot, xRot);
|
|
|
|
+ }
|
|
|
|
+
|
|
temp.setColumn(3, getPosition());
|
|
temp.setColumn(3, getPosition());
|
|
Parent::setTransform(temp);
|
|
Parent::setTransform(temp);
|
|
mRot = rot;
|
|
mRot = rot;
|
|
@@ -821,8 +958,25 @@ void Camera::_setRenderPosition(const Point3F& pos,const Point3F& rot)
|
|
MatrixF xRot, zRot;
|
|
MatrixF xRot, zRot;
|
|
xRot.set(EulerF(rot.x, 0, 0));
|
|
xRot.set(EulerF(rot.x, 0, 0));
|
|
zRot.set(EulerF(0, 0, rot.z));
|
|
zRot.set(EulerF(0, 0, rot.z));
|
|
|
|
+
|
|
MatrixF temp;
|
|
MatrixF temp;
|
|
- temp.mul(zRot, xRot);
|
|
|
|
|
|
+
|
|
|
|
+ // mDataBlock may not be defined yet as this method is called during
|
|
|
|
+ // SceneObject::onAdd().
|
|
|
|
+ if(mDataBlock && mDataBlock->cameraCanBank)
|
|
|
|
+ {
|
|
|
|
+ // Take rot.y into account to bank the camera
|
|
|
|
+ MatrixF imat;
|
|
|
|
+ imat.mul(zRot, xRot);
|
|
|
|
+ MatrixF ymat;
|
|
|
|
+ ymat.set(EulerF(0.0f, rot.y, 0.0f));
|
|
|
|
+ temp.mul(imat, ymat);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ temp.mul(zRot, xRot);
|
|
|
|
+ }
|
|
|
|
+
|
|
temp.setColumn(3, pos);
|
|
temp.setColumn(3, pos);
|
|
Parent::setRenderTransform(temp);
|
|
Parent::setRenderTransform(temp);
|
|
}
|
|
}
|
|
@@ -839,6 +993,11 @@ void Camera::writePacketData(GameConnection *connection, BitStream *bstream)
|
|
bstream->setCompressionPoint(pos);
|
|
bstream->setCompressionPoint(pos);
|
|
mathWrite(*bstream, pos);
|
|
mathWrite(*bstream, pos);
|
|
bstream->write(mRot.x);
|
|
bstream->write(mRot.x);
|
|
|
|
+ if(bstream->writeFlag(mDataBlock->cameraCanBank))
|
|
|
|
+ {
|
|
|
|
+ // Include mRot.y to allow for camera banking
|
|
|
|
+ bstream->write(mRot.y);
|
|
|
|
+ }
|
|
bstream->write(mRot.z);
|
|
bstream->write(mRot.z);
|
|
|
|
|
|
U32 writeMode = mMode;
|
|
U32 writeMode = mMode;
|
|
@@ -912,6 +1071,11 @@ void Camera::readPacketData(GameConnection *connection, BitStream *bstream)
|
|
mathRead(*bstream, &pos);
|
|
mathRead(*bstream, &pos);
|
|
bstream->setCompressionPoint(pos);
|
|
bstream->setCompressionPoint(pos);
|
|
bstream->read(&rot.x);
|
|
bstream->read(&rot.x);
|
|
|
|
+ if(bstream->readFlag())
|
|
|
|
+ {
|
|
|
|
+ // Include rot.y to allow for camera banking
|
|
|
|
+ bstream->read(&rot.y);
|
|
|
|
+ }
|
|
bstream->read(&rot.z);
|
|
bstream->read(&rot.z);
|
|
|
|
|
|
GameBase* obj = 0;
|
|
GameBase* obj = 0;
|
|
@@ -1184,6 +1348,11 @@ void Camera::consoleInit()
|
|
"- Fly Mode\n"
|
|
"- Fly Mode\n"
|
|
"- Overhead Mode\n"
|
|
"- Overhead Mode\n"
|
|
"@ingroup BaseCamera\n");
|
|
"@ingroup BaseCamera\n");
|
|
|
|
+
|
|
|
|
+ // ExtendedMove support
|
|
|
|
+ Con::addVariable("$camera::extendedMovePosRotIndex", TypeS32, &smExtendedMovePosRotIndex,
|
|
|
|
+ "@brief The ExtendedMove position/rotation index used for camera movements.\n\n"
|
|
|
|
+ "@ingroup BaseCamera\n");
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|