How do joints work and how to make screw-type joints

A place to discuss everything related to Newton Dynamics.

Moderators: Sascha Willems, walaber

How do joints work and how to make screw-type joints

Postby arkdemon » Fri Feb 14, 2014 11:12 am

Hello everybody. I want to make meshs using some joints. For example, imagine an airplane. I want it to have 2 wings which can be torn out of the plane when it collides with a tree, for example. However, joints are a new concept for me and I do not understand at all how to use them. There are examples in the SDK: Articulated Joints and Basic Ragdoll but the code is complicated, is long and depends of DemoEntity; it is confusing and on the hundreds of lines(821 for ArticulatedJoints.cpp) I think I will only need 20 lines or even less. So my questions are: How can I implement joints? and if it is possible, how can I make a screw-type joint?
Thank you for your help.
My name is arkdemon and I don't approve this message :D
User avatar
arkdemon
 
Posts: 90
Joined: Sat Jan 18, 2014 12:38 pm

Re: How do joints work and how to make screw-type joints

Postby Julio Jerez » Fri Feb 14, 2014 12:01 pm

look at standardjoints demos
Julio Jerez
Moderator
Moderator
 
Posts: 12426
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: How do joints work and how to make screw-type joints

Postby arkdemon » Fri Feb 14, 2014 12:23 pm

OK. I built newton to 3.12. While updating (I use MinGW with Qt Creator) the makefile was wrong at some points: it did not include dgMeshEffect6.cpp, dgDeformableBodiesUpdate.cpp and there was dgCollisionCompoundBreakable.cpp at the place of dgCollisionCompoundFractured.cpp. So here is the makefile (updated) which works for me:
Code: Select all
#*******************************************************
#
# Newton game dynamics
# copy right by Julio Jerez 2002 - 2012
#
#*******************************************************
#
# Generic makefile
# this make file generate the libraries:
# dg, physics, and newton
#
#******************************************************* 


# ******************************************************
#
# dg low level library
#
# ******************************************************

#   $(DG_PTHREAD_PATH)/pthread.c \

DG_PTHREAD_PATH = ../../source/pthreads.2
DG_INCLUDED_PATH = ../../source/core
DG_PATH = $(DG_INCLUDED_PATH)/
DG_SRCS = \
   $(DG_PATH)dgAABBPolygonSoup.cpp \
   $(DG_PATH)dgAsyncThread.cpp \
   $(DG_PATH)dgConvexHull3d.cpp \
   $(DG_PATH)dgConvexHull4d.cpp \
   $(DG_PATH)dg.cpp \
   $(DG_PATH)dgCRC.cpp \
   $(DG_PATH)dgDebug.cpp \
   $(DG_PATH)dgDelaunayTetrahedralization.cpp \
   $(DG_PATH)dgGeneralMatrix.cpp \
   $(DG_PATH)dgGeneralVector.cpp \
   $(DG_PATH)dgGoogol.cpp \
   $(DG_PATH)dgIntersections.cpp \
   $(DG_PATH)dgMatrix.cpp \
   $(DG_PATH)dgMemory.cpp \
   $(DG_PATH)dgMutexThread.cpp \
   $(DG_PATH)dgNode.cpp \
   $(DG_PATH)dgPolygonSoupBuilder.cpp \
   $(DG_PATH)dgPolyhedra.cpp \
   $(DG_PATH)dgPolyhedraMassProperties.cpp \
   $(DG_PATH)dgQuaternion.cpp \
   $(DG_PATH)dgRandom.cpp \
   $(DG_PATH)dgRefCounter.cpp \
   $(DG_PATH)dgRef.cpp \
   $(DG_PATH)dgSmallDeterminant.cpp \
   $(DG_PATH)dgSPDMatrix.cpp \
   $(DG_PATH)dgObb.cpp \
   $(DG_PATH)dgThread.cpp \
   $(DG_PATH)dgThreadHive.cpp \
   $(DG_PATH)dgTree.cpp \
   $(DG_PATH)dgTypes.cpp


