How to draw filled polyogon in canvas - java

I need to fill with color an area of given polygon on a custom view. I can't find a method with which I can use for this matter.
I can actually only draw lines connecting those point but I'm unsure how to fill the area surrounded by them.
The following code I use to draw lines:
for (Path p : paths) {
Paint color = p.getColor();
float strokeW = color.getStrokeWidth();
color.setStrokeWidth(pthStrokeWdth);
List<Point> currentPath = p.getCoordinateList();
for (int i = 1; i < currentPath.size(); i++) {
float startX = currentPath.get(i - 1).x;
float startY = currentPath.get(i - 1).y;
float endX = currentPath.get(i).x;
float endY = currentPath.get(i).y;
canvas.drawLine((startX * cellWidth) + xOffset + halfCellWidth, (startY * cellHeight) + yOffset + halfCellHeight,
(endX * cellWidth) + xOffset + halfCellWidth, (endY * cellHeight) + yOffset + halfCellHeight, color);
}
color.setStrokeWidth(originalStrokeWidth);
}
The above draw lines between every point forming polygons with diverse shapes. See below screenshot.
So I would likne to fill the area inside the polygone like below:
Thanks for your time.

You need to set the Paint.Style to FILL or FILL_AND_STROKE
paint.setStyle(Paint.Style.FILL);
Edit:
Paint color = p.getColor();
float strokeW = color.getStrokeWidth();
color.setStrokeWidth(pthStrokeWdth);
color.setStyle(Paint.Style.FILL);
List<Point> currentPath = p.getCoordinateList();
//for each path we want to draw a line which consist of 4 points
float[] points = new float[currentPath.size() * 4];
int count = 0;
for (int i = 1; i < currentPath.size(); i++) {
float startX = currentPath.get(i - 1).x;
float startY = currentPath.get(i - 1).y;
float endX = currentPath.get(i).x;
float endY = currentPath.get(i).y;
points[count++] = (startX * cellWidth) + xOffset + halfCellWidth;
points[count++] = (startY * cellHeight) + yOffset + halfCellHeight;
points[count++] = (endX * cellWidth) + xOffset + halfCellWidth;
points[count++] = (endY * cellHeight) + yOffset + halfCellHeight, color;
}
canvas.drawLines(points, color);

Related

How can i render triangle after multiply by projection matrix

