Jump to content
  • entries
    944
  • comments
    5,899
  • views
    929,448

Model Tools


Josh

5,241 views

 Share

I've never coded much polygonal modeling routines, instead focusing on constructive solid geometry. I wanted to include some tools for modifying surface normals and texture coordinates. I came up with a normal calculation routine that actually uses four different algorithms, depending on the settings specified.

 

One thing I learned right away is you want to do away with n*n routines. That is, NEVER do this:

for (i=0; i<surface->CountVertices(); i++)
{
for (n=0; n<surface->CountVertices(); n++)
{
	//Write some code here
}
}

Instead, I used an std::map with a custom compare function. The comparison function below, when used together with an std::map, allows the engine to quickly find a vertex at any position, within a given tolerance:

	bool SurfaceReference::UpdateNormalsCompare(Vec3 v0, Vec3 v1)
{
	if (v0.DistanceToPoint(v1)<UpdateNormalsLinearTolerance) return false;
	return v0<v1;
}

At first I was experiencing some weird results where some vertices seemed to be ignored:

blogentry-1-0-64011300-1325822642_thumb.jpg

 

I realized the problem was that my map, which used Vec3 objects for the key, were not sorting properly. Here was my original Vec3 compare function:

bool Vec3::operator<(const Vec3 v)
{
if (x<v.x) return true;
if (y<v.y) return true;
if (z<v.z) return true;
return false;
}

The above function is supposed to result in any set of Vec3 objects being sorted in order. Can you see what's wrong with it? huh.gif It's supposed to first sort Vec3s by the X component, then the Y, then the Z. Consider the following set of Vec3s:

A = Vec3(1,2,3)

B = Vec3(2,4,3)

C = Vec3(2,1,1)

 

When sorted, these three Vec3s should be in the following order:

A,C,B

 

If you look carefully at the compare function above, it doesn't give consistent results. For example, A would be less than C, but C would also be less than A.

 

Here's the correct compare function. Notice I added a second logical operation for each element:

bool Vec3::operator<(const Vec3 v)
{
if (x<v.x) return true;
if (x>v.x) return false;
if (y<v.y) return true;
if (y>v.y) return false;
if (z<v.z) return true;
return false;
}

So with that issue sorted out, the resulting code using std::maps is much, MUCH faster, although it can get pretty difficult to visualize. I think I am a hardcore C++ coder now!: biggrin.gif

	void SurfaceReference::Optimize(const float& tolerance)
{
	int i,a,b,c,v;
	Vertex vertex;
	bool(*fn_pt)(Vertex,Vertex) = OptimizeCompare;
	std::map<Vertex,std::vector<Vertex>,bool(*)(Vertex,Vertex)> vertexmap (fn_pt);
	std::map<Vertex,std::vector<Vertex>,bool(*)(Vertex,Vertex)>::iterator it;
	int vertexcount = 0;
	std::vector<Vertex> vertexarray;
	Vec3 normal;

	OptimizeTolerance = tolerance;

	//Divide the surface up into clusters and remap polygon indices
	for (i=0; i<CountIndices(); i++)
	{
		v = GetIndiceVertex(i);
		vertex = Vertex(GetVertexPosition(v),GetVertexNormal(v),GetVertexTexCoords(v,0),GetVertexTexCoords(v,1),GetVertexColor(v));
		if (vertexmap.find(vertex)==vertexmap.end())
		{
			vertex.index = vertexcount;
			vertexcount++;
		}
		vertexmap[vertex].push_back(vertex);
		SetIndiceVertex(i,vertexmap[vertex][0].index);
	}

	//Resize vector to number of vertices
	vertexarray.resize(vertexcount);

	//Average all vertices within each cluster
	for (it=vertexmap.begin(); it!=vertexmap.end(); it++)
	{
		std::vector<Vertex> vector = (*it).second;

		//Reset vertex to zero
		vertex.position = Vec3(0);
		vertex.normal = Vec3(0);
		vertex.texcoords[0] = Vec2(0);
		vertex.texcoords[1] = Vec2(0);
		vertex.color = Vec4(0);

		//Get the average vertex
		for (i=0; i<vector.size(); i++)
		{
			vertex.position += vector[i].position;
			vertex.normal += vector[i].normal;
			vertex.texcoords[0].x += vector[i].texcoords[0].x;
			vertex.texcoords[0].y += vector[i].texcoords[0].y;
			vertex.texcoords[1].x += vector[i].texcoords[1].x;
			vertex.texcoords[1].y += vector[i].texcoords[1].y;
			vertex.color += vector[i].color;
		}
		vertex.position /= vector.size();
		vertex.normal /= vector.size();
		vertex.texcoords[0].x /= vector.size();
		vertex.texcoords[1].x /= vector.size();
		vertex.texcoords[0].y /= vector.size();
		vertex.texcoords[1].y /= vector.size();
		vertex.color /= vector.size();

		//Add to vector
		vertexarray[vector[0].index] = vertex;
	}

	//Clear vertex arrays
	delete positionarray;
	delete normalarray;
	delete texcoordsarray[0];
	delete texcoordsarray[1];
	delete colorarray;
	delete binormalarray;
	delete tangentarray;
	positionarray = NULL;
	normalarray = NULL;
	texcoordsarray[0] = NULL;
	texcoordsarray[1] = NULL;
	colorarray = NULL;
	binormalarray = NULL;
	tangentarray = NULL;

	//Add new vertices into surface
	for (i=0; i<vertexarray.size(); i++)
	{
		vertex = vertexarray[i];
		AddVertex(
			vertex.position.x, vertex.position.y, vertex.position.z,
			vertex.normal.x, vertex.normal.y, vertex.normal.z,
			vertex.texcoords[0].x, vertex.texcoords[0].y,
			vertex.texcoords[1].x, vertex.texcoords[1].y,
			vertex.color.x, vertex.color.y, vertex.color.z, vertex.color.w
		);
	}

	UpdateTangentsAndBinormals();
}

