Page 1 of 1

Making (& filling) a circle

Posted: Sun Jul 18, 2004 12:44 pm
by MarauderIIC
Here's some code to draw a rough circle out from a specified point that I needed to make the other day if anyone happens to be interested. It's broken into two funcs, one that draws a line between two points and one that handles the actual circle making. I'll probably fix it up a bit, if I do, I'll edit the post. But at the moment it works pretty well as it is.

Should note that what it actually modifies is a two-dimensional vector (the C++ version of a two-dimensional array). But the points are still there, since the vector is 640x480. Should be pretty easy to change to whatever you need.

Edit: Added a few comments.
Edit: Made the angle things const static
Edit: Optimized the line drawing a little.
Edit: And circle a bit.
Edit: See post below for filling it.

Code: Select all

#include <cmath>
#include <vector>
//.....
/***********************************
* Changes terrain between points   *
* (xone, yone) and (xtwo, ytwo) to *
* be type.                         *
***********************************/
//http://ironbark.bendigo.latrobe.edu.au/courses/gdc/bitgrp/srgpdem/line2.c++
void World::changeTerrainLine(int xone, int yone, int xtwo, int ytwo, int type) {
	
	int rise = (yone-ytwo);
	int run = (xone-xtwo);
	int steps;					//How many steps to this line?
	double xInc, yInc, x, y;	//How much to increment, and current coordinates.

	if (rise == 0 && run == 0) {	//It must be a single point.
		landRes.at(yone).at(xone) = type;
		return;
	}

	//Use whichever is greater for the number of steps in this line.
	if (abs(run) > abs(rise))
		steps = abs(run);
	else
		steps = abs(rise);

	xInc = (double)run/(double)steps;
	yInc = (double)rise/(double)steps;

	x = xtwo;	//Gotta start somewhere.
	y = ytwo;

	int ix, iy;	//Integer x, integer y. Because my vector can only take integers as coordinates.
			 //If you can use float for your coordinates, do so.

	//Define the bounds
	const unsigned int maxY = landRes.size();
	const unsigned int maxX = landRes.at(0).size();

	for (unsigned int i = 0;i < steps;i++) {
		x += xInc;
		y += yInc;
		ix = (int)(x + 0.5);	//Round
		iy = (int)(y + 0.5);	//Round

		if (iy >= maxY)
			iy = maxY-1;
		else if (iy < 0)
			iy = 0;

		if (ix >= maxX)
			ix = maxX-1;
		else if (ix < 0)
			ix = 0;

		//I do the error checking so I don't need .at().
		landRes[iy][ix] = type;
	}
}

/*****************************************
* At the moment, just changes terrain in *
* an unfilled circle radius pixels from  *
* center (x, y) to be type.              *
*****************************************/
//divisions is unused as of yet.
void World::changeTerrainCircle(int x, int y, int radius, int type, int divisions) {

	//(x-x1)^2 + (y-y1)^2 = r^2
	//we know that x1,y1+radius and x1+radius,y1 are on the circle

	//Define max bounds
	const unsigned int maxX = landRes.at(0).size();
	const unsigned int maxY = landRes.size();

	if (x >= maxX)
		x = maxX-1;

	if (y >= maxY)
		y = maxY-1;

	//Eightfold circular symmetry
	//http://ironbark.bendigo.latrobe.edu.au/courses/gdc/bitgrp/lect/lect03/lect03.html
	int xOneEighty = x-radius;	//180 degrees (left from center)
	int yTwoSeventy = y-radius;	//270 degrees (down from center)
	int xZero = x+radius;	//0 degrees (right from center)
	int yNinety = y+radius;	//90 degrees (up from center)


 	const static int xThirty		= radius*cos(30*PI/180);	//"Out" distance for thirty degrees.
 	const static int yThirty		= radius*sin(30*PI/180);	//"Up" distance for thirty degrees.
 	const static int xFortyFive	= radius*cos(45*PI/180);	//"Out" distance for forty-five degrees.
 	const static int yFortyFive	= radius*sin(45*PI/180);	//"Up" distance for forty-five degrees.
 	const static int xSixty		= radius*cos(60*PI/180);	//"Out" distance for sixty degrees.
 	const static int ySixty		= radius*sin(60*PI/180);	//"Up" distance for sixty degrees.

	//Make sure it isn't outside the range of my screen vector.
	if (xOneEighty < 0)
		xOneEighty = 0;

	if (yTwoSeventy < 0)
		yTwoSeventy = 0;

	if (xZero >= maxX)
		xZero = maxX-1;

	if (yNinety >= maxY)
		yNinety = maxY-1;

	//use logic, determine if you want to go left or right of the origin
	//remember sixty goes very little x but lots of y and
	//thirty goes lots of x but very little y.
/*
those of you on forum who don't know trigonometry or what im talking about here: draw a circle on a coordinate plane (graph paper :P) draw a line straight up and down through the middle of circle and one left and right through middle of circle (or use lines already there if youre on graph paper).  then get a protractor and draw a line from the center to sixty degrees. make a point where the line goes through the rim of the circle. draw a line straight down to the x-axis. you'll note that you don't go over very far, but you do go pretty high up. thirty degrees is the opposite, whereas forty-five degrees is a line with a 1/1 slope.
*/

	//MARFIX: Generalize this?
	changeTerrainLine(xZero, y, x+xThirty, y+yThirty, type);
	changeTerrainLine(x+xThirty, y+yThirty, x+xFortyFive, y+yFortyFive, type);
	changeTerrainLine(x+xFortyFive, y+yFortyFive, x+xSixty, y+ySixty, type);
	changeTerrainLine(x+xSixty, y+ySixty, x, yNinety, type);
	changeTerrainLine(x, yNinety, x-xSixty, y+ySixty, type);
	changeTerrainLine(x-xSixty, y+ySixty, x-xFortyFive, y+yFortyFive, type);
	changeTerrainLine(x-xFortyFive, y+yFortyFive, x-xThirty, y+yThirty, type);
	changeTerrainLine(x-xThirty, y+yThirty, xOneEighty, y, type);
	changeTerrainLine(xOneEighty, y, x-xThirty, y-yThirty, type);
	changeTerrainLine(x-xThirty, y-yThirty, x-xFortyFive, y-yFortyFive, type);
	changeTerrainLine(x-xFortyFive, y-yFortyFive, x-xSixty, y-ySixty, type);
	changeTerrainLine(x-xSixty, y-ySixty, x, yTwoSeventy, type);
	changeTerrainLine(x, yTwoSeventy, x+xSixty, y-ySixty, type);
	changeTerrainLine(x+xSixty, y-ySixty, x+xFortyFive, y-yFortyFive, type);
	changeTerrainLine(x+xFortyFive, y-yFortyFive, x+xThirty, y-yThirty, type);
	changeTerrainLine(x+xThirty, y-yThirty, xZero, y, type);

	//Copy points within the square BL(xOneEighty, yTwoSeventy), TR(xZero, yNinety)
	//to the 2d array that OpenGL needs to draw stuff with.
	//Yes, my function is written a little weird.
	generateTerrain(xOneEighty, xZero, yTwoSeventy, yNinety);
}
:)