# ******************************************************
#
# Physics engine files
#
# ******************************************************
DG_INCLUDED_PHYSICS_PATH = ../../source/physics
DG_PHYSICS_PATH = $(DG_INCLUDED_PHYSICS_PATH)/
DG_PHYSICS_SRCS = \
   $(DG_PHYSICS_PATH)dgBody.cpp \
   $(DG_PHYSICS_PATH)dgDynamicBody.cpp \
   $(DG_PHYSICS_PATH)dgKinematicBody.cpp \
   $(DG_PHYSICS_PATH)dgBallConstraint.cpp \
   $(DG_PHYSICS_PATH)dgBilateralConstraint.cpp \
   $(DG_PHYSICS_PATH)dgBody.cpp \
   $(DG_PHYSICS_PATH)dgDynamicBody.cpp \
   $(DG_PHYSICS_PATH)dgKinematicBody.cpp \
   $(DG_PHYSICS_PATH)dgBodyMasterList.cpp \
   $(DG_PHYSICS_PATH)dgBroadPhase.cpp \
   $(DG_PHYSICS_PATH)dgCollisionBox.cpp \
   $(DG_PHYSICS_PATH)dgCollisionBVH.cpp \
   $(DG_PHYSICS_PATH)dgCollisionCapsule.cpp \
   $(DG_PHYSICS_PATH)dgCollisionChamferCylinder.cpp \
   $(DG_PHYSICS_PATH)dgCollisionCompoundFractured.cpp \
   $(DG_PHYSICS_PATH)dgCollisionCompound.cpp \
   $(DG_PHYSICS_PATH)dgCollisionCone.cpp \
   $(DG_PHYSICS_PATH)dgCollisionConvex.cpp \
   $(DG_PHYSICS_PATH)dgCollisionConvexHull.cpp \
   $(DG_PHYSICS_PATH)dgCollisionConvexPolygon.cpp \
   $(DG_PHYSICS_PATH)dgCollision.cpp \
   $(DG_PHYSICS_PATH)dgCollisionCylinder.cpp \
   $(DG_PHYSICS_PATH)dgCollisionDeformableClothPatch.cpp \
   $(DG_PHYSICS_PATH)dgCollisionDeformableSolidMesh.cpp \
   $(DG_PHYSICS_PATH)dgCollisionDeformableMesh.cpp \
   $(DG_PHYSICS_PATH)dgCollisionHeightField.cpp \
   $(DG_PHYSICS_PATH)dgCollisionInstance.cpp \
   $(DG_PHYSICS_PATH)dgCollisionMesh.cpp \
   $(DG_PHYSICS_PATH)dgCollisionNull.cpp \
   $(DG_PHYSICS_PATH)dgCollisionScene.cpp \
   $(DG_PHYSICS_PATH)dgCollisionSphere.cpp \
   $(DG_PHYSICS_PATH)dgCollisionTaperedCapsule.cpp \
   $(DG_PHYSICS_PATH)dgCollisionTaperedCylinder.cpp \
   $(DG_PHYSICS_PATH)dgCollisionUserMesh.cpp \
   $(DG_PHYSICS_PATH)dgConstraint.cpp \
   $(DG_PHYSICS_PATH)dgContact.cpp \
   $(DG_PHYSICS_PATH)dgCorkscrewConstraint.cpp \
   $(DG_PHYSICS_PATH)dgDeformableBody.cpp \
   $(DG_PHYSICS_PATH)dgDeformableContact.cpp \
   $(DG_PHYSICS_PATH)dgHingeConstraint.cpp \
   $(DG_PHYSICS_PATH)dgNarrowPhaseCollision.cpp \
   $(DG_PHYSICS_PATH)dgSlidingConstraint.cpp \
   $(DG_PHYSICS_PATH)dgUniversalConstraint.cpp \
   $(DG_PHYSICS_PATH)dgUpVectorConstraint.cpp \
   $(DG_PHYSICS_PATH)dgUserConstraint.cpp \
   $(DG_PHYSICS_PATH)dgWorld.cpp \
   $(DG_PHYSICS_PATH)dgWorldDynamicsParallelSolver.cpp \
   $(DG_PHYSICS_PATH)dgWorldDynamicsSimpleSolver.cpp \
   $(DG_PHYSICS_PATH)dgWorldDynamicUpdate.cpp \
   $(DG_PHYSICS_PATH)dgDeformableBodiesUpdate.cpp

   
