Implementation of Floor Casting Producing Unexpected Errors - java

I have been following and translating a YouTube tutorial about how to create a raycasting engine as the person who made the video created theirs in C++ and I am making mine in Java.
Link To The Tutorial: https://www.youtube.com/watch?v=PC1RaETIx3Y
For the most part, following the creator's tutorial has turned out exactly as it is supposed to be. However when I try to implement the floor casting that they used I constantly get the following error:
java.lang.ArrayIndexOutOfBoundsException: https://i.stack.imgur.com/lxF27.png
Following the stack trace, it says that it has an error with I try to get the color value from the map position of the floor and ceiling arrays:
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
I have tried implementing other solutions to floor casting but I have a hard time grasping what it is doing and those solutions don't fit the engine that I am building. I was wondering if anyone knows what could be wrong with my implementation as my calculations should be functionally the same as the creator's. I would also accept answers that provide a different solution that makes use of how the engine is implemeted.
Their Code:
for(y=lineOff+lineH;y<320;y++)
{
float dy=y-(320/2.0), deg=degToRad(ra), raFix=cos(degToRad(FixAng(pa-ra)));
tx=px/2 + cos(deg)*158*32/dy/raFix;
ty=py/2 - sin(deg)*158*32/dy/raFix;
int mp=mapF[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
float c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
glColor3f(c/1.3,c/1.3,c);glPointSize(8);glBegin(GL_POINTS);glVertex2i(r*8+530,y);glEnd();
mp=mapC[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
glColor3f(c/2.0,c/1.2,c/2.0);glPointSize(8);glBegin(GL_POINTS);glVertex2i(r*8+530,320-y);glEnd();
}
My Code:
//Draws Floor And Ceiling Tiles
for(int y = (int)(lineO+lineH); y<HEIGHT; y++) {
float dy = y - ((float)HEIGHT/2f), raFix = (float) Math.cos(ca);
texX = px/2 + (float)Math.cos(ra)*(HEIGHT/2)*32/dy/raFix;
texY = py/2 - (float)Math.sin(ra)*(HEIGHT/2)*32/dy/raFix;
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
Color c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpF]];
g.setColor(getShade(c, ratio));
g.drawLine(drawX, y, drawX, y);
c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpC]];
g.setColor(getShade(c, ratio));
g.drawLine(drawX, HEIGHT-y, drawX, HEIGHT-y);
}
Notes:
My engine makes use of "textures" through the use of an int array and gets the java.awt.Color object that corresponds to the value from the texture array within a java.awt.Color array
My engine does not make the use of x-planes, y-planes, or z-buffers like other ray casters do as the engine only needs the ray's hit position, the ray's angle, the player's position, and the distance between the player position and ray's position to properly calculate what it needs to draw on screen.
My engine uses three int arrays, the first being mapW, which represents the wall layouts and textures for the walls. The second being mapF, which is the floor textures for the walkable spaces. And the third being mapC which is the same in concept as the floor int array, but for the ceiling.
Any potential solutions need to allow me easy access to the value of the floor/ceiling tile that I am getting the texture from as doing so will allow me to later implement more complex lighting/shading techniques.
If it would be more helpful, here is the entire java file that I am doing the raycasting in:
package launcher;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import SwingUI.ComputerController;
import SwingUI.Program;
import world.Texture;
//This class extends a class I made in another Java Project, which I exported as a JAR file
//The class Program, is a JFrame subclass that implements the game loop
public class Game extends Program{
private static final long serialVersionUID = 1L;
private int mapX = 16, mapY = 16, mapS = 64, mapLen = mapX*mapY;
private int door = 4;
private int[] mapW = {
1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2, //16
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //32
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //48
1,0,0,0,0,0,0,4, 0,0,0,0,0,0,0,2, //64
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //80
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //96
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //112
1,1,1,1,1,1,1,1, 2,2,2,4,2,2,2,2, //128
1,1,1,1,1,1,1,1, 3,3,3,0,3,3,3,3, //144
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //160
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //176
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //192
1,0,0,0,0,0,0,0, 4,0,0,0,0,0,0,3, //208
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //224
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //240
1,1,1,1,1,1,1,1, 3,3,3,3,3,3,3,3, //256
};
private int[] mapF = {
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //16
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //32
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //48
0,1,1,1,1,1,1,0, 1,1,1,1,1,1,1,0, //64
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //80
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //96
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //112
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //128
0,0,0,0,0,0,0,0, 0,0,0,1,0,0,0,0, //144
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //160
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //176
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //192
0,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,0, //208
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //224
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //240
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //256
};
private int[] mapC = {
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //16
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //32
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //48
0,2,2,2,2,2,2,0, 2,2,2,2,2,2,2,0, //64
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //80
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //96
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //112
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //128
0,0,0,0,0,0,0,0, 0,0,0,2,0,0,0,0, //144
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //160
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //176
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //192
0,2,2,2,2,2,2,2, 0,2,2,2,2,2,2,0, //208
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //224
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //240
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //256
};
private int[] textures = Texture.textures;
private Color[] colors = Texture.colors;
public Game(String title) {
super(title);
}
#Override
public void init() {
setResizable(false);
px = 100;
py = 100;
}
float px,centX,py,centY,pdx,pdy,pa;
float velX, velY;
boolean forward,backward,left,right;
boolean tl,tr;
/**
* Updates all game objects
* In this case, it handles the camera
*/
#Override
public void updateObjects() {
forward = ComputerController.keys[KeyEvent.VK_W];
left = ComputerController.keys[KeyEvent.VK_A];
backward = ComputerController.keys[KeyEvent.VK_S];
right = ComputerController.keys[KeyEvent.VK_D];
tl = ComputerController.keys[KeyEvent.VK_LEFT];
tr = ComputerController.keys[KeyEvent.VK_RIGHT];
if(tl)
pa -= 0.05;
if(tr)
pa += 0.05;
if(pa > PI2)
pa -= PI2;
if(pa < 0)
pa += PI2;
pdx = (float)Math.cos(pa)*5;
pdy = (float)Math.sin(pa)*5;
velX = 0;
velY = 0;
if(forward) {
velX += pdx;
velY += pdy;
}
else if(backward) {
velX -= pdx;
velY -= pdy;
}
if(left) {
velX += pdy;
velY -= pdx;
}
else if(right) {
velX -= pdy;
velY += pdx;
}
int mpx = (int)(centX/mapS), mpy = (int)(centY/mapS), mpxO = (int)((centX+velX)/mapS), mpyO = (int)((centY+velY)/mapS);
if(mapW[mpy*mapX + mpxO] == 0)
px+=velX;
if(mapW[mpyO*mapX + mpx] == 0)
py+=velY;
if(ComputerController.keys[KeyEvent.VK_E]) {
mpxO = (int)((centX+pdx*10)/mapS);
mpyO = (int)((py+pdy*10)/mapS);
if(mapW[mpyO*mapX + mpxO] == door)
mapW[mpyO*mapX + mpxO] = 0;
}
centX = px+3;
centY = py+3;
}
BasicStroke brushSize = new BasicStroke(16);
BasicStroke reset = new BasicStroke(1);
/**
* Draws everything to the screen
*/
#Override
public void renderObjects(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(reset);
g2d.setColor(Color.GREEN);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
castRays(g2d);
}
int fov = 64, fineness=4, totalRays = (int) (fov*fineness*(16/brushSize.getLineWidth())), renderDistance = 8;
float PI = (float) Math.PI, PI2 = (float) (2*Math.PI), PIHalf = (float) (Math.PI/2), PI3Half = (float) (3*Math.PI/2);
float DR = (float) Math.toRadians(1), DROffset = (float) (Math.toRadians(1)/fineness), drawXOffset = brushSize.getLineWidth()/fineness;
/**
* Implements Raycasting rendering
*/
private void castRays(Graphics2D g) {
int r,mxH,myH,mxV,myV,mpH,mpV,dofH,dofV;
float ra=fixAngle(pa-(DR*fov/2)),xo1=0,yo1=0, xo2=0,yo2=0;
g.setStroke(new BasicStroke(4));
for(r=0; r < totalRays; r++) {
dofH=0; dofV=0;
float aTan = (float) (-1/Math.tan(ra)), nTan = (float) -Math.tan(ra);
float distH = 1000000000,hx=centX,hy=centY,distV = 1000000000,vx=centX,vy=centY;
// Horizontal Line Check!!!!
if(ra>PI) {hy = (float) (((int)centY/mapS)*mapS-0.0001); hx = (centY-hy)*aTan+centX; yo1 = -mapS; xo1 = -yo1*aTan;}
else if(ra<PI) {hy = ((int)centY/mapS)*mapS+mapS; hx = (centY-hy)*aTan+centX; yo1 = mapS; xo1 = -yo1*aTan;}
else if(ra==PI || ra==0) {hx=centX;hy=centY;dofH=8;}
for(dofH=0; dofH < mapX; dofH++) {
mxH=(int)hx/mapS; myH=(int)hy/mapS; mpH=myH*mapX+mxH;
boolean withinMapH = hitWall(mpH);
if(withinMapH) {distH=getDistance(hx,hy,centX,centY);dofH=mapX;}
else {hx+=xo1;hy+=yo1;}
}
// Vertical Line Check!!!!
if(ra>PIHalf&&ra<PI3Half) {vx = (float) (((int)centX/mapS)*mapS-0.0001); vy = (centX-vx)*nTan+centY; xo2 = -mapS; yo2 = -xo2*nTan;}
else if(ra<PIHalf||ra>PI3Half) {vx = ((int)centX/mapS)*mapS+mapS; vy = (centX-vx)*nTan+centY; xo2 = mapS; yo2 = -xo2*nTan;}
else if(ra==PI||ra==0) {vy=centX;vx=centY;dofV=8;}
for(dofV=0; dofV < mapY; dofV++) {
mxV=(int)vx/mapS; myV=(int)vy/mapS; mpV=myV*mapX+mxV;
boolean withinMapV = hitWall(mpV);
if(withinMapV) {distV=getDistance(vx,vy,centX,centY);dofV=mapY;}
else {vx+=xo2;vy+=yo2;}
}
//Get Shortest Distance
float distT = 0, rx = 0, ry = 0;
if(distH<distV) {distT=distH;rx=hx;ry=hy;}
else if(distV<distH) {distT=distV;rx=vx;ry=vy;}
//Fix Fish-eye effect
float ca = fixAngle(pa-ra);
distT*=Math.cos(ca);
//Prepare for drawing Psuedo 3D Environment
float lineH = (mapS*WIDTH)/distT;
int drawX = r*(int)drawXOffset;
float stepY=32/lineH;
float texYOff=0;
if(lineH > HEIGHT) {
texYOff = (lineH-HEIGHT)/2;
lineH = HEIGHT;
}
float lineO = HEIGHT/2-lineH/2;
int mx=(int)rx/mapS, my=(int)ry/mapS, mp=my*mapX+mx, mt = mapW[mp]-1;
float texY=texYOff*stepY+mt*32, texX=0;
if(rx==vx) {
texX=(int)(ry/2)%32;
if(ra>PIHalf && ra<PI3Half) {texX = 31-texX;}
}
else {
texX=(int)(rx/2)%32;
if(ra<PI) {texX = 31-texX;}
}
//Draws Walls
float ratio = distT/(mapS*renderDistance);
for(int y = 0; y < (int)lineH; y++) {
Color c = colors[textures[(int)texY*32+(int)texX]];
g.setColor(getShade(c, ratio));
g.drawLine(drawX, y+(int)(lineO), drawX, y+(int)(lineO));
texY+=stepY;
}
//Draws Floor And Ceiling Tiles
for(int y = (int)(lineO+lineH); y<HEIGHT; y++)
{
float dy = y - ((float)HEIGHT/2f), raFix = (float) Math.cos(ca);
texX = px/2 + (float)Math.cos(ra)*(HEIGHT/2)*32/dy/raFix;
texY = py/2 - (float)Math.sin(ra)*(HEIGHT/2)*32/dy/raFix;
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
Color c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpF]];
g.setColor(c);
g.drawLine(drawX, y, drawX, y);
c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpC]];
g.setColor(c);
g.drawLine(drawX, HEIGHT-y, drawX, HEIGHT-y);
}
//Get Next Angle
ra = fixAngle(ra+DROffset);
}
}
/**
* Detects if a ray has hit a wall
* #param mp
* The world-position of the ray's end point
* #return
* True if the position is within bounds and is a wall tile
*/
public boolean hitWall(int mp) {
return (mp >= 0 && mp < mapLen && mapW[mp] > 0);
}
/**
* Resets angles to the correct value if they are above 2PI radians (360 degrees) or below 0 radians (0 degrees)
* #param angle
* The angle we are fixing
* #return
* The fixed angle
*/
public float fixAngle(float angle) {
if(angle > PI2) {
angle -= PI2;
}
if(angle < 0) {
angle += PI2;
}
return angle;
}
/**
*
* Gets the distance between two points
* #param ax
* the X Coordinate of the first point
* #param ay
* the Y Coordinate of the first point
*
* #param bx
* the X Coordinate of the second point
* #param by
* the Y Coordinate of the second point
* #return
* The distance between the two points
*/
public float getDistance(float ax, float ay, float bx, float by) {
return (float)Math.sqrt( (bx-ax)*(bx-ax) + (by-ay)*(by-ay) );
}
/**
*
* Gets a shade of a given color relative to the ratio given
* #param color
* The color we want to change
* #param ratio
* What percent it is close to the color black
* #return
* The new shade
*/
private Color getShade(Color color, float ratio){
int r = (int) Math.round(Math.max(0, color.getRed() - 255 * ratio));
int g = (int) Math.round(Math.max(0, color.getGreen() - 255 * ratio));
int b = (int) Math.round(Math.max(0, color.getBlue() - 255 * ratio));
int rgb = (r << 16) | (g << 8) | b;
return new Color(rgb);
}
/**
* Creates GUI on launch
*/
public static void main(String[] args) {
new Game("RAYCASTER");
}
}
The fixAngle method resets angles to the correct value if they are above 2PI radians (360 degrees) or below 0 radians (0 degrees). For example, an angle of 5PI/2 would return PI/2, and an angle of -3PI/2 would return PI/2
The getShade method returns a shade of a given color relative to the ratio given. The ratio is what percent (in decimal form) the desired color is close to the color black