Posted: Mon Jul 19, 2004 2:43 pm
by Guest
Uhhh.......?
.....
.....
Hail to Marauder's greatness! :worship:

Posted: Wed Jul 21, 2004 9:19 pm
by Peridox
....If i'm right, you put too much time into that. but, I dont have time to read over your code to see what it all consists of. I know theres a code where if you click it'll draw a circle from that point to another point. So on. and such. I forget what it is. But hey, I cant be certain since my dad is making me get off. ><

Posted: Wed Jul 21, 2004 10:46 pm
by MarauderIIC
I wrote it to modify stuff in my vector that stores my terrain. So it does more than draw it. It can be used with anything that stores coordinates with little modification.

Meanwhile, here's how to fill it. (Fill anything convex.)
Please note the existence of the QuickFill algorithm @ http://www.codeproject.com/gdi/QuickFill.asp which would be faster than this but it would require modification for me to use with my stuff :P

Code: Select all

void World::fillCircle(int left, int bottom, int right, int top, int withType, int stopType, bool overwrite) {

	bool inside;

	bool lastWasEmpty;
	bool currentIsEmpty;

	unsigned int yloop;
	unsigned int xloop;
	int temp;
	if (bottom > top) {
		temp = top;
		top = bottom;
		bottom = temp;
	}
	if (left > right) {
		temp = right;
		right = left;
		left = temp;
	}
	if (bottom < 0)
		bottom = 0;
	if (top >= worldHeight)
		top = worldHeight-1;
	if (left < 0)
		left = 0;
	if (right >= worldLength)
		right = worldLength-1;

	int height = top-bottom;
	int width = right-left;
	int y, x;
	int curType = 0;
	int lastType;

	for (yloop = 0;yloop <= height;++yloop) {

		inside = false;
		lastWasEmpty = currentIsEmpty = true;
		y = yloop+bottom;

		for (xloop = 0;xloop <= width;++xloop) {
			lastType = curType;
			x = xloop+left;
			curType = landRes.at(y).at(x);

			lastWasEmpty = currentIsEmpty;

			if (curType != stopType)
				currentIsEmpty = true;
			else
				currentIsEmpty = false;

			if (!inside) {
				if (!currentIsEmpty && overwrite) {
					landRes[y][x] = withType;
				//Last one was border but we're past that now.
				} else if (!lastWasEmpty && currentIsEmpty) {
					//Break if we expect the border on this row to be continuous, and
					//we aren't overwriting it, because if not everything to
					//then the right of the border will be drawn as if it were "inside."
					if (yloop == 0 || yloop == height) {
						debug("yloop == 0 or yloop == height.");
						break;
					}
					landRes[y][x] = withType;
					inside = true;
					continue;		//Don't count as inside quite yet.
				}
			}

			if (inside) {
				if (overwrite) {
					if (!currentIsEmpty) {
						landRes[y][x] = withType;
						continue;
					} else if (currentIsEmpty && !lastWasEmpty) {
						break;	//Done overwriting the border from inside->outside.
					}
				}

				//Have run into the border.
				if (!currentIsEmpty)
					break;
				landRes[y][x] = withType;
			}
		}
	}
}