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
Related
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).
I am unable to create several instances of the waveClock object even though I have put it in an array and marked the centre positions for each object. I would like to create 4 objects in one window, all responding to different sound frequencies/beat onsets etc
Could someone shed some light on how to go about this? I believe it may be an issue with the centerX and centerY variables in the waveClock class
ArrayList<waveClock> waveClocks = new ArrayList<waveClock>();
//global variables
float angnoise, radiusnoise;
float xnoise, ynoise;
float angle = -PI/6;
float radius;
float strokeCol = 254;
int strokeChange = -1;
int speed; //changes speed of visualisation once beat is detected?
void setup()
//for every waveClock we need 180 pixels width, then add 20 pixels for first gap
size(740, 650);
background(255);
//code is called
waveClocks.add(new waveClock(100, height/2, minRadius, bassColour, lowBassBand, highBassBand, numberOfLowOnsetsThreshold));
waveClocks.add(new waveClock(280, height/2, minRadius, midColour, lowMidBand, highMidBand, numberOfMidOnsetsThreshold));
waveClocks.add(new waveClock(460, height/2, minRadius, highColour, lowHighBand, highHighBand, numberOfHighOnsetsThreshold));
waveClocks.add(new waveClock(640, height/2, minRadius, veryHighColour, lowVeryHighBand, highVeryHighBand, numberOfVeryHighOnsetsThreshold));
//set the min and max radius of each of the viz circles
/* for (int i = 0; i < waveClocks.size(); i++) {
//go through the arraylist of waveClocks and set the min and max radius of each circle
waveClocks.get(i).setMinMaxRadius(minRadius, maxRadius);
}*/
song.play();
beat = new BeatDetect(song.bufferSize(), song.sampleRate());
bl = new BeatListener(beat, song);
}
void draw() {
//clear the screen by painting it black
//background(0);
for (int i = 0; i < waveClocks.size(); i++) {
//has there been a beat in the range? get(circle ID).low band, high band etc.
if (beat.isRange(waveClocks.get(i).getLowBand(), waveClocks.get(i).getHighBand(), waveClocks.get(i).getOnsetThreshold())) {
waveClocks.get(i).setMaxRadius();
}
//waveClocks.get(i).drawCircle();
waveClocks.get(i).drawWaveClock();
}
}
waveClock class in a separate tab
//class is an architecture blueprint
//objects are the actual buildings built from the methods (can make as many as you like)
//constructor is the builder/constructor literally
class waveClock {
float centerX; //co-ordinates of circle's position
float centerY; //co-ordinates of circle's position
float radius; //avg radius
// float minRadius; //smallest size it can be
// float maxRadius; //biggest size it can be
color col; //colour
int onsetThreshold; //
int lowBand; //looks at lowest band of frequency and makes circle sensitive to it
int highBand; //looks at highest band of frequency and makes circle sensitive to it
boolean onset; //has there been an onset (beat has occurred or not?)
//the constructor
waveClock(float x, float y, float r, color c, int lb, int hb, int t) {
centerX = x;
centerY = y;
radius = r;
col = c;
lowBand = lb;
highBand = hb;
onsetThreshold = t;
}
void drawWaveClock() {
radiusnoise += 0.005;
radius = (noise(radiusnoise)*350) + 1;
angnoise += 0.005;
angle += (noise(angnoise)*6) - 3;
if (angle > 360) {
angle -= 360;
} else if (angle < 0) {
angle += 360;
}
xnoise += 0.01;
ynoise =+ 0.01;
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
float rad = radians(angle);
float x1 = centerX + (radius*cos(rad));
float y1 = centerY + (radius*sin(rad));
float opprad = rad + PI;
float x2 = centerX + (radius*cos(opprad));
float y2 = centerY + (radius*sin(opprad));
strokeCol += strokeChange;
if (strokeCol > 354) {
strokeChange = -1;
} else if (strokeCol < 0) {
strokeChange = 1;
}
stroke(strokeCol, 60);
strokeWeight(1);
line(x1, y1, x2, y2);
}
}
You aren't ever using the class-level centerX and centerY variables. Instead, you're recalculating a new centerX and centerY in the drawWaveClock() function.
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
These are all drawn from the center of the screen, so the waves will end up in the same position.
In the future, please try to narrow your problem down to a MCVE that demonstrates the problem. Also please use proper naming conventions- classes start with an upper-case letter, for example. Good luck.
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()));
Currently I have an ArrayList of vertices in a 3-dimensional cartesian coordinates system. The polygon is random. It can be a car, a cup or even a dragon.
Assuming the density does not change, how to calculate the centre of mass (x,y,z) of this 3D object?
I am storing the faces and vertices in ArrayList.
public ArrayList<stlFace> StlFaces = new ArrayList<stlFace>();
public ArrayList<VertexGeometric> VertexList = new ArrayList<VertexGeometric>();
I was using this for calculating surface which is proportional to mass of each face or triangle. And to calculate center off mass of each triangle and center of mass of whole object I was using this. I added helper methods getCenter() and getSurface() to Face class to encapsulate calculations specific to just one face/triangle.
public static class Vertex {
public float x = 0;
public float y = 0;
public float z = 0;
public Vertex(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
}
public static class Face {
public Vertex v1;
public Vertex v2;
public Vertex v3;
public Face(Vertex v1, Vertex v2, Vertex v3) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
public Vertex getCenter() {
Vertex triangleCenter = new Vertex(0, 0, 0);
triangleCenter.x += v1.x;
triangleCenter.x += v2.x;
triangleCenter.x += v3.x;
triangleCenter.y += v1.y;
triangleCenter.y += v2.y;
triangleCenter.y += v3.y;
triangleCenter.z += v1.z;
triangleCenter.z += v2.z;
triangleCenter.z += v3.z;
triangleCenter.x /= 3;
triangleCenter.y /= 3;
triangleCenter.z /= 3;
return triangleCenter;
}
public float getSurface() {
float x1 = v1.x - v2.x;
float x2 = v1.y - v2.y;
float x3 = v1.z - v2.z;
float y1 = v1.x - v3.x;
float y2 = v1.y - v3.y;
float y3 = v1.z - v3.z;
return (float) Math.sqrt(
Math.pow(x2 * y3 - x3 * y2, 2) +
Math.pow(x3 * y1 - x1 * y3, 2) +
Math.pow(x1 * y2 - x2 * y1, 2)
) / 2f;
}
}
public static Vertex calculateMassCenter(List<Face> faces) {
Vertex massCenter = new Vertex(0, 0, 0);
float mass = 0;
for (Face face : faces) {
Vertex triangleCenter = face.getCenter();
float faceMass = face.getSurface();
mass += faceMass;
massCenter.x += faceMass * triangleCenter.x;
massCenter.y += faceMass * triangleCenter.y;
massCenter.z += faceMass * triangleCenter.z;
}
massCenter.x /= mass;
massCenter.y /= mass;
massCenter.z /= mass;
return massCenter;
}
I tried to implement collision between models in my LWJGL game and it seems that the objects are in constant collision, even when the collision radius is just 0.I have put the code for the collision below, as well as a link to a source that I was using to help with the bounding sphere collision.
package model;
import org.lwjgl.util.vector.Vector3f;
public class BoundingSphere {
private Vector3f mid = new Vector3f();
private float radius;
public BoundingSphere(Vector3f midpoint, float radius) {
this.mid = midpoint;
this.radius = radius;
}
public boolean isColliding(BoundingSphere other){
float diffX = (other.mid.x - mid.x);
float diffY = (other.mid.y - mid.y);
float diffZ = (other.mid.z - mid.z);
float diffXSquared = (float) Math.pow(diffX, 2);
float diffYSquared = (float) Math.pow(diffY, 2);
float diffZSquared = (float) Math.pow(diffZ, 2);
float radiusSums = (other.radius + radius);
float radiusSumsSquared = (float)Math.pow(radiusSums, 2);
if (diffXSquared + diffYSquared + diffZSquared > radiusSumsSquared){
return true;
}
else{
return false;
}
}
}
Collision Detection Page
It appears that you have inverted the condition. It is colliding only if:
((x2 + y2 + z2) <= r2)
If you want overlap instead of collision then "<=" will be "<"