Below, you can see what happens when you use the angular threshhold method, with angular tolerance set to zero:

blogentry-1-0-11438900-1325821803_thumb.jpg

 

And here it is with a more reasonable tolerance of 30 degrees:

blogentry-1-0-14600400-1325821808_thumb.jpg

 

You can calculate texture coordinates for a model using box, plane, cylinder, and sphere texture mapping. You can also do a pure matrix transformation on the texcoords. The editor automatically calculates the bounds of the object and uses those by default, but you can translate, scale, and rotate the texture mapping shape to adjust the coordinates. Box and plane mapping were easy to figure out. Sphere and cylinder mapping were more difficult to visualize. I first cracked cylinder mapping when I realized the x component of the normalized vertex position could be used for the U texture coordinate, and then sphere mapping was just like that for both X/U and Y/V:

blogentry-1-0-50071700-1325821797_thumb.jpg

 

Box mapping is good for mechanical stuff and buildings, but bad for organic shapes, as you can see from the visible seam that is created here. Good thing we have four more modes to choose from!:

blogentry-1-0-06459000-1325821815_thumb.jpg

 

You also get lots of powerful commands in the surface class. Here's a little taste of the header file:

virtual void Optimize(const float& tolerance=0.01);
virtual void UpdateTexCoords(const int& mode, const Mat4& mat=Mat4(), const float& tilex=1, const float& tiley=1, const int& texcoordset=0);
virtual void Transform(const Mat4& mat);
virtual void Unweld();
virtual void Facet();
virtual void UpdateNormals(const int& mode, const float& distancetolerance=0.01, const float& angulartolerance=180.0);

To conclude, here's some other random and funny images I came up with while developing these features. I think they are beautiful in their own flawed way:

blogentry-1-0-58625800-1325822648_thumb.jpg

 

blogentry-1-0-06449800-1325822637_thumb.jpg

 Share

8 Comments


Recommended Comments

Looks good. I recommend to apply a generic square box mapping as default.

 

Like a box with 1m x 1m x 1m as a default. in 80% of all hard-surface models this is a good start for mapping an generic props.

I even have a script in 3dmax which applies me such a box with a maxscript because its so often used and looking okay.

For further tweaking/tiling the tile functionality would be used.

Link to comment

Yep, that's the advantage of indexing and what made database technology possible. With smaller quantities the cost of a sequential n*n search would not be significant but the cost rises substantially as the numbers grow.

Link to comment

Handy if you're making simple editors or programmer art.

 

From a practical POV, of more interest is taking something already mapped and built by an artist or 3rd party store, then applying autmated animation effects, UV scrolling. I'm sure CSG might be handy and no reason not to have more toys.

 

Just my 0.002 euros.

Link to comment

Yes, i think it's specially designed for CSG inside LE3D world Editor !

"box" mapping is not the only way, you have Atlas mapping also and others ...

Link to comment

Handy if you're making simple editors or programmer art.From a practical POV, of more interest is taking something already mapped and built by an artist or 3rd party store, then applying autmated animation effects, UV scrolling. I'm sure CSG might be handy and no reason not to have more toys.Just my 0.002 euros.

For real-time effects, I would modify the material texture matrices.

Link to comment

Ah yes, those commands you added to allow easy modification of Texture matricies ;)

 

I really liked (and made good use of) the material animation parameters you could add for OGRE3D materials. Waterfalls, wormholes, engine effects were pretty much fire and forget after making the material with those.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...