strange thing with cylinder algorithm - java

I want to render a cylinder in Opengl. For that i wrote an simple algorithm, that
generates me the points mesh by the parameters radius, height, xSubDivisions and ySubDivisions:
(Java)
for(int yDivision = 0; yDivision < yDivisionCount; yDivision++){
for(int xDivision = 0; xDivision < xDivisionCount; xDivision++){
float line[] = getVboLine(xDivision, yDivision, radius, height, xDivisionCount, yDivisionCount);
string.append(line[0] + ", " + line[1] + ", " + line[2] + ", " + line[3] + ", " + line[4] + ", ");
}
}
public float[] getVboLine(int xDivision, int yDivision, float radius, float height, int xDivisionCount, int yDivisionCount){
float xDegrees = 360.0f / xDivisionCount * xDivision;
float xRadian = (float) Math.toRadians(xDegrees);
float x = (float) Math.sin(xRadian) * radius;
float z = (float) Math.cos(xRadian) * radius;
float y = (float) yDivision * (height / (yDivisionCount - 1));
float s = xDegrees * (1.0f / 360.0f);
float t = yDivision * (1.0f / (yDivisionCount - 1));
return new float[]{
x, y, z, s, t
};
}
The result is actually an cylinder, (i created an IBO to render this points) but sometimes, with different inputs for x and yDivisions there is a strange gap in it.
I couldn't find a rule, but the values i found this bug with were 200, 100.
To debug i rendered only the points. The result was:
How is this possible? One points is just missing (where i added the reed circle with paint).
Where is the problem with my algorithm?

I am not JAVA coder but you are mixing int and float together
for example:
xDegrees = 360.0f / xDivisionCount * xDivision
[float] [float] [int] [int]
I would rather use this:
xDegrees = float(360*xDivision)/float(xDivisionCount)
multiplication should go always first (if operands are >= 1)
and division after that to preserve accuracy
some weird rounding could cause your problem but it would be more noticeable for lower xDivisionCount not bigger one
Bug breakpoint
add to your code last generated point
after new point computation compute the distance from last point
add if (|distance-some_avg_distance|>1e-10)
and add breakpoint inside
some_avg_distance set by distance that should be there (get it from trace)
this way you can breakpoint the point causing problems (or the next point to it)
so you can actually see what is wrong
my bet is that by rounding you get the same angle as prev/next point
and therefore you do not have missing point but some duplicate instead
you can check that also by Blending

Related

Floorcasting not scrolling in raycasting engine?