I'm trying to make a 3d game with java from scratch but a have a problem with rendering a triangle after I multiply each vertices with projection matrix
I already try to use the projected vertices x and y but the result is all the vertices the in the same X, so I try to rotate the triangle X or Y or Z axis but the result is the same.
The result of rendering (draw in paint):
I know that the triangle is align with the camera but I tried to move the vertex that is overlapping out by changing its X or Y or Z coordinate but It didn't work
import java.awt.Color;
import java.awt.Graphics;
import measurement.MatrixF;
import measurement.Vector3f;
import model.Mesh;
import model.Triangle;
import toolbox.GE;
import toolbox.Matrix;
import toolbox.Vector;
public class MeshRenderer {
private int width, height;
private float fNear, fFar;
private float fov;
private float fAspectRatio;
private float fovRad;
private float theta;
private MatrixF projectionMatrix;
private MatrixF rotXMatrix;
private MatrixF rotYMatrix;
private MatrixF rotZMatrix;
private Vector3f globalTranslation;
public MeshRenderer(float fNear, float fFar, float fov, int width, int height) {
this.fNear = fNear;
this.fFar = fFar;
this.fov = fov;
this.fAspectRatio = height / width;
this.width = width;
this.height = height;
this.fovRad = (float) (1.0f / Math.tan(Math.toRadians(fov / 2)));
projectionMatrix = new MatrixF(4, 4);
rotXMatrix = new MatrixF(4, 4);
rotYMatrix = new MatrixF(4, 4);
rotZMatrix = new MatrixF(4, 4);
projectionMatrix.m[0][0] = fAspectRatio * fovRad;
projectionMatrix.m[1][1] = fovRad;
projectionMatrix.m[2][2] = (-(fFar + fNear)) / (fFar - fNear);
projectionMatrix.m[3][2] = (-2 * fFar * fNear) / (fFar - fNear);
projectionMatrix.m[2][3] = -1.0f;
projectionMatrix.m[3][3] = 0.0f;
rotXMatrix.m[0][0] = 1;
rotXMatrix.m[1][1] = (float) Math.cos(theta);
rotXMatrix.m[2][1] = (float) -Math.sin(theta);
rotXMatrix.m[1][2] = (float) Math.sin(theta);
rotXMatrix.m[2][2] = (float) Math.cos(theta);
rotYMatrix.m[0][0] = (float) Math.cos(theta);
rotYMatrix.m[2][0] = (float) Math.sin(theta);
rotYMatrix.m[1][1] = (float) 1.0;
rotYMatrix.m[0][2] = (float) -Math.sin(theta);
rotYMatrix.m[2][2] = (float) Math.cos(theta);
rotXMatrix.m[2][2] = 1;
rotXMatrix.m[0][0] = (float) Math.cos(theta);
rotXMatrix.m[1][0] = (float) -Math.sin(theta);
rotXMatrix.m[0][1] = (float) Math.sin(theta);
rotXMatrix.m[1][1] = (float) Math.cos(theta);
//projectionMatrix = Matrix.transpose(projectionMatrix);
globalTranslation = new Vector3f(0.0f, 0.0f, 0.0f);
}
public void renderMesh(Mesh mesh, Graphics g) {
for(int i = 0; i < mesh.tris.length; i++) {
Triangle tri = new Triangle(mesh.tris[i].p[0], mesh.tris[i].p[1], mesh.tris[i].p[2]);
Triangle translatedTri = tri;
Triangle projectedTri = new Triangle();
theta += 0.0001;
this.calculateRotationMatrix(theta);
translatedTri.p[0] = Matrix.multiplyMatrixVector(tri.p[0], rotYMatrix);
translatedTri.p[1] = Matrix.multiplyMatrixVector(tri.p[1], rotYMatrix);
translatedTri.p[2] = Matrix.multiplyMatrixVector(tri.p[2], rotYMatrix);
translatedTri.p[0].z = tri.p[0].z + globalTranslation.z;
translatedTri.p[1].z = tri.p[1].z + globalTranslation.z;
translatedTri.p[2].z = tri.p[2].z + globalTranslation.z;
projectedTri.p[0] = Matrix.multiplyMatrixVector(translatedTri.p[0], projectionMatrix);
projectedTri.p[1] = Matrix.multiplyMatrixVector(translatedTri.p[1], projectionMatrix);
projectedTri.p[2] = Matrix.multiplyMatrixVector(translatedTri.p[2], projectionMatrix);
projectedTri.p[0].x += 1.0f; projectedTri.p[0].y += 1.0f;
projectedTri.p[1].x += 1.0f; projectedTri.p[1].y += 1.0f;
projectedTri.p[2].x += 1.0f; projectedTri.p[2].y += 1.0f;
float scale = 0.5f;
projectedTri.p[0].x *= scale * width;
projectedTri.p[0].y *= scale * height;
projectedTri.p[1].x *= scale * width;
projectedTri.p[1].y *= scale * height;
projectedTri.p[2].x *= scale * width;
projectedTri.p[2].y *= scale * height;
GE.drawTriangle(projectedTri.p[0].x, projectedTri.p[0].y, projectedTri.p[1].x, projectedTri.p[1].y, projectedTri.p[2].x, projectedTri.p[2].y, Color.WHITE, g);
for(int j = 0; j < projectedTri.p.length; j++) {
g.setColor(new Color(255, 0, (j * 50)));
g.fillRect((int)projectedTri.p[j].x - 8, (int)projectedTri.p[j].y - 8, 16 - j, 16 - j);
}
translatedTri.p[0].z = tri.p[0].z - globalTranslation.z;
translatedTri.p[1].z = tri.p[1].z - globalTranslation.z;
translatedTri.p[2].z = tri.p[2].z - globalTranslation.z;
}
}
private void calculateRotationMatrix(float theta) {
rotXMatrix.m[0][0] = 1;
rotXMatrix.m[1][1] = (float) Math.cos(theta);
rotXMatrix.m[2][1] = (float) -Math.sin(theta);
rotXMatrix.m[1][2] = (float) Math.sin(theta);
rotXMatrix.m[2][2] = (float) Math.cos(theta);
rotYMatrix.m[0][0] = (float) Math.cos(theta);
rotYMatrix.m[2][0] = (float) Math.sin(theta);
rotYMatrix.m[1][1] = (float) 1.0;
rotYMatrix.m[0][2] = (float) -Math.sin(theta);
rotYMatrix.m[2][2] = (float) Math.cos(theta);
rotXMatrix.m[2][2] = 1;
rotXMatrix.m[0][0] = (float) Math.cos(theta);
rotXMatrix.m[1][0] = (float) -Math.sin(theta);
rotXMatrix.m[0][1] = (float) Math.sin(theta);
rotXMatrix.m[1][1] = (float) Math.cos(theta);
}
public Vector3f getTranslation() {
return globalTranslation;
}
public float getfNear() {
return fNear;
}
public float getfFar() {
return fFar;
}
public float getFov() {
return fov;
}
public float getfAspectRatio() {
return fAspectRatio;
}
public float getFovRad() {
return fovRad;
}
}
The matrix (4x4) multiply with vector3 function just in case:
Vector3f o = new Vector3f(0, 0, 0);
o.x = (i.x * m.m[0][0]) + (i.y * m.m[1][0]) + (i.z * m.m[2][0]) + m.m[3][0];
o.y = (i.x * m.m[0][1]) + (i.y * m.m[1][1]) + (i.z * m.m[2][1]) + m.m[3][1];
o.z = (i.x * m.m[0][2]) + (i.y * m.m[1][2]) + (i.z * m.m[2][2]) + m.m[3][2];
float w = (i.x * m.m[0][3]) + (i.y * m.m[1][3]) + (i.z * m.m[2][3]) + m.m[3][3];
if (w != 0.0f)
{
o.x /= w; o.y /= w; o.z /= w;
}
return o;
}
Without seeing exactly how this class is being used, it's hard to say exactly what the problem is, but FWIW I'm not seeing too much wrong with the math:
There are a couple places where you probably intended to initialize rotZMatrix instead of reinitializing rotXMatrix, but the code is not actually using either.
When adding in globalTranslation, you are overwriting the rotated z coordinate with the pre-rotated z coordinate, when you probably just want to update the rotated coordinate.
It's not apparent whether MatrixF is initialized to the identity or to zeros -- but if the latter, you probably should be populating the m[3][3] element of the rotation matrices with 1.0.
Naturally, you probably want to lift the theta increment and rotation calculation outside of the triangle loop, once you have more than one triangle.
I'm guessing that the problem is that you are leaving globalTranslation at zero and that the mesh is near the origin -- hence the transformed geometry is on the wrong side of the near plane and outside of the view frustum. Most graphic engines would cull such geometry, since the post-transform results will lie outside of clip space and will look increasingly anomalous around and behind the eye point.
I'd recommend trying to adjust globalTranslation.z to ensure 0 < fNear < translatedTri.p[i].z < fFar, for all the translated points.
(You could also try temporarily swapping the perspective matrix with a orthographic projection matrix, to determine whether the problem is in the projection/homogenization math or elsewhere.)