I'll be a bit more helpful about answering your question. I recently had the same problem and was coincidentally working from the same tutorial as you were. I ran into the same problem as you and it took me 3 days to figure out the solution. I apologize for not understanding the maths behind it, as I am still in high school and I have not taken triginometry. However, after trial and error, I have found a solution. I really hope that this helps you with your project and good luck with future projects!
for (int y=(int)(lineO+lineH); y<height; y++)
{
//------------------------draw floor------------------------\\
float dy=y-(320/2.0), raFix=cos(ca);
tx=px/2 + cos(ra)*158*32/dy/raFix;
ty=py/2 + sin(ra)*158*32/dy/raFix;
int MP=mapF[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
float c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+MP]*0.7;
fill(c/1.3*255, c/1.3*255, c*255);//code to change the color of the boxes
rect(r*8+530, y, 8, 8);//code to draw the boxes
//------------------------draw ceiling------------------------\\
mp=mapC[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
fill(c/2.0*255, c/1.2*255, c/2.0*255); //code to change the color of the boxes
rect(r*8+530, 320-y, 8, 8); //code to draw the boxes
}

Related

SUNFLOW: Paint only contour edges of 3D mesh

This is literal copy of WireframeShader from really old and long time abandoned java app called Sunflow:
package org.sunflow.core.shader;
import org.sunflow.SunflowAPI;
import org.sunflow.core.ParameterList;
import org.sunflow.core.Shader;
import org.sunflow.core.ShadingState;
import org.sunflow.image.Color;
import org.sunflow.math.Matrix4;
import org.sunflow.math.Point3;
public class WireframeShader implements Shader {
private Color lineColor;
private Color fillColor;
private float width;
private float cosWidth;
public WireframeShader() {
lineColor = Color.BLACK;
fillColor = Color.WHITE;
// pick a very small angle - should be roughly the half the angular width of a pixel
width = (float) (Math.PI * 0.5 / 4096);
cosWidth = (float) Math.cos(width);
}
public boolean update(ParameterList pl, SunflowAPI api) {
lineColor = pl.getColor("line", lineColor);
fillColor = pl.getColor("fill", fillColor);
width = pl.getFloat("width", width);
cosWidth = (float) Math.cos(width);
return true;
}
public Color getMaterialColor() {
return lineColor;
}
public Color getFillColor(ShadingState state) {
return fillColor;
}
public Color getLineColor(ShadingState state) {
return lineColor;
}
public Color getRadiance(ShadingState state) {
Point3[] p = new Point3[3];
if (!state.getTrianglePoints(p)) {
return getFillColor(state);
}
// transform points into camera space
Point3 center = state.getPoint();
Matrix4 w2c = state.getWorldToCamera();
center = w2c.transformP(center);
for (int i = 0; i < 3; i++) {
p[i] = w2c.transformP(state.getInstance().transformObjectToWorld(p[i]));
}
float cn = 1.0f / (float) Math.sqrt(center.x * center.x + center.y * center.y + center.z * center.z);
for (int i = 0, i2 = 2; i < 3; i2 = i, i++) {
// compute orthogonal projection of the shading point onto each triangle edge as in:
// http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
float t = (center.x - p[i].x) * (p[i2].x - p[i].x);
t += (center.y - p[i].y) * (p[i2].y - p[i].y);
t += (center.z - p[i].z) * (p[i2].z - p[i].z);
t /= p[i].distanceToSquared(p[i2]);
float projx = (1 - t) * p[i].x + t * p[i2].x;
float projy = (1 - t) * p[i].y + t * p[i2].y;
float projz = (1 - t) * p[i].z + t * p[i2].z;
float n = 1.0f / (float) Math.sqrt(projx * projx + projy * projy + projz * projz);
// check angular width
float dot = projx * center.x + projy * center.y + projz * center.z;
if (dot * n * cn >= cosWidth) {
return getLineColor(state);
}
}
return getFillColor(state);
}
public void scatterPhoton(ShadingState state, Color power) {
}
#Override
public float getReflectionValue() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
It would render any 3D mesh so that every edge of mesh triangle would be painted thus creating a wireframe-like visual (see pic below)
My question is: does anybody know how to change/update the code (specifically getRadiance() method) so it would only paint contour edges of mesh so it would look like in the pic below?
This is harder than you think, because it cannot be done by using information from just a single triangle. You need to check all edges in the mesh and for each edge take the two faces which contain it. You draw the edge if and only if the normals of these two faces are not the same (differ enough).

Javelin throw simulation calculation of slope of tangent line at every javelin trajectory point

I am trying to simulate javelin throw on android. I calculate slope of tangent line in every point of javelin trajectory. To calculate trajectory coordinates I am using Projectile motion equations
x = (int) (x0 + v0 * t * Math.cos(radians)); //for coordinate x
and
y = (int) (y0 - v0 * t * Math.sin(radians) + 0.5 * g * t * t);
To calculate slope of tangent line to javelin trajectory I derivated this equation with respect to x:
y = Math.tan(radians) * x - g / (2 * Math.pow(v0, 2) * Math.pow(Math.cos(radians), 2)) * x^2
dy = Math.tan(radians) - (g * x) / (Math.pow(v0, 2) * Math.pow(Math.cos(radians), 2))
Problem is, that it works correctly with elevation angle < than approximately 60 degrees.
If elevation angle is bigger, it doesn't calculate correct slope.
Here is the code:
public class ThrowJavelin extends ImageView {
private Context mContext;
int x0 = -1;
int y0 = -1;
int x = x0;
int y = y0;
private Handler h;
private final int FRAME_RATE = 5;
private double t = 0;
private float g = 9.81f;
//initial velocity
private int v0;
//elevation angle in radians
private double radians;
//javelin current angle in degrees
private double javelin_angle;
public ThrowJavelin(Context context, AttributeSet attr) { super(context, attr); }
public ThrowJavelin(Context context, AttributeSet attrs, int defStyleAttr){ super(context, attrs, defStyleAttr); }
public ThrowJavelin(Context context, Bundle args) {
super(context);
mContext = context;
h = new Handler();
//input values
v0 = args.getInt("velocity");
radians = args.getDouble("radians");
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
protected void onDraw(Canvas c) {
Bitmap javelin = BitmapFactory.decodeResource(getResources(), R.drawable.jav);
DerivativeStructure alpha = null;
if (x < 0 && y < 0) {
x0 = 0;
y0 = c.getHeight() - 200;
x = x0;
y = y0;
javelin = rotateBitmap(javelin, (float) Math.toDegrees(radians));
} else if (y > y0) { //reset to beginning
x = x0;
y = y0;
t = 0;
javelin = rotateBitmap(javelin, (float) Math.toDegrees(radians));
} else {
//calculate current coordinates (depends on t)
x = (int) (x0 + v0 * t * Math.cos(radians));
y = (int) (y0 - v0 * t * Math.sin(radians) + 0.5 * g * t * t);
if (x == 0) {
javelin_angle = Math.toDegrees(radians);
} else {
// dy of 3rd equation
javelin_angle = Math.toDegrees(Math.tan(radians) - (g * x) / (Math.pow(v0, 2) * Math.pow(Math.cos(radians), 2)));
}
javelin = rotateBitmap(javelin, javelin_angle);
t += 0.3;
}
c.drawBitmap(javelin, x, y, null);
h.postDelayed(r, FRAME_RATE);
}
public Bitmap rotateBitmap(Bitmap image, double angle){
float alpha = (float) angle;
Matrix mat = new Matrix();
System.out.println(-alpha);
mat.postRotate(-alpha);
return Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), mat, true);
}
}
I really don't understand, why ot doesn't work correctly for bigger angles. Any ideas please?
Firstly, your solution for y(x) seems to drop a few variables (e.g. x0). Here is the full solution:
y(x) = y0 + (0.5 * g * (x - x0)^2)/(v0^2 * cos(radians)^2) - (x - x0) * tan(radians)
The derivative with respect to x is:
dy/dx = (g * (x - x0)) / (v0^2 * cos^2(radians)) - tan(radians)
Your solution looks very similar except that its y-axis is inverted and it misses the initial positions.
The angle that corresponds to this derivative is its arctangens:
double c = Math.cos(radians);
javelin_angle = Math.toDegrees(Math.atan((g * (x - x0) / (v0 * v0 * c * c) - Math.tan(radians)));
I assume, there was a reason why you swapped the y-axis. So you may do that again in this formula.
The reason why your formula worked for small angles is that the arctangens is close to the identity for small angles (identity in red, arctangens in blue):

Ellipse2D draws with poor accuracy

I'm making an application about space physics, so I do lots with orbits. Naturally, I encounter the Ellipse2D.Double to draw my orbits on the screen.
Whenever my JPanel refreshes, I draw the orbit of a body using an Ellipse2D, as well as the body itself with a different method.
Essentially, I discovered that when numbers get very large (whether it be the size of the orbits get large or the visualization is zoomed in very far), the position of the body and the Ellipse2D do not line up.
I calculate the position of the body using a conversion from polar coordinates to rectangular coordinates, and I leave the math for the Ellipse2D up to the geom package.
Take a look at this code sample. It's the most self-contained version of my problem that I can make, since scale of the circle has to be very large:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
The code above essentially draws a circle on the screen very far away with a large radius. I've picked the dimension so that a piece of the circle is visible in the small window.
Then it draws a line from the center of the circle to a point on the circle that's visible in the window: I picked an angle that was visible on the window and used geometry to convert that angle and the radius of the circle into rectangular coordinates.
This is what the program displays:
Notice that the line doesn't actually end up touching the ellipse. Now, I decided I had to find out whether it was the point I calculated or the ellipse that were incorrect. I did the math on my calculator, and found that the line was correct, and the ellipse incorrect:
Considering that the calculator is probably not wrong, I am led to believe the Ellipse2D is not drawing correctly. However, I tried many other angles, and this is the pattern I found:
And that leads me to believe the calculations are somehow wrong.
So that's my problem. Should I be using something other than Ellipse2D? Maybe Ellipse2D is not accurate enough? I used BigDecimals in my code sample because I thought it would give me more precision - is that the wrong approach? My ultimate goal is to be able to calculate the rectangular position of a point on an ellipse at a specific angle.
Thanks in advance.
You see this error because Ellipse2D is approximated by four cubic curves. To make sure just take a look at its path iterator defining shape border: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
To improve quality we should approximate ellipse by higher number of cubic curves. Here is an extention of standard java implementation with changeable number of segments:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
#Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
#Override
public void next() {
index++;
}
#Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
#Override
public boolean isDone() {
return index > getSegments() + 1;
}
#Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
#Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
And here is how you can use it in your code instead of standard one (I use 100 segments here):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));