# ******************************************************
#
# mesh gemotry
#
# ******************************************************
DG_INCLUDED_MESH_PATH = ../../source/meshUtil
DG_MESH_PATH = $(DG_INCLUDED_MESH_PATH)/
DG_MESH_SRCS = \
   $(DG_MESH_PATH)dgMeshEffect1.cpp \
   $(DG_MESH_PATH)dgMeshEffect2.cpp \
   $(DG_MESH_PATH)dgMeshEffect3.cpp \
   $(DG_MESH_PATH)dgMeshEffect4.cpp \
   $(DG_MESH_PATH)dgMeshEffect5.cpp \
   $(DG_MESH_PATH)dgMeshEffect6.cpp

# ******************************************************
#
# open cl
#
# ******************************************************
DG_INCLUDED_OPENCL_PATH = ../../source/openCL
DG_OPENCL_PATH = $(DG_INCLUDED_OPENCL_PATH)/
#DG_OPENCL_SRCS = \
#   $(DG_OPENCL_PATH)dgOpencl.cpp \
#   $(DG_OPENCL_PATH)dgOpenclInstance.cpp \
#   $(DG_OPENCL_PATH)dgOpenclBroadPhase.cpp

   

# ******************************************************
#
# Newton engine files
#g++ -shared -o libNewton.so libNewton.a
# ******************************************************
DG_INCLUDED_NEWTON_PATH = ../../source/newton
DG_NEWTON_PATH = $(DG_INCLUDED_NEWTON_PATH)/
DG_NEWTON_SRCS = \
   $(DG_NEWTON_PATH)Newton.cpp \
   $(DG_NEWTON_PATH)NewtonClass.cpp

# ******************************************************
#
# Allsource files
#
# ******************************************************
ALL_SRC_FILES = $(DG_SRCS) $(DG_PHYSICS_SRCS) $(DG_OPENCL_SRCS) $(DG_MESH_SRCS) $(DG_NEWTON_SRCS)
DG_OBJ_FILES = $(ALL_SRC_FILES:.cpp=.o)

COMPILER = gcc

# POSIX options    gcc 4.4.2
#CPU_FLAGS = -m32 -O0 -fPIC -g -msse -msse2 -mfpmath=sse
#CPU_FLAGS = -m32 -O0 -fPIC -g -msse -msse4.1 -mfpmath=sse
#CPU_FLAGS = -m32 -O2 -fpic -g -msse -msse4.1 -mfpmath=sse -ffloat-store -ffast-math -freciprocal-math -funsafe-math-optimizations -fsingle-precision-constant
CPU_FLAGS = -m32 -O2 -g -msse -msse3 -mfpmath=sse -ffloat-store -ffast-math -freciprocal-math -funsafe-math-optimizations -fsingle-precision-constant

FLAGS  = -c -Wall -Wno-strict-aliasing -DPTW32_BUILD -DPTW32_STATIC_LIB -D_NEWTON_STATIC_LIB -D_MINGW_32_VER $(CPU_FLAGS) -I$(DG_PTHREAD_PATH) -I$(DG_INCLUDED_PATH) -I$(DG_INCLUDED_PHYSICS_PATH) -I$(DG_INCLUDED_MESH_PATH) -I$(DG_INCLUDED_OPENCL_PATH)

INSTALL_PATH = mingw32


.SUFFIXES : .o .cpp
.cpp.o :
   $(COMPILER) $(FLAGS) -o $@ $<

# main target
engine : libNewton.a


# clean all objects target
clean :
   del /S ..\..\*.o

# libraries targets
libNewton.a : $(DG_OBJ_FILES)
   ar r $@ $?




For the Joints, I will look into it and post the results. Thank you for your help
My name is arkdemon and I don't approve this message :D
User avatar
arkdemon
 
Posts: 90
Joined: Sat Jan 18, 2014 12:38 pm

Re: How do joints work and how to make screw-type joints

Postby Julio Jerez » Fri Feb 14, 2014 12:37 pm

in what sub directory is that make file?
Julio Jerez
Moderator
Moderator
 
Posts: 12426
Joined: Sun Sep 14, 2003 2:18 pm
Location: Los Angeles

Re: How do joints work and how to make screw-type joints

Postby arkdemon » Fri Feb 14, 2014 12:44 pm

It's here: ../coreLibrary_300/projects/mingw32

It works! Thanks for your help
My name is arkdemon and I don't approve this message :D
User avatar
arkdemon
 