I'm currently working on a raycaster in Java, and so far, I have the floor correctly textured. The problem, however, is that the floor doesn't scroll. In other words, when I move the camera in the projection, the floor stays the same, yet the walls move as expected. I'm really not sure what I'm doing wrong. I took almost all the code from this reference. Note that I took some liberties when pasting the code in that I used some pseudocode.
I tried applying a player offset to the tileX and tileY variables, e.g., tileX += player.x, and all I got was a floor that scrolls far too quickly and incorrectly.
for every ray:
... // other stuff relating to the walls above here.
int start = (int)(wallY + wallHeight + 1);
double directionCos = cos(rad(ray.getAngle()));
double directionSin = sin(rad(ray.getAngle()));
int textureDim = 16;
for (int y = start; y < screenHeight; y++) {
double distance = screenHeight / (2.f * y - screenHeight);
distance /= cos(rad(player.getAngle()) - rad(ray.getAngle()));
// The source I grabbed the code from actually appends the player's x and y to the tileX and tileY variables, but this completely messes up the textures when I try to.
double tileX = distance * directionCos;
double tileY = distance * directionSin;
int textureX = Math.floorMod((int)(tileX * textureDim), textureDim);
int textureY = Math.floorMod((int)(tileY * textureDim), textureDim);
int rgb = floorTexture.getRGB(textureX, textureY);
projectionFloor.setRGB((int)wallX, y, rgb);
}
Below is an image of the floor.
Below is an animation visualizing the problem.
Below is an animation visualizing what happens if I try to apply a player position offset:
Fixed it on my own. Turns out that, yes, you do have to account for the player's position (shocker!); the source I got the code from just didn't do it correctly.
DTPP = distance to projection plane.
for every pixel y from wallY + wallHeight + 1 to projectionHeight:
double r = y - this.getPreferredSize().height / 2.f;
double d = (CAMERA_HEIGHT * DTPP / r) / ANGLE;
double tileX = CAMERA_X + d * RAY_COSANGLE;
double tileY = CAMERA_Y + d * RAY_SINANGLE;
int textureX = Math.floorMod((int) (tileX * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
int textureY = Math.floorMod((int) (tileY * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
... (drawing occurs here)

Bilinear interpolation anomaly

I wrote a function that takes the subpixels of an image for the purpose of upscaling, and the subpixel is generated by bilinear interpolation, but I am having some weird artifacts.
Here is my code:
public static int getSubPixel(BufferedImage bi, double x, double y) {
float[] topleft = new Color(bi.getRGB((int) Math.floor(x), (int) Math.floor(y))).getColorComponents(null);
float[] topright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), (int) Math.floor(y))).getColorComponents(null);
float[] bottomleft = new Color(bi.getRGB((int) Math.floor(x), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
float[] bottomright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
for (int i = 0; i < 3; i++) {
topleft[i] *= topleft[i];
topright[i] *= topright[i];
bottomleft[i] *= bottomleft[i];
bottomright[i] *= bottomright[i];
}
double decX = x % 1;
double decY = y % 1;
double inv_DecX = 1 - decX;
double inv_DecY = 1 - decY;
float red = (float) Math.sqrt((topleft[0] * inv_DecX + topright[0] * decX) * inv_DecY + (bottomleft[0] * inv_DecX + bottomright[0] * decX) * decY);
float green = (float) Math.sqrt((topleft[1] * inv_DecX + topright[1] * decX) * inv_DecY + (bottomleft[1] * inv_DecX + bottomright[1] * decX) * decY);
float blue = (float) Math.sqrt((topleft[2] * inv_DecX + topright[2] * decX) * inv_DecY + (bottomleft[2] * inv_DecX + bottomright[2] * decX) * decY);
return new Color(red, green, blue).getRGB();
}
This is the result of scaling up a 16x16 image 20 times:
As you can see, there is weird streaking going on. I did go out of my way to square the colors before averaging, then taking the square root of the result, but something does not seem right here. Any insight?
PS: I understand functions already exist to do this. This is an educational exercise. I am trying to understand the process by doing it on my own.
The stripe artifacts that you are seeing are caused by the linear interpolation scheme. Your implementation is correct (except for the squaring, which is unnecessary and causes the stripes to be stronger in darker regions of the image). This is what I'm seeing with a correct linear interpolation (16x instead of 20x as in the OP, I goofed) but without squaring (note less stripes in the dark blue parts):
If you want to get rid of the stripes, use a better interpolation scheme, such as cubic spline interpolation:

Draw an arc in opengl GL10

I want to draw an arc using center point,starting point,ending point on opengl surfaceview.I have tried this given below code so far. This function draws the expected arc if we give the value for start_line_angle and end_line_angle manually (like start_line_angle=0 and end_line_angle=90) in degree.
But I need to draw an arc with the given co-ordinates(center point,starting point,ending point) and calculating the start_line_angle and end_line_angle programatically.
This given function draws an arc with the given parameters but not giving the desire result. I've wasted my 2 days for this. Thanks in advance.
private void drawArc(GL10 gl, float radius, float cx, float cy, float start_point_x, float start_point_y, float end_point_x, float end_point_y) {
gl.glLineWidth(1);
int start_line_angle;
double sLine = Math.toDegrees(Math.atan((cy - start_point_y) / (cx - start_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line first
double eLine = Math.toDegrees(Math.atan((cy - end_point_y) / (cx - end_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line second
//cast from double to int after round
int start_line_Slope = (int) (sLine + 0.5);
/**
* mapping the tiriogonometric angle system to glsurfaceview angle system
* since angle system in trigonometric system starts in anti clockwise
* but in opengl glsurfaceview angle system starts in clock wise and the starting angle is 90 degree of general trigonometric angle system
**/
if (start_line_Slope <= 90) {
start_line_angle = 90 - start_line_Slope;
} else {
start_line_angle = 360 - start_line_Slope + 90;
}
// int start_line_angle = 270;
// int end_line_angle = 36;
//casting from double to int
int end_line_angle = (int) (eLine + 0.5);
if (start_line_angle > end_line_angle) {
start_line_angle = start_line_angle - 360;
}
int nCount = 0;
float[] stVertexArray = new float[2 * (end_line_angle - start_line_angle)];
float[] newStVertextArray;
FloatBuffer sampleBuffer;
// stVertexArray[0] = cx;
// stVertexArray[1] = cy;
for (int nR = start_line_angle; nR < end_line_angle; nR++) {
float fX = (float) (cx + radius * Math.sin((float) nR * (1 * (Math.PI / 180))));
float fY = (float) (cy + radius * Math.cos((float) nR * (1 * (Math.PI / 180))));
stVertexArray[nCount * 2] = fX;
stVertexArray[nCount * 2 + 1] = fY;
nCount++;
}
//taking making the stVertextArray's data in reverse order
reverseArray = new float[stVertexArray.length];//-2 so that no repeatation occurs of first value and end value
int count = 0;
for (int i = (stVertexArray.length) / 2; i > 0; i--) {
reverseArray[count] = stVertexArray[(i - 1) * 2 + 0];
count++;
reverseArray[count] = stVertexArray[(i - 1) * 2 + 1];
count++;
}
//reseting the counter to initial value
count = 0;
int finalArraySize = stVertexArray.length + reverseArray.length;
newStVertextArray = new float[finalArraySize];
/**Now adding all the values to the single newStVertextArray to draw an arc**/
//adding stVertextArray to newStVertextArray
for (float d : stVertexArray) {
newStVertextArray[count++] = d;
}
//adding reverseArray to newStVertextArray
for (float d : reverseArray) {
newStVertextArray[count++] = d;
}
Log.d("stArray", stVertexArray.length + "");
Log.d("reverseArray", reverseArray.length + "");
Log.d("newStArray", newStVertextArray.length + "");
ByteBuffer bBuff = ByteBuffer.allocateDirect(newStVertextArray.length * 4);
bBuff.order(ByteOrder.nativeOrder());
sampleBuffer = bBuff.asFloatBuffer();
sampleBuffer.put(newStVertextArray);
sampleBuffer.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, sampleBuffer);
gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, nCount * 2);
gl.glLineWidth(1);
}
To begin with the trigonometry you may not simply use the atan to find degrees of the angle. You need to check what quadrant the vector is in and increase or decrease the result you get from atan. Better yet use atan2 which should include both dx and dy and do the job for you.
You seem to create the buffer so that a point is created per degree. This is not the best solution as for large radius that might be too small and for small radius this is way too much. Tessellation should include the radius as well such that number of points N is N = abs((int)(deltaAngle*radius*tessellationFactor)) then use angleFragment = deltaAngle/N but make sure that N is greater then 0 (N = N?N:1). The buffer size is then 2*(N+1) of floats and the iteration if for(int i=0; i<=N; i++) angle = startAngle + angleFragment*i;.
As already pointed out you need to define the radius of the arc. It is quite normal to use an outside source the way you do and simply force it to that value but use the 3 points for center and the two borders. Some other options that usually make sense are:
getting the radius from the start line
getting the radius from the shorter of the two lines
getting the average of the two
interpolate the two to get an elliptic curve (explained below)
To interpolate the radius you need to get the two radiuses startRadius and endRadius. Then you need to find the overall radius which was already used as deltaAngle above (watch out when computing this one, it is more complicated as it seems, for instance drawing from 320 degrees to 10 degrees results in deltaAngle = 50). Anyway the radius for a specific point is then simply radius = startRadius + (endRadius-startRadius)*abs((angleFragment*i)/deltaAngle). This represents a simple linear interpolation in polar coordinate system which is usually used to interpolate vector in matrices and is the core functionality to get nice animations.
There are some other ways of getting the arc points which may be better performance wise but I would not suggest them unless and until you need to optimize your code which should be very late in production. You may simply keep stepping toward the next point and correcting the radius (this is only a concept):
vec2 start, end, center; // input values
float radius; // input value
// making the start and end relative to center
start -= center;
end -= center;
vec2 current = start/length(start) * radius; // current position starts in first vector
vec2 target = end/length(end) * radius; // should be the last point
outputBuffer[0] = current+center; // insert the first point
for(int i=1;; i++) { // "break" will need to exit the loop, we need index only for the buffer
vec2 step = vec2(current.y, -(current.x)); // a tangential vector from current start point according to center
step = step/length(step) / tessellationScale; // normalize and apply tessellation
vec2 next = current + step; // move tangentially
next = next/length(next) * radius; // normalize and set the
if(dot(current-target, next-target) > .0) { // when we passed the target vector
current = next; // set the current point
outputBuffer[i] = current+center; // insert into buffer
}
else {
current = target; // simply use the target now
outputBuffer[i] = current+center; // insert into buffer
break; // exit
}
}

Smooth Terrain Collision - 3D

I would like to have smooth terrain collision in my game engine, when i say smooth I mean the player's height isn't determined by one vertex. I belive barycentric coordinates are the way to go. And I've spent a good 7 hours researching this, but none of the code I've seen actually works and it doesn't explain it in plain-english either.
This is all I have so far. :(
public float getHeightAt(float xPos, float zPos) {
Vector3f one = new Vector3f((float)xPos, ((float)new Color(heightMap.getRGB((int)xPos, (int)zPos)).getRed())/255f*exaggeration*scale, (float)zPos);
Vector3f two = new Vector3f((float)xPos+1, ((float)new Color(heightMap.getRGB((int)xPos+1, (int)zPos)).getRed())/255f*exaggeration*scale, (float)zPos);
Vector3f three = new Vector3f((float)xPos, ((float)new Color(heightMap.getRGB((int)xPos, (int)zPos+1)).getRed())/255f*exaggeration*scale, (float)zPos+1);
float height = mid(one, two, three, new Vector3f(xPos, 0f, zPos));
System.out.println(height);
return height + 0.25f;
}
private float mid(Vector3f a, Vector3f b, Vector3f c, Vector3f p) {
Vector3f AB = a.mul(b);
Vector3f BC = b.mul(c);
Vector3f norm = AB.cross(BC);
float n0 = norm.getX();
float n1 = norm.getY();
float n2 = norm.getZ();
return (n0*a.getX() + n1*a.getY() + n2*a.getZ() - n0*p.getX() - n2*p.getZ()) / n1;
}
It works but it isn't smooth and I don't even know ifit is barycentric.
Here is an example of what I want: https://www.youtube.com/watch?v=ngJ6ISfXG3I
To get the smoothed height, there are two main steps:
I - Create a function to get the height from position
Create the function public float getHeightAt(float xPos, float zPos) following these instructions:
Check if the camera/player is inside the ground square
if(xPos > 0 && xPos < nbVerticesX && zPos > 0 && zPos < nbVerticesZ)
Get the point P nearest xPos and zPos
Get the normal N or compute it
Compute constant d of the plane equation
double d = -(P.x * N.x + P.y * N.y + P.z * N.z);
Return compute height
return -(d + N.z * zPos + N.x * xPos)/N.y;
II - Compute approximate height
Use this function to get the smoothed height:
public float getHeightApprox(float x, float z)
{
return ( (getHeightAt(x,z)
+ getHeightAt(x + 1, z)
+ getHeightAt(x - 1, z)
+ getHeightAt(x, z + 1)
+ getHeightAt(x, z - 1)) / 5);
}
Maybe you will have to adapt your code, but these pieces of code works fine for me. Hope this would help you.
Position and slope
Player position can be determined by one point. The case here is to create a relatively smooth function from the distinct values on the height map.
Interpolation should do the trick. It will in the simplest case provide a slope on the whole heightmap.
Bi-linear interpolation (quad)
At any point in time the palyer position in in some rectangle (quad) of the heightmap. We can evaluate the height in any point of this rectangle by doing bi-linear interpolation.
We do this for one axis on both edges and then on the second axis for the remaining edge.
^
| A--------B
| | |
| | P |
| | |
| C--------D
Y
*X------------>
// This could be different depending on how you get points
// (basically generates a [0, 1] value depending on the position in quad;
px = P.x - (int)P.x
py = P.y - (int)P.y
AB = A.h * (1.0 - px) + B.h * px;
CD = C.h * (1.0 - px) + D.h * px;
ABCD = AB * (1.0 - py) + CD * py;
ABCD is the resulting height
Considerations
This method is not perfect and might produce visual glitches depending on how you actually draw the quad in your rendering pipeline.
Also keep in mind that this works best if quads are bigger than your actual moving actor. In case when actor simultaneously is standing on several tiles a some kind averaged method should be used.

How to draw a smooth line through a set of points using Bezier curves?

I need to draw a smooth line through a set of vertices. The set of vertices is compiled by a user dragging their finger across a touch screen, the set tends to be fairly large and the distance between the vertices is fairly small. However, if I simply connect each vertex with a straight line, the result is very rough (not-smooth).
I found solutions to this which use spline interpolation (and/or other things I don't understand) to smooth the line by adding a bunch of additional vertices. These work nicely, but because the list of vertices is already fairly large, increasing it by 10x or so has significant performance implications.
It seems like the smoothing should be accomplishable by using Bezier curves without adding additional vertices.
Below is some code based on the solution here:
http://www.antigrain.com/research/bezier_interpolation/
It works well when the distance between the vertices is large, but doesn't work very well when the vertices are close together.
Any suggestions for a better way to draw a smooth curve through a large set of vertices, without adding additional vertices?
Vector<PointF> gesture;
protected void onDraw(Canvas canvas)
{
if(gesture.size() > 4 )
{
Path gesturePath = new Path();
gesturePath.moveTo(gesture.get(0).x, gesture.get(0).y);
gesturePath.lineTo(gesture.get(1).x, gesture.get(1).y);
for (int i = 2; i < gesture.size() - 1; i++)
{
float[] ctrl = getControlPoint(gesture.get(i), gesture.get(i - 1), gesture.get(i), gesture.get(i + 1));
gesturePath.cubicTo(ctrl[0], ctrl[1], ctrl[2], ctrl[3], gesture.get(i).x, gesture.get(i).y);
}
gesturePath.lineTo(gesture.get(gesture.size() - 1).x, gesture.get(gesture.size() - 1).y);
canvas.drawPath(gesturePath, mPaint);
}
}
}
private float[] getControlPoint(PointF p0, PointF p1, PointF p2, PointF p3)
{
float x0 = p0.x;
float x1 = p1.x;
float x2 = p2.x;
float x3 = p3.x;
float y0 = p0.y;
float y1 = p1.y;
float y2 = p2.y;
float y3 = p3.y;
double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0;
double len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3);
double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1;
double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2;
// Resulting control points. Here smooth_value is mentioned
// above coefficient K whose value should be in range [0...1].
double k = .1;
float ctrl1_x = (float) (xm1 + (xc2 - xm1) * k + x1 - xm1);
float ctrl1_y = (float) (ym1 + (yc2 - ym1) * k + y1 - ym1);
float ctrl2_x = (float) (xm2 + (xc2 - xm2) * k + x2 - xm2);
float ctrl2_y = (float) (ym2 + (yc2 - ym2) * k + y2 - ym2);
return new float[]{ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y};
}
Bezier Curves are not designed to go through the provided points! They are designed to shape a smooth curve influenced by the control points.
Further you don't want to have your smooth curve going through all data points!
Instead of interpolating you should consider filtering your data set:
Filtering
For that case you need a sequence of your data, as array of points, in the order the finger has drawn the gesture:
You should look in wiki for "sliding average".
You should use a small averaging window. (try 5 - 10 points). This works as follows: (look for wiki for a more detailed description)
I use here an average window of 10 points:
start by calculation of the average of points 0 - 9, and output the result as result point 0
then calculate the average of point 1 - 10 and output, result 1
And so on.
to calculate the average between N points:
avgX = (x0+ x1 .... xn) / N
avgY = (y0+ y1 .... yn) / N
Finally you connect the resulting points with lines.
If you still need to interpolate between missing points, you should then use piece - wise cubic splines.
One cubic spline goes through all 3 provided points.
You would need to calculate a series of them.
But first try the sliding average. This is very easy.
Nice question. Your (wrong) result is obvious, but you can try to apply it to a much smaller dataset, maybe by replacing groups of close points with an average point. The appropriate distance in this case to tell if two or more points belong to the same group may be expressed in time, not space, so you'll need to store the whole touch event (x, y and timestamp). I was thinking of this because I need a way to let users draw geometric primitives (rectangles, lines and simple curves) by touch
What is this for? Why do you need to be so accurate? I would assume you only need something around 4 vertices stored for every inch the user drags his finger. With that in mind:
Try using one vertex out of every X to actually draw between, with the middle vertex used for specifying the weighted point of the curve.
int interval = 10; //how many points to skip
gesture.moveTo(gesture.get(0).x, gesture.get(0).y);
for(int i =0; i +interval/2 < gesture.size(); i+=interval)
{
Gesture ngp = gesture.get(i+interval/2);
gesturePath.quadTo(ngp.x,ngp.y, gp.x,gp.y);
}
You'll need to adjust this to actually work but the idea is there.

Categories