How to make star shape in Java?

I'm trying to make some shapes with Java. I created two rectangles with two different colors but I want to create a star shape and I can't find useful source to help me doing this.
Here is my code:
import java.awt.*;
import javax.swing.*;
public class shapes extends JPanel{
#Override
public void paintComponent(Graphics GPHCS){
super.paintComponent(GPHCS);
GPHCS.setColor(Color.BLUE);
GPHCS.fillRect(25,25,100,30);
GPHCS.setColor(Color.GRAY);
GPHCS.fillRect(25,65,100,30);
GPHCS.setColor(new Color(190,81,215));
GPHCS.drawString("This is my text", 25, 120);
}
}
You could try using a polygon and some basic math:
int midX = 500;
int midY = 340;
int radius[] = {118,40,90,40};
int nPoints = 16;
int[] X = new int[nPoints];
int[] Y = new int[nPoints];
for (double current=0.0; current<nPoints; current++)
{
int i = (int) current;
double x = Math.cos(current*((2*Math.PI)/max))*radius[i % 4];
double y = Math.sin(current*((2*Math.PI)/max))*radius[i % 4];
X[i] = (int) x+midX;
Y[i] = (int) y+midY;
}
g.setColor(Color.WHITE);
g.fillPolygon(X, Y, nPoints);
You can also use existing classes e.g. http://java-sl.com/shapes.html for regular polygons and stars.
The Polygon class can be considered as a legacy class that has been there since Java 1.0, but should hardly be used any more in new code. The odd way of specifying the x/y coordinates in separate arrays, and, more importantly, the fact that it only supports int[] arrays limits its application areas. Although it implements the Shape interface, there are more modern implementations of this interface that can be used to represent polygons. In most cases, describing the polygon as a Path2D is easier and more flexible. One can create a Path2D p = new Path2D.Double(); and then do a sequence of moveTo and lineTo calls to geneate the desired shape.
The following program shows how the Path2D class may be used to generate star shapes. The most important method is the createStar method. It is very generic. It receives
the center coordinates for the star
the inner and outer radius of the star
the number of rays that the star should have
the angle where the first ray should be (i.e. the rotation angle of the star)
If desired, a simpler method may be wrapped around this one - as with the createDefaultStar example in the code below.
The program shows different stars, painted as lines and filled with different colors and radial gradient paints, as examples:
The complete program as a MCVE:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawStarShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new DrawStarShapePanel());
f.setSize(600, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class DrawStarShapePanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.draw(createDefaultStar(50, 200, 200));
g.setPaint(Color.RED);
g.fill(createStar(400, 400, 40, 60, 10, 0));
g.setPaint(new RadialGradientPaint(
new Point(400, 200), 60, new float[] { 0, 1 },
new Color[] { Color.RED, Color.YELLOW }));
g.fill(createStar(400, 200, 20, 60, 8, 0));
g.setPaint(new RadialGradientPaint(
new Point(200, 400), 50, new float[] { 0, 0.3f, 1 },
new Color[] { Color.RED, Color.YELLOW, Color.ORANGE }));
g.fill(createStar(200, 400, 40, 50, 20, 0));
}
private static Shape createDefaultStar(double radius, double centerX,
double centerY)
{
return createStar(centerX, centerY, radius, radius * 2.63, 5,
Math.toRadians(-18));
}
private static Shape createStar(double centerX, double centerY,
double innerRadius, double outerRadius, int numRays,
double startAngleRad)
{
Path2D path = new Path2D.Double();
double deltaAngleRad = Math.PI / numRays;
for (int i = 0; i < numRays * 2; i++)
{
double angleRad = startAngleRad + i * deltaAngleRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double relX = ca;
double relY = sa;
if ((i & 1) == 0)
{
relX *= outerRadius;
relY *= outerRadius;
}
else
{
relX *= innerRadius;
relY *= innerRadius;
}
if (i == 0)
{
path.moveTo(centerX + relX, centerY + relY);
}
else
{
path.lineTo(centerX + relX, centerY + relY);
}
}
path.closePath();
return path;
}
}
I have 2 method.
1)
public static Bitmap drawStar(int W, int H, int color, boolean andRing)
{
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
float midW ,min ,fat ,half ,radius;
if(andRing)
{
midW = W / 2;
min = Math.min(W, H);
half = min / 2;
midW = midW - half;
fat = min / 17;
radius = half - fat;
paint.setStrokeWidth(fat);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(midW + half, half, radius, paint);
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.5f, half * 0.84f);
path.lineTo( half * 1.5f, half * 0.84f);
path.lineTo( half * 0.68f, half * 1.45f);
path.lineTo( half * 1.0f, half * 0.5f);
path.lineTo( half * 1.32f, half * 1.45f);
path.lineTo( half * 0.5f, half * 0.84f);
}
else
{
min = Math.min(W, H);
half = min/2;
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.1f , half * 0.65f);
path.lineTo( half * 1.9f , half * 0.65f);
path.lineTo( half * 0.40f , half * 1.65f);
path.lineTo( half , 0 );
path.lineTo( half * 1.60f, half * 1.65f);
path.lineTo( half * 0.1f, half * 0.65f);
}
canvas.drawPath(path, paint);
return output;
}
2)
public static Bitmap drawStar(int W,int H,int spikes,int innerRadius,int outerRadius, int backColor,boolean border, int borderColor)
{
if(W < 10)
W = 10;
if(H < 10)
H = 10;
if(spikes < 5)
spikes = 5;
int smallL = W;
if(H < W)
smallL = H;
if(outerRadius > smallL/2)
outerRadius = smallL/2;
if(innerRadius < 5)
innerRadius = 5;
if(border)
{
outerRadius -=2;
innerRadius -=2;
}
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(backColor);
int cx = W/2;
int cy = H/2;
double rot = Math.PI / 2 * 3;
float x,y;
double step = Math.PI / spikes;
path.moveTo(cx, cy - outerRadius);
for (int i = 0; i < spikes; i++)
{
x = (float) (cx + Math.cos(rot) * outerRadius);
y = (float) (cy + Math.sin(rot) * outerRadius);
path.lineTo(x, y);
rot += step;
x = (float) (cx + Math.cos(rot) * innerRadius);
y = (float) (cy + Math.sin(rot) * innerRadius);
path.lineTo(x, y);
rot += step;
}
path.lineTo(cx, cy - outerRadius);
path.close();
canvas.drawPath(path, paint);
if(border)
{
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(borderColor);
canvas.drawPath(path, paint);
}
return output;
}

