Drawing 2D Volumetric Lines (Without Geometry Shaders)

Started by
3 comments, last by MarkS_ 8 years, 6 months ago

My game was originally written using OpenGL [ES], and thanks to libANGLE, I was able to port my game to Direct3D11 almost effortlessly. Now I have run into a small problem, there is no equivalent in D3D11 to glLineWidth (not even ANGLE supports this; plus the devs explicitly stated it will not be supported). So atm, the best I can do would be to draw a volume line using a triangle strip. The problem is, I'm having trouble wrapping my head around the math required to do it. As stated above, I'm intentionally not using geometry shaders. This is because my game has to support 9_3 for Windows Phone and Surface models that don't support anything higher. And yes, I have been googling this for quite some time now, still haven't quite found what I'm looking for. Any ideas? Thanks.

Shogun.

Advertisement

Even in OpenGL, glLineWidth is wonky and may give different results on different cards for widths other than 1.

I'm pretty poor at math, but this is what I do:


//Half the width of the desired line width.
float halfWidth = (lineWidth * 0.5f);

//Set the four corners.
positionCorners[0] = lineStart + cPointF(-halfWidth, 0.0f);
positionCorners[1] = lineStart + cPointF( halfWidth, 0.0f);
positionCorners[2] = lineEnd   + cPointF( halfWidth, 0.0f);
positionCorners[3] = lineEnd   + cPointF(-halfWidth, 0.0f);

//Rotate the positions so they are angled (we don't want a rhombas).
float angle = cPointF::AngleBetween(lineStart, lineEnd);
cPointF::RotateAround(lineStart, angle, {positionCorners[0], positionCorners[1]});
cPointF::RotateAround(lineEnd,   angle, {positionCorners[2], positionCorners[3]});


Code to RotateAround() and AngleBetween():
[spoiler]


//On a cartesian grid, centered around 'center', returns the angle (in degrees) between 12 oclock (+Y) and 'pos'.
static float AngleBetween(const Point &center, const Point &pos)
{
    //Calculate the angle.
    float radians = std::atan2(-(center.y - pos.y), -(center.x - pos.x));

    //Convert to degrees, and add 90 to make 0-degrees be 12-oclock..
    return RadiansToDegrees(radians) + 90;
}

//Rotate multiple points around a shared center.
static void RotateAround(const Point &center, float rotation, std::vector<Point> &points)
{
    //Calculate and apply the rotation.
    float radians = DegreesToRadians(rotation);
    float cosine = std::cos(radians);
    float sine = std::sin(radians);

    //Rotate each point individually (but using the same cosine and sine we've already calculated).
    for(auto &point : points)
    {
        priv_Point_RotateAround_RotatePoint(point, center, sine, cosine);
    }
}

static void priv_Point_RotateAround_RotatePoint(Point &point, const Point &center, float sine, float cosine)
{
    //Move the problem domain to make the center be (0,0), which is what we'll rotate around.
    float offsetX = static_cast<float>(point.x - center.x);
    float offsetY = static_cast<float>(point.y - center.y);

    //Calculate the new offset.
    point.x = static_cast<Point::value_type>((offsetX * cosine) - (offsetY * sine));
    point.y = static_cast<Point::value_type>((offsetX * sine)   + (offsetY * cosine));

    //Re-apply the center to the new offset and return.
    point += center;

    return;
}

[/spoiler]

Basically, first I calculate this:
40d5f6acf2.png

Then I rotate the points:
0429418e94.png

And draw it as two triangles:
beb9481b29.png

I also repeat the texture, so no matter how long the line is, the texture keeps repeating (but the texture gets stretched horizontally of the line's width is larger than the texture's width - it doesn't repeat horizontally, only vertically).

It gives me good results:

f8bcc410e3.png

That is coming from this texture: (zoomed up)
68201b82c8.png

one simple solution can be to simply draw several 1pixel lines. if your goal is 2d rendering, then you can exactly specify the start and ending pixel (which is like 2d line drawing by itself with orthogonal direction to your current line).

if your line is defined by x0|y0 - x1|y1, the direction is (x1-x0)|(y1-y0), the orthogonal direction is -(y1-y0)|(x1-x0)

you walk in that direction with a line algorithm e.g.

https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

for the start and for the end point and draw a line.

curver_curve_structure.png

Yay for posting pics. I wrote a small blog post quickly about curves in Curver, a program I'm working on currently. Unfortunately at the time I didn't have enough time to describe it in great detail, but maybe it will provide some insight. Click

Here is some old code I wrote using OpenGL. It is ugly, but it works and is fast.

class vertex{
public:
float x,y;
};

void DrawWideLine(vertex p1, vertex p2, float width,int end_cap,int num_segments)
{
	float	dx, dy;
	float	dist, w2;
	vertex	v1, v2, v3, v4;

	w2 = width / 2.0f;

	dx = p1.x - p2.x;
	dy = p1.y - p2.y;
	dist = sqrt(dx*dx + dy*dy);
	dx /= dist;
	dy /= dist;

	v1.x = p1.x + w2 * dy;
	v1.y = p1.y - w2 * dx;

	v2.x = p1.x - w2 * dy;
	v2.y = p1.y + w2 * dx;

	v3.x = p2.x + w2 * dy;
	v3.y = p2.y - w2 * dx;

	v4.x = p2.x - w2 * dy;
	v4.y = p2.y + w2 * dx;

	if (end_cap == 0) // No end cap. Do nothing
	{
	}else if (end_cap == 1){ // Square end cap
		v1.x += dx * w2;
		v1.y += dy * w2;

		v2.x += dx * w2;
		v2.y += dy * w2;

		v3.x -= dx * w2;
		v3.y -= dy * w2;

		v4.x -= dx * w2;
		v4.y -= dy * w2;
	}else if (end_cap == 2){ // Round end cap
		float	theta;
		float	tangetial_factor;
		float	radial_factor;
		float	angle;
		float	x, y;
		int	i;

		angle = atan2(p2.y - p1.y,p2.x - p1.x);

		theta = 3.14159265 / float(num_segments - 1);
		tangetial_factor = tanf(theta);
		radial_factor = cosf(theta);

		x = w2 * cosf(angle + 1.5707);
		y = w2 * sinf(angle + 1.5707);

		glBegin(GL_TRIANGLE_FAN);
			glColor3f(0.0, 0.0, 1.0); // Blue, 'cause I like blue...
			for (i = 0; i < num_segments; i++)
			{
				vertex	v = { x,y };

				float tx = -y;
				float ty = x;

				x += tx * tangetial_factor;
				y += ty * tangetial_factor;

				x *= radial_factor;
				y *= radial_factor;

				glVertex2f(p1.x, p1.y);
				glVertex2f(p1.x + v.x, p1.y + v.y);
				glVertex2f(p1.x + x, p1.y + y);

				glVertex2f(p2.x, p2.y);
				glVertex2f(p2.x - v.x, p2.y - v.y);
				glVertex2f(p2.x - x, p2.y - y);
			}
		glEnd();
	}

	glBegin(GL_TRIANGLES);
		glColor3f(0.0, 0.0, 1.0); // Blue, 'cause I like blue...

		glVertex2f(v1.x, v1.y);
		glVertex2f(v2.x, v2.y);
		glVertex2f(v3.x, v3.y);

		glVertex2f(v2.x, v2.y);
		glVertex2f(v3.x, v3.y);
		glVertex2f(v4.x, v4.y);
	glEnd();
}
Sorry for all of the edits. I've been having fun playing with this.

This topic is closed to new replies.

Advertisement