Posts: 90
Joined: Sat Jan 18, 2014 12:38 pm

Re: How do joints work and how to make screw-type joints

Postby arkdemon » Fri Feb 14, 2014 12:45 pm

PS: I did not use the CustomJoint class because I could not compile it. Anyway, here is the code I used (It is nearly a copy/paste but I replaced dMatrix by dgMatrix and dVector by dgVector and little other things)
Attention: I replaced dSin, dCos, ... by glm functions

customjoint.h:
Code: Select all
class CustomAlloc
{
public:
    void* operator new (size_t size){
        return NewtonAlloc(int (size));
    }

    void operator delete (void* ptr){
        NewtonFree(ptr);
    }

    CustomAlloc() { }

    virtual ~CustomAlloc()  { }
};

struct NewtonUserJoint;
typedef void (*JointUserDestructorCallback) (const NewtonUserJoint* const me);
typedef void (*JointUserSubmitConstraintCallback) (const NewtonUserJoint* const me, dFloat timestep, int threadIndex);


// this is the base class to implement custom joints, it is not a joint it just provide functionality
// for the user to implement it own joints
class CustomJoint: public CustomAlloc
{
    public:
    struct AngularIntegration
    {
        AngularIntegration()
        {
            m_angle = 0.0f;
        }

        dFloat CalculateJointAngle (dFloat newAngleCos, dFloat newAngleSin)
        {
            // the joint angle can be determine by getting the angle between any two non parallel vectors
            dFloat sinJointAngle = glm::sin(m_angle);
            dFloat cosJointAngle = glm::cos(m_angle);

            dFloat sin_da = newAngleSin * cosJointAngle - newAngleCos * sinJointAngle;
            dFloat cos_da = newAngleCos * cosJointAngle + newAngleSin * sinJointAngle;

            m_angle += atan2 (sin_da, cos_da);
            return  m_angle;
        }
        dFloat m_angle;
    };

     CustomJoint();
     CustomJoint(int maxDOF, NewtonBody* const body0, NewtonBody* const body1);
     virtual ~CustomJoint();

     void SetBodiesCollisionState (int state);
     int GetBodiesCollisionState () const;

     NewtonBody* GetBody0 () const;
     NewtonBody* GetBody1 () const;
     NewtonJoint* GetJoint () const;

     void JointSetSolverMode (bool mode, int maxContactJoints) const;


    // the application needs to implement this function for serialization
     virtual void GetInfo (NewtonJointRecord* const info) const;
     virtual void ProjectError () const;

    // these member function are only used by the C interface or for hooking callback to customize a particular
    // joint without deriving a new one
    // note: this is not a extension of a virtual function, DO NOT CALL the base class SubmitConstraints!!
     void SetUserData (void* userData) {m_userData = userData;}
     void* GetUserData () const {return m_userData;}
     void SetUserDestructorCallback (JointUserDestructorCallback callback) {m_userDestructor = callback;}
     void SetUserSubmintConstraintCallback (JointUserSubmitConstraintCallback callback) {m_userConstrationCallback = callback;}

    private:
    // this are the callback needed to have transparent c++ method interfaces
    static void Destructor (const NewtonJoint* me);
    static void SubmitConstraints (const NewtonJoint* const me, dFloat timestep, int threadIndex);
    static void GetInfo (const NewtonJoint* const me, NewtonJointRecord* info);


    protected:
     void Init (int maxDOF, NewtonBody* const body0, NewtonBody* const body1);

    // the application needs to implement this function for each derived joint. See examples for more detail
     virtual void SubmitConstraints (dFloat timestep, int threadIndex);
     void CalculateGlobalMatrix (const dgMatrix& localMatrix0, const dgMatrix& localMatrix1, dgMatrix& matrix0, dgMatrix& matrix1) const;
     void CalculateLocalMatrix (const dgMatrix& pinsAndPivotFrame, dgMatrix& localMatrix0, dgMatrix& localMatrix1) const;


    void* m_userData;
    NewtonBody* m_body0;
    NewtonBody* m_body1;
    NewtonJoint* m_joint;
    NewtonWorld* m_world;
    JointUserDestructorCallback m_userDestructor;
    JointUserSubmitConstraintCallback m_userConstrationCallback;
    int m_maxDof;
    int m_autoDestroy;
};