Perlin Noise in Java

For a cellular automata project I'm working on I need to generate two dimensional boolean arrays randomly using different algorithms and techniques. At the moment I have just one type of randomization in the application - looping through every cell in the array and generating a random double variable, then if the random number is higher than 0.5 then I set that cell to true, if not it gets set to false.
I would like to look into generating these boolean matrices using more interesting algorithms such as Perlin Noise or something like that. Noise generators that are used in terrain generation or something like that might be good if you know of any other than Perlin Noise (Minecraft's world generation gave me this idea).
The only problem is I have no idea where to start (any ideas?) :)
The first thing I thought of was a random displacement fractal. It is also used to generate terrain and is easier than Perlin Noise.
package so;
import java.util.Random;
public class Noise {
/** Source of entropy */
private Random rand_;
/** Amount of roughness */
float roughness_;
/** Plasma fractal grid */
private float[][] grid_;
/** Generate a noise source based upon the midpoint displacement fractal.
*
* #param rand The random number generator
* #param roughness a roughness parameter
* #param width the width of the grid
* #param height the height of the grid
*/
public Noise(Random rand, float roughness, int width, int height) {
roughness_ = roughness / width;
grid_ = new float[width][height];
rand_ = (rand == null) ? new Random() : rand;
}
public void initialise() {
int xh = grid_.length - 1;
int yh = grid_[0].length - 1;
// set the corner points
grid_[0][0] = rand_.nextFloat() - 0.5f;
grid_[0][yh] = rand_.nextFloat() - 0.5f;
grid_[xh][0] = rand_.nextFloat() - 0.5f;
grid_[xh][yh] = rand_.nextFloat() - 0.5f;
// generate the fractal
generate(0, 0, xh, yh);
}
// Add a suitable amount of random displacement to a point
private float roughen(float v, int l, int h) {
return v + roughness_ * (float) (rand_.nextGaussian() * (h - l));
}
// generate the fractal
private void generate(int xl, int yl, int xh, int yh) {
int xm = (xl + xh) / 2;
int ym = (yl + yh) / 2;
if ((xl == xm) && (yl == ym)) return;
grid_[xm][yl] = 0.5f * (grid_[xl][yl] + grid_[xh][yl]);
grid_[xm][yh] = 0.5f * (grid_[xl][yh] + grid_[xh][yh]);
grid_[xl][ym] = 0.5f * (grid_[xl][yl] + grid_[xl][yh]);
grid_[xh][ym] = 0.5f * (grid_[xh][yl] + grid_[xh][yh]);
float v = roughen(0.5f * (grid_[xm][yl] + grid_[xm][yh]), xl + yl, yh
+ xh);
grid_[xm][ym] = v;
grid_[xm][yl] = roughen(grid_[xm][yl], xl, xh);
grid_[xm][yh] = roughen(grid_[xm][yh], xl, xh);
grid_[xl][ym] = roughen(grid_[xl][ym], yl, yh);
grid_[xh][ym] = roughen(grid_[xh][ym], yl, yh);
generate(xl, yl, xm, ym);
generate(xm, yl, xh, ym);
generate(xl, ym, xm, yh);
generate(xm, ym, xh, yh);
}
/**
* Dump out as a CSV
*/
public void printAsCSV() {
for(int i = 0;i < grid_.length;i++) {
for(int j = 0;j < grid_[0].length;j++) {
System.out.print(grid_[i][j]);
System.out.print(",");
}
System.out.println();
}
}
/**
* Convert to a Boolean array
* #return the boolean array
*/
public boolean[][] toBooleans() {
int w = grid_.length;
int h = grid_[0].length;
boolean[][] ret = new boolean[w][h];
for(int i = 0;i < w;i++) {
for(int j = 0;j < h;j++) {
ret[i][j] = grid_[i][j] < 0;
}
}
return ret;
}
/** For testing */
public static void main(String[] args) {
Noise n = new Noise(null, 1.0f, 250, 250);
n.initialise();
n.printAsCSV();
}
}
I have some perlin noise implementations and some other noise generation functions in my library code:
http://code.google.com/p/mikeralib/source/browse/#svn%2Ftrunk%2FMikera%2Fsrc%2Fmain%2Fjava%2Fmikera%2Fmath
Feel free to explore / use (code is open source GPL, based on the J3D code).

Categories