Page 1 of 1

Can't See 3D Lines Drawn

Posted: Sun May 02, 2010 8:47 am
by so1odo1o
I am having trouble displaying 3D lines using draw3DLine.

In my program I have a list of "GraphNodes" which contain the id and the coordinate of the nodes. I am successful in rendering the sphere nodes at the correct positions.

It is when I have to draw the 3D lines that is bugging me. I go through a file that contains 2 ids of the nodes to connect. I use those ids to find the nodes and connect their coordinates with draw3DLine.

I am sure that the nodes I get from the ids and coordinates I pass into draw3D line are correct. It is just that draw3Dline isn't doing what I expect it to do. All I see when I run the program is the sphere nodes.

Here is a portion of my code beginning where I create the scene:

Code: Select all

	// Create nodes in scene editor
	IrrlichtDevice *device =
		createDevice(video::EDT_OPENGL, core::dimension2d<u32>(640, 480));
	video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();
	
	// Add nodes to graph
	for(int i = 0; i < num; i++)
	{
		GraphNode gn = nodes.at(i);

		smgr->addSphereSceneNode(1.0f, 16, 0, gn.getName(), 
			core::vector3df(gn.getX(), gn.getY(), gn.getZ()));
	}

	// Create relationships by searching through vector
	while(!myfile.eof())
	{
		string p1, p2;
		getline(myfile, p1, ' ');
		getline(myfile, p2);
		int id1, id2;
		istringstream str2int1(p1);
		istringstream str2int2(p2);
		str2int1 >> id1;
		str2int2 >> id2;

		driver->draw3DLine(
			smgr->getSceneNodeFromId(id1)->getPosition(),
			smgr->getSceneNodeFromId(id2)->getPosition());
	}

	// Add camera
	smgr->addCameraSceneNodeFPS();
	device->getCursorControl()->setVisible(false);

	int lastFPS = -1;

    while(device->run())
	{
		if (device->isWindowActive())
        {
			driver->beginScene(true, true, video::SColor(255,200,200,200));
			smgr->drawAll();
			driver->endScene();

			int fps = driver->getFPS();

			if (lastFPS != fps)
			{
					lastFPS = fps;
			}
        }
        else
		{
			device->yield();
        }
	}

	myfile.close();
	device->drop();

Posted: Sun May 02, 2010 10:03 am
by B@z
you have to call drawLine3D every time u want it to see, between driver->beginScene and driver->endScene
also, u might want to set the material, and the transformation, check the API for that

Posted: Sun May 02, 2010 1:41 pm
by so1odo1o
I've changed my code above to put what you said between beginscene and endscene. But it still isn't showing up.

Code: Select all