class CustomDistance: public CustomJoint
{
    public:
    CustomDistance (const dgVector& pivotInChildInGlobalSpace, const dgVector& pivotInParentInGlobalSpace, NewtonBody* const child, NewtonBody* const parent)
        :CustomJoint(3, child, parent)
    {
        dgVector dist (pivotInChildInGlobalSpace - pivotInParentInGlobalSpace) ;
        m_distance = glm::sqrt (dist % dist);

        dgMatrix childMatrix (dgGetIdentityMatrix());
        dgMatrix parentMatrix (dgGetIdentityMatrix());

        childMatrix.m_posit = pivotInChildInGlobalSpace;
        parentMatrix.m_posit = pivotInParentInGlobalSpace;
        childMatrix.m_posit.m_w = 1.0f;
        parentMatrix.m_posit.m_w = 1.0f;

        dgMatrix dummy;
        CalculateLocalMatrix (childMatrix, m_localPivot, dummy);
        CalculateLocalMatrix (parentMatrix, dummy, m_parentPivot);
    }

    void SubmitConstraints (dFloat timestep, int threadIndex)
    {
        dgMatrix matrix0;
        dgMatrix matrix1;

        // calculate the position of the pivot point and the Jacobian direction vectors, in global space.
        CalculateGlobalMatrix (m_localPivot, m_parentPivot, matrix0, matrix1);

        dgVector p0 (matrix0.m_posit);
        dgVector p1 (matrix1.m_posit);

        dgVector dist (p1 - p0);
        dFloat mag2 = dist % dist;
        if (mag2 > 0.0f) {
            dist = dist.Scale3(1.0f / glm::sqrt (mag2));
            p0 += dist.Scale3(m_distance);
        }

        // Restrict the movement on the pivot point along all tree orthonormal direction
        NewtonUserJointAddLinearRow (m_joint, &p0[0], &p1[0], &matrix1.m_front[0]);
        NewtonUserJointAddLinearRow (m_joint, &p0[0], &p1[0], &matrix1.m_up[0]);
        NewtonUserJointAddLinearRow (m_joint, &p0[0], &p1[0], &matrix1.m_right[0]);
    }

    dgMatrix m_localPivot;
    dgMatrix m_parentPivot;
    dFloat m_distance;
};


customjoint.cpp:
Code: Select all
#include "joint.h"

CustomJoint::CustomJoint ()
    :m_userData(NULL)
    ,m_body0(NULL)
    ,m_body1(NULL)
    ,m_joint(NULL)
    ,m_world(NULL)
    ,m_userDestructor(NULL)
    ,m_userConstrationCallback(NULL)
    ,m_maxDof(0)
    ,m_autoDestroy(0)
{
}

CustomJoint::CustomJoint (int maxDOF, NewtonBody* const body0, NewtonBody* const body1)
{
    Init (maxDOF, body0, body1);
}

CustomJoint::~CustomJoint()
{
    //dAssert (m_joint);

    //if the joint has user data it means the application is destroying the joint

// if there is a C destructor call it form here
    CustomJoint* const joint = (CustomJoint*) NewtonJointGetUserData (m_joint);
    if (joint->m_userDestructor) {
        joint->m_userDestructor ((const NewtonUserJoint*) joint);
    }

//   if (NewtonJointGetUserData (m_joint)) {
    if (!m_autoDestroy) {
        // set the joint call to NULL to prevent infinite recursion
        NewtonJointSetUserData (m_joint, NULL);
        NewtonJointSetDestructor (m_joint, NULL);

        // destroy this joint
        NewtonDestroyJoint(m_world, m_joint);
    }
}



void CustomJoint::Init (int maxDOF, NewtonBody* const body0, NewtonBody* const body1)
{
    m_autoDestroy = 0;
    m_joint = NULL;
    m_body0 = body0;
    m_body1 = body1;
    m_maxDof = maxDOF;
    m_world   = NewtonBodyGetWorld (body0);
    m_joint = NewtonConstraintCreateUserJoint (m_world, maxDOF, SubmitConstraints, GetInfo, m_body0, m_body1);

    NewtonJointSetUserData (m_joint, this);
    NewtonJointSetDestructor (m_joint, Destructor);

    m_userData = NULL;
    m_userDestructor = NULL;
    m_userConstrationCallback = NULL;

}


