Trying to implement a rope, but it freeze

Post Reply
allfoxwy
Posts: 3
Joined: Wed May 16, 2018 10:22 am

Trying to implement a rope, but it freeze

Post by allfoxwy »

Greetings.

I'm trying to implement a rope.

I'm using a simplified model:
a kinematic obj on the top <- a P2P constraint <- a mass obj in the middle <- a P2P constraint <- a tail obj to connect payload

So I wrote this

Code: Select all

typedef struct {
	btRigidBody* body;
	GLfloat size;
} draw_box;

//for OpenGL.
//things in it would be drew on the screen
std::vector<draw_box> draw;

btRigidBody* rope_top = nullptr;
btRigidBody* rope_tail = nullptr;
btVector3 rope_pos(0, 9, 0);
btScalar rope_length = 8.f;
btScalar rope_fat(0.2f);

{
    btScalar fat(rope_fat);
    {
        //kinematic obj on top, so I could move the rope
        btCollisionShape* shape = new btSphereShape(fat);
        collision_shapes.push_back(shape);

        btTransform start_pos;
        start_pos.setIdentity();
        start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y(), rope_pos.z()));

        btScalar mass(0.f);
        btMotionState* motion = new btDefaultMotionState(start_pos);
        btRigidBody* body = new btRigidBody(mass, motion, shape);

        rope_top = body;
        world->addRigidBody(body);

        draw_box my_draw;
        my_draw.body = body;
        my_draw.size = fat;
        draw.push_back(my_draw);
    }

    btRigidBody* rope_middle = nullptr;
    {
        //mass obj at the middle
        btCollisionShape* shape = new btSphereShape(fat);
        collision_shapes.push_back(shape);

        btTransform start_pos;
        start_pos.setIdentity();
        start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y() - rope_length / 2, rope_pos.z()));

        btScalar mass(1.f);
        btVector3 inertia(0.f, 0.f, 0.f);
        shape->calculateLocalInertia(mass, inertia);

        btMotionState* motion = new btDefaultMotionState(start_pos);
        btRigidBody* body = new btRigidBody(mass, motion, shape, inertia);
        rope_middle = body;
        world->addRigidBody(body);

        btTypedConstraint* pivot = new btPoint2PointConstraint(*rope_top, *body, btVector3(0.f, -fat * 2, 0.f), btVector3(0.f, rope_length / 2 - fat * 2, 0.f));
        world->addConstraint(pivot);

        draw_box my_draw;
        my_draw.body = body;
        my_draw.size = fat;
        draw.push_back(my_draw);
    }

    {
        //tail obj to connect payload
        btCollisionShape* shape = new btSphereShape(fat);
        collision_shapes.push_back(shape);

        btTransform start_pos;
        start_pos.setIdentity();
        start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y() - rope_length, rope_pos.z()));

        btScalar mass(1.f);
        btVector3 inertia(0.f, 0.f, 0.f);
        shape->calculateLocalInertia(mass, inertia);

        btMotionState* motion = new btDefaultMotionState(start_pos);
        btRigidBody* body = new btRigidBody(mass, motion, shape, inertia);
        rope_tail = body;
        world->addRigidBody(body);

        btTypedConstraint* pivot = new btPoint2PointConstraint(*rope_middle, *body, btVector3(0.f, -fat * 2, 0.f), btVector3(0.f, rope_length / 2 - fat * 2, 0.f));
        world->addConstraint(pivot);

        draw_box my_draw;
        my_draw.body = body;
        my_draw.size = fat;
        draw.push_back(my_draw);
    }
}
The problem is that, when the program starts, then I move the top kinematic obj immediately, things works. However, if I wait for a few seconds, then the dynamic objs wouldn't move anymore, only the kinematic obj could be moved by me.

And if I move the top obj when it still work, then I have to keep moving my hand to keep things work. If I stop, then some time latter, dynamic objs would freeze either.

It feels like the system would freeze if things calm down once.

I'm quite confused. Could anyone give me some hint?



All of the code is below. Using libSDL and GLEW.

The load_obj.h is loading a box.obj file, a size 1x1x1 box.
vmath.h is from the OpenGL redbook, which would generate rotation and scaling matrix: https://github.com/openglredbook/exampl ... de/vmath.h

Vertex Shader:

Code: Select all

#version 330 core

uniform mat4 change;

in vec4 in_position;

out vec4 fragment_color;

void
main()
{
	gl_Position = change * in_position;
	fragment_color = vec4(1.0, 1.0, 1.0, 1.0);
}
Fragment Shader:

Code: Select all

#version 330 core


