SUNFLOW: Paint only contour edges of 3D mesh - java
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).
Related
Implementation of Floor Casting Producing Unexpected Errors
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 }
How to calculate if 3D model is intersecting terrain surface LWJGL
I am currently working on a project using 3d simplex noise and the marching cubes algorithm in order to procedurally generated terrain. I am trying to implement collision detection between the player object and terrain mesh but I have no clue how to start. I have read some articles and posts about using JBullet and other libraries but they do not support complex meshes such as the ones generated by simplex noise. In order to simplify things for myself I decided to make it so that the player can only move in the direction I point meaning that I would only need to check if a singular point on the player is intersecting with the terrain. Are there any methods to implement such a process? (edit: I've already looked into barycentric coordinates but I have no idea how to implement into the game) Current Player Code package Entities; import org.lwjgl.glfw.GLFW; import Engine.Input; import Maths.Vector3f; import Models.TexturedModel; public class Player extends Entity { public float xspeed = 0,zspeed = 0, yspeed = 0; public Vector3f mousePos; public float yrotation = 0, zrotation = 0; public float maxYRotation = 75f; private double lastMousePosX = 600 , newMousePosX; private double lastMousePosY = 500 , newMousePosY; private float speed = 3; public Player(TexturedModel model, Vector3f position, float rotX, float rotY, float rotZ, float scale) { super(model, position, rotX, rotY, rotZ, scale); } public void move(){ checkInput(); System.out.println("x ="+this.position.x+" y ="+this.position.y+" z ="+this.position.z); checkCollision(); } public boolean checkCollision(){ if(terrain != null){ for(int i = 0; i<terrain.getVertices().length; i+=9){ Vector3f vertex1 = new Vector3f(terrain.getVertices()[i],terrain.getVertices()[i+1],terrain.getVertices()[i+2]); Vector3f vertex2 = new Vector3f(terrain.getVertices()[i+3],terrain.getVertices()[i+4],terrain.getVertices()[i+5]); Vector3f vertex3 = new Vector3f(terrain.getVertices()[i+6],terrain.getVertices()[i+7],terrain.getVertices()[i+8]); //Check if point p is interseting triangle (vertex1, vertex2, vertex3) if(someCalculationFunction(position, vertex1, vertex2, vertex3){ return true; } } } return false; } public void checkInput(){ newMousePosX = Input.getMouseX(); newMousePosY = Input.getMouseY(); float dx = (float)(newMousePosX-lastMousePosX)*0.07f; float dy = (float)(newMousePosY-lastMousePosY)*0.07f; if(!Input.isMouseDown(GLFW.GLFW_MOUSE_BUTTON_1)){ this.rotY -= dx/2; this.rotX -= dy*0.8f; } if(Math.abs(rotX) > 50){ this.rotX = Math.abs(rotX)/rotX*50; } if(this.rotY<0){ this.rotY = 360; } float horizontalDistance = speed*(float)(Math.cos(Math.toRadians(rotX))); float verticleDistance = speed*(float)(Math.sin(Math.toRadians(rotX))); if(Input.isKeyDown(GLFW.GLFW_KEY_W)){ this.position.x += horizontalDistance*Math.sin(Math.toRadians(-rotY)); this.position.z -= horizontalDistance*Math.cos(Math.toRadians(-rotY)); this.position.y += verticleDistance; }else if(Input.isKeyDown(GLFW.GLFW_KEY_S)){ this.position.x -= horizontalDistance*Math.sin(Math.toRadians(-rotY)); this.position.z += horizontalDistance*Math.cos(Math.toRadians(-rotY)); this.position.y -= verticleDistance; } lastMousePosX = newMousePosX; lastMousePosY = newMousePosY; } }
I'm not positive if I understood the question right, but this answer will address the problem of ensuring the players height is that of the terrain that it is standing on With barycentric coordinates you can calculate what the players height is supposed to be by using the heights of the three vertices that make up that triangle: public static float baryCentric(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f pos) { float det = (p2.z - p3.z) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.z - p3.z); float l1 = ((p2.z - p3.z) * (pos.x - p3.x) + (p3.x - p2.x) * (pos.y - p3.z)) / det; float l2 = ((p3.z - p1.z) * (pos.x - p3.x) + (p1.x - p3.x) * (pos.y - p3.z)) / det; float l3 = 1.0f - l1 - l2; return l1 * p1.y + l2 * p2.y + l3 * p3.y; } In order to get these three points you can make a calculation using the world coordinates of your player: //Assuming the world is constructed of equal height and width sized triangles float gridSquareSize = SIZE_OF_TERRAIN_MESH / NUM_TRIANGLES_PER_ROW; float xCoord = worldX % gridSquareSize / gridSquareSize; float zCoord = worldZ % gridSquareSize / gridSquareSize; With the xCoord and zCoord you can determine the 3 points that you need to use for your baryCentric calculation
why I cant rotate 2 points to draw in the same line
I have this code to draw some vertex and edges, and I have tried almost all the possibilities that were within my reach, but I believe the bug is in the method rotate or in the construtor but I'm not sure public static int CONFIG_NODE_DIAMETER = 20; //pixels //construtor public GraphDraw(Graph<V, E> graph) { //count of vertex int N = graph.numVertex(); double width = this.getWidth(); double height = this.getHeight(); Point2D center = new Point2D(width / 2, height / 2); double angleIncrement = 360f / N; //get all vertex from graph ArrayList<Vertex<V>> vertex = graph.getVertex(); //draw the line and point boolean first = true; Point2D p = null; for (Vertex<V> v : vertex ) { { if (first) { if (width > height) { p = new Point2D(center.getX(), center.getY() - height / 2 + CONFIG_NODE_DIAMETER * 2); } else { p = new Point2D(center.getX(), center.getY() - width / 2 + CONFIG_NODE_DIAMETER * 2); } first = false; } else { p = rotate(p, center, angleIncrement); } } } } the method that makes the rotation between 2 points private static Point2D rotate(Point2D point, Point2D pivot, double angle_degrees) { double angle = Math.toRadians(angle_degrees); double sin = Math.sin(angle); double cos = Math.cos(angle); //translate to origin Point2D result = point.subtract(pivot); // rotate point Point2D rotatedOrigin = new Point2D( result.getX() * cos - result.getY() * sin, result.getX() * sin + result.getY() * cos); // translate point back result = rotatedOrigin.add(pivot); return result; } I wanna do like the image below and I tried to rotate but it is not working any suggestion? in this link, you can check all method in class GraphDraw, and I dont put because the post would be extensive
Tile Projection Google Maps: Convert Distance to Screen Dimensions
I am using the CanvasTileProvider in Google Maps Android v2. I can convert lat long points to screen pixels. However I would like to create a method to convert a distance to screen pixels. This will allow me to draw a circle of x radius. Can anyone help with this? The code below I have butchered and modified from somewhere else so credit to the original author. /** * Converts between LatLng coordinates and the pixels inside a tile. */ public class TileProjection { public int x; public int y; private int zoom; private int TILE_SIZE; private DoublePoint pixelOrigin_; private double pixelsPerLonDegree_; private double pixelsPerLonRadian_; TileProjection(int tileSize, int x, int y, int zoom) { this.TILE_SIZE = tileSize; this.x = x; this.y = y; this.zoom = zoom; pixelOrigin_ = new DoublePoint(TILE_SIZE / 2, TILE_SIZE / 2); pixelsPerLonDegree_ = TILE_SIZE / 360d; pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI); } /** * Get the dimensions of the Tile in LatLng coordinates */ public LatLngBounds getTileBounds() { DoublePoint tileSW = new DoublePoint(x * TILE_SIZE, (y + 1) * TILE_SIZE); DoublePoint worldSW = pixelToWorldCoordinates(tileSW); LatLng SW = worldCoordToLatLng(worldSW); DoublePoint tileNE = new DoublePoint((x + 1) * TILE_SIZE, y * TILE_SIZE); DoublePoint worldNE = pixelToWorldCoordinates(tileNE); LatLng NE = worldCoordToLatLng(worldNE); return new LatLngBounds(SW, NE); } /** * Calculate the pixel coordinates inside a tile, relative to the left upper * corner (origin) of the tile. */ public PointF latLngToPoint(LatLng latLng) { DoublePoint result = new DoublePoint(1, 1); // Log.d("Aero","x " + String.valueOf(x)); // Log.d("Aero","y " + String.valueOf(y)); latLngToWorldCoordinates(latLng, result); worldToPixelCoordinates(result, result); result.x -= x * TILE_SIZE; int numTiles = 1 << zoom; if (latLng.longitude < 0) { result.x = result.x + (numTiles * TILE_SIZE); } result.y -= y * TILE_SIZE; return new PointF((float) result.x, (float) result.y); } private DoublePoint pixelToWorldCoordinates(DoublePoint pixelCoord) { int numTiles = 1 << zoom; DoublePoint worldCoordinate = new DoublePoint(pixelCoord.x / numTiles, pixelCoord.y / numTiles); return worldCoordinate; } /** * Transform the world coordinates into pixel-coordinates relative to the * whole tile-area. (i.e. the coordinate system that spans all tiles.) * <p/> * <p/> * Takes the resulting point as parameter, to avoid creation of new objects. */ private void worldToPixelCoordinates(DoublePoint worldCoord, DoublePoint result) { int numTiles = 1 << zoom; result.x = worldCoord.x * numTiles; result.y = worldCoord.y * numTiles; } private LatLng worldCoordToLatLng(DoublePoint worldCoordinate) { DoublePoint origin = pixelOrigin_; double lng = (worldCoordinate.x - origin.x) / pixelsPerLonDegree_; double latRadians = (worldCoordinate.y - origin.y) / -pixelsPerLonRadian_; double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2); return new LatLng(lat, lng); } /** * Get the coordinates in a system describing the whole globe in a * coordinate range from 0 to TILE_SIZE (type double). * <p/> * Takes the resulting point as parameter, to avoid creation of new objects. */ private void latLngToWorldCoordinates(LatLng latLng, DoublePoint result) { DoublePoint origin = pixelOrigin_; result.x = origin.x + latLng.longitude * pixelsPerLonDegree_; // Truncating to 0.9999 effectively limits latitude to 89.189. This is // about a third of a tile past the edge of the world tile. double siny = bound(Math.sin(Math.toRadians(latLng.latitude)), -0.9999, 0.9999); result.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -pixelsPerLonRadian_; } ; /** * Return value reduced to min and max if outside one of these bounds. */ private double bound(double value, double min, double max) { value = Math.max(value, min); value = Math.min(value, max); return value; } /** * A Point in an x/y coordinate system with coordinates of type double */ public static class DoublePoint { double x; double y; public DoublePoint(double x, double y) { this.x = x; this.y = y; } } } This is what I am proposing to use: public Double MetersToPixels(LatLng latLng, Double distance){ double tileScale = TILE_SIZE / 256; double pixelsPerMeter =1 / (156543.03392 * Math.cos(latLng.latitude * Math.PI / 180) / Math.pow(2, zoom)) * tileScale; return pixelsPerMeter * distance; }
At first you should be aware of the fact, that a circle on the surface of the earth is not exactly a circle on the map. But if you ignore this inaccuracy, you just need to create a LatLng point in 25nm distance, and then use latLngToPoint method to get the pixels. Comparing them with the pixels of the center, gives you the radius. For creating a LatLng in a given distance see the answer to this SO question (method move)
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()));