NewtonBody* CustomJoint::GetBody0 () const
{
    return m_body0;
}

NewtonBody* CustomJoint::GetBody1 () const
{
    return m_body1;
}

NewtonJoint* CustomJoint::GetJoint () const
{
    return m_joint;
}


void CustomJoint::Destructor (const NewtonJoint* me)
{
    // get the pointer to the joint class
    CustomJoint* const joint = (CustomJoint*) NewtonJointGetUserData (me);

    joint->m_autoDestroy = 1;

    // delete the joint class
    delete joint;
}


void  CustomJoint::SubmitConstraints (const NewtonJoint* const me, dFloat timestep, int threadIndex)
{
    // get the pointer to the joint class
    CustomJoint* const joint = (CustomJoint*) NewtonJointGetUserData (me);

    // call the constraint call back
    joint->SubmitConstraints(timestep, threadIndex);

    // if there is a user define callback call it from here;
    if (joint->m_userConstrationCallback) {
        joint->m_userConstrationCallback ((const NewtonUserJoint*) joint, timestep, threadIndex);
    }

}

void CustomJoint::GetInfo (const NewtonJoint* const me, NewtonJointRecord* info)
{
    // get the pointer to the joint class
    CustomJoint* const joint = (CustomJoint*) NewtonJointGetUserData (me);
    joint->GetInfo(info);
}


void CustomJoint::CalculateLocalMatrix (const dgMatrix& pinsAndPivotFrame, dgMatrix& localMatrix0, dgMatrix& localMatrix1) const
{
    dgMatrix matrix0;

    // Get the global matrices of each rigid body.
    NewtonBodyGetMatrix(m_body0, &matrix0[0][0]);
    dgMatrix matrix1 (dgGetIdentityMatrix());
    if (m_body1) {
        NewtonBodyGetMatrix(m_body1, &matrix1[0][0]);
    }

    // calculate the relative matrix of the pin and pivot on each body
    localMatrix0 = pinsAndPivotFrame * matrix0.Inverse();
    localMatrix1 = pinsAndPivotFrame * matrix1.Inverse();
}


void CustomJoint::CalculateGlobalMatrix (const dgMatrix& localMatrix0, const dgMatrix& localMatrix1, dgMatrix& matrix0, dgMatrix& matrix1) const
{
    dgMatrix body0Matrix;
    // Get the global matrices of each rigid body.
    NewtonBodyGetMatrix(m_body0, &body0Matrix[0][0]);

    dgMatrix body1Matrix (dgGetIdentityMatrix());
    if (m_body1) {
        NewtonBodyGetMatrix(m_body1, &body1Matrix[0][0]);
    }
    matrix0 = localMatrix0 * body0Matrix;
    matrix1 = localMatrix1 * body1Matrix;
}


void CustomJoint::GetInfo (NewtonJointRecord* const info) const
{
}


void CustomJoint::ProjectError () const
{
}

void CustomJoint::SetBodiesCollisionState (int state)
{
    NewtonJointSetCollisionState (m_joint, state);
}

int CustomJoint::GetBodiesCollisionState () const
{
    return NewtonJointGetCollisionState (m_joint);
}

void CustomJoint::JointSetSolverMode (bool mode, int maxContactJoints) const
{
    NewtonUserJointSetSolver (m_joint, mode, maxContactJoints);
}


void CustomJoint::SubmitConstraints (dFloat timestep, int threadIndex)
{
}

My name is arkdemon and I don't approve this message :D
User avatar
arkdemon
 
Posts: 90
Joined: Sat Jan 18, 2014 12:38 pm

Re: How do joints work and how to make screw-type joints

Postby arkdemon » Sat Feb 15, 2014 9:17 am

Hi again. I have a problem with the joints when I try to make a Screw: the body 1 (child) does not rotate when the Body 0 (parent) rotates and I do not know how to make it work. I am trying to make the body 1 do exactly the same thing the body 0 does (ie rotate and move) and it should not be allowed to move at all otherwise like airplane wings for example. In a way, it should be attached to the parent body but should be detached when there is a strong collision. Thank you for your answers
My name is arkdemon and I don't approve this message :D
User avatar
arkdemon
 
Posts: 90
Joined: Sat Jan 18, 2014 12:38 pm


Return to General Discussion

Who is online

Users browsing this forum: No registered users and 1 guest

cron