Coordinate values arent stored in processing

I really have no idea why the x and y values wont go to the drawLines function
float x, x1, x2;
float y, y1, y2;
float rad; //radius
int lines = 30; //number of lines
int colorNumber = 1;
void setup() {
background(#FFFFFF);
size (800, 600);
rad = 8;
}
void draw() {
}
This creates the three dots or vertices of the mathematical envelope
void mouseClicked() {
float x = mouseX;
float x1 = mouseX;
float x2 = mouseX;
float y = mouseY;
float y1 = mouseY;
float y2 = mouseY;
if (colorNumber == 1) {
fill(#9393ff);
ellipse(x, y, rad, rad);
} else if (colorNumber == 2) {
fill(#FF9393);
ellipse(x1, y1, rad, rad);
} else if (colorNumber == 3) {
fill(#93ff93);
ellipse(x2, y2, rad, rad);
}
}
This is supposed to draw the envelope using the coordinates of the vertices
void drawLines(int numLines) {
for (int i = 0; i < numLines; i = i + 1) {
float x = mouseX;
float x1 = mouseX;
float x2 = mouseX;
float y = mouseY;
float y1 = mouseY;
float y2 = mouseY;
float t = (float) i/(numLines-1);
float startX = x + t * (x1 - x);
float startY = y + t * (y1 - y);
float endX = x1 + t * (x2 - x1);
float endY = y1 + t * (y2 - y1);
line (startX, startY, endX, endY);
}
}
void mouseReleased() {
colorNumber++;
if (colorNumber == 4) {
colorNumber = 1;
}
println(colorNumber);
}
void keyPressed() {
if (keyPressed == true) {
background(#FFFFFF);
}
}
this last stuff just tells the code if you press a key, it will reset the backround
I understand your intention with using mouseX and mouseY to specify the coordinates of one of the 3 points of the envelope on click. The current issue is that all 3 points are being set to the same coordinate with each click. You need to introduce a variable to keep track of which coordinate to set on-click, such that only one pair is set. Then, only once all 3 coordinates are set, drawLines() can be called.
I propose the following:
Introduce 2 variables, one to keep track of which point is being modified; the other an array of PVectors (just to make it cleaner).
int index = 0;
PVector[] coords = new PVector[3];
Modify mouseClicked() to include the following:
void mouseClicked() {
ellipse(mouseX, mouseY, 8, 8);
coords[index] = new PVector(mouseX, mouseY);
index += 1;
if (index == 3) {
drawLines(lines);
}
index %= 3;
}
drawLines() becomes:
void drawLines(int numLines) {
for (int i = 0; i < numLines; i = i + 1) {
x = coords[0].x;
x1 = coords[1].x;
x2 = coords[2].x;
y = coords[0].y;
y1 = coords[1].y;
y2 = coords[2].y;
float t = (float) i / (numLines - 1);
float startX = x + t * (x1 - x);
float startY = y + t * (y1 - y);
float endX = x1 + t * (x2 - x1);
float endY = y1 + t * (y2 - y1);
line(startX, startY, endX, endY);
}
}
Finally, since your drawing on a black background, and the default stroke colour is black, use strokeColour() to change the colour of the lines so that you can see the envelope once its drawn.

Coloring heightmap faces instead of vertices

I'm trying to create a heightmap colored by face, instead of vertex. For example, this is what I currently have:
But this is what I want:
I read that I have to split each vertex into multiple vertices, then index each separately for the triangles. I also know that blender has a function like this for its models (split vertices, or something?), but I'm not sure what kind of algorithm I would follow for this. This would be the last resort, because multiplying the amount of vertices in the mesh for no reason other than color doesn't seem efficient.
I also discovered something called flatshading (using the flat qualifier on the pixel color in the shaders), but it seems to only draw squares instead of triangles. Is there a way to make it shade triangles?
For reference, this is my current heightmap generation code:
public class HeightMap extends GameModel {
private static final float START_X = -0.5f;
private static final float START_Z = -0.5f;
private static final float REFLECTANCE = .1f;
public HeightMap(float minY, float maxY, float persistence, int width, int height, float spikeness) {
super(createMesh(minY, maxY, persistence, width, height, spikeness), REFLECTANCE);
}
protected static Mesh createMesh(final float minY, final float maxY, final float persistence, final int width,
final int height, float spikeness) {
SimplexNoise noise = new SimplexNoise(128, persistence, 2);// Utils.getRandom().nextInt());
float xStep = Math.abs(START_X * 2) / (width - 1);
float zStep = Math.abs(START_Z * 2) / (height - 1);
List<Float> positions = new ArrayList<>();
List<Integer> indices = new ArrayList<>();
for (int z = 0; z < height; z++) {
for (int x = 0; x < width; x++) {
// scale from [-1, 1] to [minY, maxY]
float heightY = (float) ((noise.getNoise(x * xStep * spikeness, z * zStep * spikeness) + 1f) / 2
* (maxY - minY) + minY);
positions.add(START_X + x * xStep);
positions.add(heightY);
positions.add(START_Z + z * zStep);
// Create indices
if (x < width - 1 && z < height - 1) {
int leftTop = z * width + x;
int leftBottom = (z + 1) * width + x;
int rightBottom = (z + 1) * width + x + 1;
int rightTop = z * width + x + 1;
indices.add(leftTop);
indices.add(leftBottom);
indices.add(rightTop);
indices.add(rightTop);
indices.add(leftBottom);
indices.add(rightBottom);
}
}
}
float[] verticesArr = Utils.listToArray(positions);
Color c = new Color(147, 105, 59);
float[] colorArr = new float[positions.size()];
for (int i = 0; i < colorArr.length; i += 3) {
float brightness = (Utils.getRandom().nextFloat() - 0.5f) * 0.5f;
colorArr[i] = (float) c.getRed() / 255f + brightness;
colorArr[i + 1] = (float) c.getGreen() / 255f + brightness;
colorArr[i + 2] = (float) c.getBlue() / 255f + brightness;
}
int[] indicesArr = indices.stream().mapToInt((i) -> i).toArray();
float[] normalArr = calcNormals(verticesArr, width, height);
return new Mesh(verticesArr, colorArr, normalArr, indicesArr);
}
private static float[] calcNormals(float[] posArr, int width, int height) {
Vector3f v0 = new Vector3f();
Vector3f v1 = new Vector3f();
Vector3f v2 = new Vector3f();
Vector3f v3 = new Vector3f();
Vector3f v4 = new Vector3f();
Vector3f v12 = new Vector3f();
Vector3f v23 = new Vector3f();
Vector3f v34 = new Vector3f();
Vector3f v41 = new Vector3f();
List<Float> normals = new ArrayList<>();
Vector3f normal = new Vector3f();
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (row > 0 && row < height - 1 && col > 0 && col < width - 1) {
int i0 = row * width * 3 + col * 3;
v0.x = posArr[i0];
v0.y = posArr[i0 + 1];
v0.z = posArr[i0 + 2];
int i1 = row * width * 3 + (col - 1) * 3;
v1.x = posArr[i1];
v1.y = posArr[i1 + 1];
v1.z = posArr[i1 + 2];
v1 = v1.sub(v0);
int i2 = (row + 1) * width * 3 + col * 3;
v2.x = posArr[i2];
v2.y = posArr[i2 + 1];
v2.z = posArr[i2 + 2];
v2 = v2.sub(v0);
int i3 = (row) * width * 3 + (col + 1) * 3;
v3.x = posArr[i3];
v3.y = posArr[i3 + 1];
v3.z = posArr[i3 + 2];
v3 = v3.sub(v0);
int i4 = (row - 1) * width * 3 + col * 3;
v4.x = posArr[i4];
v4.y = posArr[i4 + 1];
v4.z = posArr[i4 + 2];
v4 = v4.sub(v0);
v1.cross(v2, v12);
v12.normalize();
v2.cross(v3, v23);
v23.normalize();
v3.cross(v4, v34);
v34.normalize();
v4.cross(v1, v41);
v41.normalize();
normal = v12.add(v23).add(v34).add(v41);
normal.normalize();
} else {
normal.x = 0;
normal.y = 1;
normal.z = 0;
}
normal.normalize();
normals.add(normal.x);
normals.add(normal.y);
normals.add(normal.z);
}
}
return Utils.listToArray(normals);
}
}
Edit
I've tried doing a couple things. I tried rearranging the indices with flat shading, but that didn't give me the look I wanted. I tried using a uniform vec3 colors and indexing it with gl_VertexID or gl_InstanceID (I'm not entirely sure the difference), but I couldn't get the arrays to compile.
Here is the github repo, by the way.
flat qualified fragment shader inputs will receive the same value for the same primitive. In your case, a triangle.
Of course, a triangle is composed of 3 vertices. And if the vertex shaders output 3 different values, how does the fragment shader know which value to get?
This comes down to what is called the "provoking vertex." When you render, you specify a particular primitive to use in your glDraw* call (GL_TRIANGLE_STRIP, GL_TRIANGLES, etc). These primitive types will generate a number of base primitives (ie: single triangle), based on how many vertices you provided.
When a base primitive is generated, one of the vertices in that base primitive is said to be the "provoking vertex". It is that vertex's data that is used for all flat parameters.
The reason you're seeing what you are seeing is because the two adjacent triangles just happen to be using the same provoking vertex. Your mesh is smooth, so two adjacent triangles share 2 vertices. Your mesh generation just so happens to be generating a mesh such that the provoking vertex for each triangle is shared between them. Which means that the two triangles will get the same flat value.
You will need to adjust your index list or otherwise alter your mesh generation so that this doesn't happen. Or you can just divide your mesh into individual triangles; that's probably much easier.
As a final resort, I just duplicated the vertices, and it seems to work. I haven't been able to profile it to see if it makes a big performance drop. I'd be open to any other suggestions!
for (int z = 0; z < height; z++) {
for (int x = 0; x < width; x++) {
// scale from [-1, 1] to [minY, maxY]
float heightY = (float) ((noise.getNoise(x * xStep * spikeness, z * zStep * spikeness) + 1f) / 2
* (maxY - minY) + minY);
positions.add(START_X + x * xStep);
positions.add(heightY);
positions.add(START_Z + z * zStep);
positions.add(START_X + x * xStep);
positions.add(heightY);
positions.add(START_Z + z * zStep);
}
}
for (int z = 0; z < height - 1; z++) {
for (int x = 0; x < width - 1; x++) {
int leftTop = z * width + x;
int leftBottom = (z + 1) * width + x;
int rightBottom = (z + 1) * width + x + 1;
int rightTop = z * width + x + 1;
indices.add(2 * leftTop);
indices.add(2 * leftBottom);
indices.add(2 * rightTop);
indices.add(2 * rightTop + 1);
indices.add(2 * leftBottom + 1);
indices.add(2 * rightBottom + 1);
}
}

Rotate line by specified angle (Java/ Android)

this is a more math related question.
In my android app I have a canvas that draws a line from start to endpoint.
I also have an angle attribute to rotate the line.
I tried to apply the code bellow to my cocept, but it jumps around weirdly, not pointing in the direction it is supposed to. (0deg = horizontal line)
//Coordinates for P1 and P2
int startx = 0;
int starty = 66;
int endx = 420;
int endy = 66;
//Alpha
float angle = 0.000F;
final float radius = 209.500F;
final float extra_radius = 20.000F; //required later - don't mind it
private void reCal(float[] vals) {
float xAcc = vals[0]*(-1);
float yAcc = vals[1]*(-1);
angle = yAcc / 10.000F * 90.000F;
final float rRadius = radius + extra_radius;
startx = (int) (radius - Math.cos(angle) * rRadius); //left
endx = (int) (radius + Math.cos(angle) * rRadius); //right
starty = 66 + (int) (Math.sin(angle) * rRadius); //top
endy = 66 - (int) (Math.sin(angle) * rRadius); //bot
}
Are their any type conversions that might cause this, or is my math wrong?
UPDATE:
I looked at Math.cos(angle) and Math.sin(angle) at runtime.They jump around between 0 and 1, even if angle is only changed by a little amount.
Now I am wondering: DO THE METHODS TAKE RAD OR DEG AS PARAMETER? (I need deg, obviously)
if you want to rotate a line starting at [cx, cy] and length of dx, use that code snippet:
canvas.save();
canvas.rotate(angle, cx, cy);
canvas.drawLine(cx, cy, cx + dx, cy, paint);
canvas.restore();

Application not working as expected

I have a standalone Java application below that is:
Generating a random line
Applied to a 2D grid where each cell value is the distance along the line perpindicular to the line
Finds the rise/run and attempts to calculate the original linear equation from the grid
Applies new line to another grid and prints out the greatest difference compared to the first grid
I expected the two grids to have identical values. The gradient lines may be different since the lines can extend outside the area of the grid, but should be similar and in two cases identical.
So is the problem a poor understanding of math, a bug in my code or a misunderstanding of floating point values?
import java.awt.geom.Point2D;
import java.awt.geom.Line2D;
import java.util.Iterator;
import java.util.ArrayList;
public final class TestGradientLine {
private static int SIZE = 3;
public TestGradientLine() {
super();
}
//y = mx + b
//b = y - mx
//m is rise / run = gradient
//width and height of bounding box
//for a box 10x10 then width and height are 9,9
public static Line2D getGradientLine(double run, double rise, double width, double height, double x, double y) {
if (run == 0 && rise == 0) {
return new Line2D.Double(x, y, x + width, y + height);
}
//calculate hypotenuse
//check for a vertical line
if (run == 0) {
return new Line2D.Double(x, y, x, y + height);
}
//check for a horizontal line
if (rise == 0) {
return new Line2D.Double(x, y, x + width, y);
}
//calculate gradient
double m = rise / run;
Point2D start;
Point2D opposite;
if (m < 0) {
//lower left
start = new Point2D.Double(x, y + height);
opposite = new Point2D.Double(x + width, y);
} else {
//upper left
start = new Point2D.Double(x, y);
opposite = new Point2D.Double(x + width, y + height);
}
double b = start.getY() - (m * start.getX());
//now calculate another point along the slope
Point2D next = null;
if (m > 0) {
next = new Point2D.Double(start.getX() + Math.abs(run), start.getY() + Math.abs(rise));
} else {
if (rise < 0) {
next = new Point2D.Double(start.getX() + run, start.getY() + rise);
} else {
next = new Point2D.Double(start.getX() - run, start.getY() - rise);
}
}
final double actualWidth = width;
final double actualHeight = height;
final double a = Math.sqrt((actualWidth * actualWidth) + (actualHeight * actualHeight));
extendLine(start, next, a);
Line2D gradientLine = new Line2D.Double(start, next);
return gradientLine;
}
public static void extendLine(Point2D p0, Point2D p1, double toLength) {
final double oldLength = p0.distance(p1);
final double lengthFraction =
oldLength != 0.0 ? toLength / oldLength : 0.0;
p1.setLocation(p0.getX() + (p1.getX() - p0.getX()) * lengthFraction,
p0.getY() + (p1.getY() - p0.getY()) * lengthFraction);
}
public static Line2D generateRandomGradientLine(int width, int height) {
//so true means lower and false means upper
final boolean isLower = Math.random() > .5;
final Point2D start = new Point2D.Float(0, 0);
if (isLower) {
//change origin for lower left corner
start.setLocation(start.getX(), height - 1);
}
//radius of our circle
double radius = Math.sqrt(width * width + height * height);
//now we want a random theta
//x = r * cos(theta)
//y = r * sin(theta)
double theta = 0.0;
if (isLower) {
theta = Math.random() * (Math.PI / 2);
} else {
theta = Math.random() * (Math.PI / 2) + (Math.PI / 2);
}
int endX = (int)Math.round(radius * Math.sin(theta));
int endY = (int)Math.round(radius * Math.cos(theta)) * -1;
if (isLower) {
endY = endY + (height - 1);
}
final Point2D end = new Point2D.Float(endX, endY);
extendLine(start, end, radius);
return new Line2D.Float(start, end);
}
public static Point2D getNearestPointOnLine(Point2D end, Line2D line) {
final Point2D point = line.getP1();
final Point2D start = line.getP2();
double a = (end.getX() - point.getX()) * (start.getX() - point.getX()) + (end.getY() - point.getY()) * (start.getY() - point.getY());
double b = (end.getX() - start.getX()) * (point.getX() - start.getX()) + (end.getY() - start.getY()) * (point.getY() - start.getY());
final double x = point.getX() + ((start.getX() - point.getX()) * a)/(a + b);
final double y = point.getY() + ((start.getY() - point.getY()) * a)/(a + b);
final Point2D result = new Point2D.Double(x, y);
return result;
}
public static double length(double x0, double y0, double x1, double y1) {
final double dx = x1 - x0;
final double dy = y1 - y0;
return Math.sqrt(dx * dx + dy * dy);
}
public static void main(String[] args) {
final Line2D line = generateRandomGradientLine(SIZE, SIZE);
System.out.println("we're starting with line " + line.getP1() + " " + line.getP2());
double[][] region = new double[SIZE][SIZE];
//load up the region with data from our generated line
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, line);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() + 1,
nearestPoint.getY() + 1);
region[x][y] = distance;
}
}
//now figure out what our line is from the region
double runTotal = 0;
double riseTotal = 0;
double runCount = 0;
double riseCount = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
if (x < SIZE - 1) {
runTotal += region[x + 1][y] - region[x][y];
runCount++;
}
if (y < SIZE - 1) {
riseTotal += region[x][y + 1] - region[x][y];
riseCount++;
}
}
}
double run = 0;
if (runCount > 0) {
run = runTotal / runCount;
}
double rise = 0;
if (riseCount > 0) {
rise = riseTotal / riseCount;
}
System.out.println("rise is " + rise + " run is " + run);
Line2D newLine = getGradientLine(run, rise, SIZE - 1, SIZE - 1, 0, 0);
System.out.println("ending with line " + newLine.getP1() + " " + newLine.getP2());
double worst = 0.0;
int worstX = 0;
int worstY = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, newLine);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() + 1,
nearestPoint.getY() + 1);
final double diff = Math.abs(region[x][y] - distance);
if (diff > worst) {
worst = diff;
worstX = x;
worstY = y;
}
}
}
System.out.println("worst is " + worst + " x: " + worstX + " y: " + worstY);
}
}
I think I have fixed your program.
a) I took out the integer cast.
b) I removed all the 'x + 1' and 'x - 1' fudges you had used.
I think when dealing with floats and doubles, subtracting '1' from the end of a line is a No-No! What is 1 anyway? - it's ok to do this just before you plot it on the screen once it's an integer. But not while calculating! line length is a 'zero-based' quantity.
This version returns approx 4E-16 always.
import java.awt.geom.Point2D;
import java.awt.geom.Line2D;
import java.awt.geom.QuadCurve2D;
import java.util.Iterator;
import java.util.ArrayList;
public final class TestGradientLine {
private static int SIZE = 3;
public TestGradientLine() {
super();
}
//y = mx + b
//b = y - mx
//m is rise / run = gradient
//width and height of bounding box
//for a box 10x10 then width and height are 9,9
public static Line2D getGradientLine(double run, double rise, double width, double height, double x, double y) {
if (run == 0 && rise == 0) {
return new Line2D.Double(x, y, x + width, y + height);
}
//calculate hypotenuse
//check for a vertical line
if (run == 0) {
return new Line2D.Double(x, y, x, y + height);
}
//check for a horizontal line
if (rise == 0) {
return new Line2D.Double(x, y, x + width, y);
}
//calculate gradient
double m = rise / run;
Point2D start;
Point2D opposite;
if (m < 0) {
//lower left
start = new Point2D.Double(x, y + height);
opposite = new Point2D.Double(x + width, y);
} else {
//upper left
start = new Point2D.Double(x, y);
opposite = new Point2D.Double(x + width, y + height);
}
double b = start.getY() - (m * start.getX());
//now calculate another point along the slope
Point2D next = null;
if (m > 0) {
next = new Point2D.Double(start.getX() + Math.abs(run), start.getY() + Math.abs(rise));
} else {
if (rise < 0) {
next = new Point2D.Double(start.getX() + run, start.getY() + rise);
} else {
next = new Point2D.Double(start.getX() - run, start.getY() - rise);
}
}
final double actualWidth = width;
final double actualHeight = height;
final double a = Math.sqrt((actualWidth * actualWidth) + (actualHeight * actualHeight));
extendLine(start, next, a);
Line2D gradientLine = new Line2D.Double(start, next);
return gradientLine;
}
public static void extendLine(Point2D p0, Point2D p1, double toLength) {
final double oldLength = p0.distance(p1);
final double lengthFraction =
oldLength != 0.0 ? toLength / oldLength : 0.0;
p1.setLocation(p0.getX() + (p1.getX() - p0.getX()) * lengthFraction,
p0.getY() + (p1.getY() - p0.getY()) * lengthFraction);
}
public static Line2D generateRandomGradientLine(int width, int height) {
//so true means lower and false means upper
final boolean isLower = Math.random() > .5;
final Point2D start = new Point2D.Float(0, 0);
if (isLower) {
//change origin for lower left corner
start.setLocation(start.getX(), height );
}
//radius of our circle
double radius = Math.sqrt(width * width + height * height);
//now we want a random theta
//x = r * cos(theta)
//y = r * sin(theta)
double theta = 0.0;
if (isLower) {
theta = Math.random() * (Math.PI / 2);
} else {
theta = Math.random() * (Math.PI / 2) + (Math.PI / 2);
}
float endX = (float)(radius * Math.sin(theta));
float endY = (float)(radius * Math.cos(theta)) * -1;
if (isLower) {
endY = endY + (height );
}
final Point2D end = new Point2D.Float(endX, endY);
extendLine(start, end, radius);
return new Line2D.Float(start, end);
}
public static Point2D getNearestPointOnLine(Point2D end, Line2D line) {
final Point2D point = line.getP1();
final Point2D start = line.getP2();
double a = (end.getX() - point.getX()) * (start.getX() - point.getX()) + (end.getY() - point.getY()) * (start.getY() - point.getY());
double b = (end.getX() - start.getX()) * (point.getX() - start.getX()) + (end.getY() - start.getY()) * (point.getY() - start.getY());
final double x = point.getX() + ((start.getX() - point.getX()) * a)/(a+b);
final double y = point.getY() + ((start.getY() - point.getY()) * a)/(a+b);
final Point2D result = new Point2D.Double(x, y);
return result;
}
public static double length(double x0, double y0, double x1, double y1) {
final double dx = x1 - x0;
final double dy = y1 - y0;
return Math.sqrt(dx * dx + dy * dy);
}
public static void main(String[] args) {
final Line2D line = generateRandomGradientLine(SIZE, SIZE);
System.out.println("we're starting with line " + line.getP1() + " " + line.getP2());
double[][] region = new double[SIZE][SIZE];
//load up the region with data from our generated line
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, line);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() ,
nearestPoint.getY() );
region[x][y] = distance;
}
}
//now figure out what our line is from the region
double runTotal = 0;
double riseTotal = 0;
double runCount = 0;
double riseCount = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
if (x < SIZE - 1) {
runTotal += region[x + 1][y] - region[x][y];
runCount++;
}
if (y < SIZE - 1) {
riseTotal += region[x][y + 1] - region[x][y];
riseCount++;
}
}
}
double run = 0;
if (runCount > 0) {
run = runTotal / runCount;
}
double rise = 0;
if (riseCount > 0) {
rise = riseTotal / riseCount;
}
System.out.println("rise is " + rise + " run is " + run);
Line2D newLine = getGradientLine(run, rise, SIZE, SIZE , 0, 0);
System.out.println("ending with line " + newLine.getP1() + " " + newLine.getP2());
double worst = 0.0;
int worstX = 0;
int worstY = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, newLine);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() ,
nearestPoint.getY() );
final double diff = Math.abs(region[x][y] - distance);
if (diff > worst) {
worst = diff;
worstX = x;
worstY = y;
}
}
}
System.out.println("worst is " + worst + " x: " + worstX + " y: " + worstY);
}
}
why do you multiply by -1 at the end of this line?
int endY = (int)Math.round(radius * Math.cos(theta)) * -1;
this means that endY is always negative except radius is below 0. (cosinus always returns positive value)
is this intended or am i getting something wrong?
regards
You probably misunderstand float and/or double. This is a common problem with any language that implements the ieee spec for floats and doubles, which Java, C, C++ and just about every other language does.
Essentially
double val = 0;
for(int i=0;i<10;i++) {
val+=0.1;
System.out.println(val);
}
results in
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
And sometimes even worse. Either use BigDecimal, which alleviates a lot of the problem, or use integers.

Categories

Resources