in vec4 fragment_color;
out vec4 out_color;

void
main()
{
	out_color = fragment_color;
}

main.cpp:

Code: Select all


#include <iostream>
#include <vector>
#include <SDL.h>
#include <GL/glew.h>
#include <SDL_opengl.h>
#include <GL/GLU.h>
#include <btBulletDynamicsCommon.h>



#include "load_shader.h"
#include "load_obj.h"
#include "vmath.h"

int main(int, char**) {
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0) {
		std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
		return 1;
	}


	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);



	SDL_Window *win = SDL_CreateWindow("Greetings", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_OPENGL);
	if (win == nullptr) {
		std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
		SDL_Quit();
		return 1;
	}



	SDL_GLContext gl_context = SDL_GL_CreateContext(win);
	if (gl_context == nullptr) {
		std::cerr << "SDL_GL_CreateContext Error: " << SDL_GetError() << std::endl;
		SDL_DestroyWindow(win);
		SDL_Quit();
		return 1;
	}



	glewExperimental = GL_TRUE;
	GLenum glewError = glewInit();
	if (glewError != GLEW_OK) {
		std::cerr << "glewInit Error: " << glewGetErrorString(glewError) << std::endl;
		SDL_DestroyWindow(win);
		SDL_Quit();
		return 1;
	}



	// try adaptive vsync first (-1)
	// when failed, try normal vsync (1)
	if (SDL_GL_SetSwapInterval(-1) < 0 && SDL_GL_SetSwapInterval(1) < 0) {
		std::cerr << "SDL_GL_SetSwapInterval Error: " << SDL_GetError() << std::endl;
		SDL_DestroyWindow(win);
		SDL_Quit();
		return 1;
	}





	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);






	GLuint vertex_shader = load_shader(GL_VERTEX_SHADER, "vertex_shader.glsl");
	GLuint fragment_shader = load_shader(GL_FRAGMENT_SHADER, "fragment_shader.glsl");

	GLuint gpu_program = glCreateProgram();
	glAttachShader(gpu_program, vertex_shader);
	glAttachShader(gpu_program, fragment_shader);
	glLinkProgram(gpu_program);

	GLint link_result;
	glGetProgramiv(gpu_program, GL_LINK_STATUS, &link_result);
	if (link_result != GL_TRUE)
	{
		GLchar error_log[1024];
		glGetProgramInfoLog(gpu_program, 1024, nullptr, error_log);
		std::cerr << "failed to link GPU program: " << error_log << std::endl;
		return 1;
	}

	GLuint in_position = glGetAttribLocation(gpu_program, "in_position");
	GLuint change = glGetUniformLocation(gpu_program, "change");

	

	

	Model box; // from load_obj.h, just loading a 1x1x1 box here
	if (!box.load("box.obj"))
	{
		glDeleteShader(vertex_shader);
		glDeleteShader(fragment_shader);
		glDeleteProgram(gpu_program);
		SDL_DestroyWindow(win);
		SDL_Quit();
		return 1;
	}


	
	GLuint gpu_vao = 0;
	glGenVertexArrays(1, &gpu_vao);
	glBindVertexArray(gpu_vao);
	glBindBuffer(GL_ARRAY_BUFFER, box.my_buffer);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, box.my_element_buffer);
	glVertexAttribPointer(in_position, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);
	glEnableVertexAttribArray(in_position);
		




	btDefaultCollisionConfiguration* collision_config = new btDefaultCollisionConfiguration();
	btCollisionDispatcher* collision_dispatcher = new btCollisionDispatcher(collision_config);
	btBroadphaseInterface* overlapping_pair_cache = new btDbvtBroadphase();
	btSequentialImpulseConstraintSolver* constraint_solver = new btSequentialImpulseConstraintSolver();
	btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld(collision_dispatcher, overlapping_pair_cache, constraint_solver, collision_config);




	world->setGravity(btVector3(0, -10, 0));
	btAlignedObjectArray<btCollisionShape*> collision_shapes;



	{
		//ground
		btCollisionShape* ground = new btBoxShape(btVector3(btScalar(50), btScalar(1), btScalar(50)));
		collision_shapes.push_back(ground);

		btTransform groundTransform;
		groundTransform.setIdentity();
		groundTransform.setOrigin(btVector3(0, -10, 0));

		btScalar mass(0.);

		//rigidbody is dynamic if and only if mass is non zero, otherwise static
		bool isDynamic = (mass != 0.f);

		btVector3 localInertia(0, 0, 0);
		if (isDynamic)
			ground->calculateLocalInertia(mass, localInertia);

		//using motionstate is optional, it provides interpolation capabilities, and only synchronizes 'active' objects
		btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
		btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, ground, localInertia);
		btRigidBody* body = new btRigidBody(rbInfo);
		body->setContactStiffnessAndDamping(300, 100);

		//add the body to the dynamics world
		world->addRigidBody(body);

	}

	typedef struct {
		btRigidBody* body;
		GLfloat size;
	} draw_box;

	std::vector<draw_box> draw; //things in it would be draw on the screen
	
	
	btRigidBody* rope_top = nullptr;
	btRigidBody* rope_tail = nullptr;
	btVector3 rope_pos(0, 9, 0);
	btScalar rope_length = 8.f;
	btScalar rope_fat(0.2f);
	
	{
		//rope
		btScalar fat(rope_fat);
		{
			//top obj, so I could move the rope
			btCollisionShape* shape = new btSphereShape(fat);
			collision_shapes.push_back(shape);

			btTransform start_pos;
			start_pos.setIdentity();
			start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y(), rope_pos.z()));

			btScalar mass(0.f);
			btMotionState* motion = new btDefaultMotionState(start_pos);
			btRigidBody* body = new btRigidBody(mass, motion, shape);

			rope_top = body;
			world->addRigidBody(body);

			draw_box my_draw;
			my_draw.body = body;
			my_draw.size = fat;
			draw.push_back(my_draw);
		}

		btRigidBody* rope_middle = nullptr;
		{
			//mass at the middle
			btCollisionShape* shape = new btSphereShape(fat);
			collision_shapes.push_back(shape);

			btTransform start_pos;
			start_pos.setIdentity();
			start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y() - rope_length / 2, rope_pos.z()));
			
			btScalar mass(1.f);
			btVector3 inertia(0.f, 0.f, 0.f);
			shape->calculateLocalInertia(mass, inertia);

			btMotionState* motion = new btDefaultMotionState(start_pos);
			btRigidBody* body = new btRigidBody(mass, motion, shape, inertia);
			rope_middle = body;
			world->addRigidBody(body);

			btTypedConstraint* pivot = new btPoint2PointConstraint(*rope_top, *body, btVector3(0.f, -fat * 2, 0.f), btVector3(0.f, rope_length / 2 - fat * 2, 0.f));
			world->addConstraint(pivot);

			draw_box my_draw;
			my_draw.body = body;
			my_draw.size = fat;
			draw.push_back(my_draw);
		}

		{
			//tail obj to connect payload
			btCollisionShape* shape = new btSphereShape(fat);
			collision_shapes.push_back(shape);

			btTransform start_pos;
			start_pos.setIdentity();
			start_pos.setOrigin(btVector3(rope_pos.x(), rope_pos.y() - rope_length, rope_pos.z()));

			btScalar mass(1.f);
			btVector3 inertia(0.f, 0.f, 0.f);
			shape->calculateLocalInertia(mass, inertia);

			btMotionState* motion = new btDefaultMotionState(start_pos);
			btRigidBody* body = new btRigidBody(mass, motion, shape, inertia);
			rope_tail = body;
			world->addRigidBody(body);

			btTypedConstraint* pivot = new btPoint2PointConstraint(*rope_middle, *body, btVector3(0.f, -fat * 2, 0.f), btVector3(0.f, rope_length / 2 - fat * 2, 0.f));
			world->addConstraint(pivot);

			draw_box my_draw;
			my_draw.body = body;
			my_draw.size = fat;
			draw.push_back(my_draw);
		}
	}

	bool fin = false;
	unsigned int last_render_time = SDL_GetTicks();
	btScalar top_posX(rope_pos.x());
	btScalar top_posY(rope_pos.y());
	unsigned int fps = 0;
	unsigned int fps_timer = SDL_GetTicks();
	while (!fin) {
		SDL_Event e;
		while (SDL_PollEvent(&e)) {

			if (e.type == SDL_QUIT) {
				fin = true;
				break;
			}
			if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) {
				fin = true;
				break;
			}
			if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP) {
				top_posY += 0.2f;
				continue;
			}
			if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN) {
				top_posY -= 0.2f;
				continue;
			}
			if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT) {
				top_posX += 0.2f;
				continue;
			}
			if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT) {
				top_posX -= 0.2f;
				continue;
			}

		}

		btTransform move_rope;
		move_rope.setIdentity();
		move_rope.setOrigin(btVector3(top_posX, top_posY, rope_pos.z()));
		rope_top->setWorldTransform(move_rope);
	


		unsigned int now = SDL_GetTicks();

		if (now - fps_timer > 1000)
		{
			std::cout << "fps: " << fps << std::endl;
			fps_timer = now;
			fps = 0;
		}

		if (now - last_render_time < 1000 / 30)
		{
			SDL_Delay(1);
			continue;
		}


		
		
		

		world->stepSimulation((now - last_render_time) / 1000.f, (now - last_render_time) / 1000.f / (1.f / 60.f) + 2, btScalar(1.) / btScalar(60.));


		static const GLfloat black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, black);



		for (unsigned int i = 0; i < draw.size(); ++i)
		{
			btRigidBody* body = draw[i].body;

			btTransform trans;
			if (body->isStaticOrKinematicObject())
			{
				trans = body->getWorldTransform();
			}
			else
			{
				body->getMotionState()->getWorldTransform(trans);
			}
			vmath::mat4 physic;
			trans.getOpenGLMatrix(physic);

			GLfloat world_size_factor = 0.1f;

			physic[3][0] *= world_size_factor;
			physic[3][1] *= world_size_factor;
			physic[3][2] *= world_size_factor;

			

			glUseProgram(gpu_program);
			
			glUniformMatrix4fv(change, 1, GL_FALSE, physic* vmath::scale(world_size_factor) * vmath::scale(draw[i].size));


			glDrawElements(GL_TRIANGLES, box.my_size, GL_UNSIGNED_INT, NULL);
		}
		

		SDL_GL_SwapWindow(win);

		last_render_time = SDL_GetTicks();
		++fps;
	}

	while (world->getNumConstraints() > 0)
	{
		btTypedConstraint* ptr = world->getConstraint(0);
		world->removeConstraint(ptr);
		delete ptr;
	}


	while (world->getNumCollisionObjects() > 0)
	{
		btCollisionObject* obj = world->getCollisionObjectArray()[0];
		btRigidBody* body = btRigidBody::upcast(obj);
		if (body && body->getMotionState())
		{
			delete body->getMotionState();
		}
		world->removeCollisionObject(obj);
		delete obj;
	}

	for (int i = 0; i < collision_shapes.size(); ++i)
	{
		delete collision_shapes[i];
	}


	delete world;
	delete constraint_solver;
	delete overlapping_pair_cache;
	delete collision_dispatcher;
	delete collision_config;


	glDeleteVertexArrays(1, &gpu_vao);

	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);
	glDeleteProgram(gpu_program);

	SDL_DestroyWindow(win);
	SDL_Quit();
	return 0;
}