while(device->run())
	{
		if (device->isWindowActive())
        {
			driver->beginScene(true, true, video::SColor(255,200,200,200));

			while(!myfile.eof())
			{
				string p1, p2;
				getline(myfile, p1, ' ');
				getline(myfile, p2);
				int id1, id2;
				istringstream str2int1(p1);
				istringstream str2int2(p2);
				str2int1 >> id1;
				str2int2 >> id2;
				
				video::SMaterial m; 
				m.Lighting=false; 
				driver->setMaterial(m); 
				driver->setTransform(video::ETS_WORLD, core::matrix4());
				driver->draw3DLine(
					smgr->getSceneNodeFromId(id1)->getPosition(),
					smgr->getSceneNodeFromId(id2)->getPosition());
			}	

			smgr->drawAll();
			driver->endScene();

			int fps = driver->getFPS();

			if (lastFPS != fps)
			{
					lastFPS = fps;
			}
        }
        else
		{
			device->yield();
        }

Posted: Sun May 02, 2010 4:50 pm
by vitek
Your new code has nearly the same problem as the old code. If you want to see the lines, you have to draw them between the beginScene() and endScene() calls.

As you currently have it, the file myfile will be read from start to end on the first pass through the main loop. On every pass after that, the file will already be at eof and your inner loop will be skipped.

Looking at your code, I have some suggestions.

You have code to read two integers from a file. So you read two strings (separated by whitespace) from the file, convert those strings into streams, and then read integers from the streams. It would be much simpler and cheaper to read the integers from the file directly.

Another issue is that you are attempting to parse the file on every pass through the main loop. Parsing is expensive. I suggest that, unless you expect the file to change while the program is running, you parse the file once, and store the result in a collection of some type.

Since you're setting the world transform to the identity matrix, you need to draw your lines in world coordinates. If any of your scene nodes have a parent other than the root node, you need to use the absolute transform of the node to get the actual position, otherwise the lines won't appear in the right places.

One final suggestion would be to avoid setting the material and world transform to the same thing over and over again. Just set it once and use it.

In the end, you should get something like this...

Code: Select all

struct pair_of_id
{
  int id1, id2;
};

core::array<pair_of_id> ids;

// parse id list into array before our loop below
std::ifstream myfile("my_file.txt");
while (!myfile.eof())
{
  // simple code to parse pairs of integers from a file
  pair_of_id pid;
  myfile >> pid.id1 >> pid.id2;

  ids.push_back(pid);
}

// then in your main loop
while (device->run())
{
  driver->beginSecne(...);

  // set the material once
  video::SMaterial mat;
  mat.Lighting = false;
  driver->setMaterial(mat);

  // set the world transform once
  core::matrix4 identity;
  driver->setTransform(video::ETS_WORLD, identity);

  for (int i = 0; i < ids.size(); ++i)
  {
    // draw the lines in the correct coordinate system
    driver->draw3DLine( 
      smgr->getSceneNodeFromId(ids[i].id1)->getAbsolutePosition(), 
      smgr->getSceneNodeFromId(ids[i].id2)->getAbsolutePosition()); 
  }

  driver->endScene();
}
Travis

Posted: Sun May 02, 2010 10:53 pm
by so1odo1o
Thank you, that helped a lot. The only thing is that the file myfile that I read from does not only include the two integer ids to connect. It contains the number of nodes to draw, the id of each node and its coordinates, and which node ids to connect. For example:

4
1 2.0 3.0 4.0
2 2.0 3.0 8.0
3 5.0 2.0 1.0
4 9.2 7.8 4.1
1 2
1 3
4 3

I have just been using getline to go through each line of the file.

Reading first line, number of nodes:

Code: Select all

	// Read in number of nodes
	string input;
	getline(myfile, input);
	int num;
	istringstream str2int(input);
	str2int >> num;
Reading nodes to draw (GraphNode contains id and coordinates of node):

Code: Select all

// Create objects of all nodes in vector, to keep track
	vector<GraphNode> nodes;
	for(int i = 0; i < num; i++)
	{
		string sn, sx, sy, sz;
		getline(myfile, sn, ' ');
		getline(myfile, sx, ' ');
		getline(myfile, sy, ' ');
		getline(myfile, sz);

		// Node name
		int n = 0;
		// Node coordinates
		float x, y, z;
		istringstream str2int(sn);
		istringstream str2float1(sx);
		istringstream str2float2(sy);
		istringstream str2float3(sz);
		str2int >> n;
		str2float1 >> x;
		str2float2 >> y;
		str2float3 >> z;

		// Push node into vector
		nodes.push_back(GraphNode(n, x, y, z));
	}
And for the relationships to draw 3D lines:

Code: Select all

// Create relationships by searching through vector
	struct pair_of_id
	{
		int id1, id2;
	};

	core::array<pair_of_id> ids;

	while(!myfile.eof())
	{
		pair_of_id pid;
		string p1, p2;
		getline(myfile, p1, ' ');
		getline(myfile, p2);
		istringstream str2int1(p1);
		istringstream str2int2(p2);
		str2int1 >> pid.id1;
		str2int2 >> pid.id2;
		ids.push_back(pid);
	}
I am fairly new to C++ so I chose the first thing I could find which was istringstream. If you could post a more efficient way or another library to look at, that would be great. Thanks.

Posted: Mon May 03, 2010 6:33 am
by vitek

Code: Select all

// Read in number of nodes 
string input; 
getline(myfile, input); 
int num; 
istringstream str2int(input); 
str2int >> num;
You do not need to parse each space delimited word into a string so you can parse it into a number. The C++ iostreams can do all of the parsing for you. The above code can be reduced down to...

Code: Select all

int num;
myfile >> num;
Same thing for this code...

Code: Select all

vector<GraphNode> nodes; 
for(int i = 0; i < num; i++) 
{ 
   string sn, sx, sy, sz; 
   getline(myfile, sn, ' '); 
   getline(myfile, sx, ' '); 
   getline(myfile, sy, ' '); 
   getline(myfile, sz); 

   // Node name 
   int n = 0; 
   // Node coordinates 
   float x, y, z; 
   istringstream str2int(sn); 
   istringstream str2float1(sx); 
   istringstream str2float2(sy); 
   istringstream str2float3(sz); 
   str2int >> n; 
   str2float1 >> x; 
   str2float2 >> y; 
   str2float3 >> z; 

   // Push node into vector 
   nodes.push_back(GraphNode(n, x, y, z)); 
} 
This could probably be written as...

Code: Select all

vector<GraphNode> nodes; 
nodes.reserve (num);

for (int i = 0; i < num; ++i)
{
  GraphNode node;
  myfile >> node.id
         >> node.x >> node.y >> node.z;
  nodes.push_back(node);
}
Travis

Posted: Mon May 03, 2010 9:50 am
by bonsalty
Its always better using customscene nodes, in this case a container class for 3D lines. The good thing with custom scene nodes, you dont have to call it every time when you render it.

Posted: Mon May 17, 2010 5:01 pm
by Ulf
If you need to set the material before calling draw3DLine to give it a color, then what does the color parameter do?
Because it doesn't seem to make any difference. I seem to need to set a new material each time to change color of line drawing.

Posted: Mon May 17, 2010 8:15 pm
by hybrid
If you have lighting enabled this might be the case. Otherwise the vertex color will take over, which is set by the parameter.

Posted: Mon May 17, 2010 9:14 pm
by Ulf
Thank you. I turned lighting off in the set material. Works fine.