User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Trying to implement a rope, but it freeze

Post by drleviathan »

My guess is: the dynamic objects are going inactive and then are not re-activated automagically by the constraint.

By default Bullet will deactivate dynamic objects whose velocities are below both the "sleeping" thresholds for more than 2 seconds. This allows the physics simulation to not bother integrating the motion of things that aren't moving.

BTW, the default "sleeping" velocity thresholds for dynamic objects are: linear = 0.8m/sec, angular = 1.0rad/sec, but they can be set otherwise on a per-RigidBody basis using:

Code: Select all

btRigidBody::setSleepingThresholds(btScalar linear,btScalar angular);
When an active dynamic or kinematic object potentially interacts with (e.g. its bounding box overlaps with) an inactive dynamic object the inactive object is automatically activated. However, based on your experience it sounds like creating or updating a constraint between two RigidBodies does NOT automatically activate either end.

If you want an inactive object to wake up on a mouse click or some other external event then you need to manually call btCollisionObject::activate() on it. I recommend when doing this that you first check to see if it is active because otherwise the activate() call will reset the 2 second timer and the object will NEVER settle down and go inactive.

Code: Select all

if (!body->isActive()) {
    body->activate();
}
Finally, there is a simple way to configure an object to NEVER go inactive:

Code: Select all

btCollisionObject::setActivationState(DISABLE_DEACTIVATION)
allfoxwy
Posts: 3
Joined: Wed May 16, 2018 10:22 am

Re: Trying to implement a rope, but it freeze

Post by allfoxwy »

Thanks for telling me the activition theory!

I guess I would rewrite the code.

I see the manual said the kinematic obj needed to be marked, otherwise they would be static obj which should not be moved.

Those length calculations are not considered carefully either, might be some "pivot inside an object" there.

Thanks for quick response!
allfoxwy
Posts: 3
Joined: Wed May 16, 2018 10:22 am

Re: Trying to implement a rope, but it freeze

Post by allfoxwy »

I'm now sure @drleviathan is right.

When move the kinematic object, dyamic objects connected to it via some kind of constraint pivot would not be activated.

They need to be manually activated.
Post Reply