java-processing floating point rounding error, how to keep radians rational - java

I have modified this arcball class so that every call to arcball.rollforward(PI/180); rotates a matrix 1 degree.
I have tried to set it up so arcball.rollback() is called with the accumulated float rotatebywithincludedfloaterror but it has had the same degree error as rolling back 360 degrees without the float error.
this is how far it is off after 1000 full rotations, it should be a 1:1 reflection of the top cube over x
here is main function with a loop of 1 * 360 degree rotation and framerate for testing (set framerate to 900 for multiple rotations so it dose not take forever)
Arcball arcball;
int i;
//framecount
int fcount, lastm;
float frate;
int fint = 3;
boolean[] keys = new boolean[13];
final int w = 0;
void setup() {
size(900, 700, P3D);
frameRate(60);
noStroke();
arcball = new Arcball(width/2, height/2, 100); //100 is radius
}
void draw() {
lights();
background(255,160,122);
print(" \n degree = " + i );
i++;
if(i <= (360 * 1)) { arcball.rollforward(PI/180); }
else { print(" break"); }
if(keys[w]) { arcball.rollforward(PI/180); }
translate(width/2, height/2-100, 0);
box(50);
translate(0, 200, 0);
arcball.run();
box(50);
fcount += 1;
int m = millis();
if (m - lastm > 1000 * fint) {
frate = float(fcount) / fint;
fcount = 0;
lastm = m;
println("fps: " + frate);
}
}
void keyPressed() {
switch(key) {
case 119:
keys[w] = true;
break;
}
}
void keyReleased() {
switch(key) {
case 119:
keys[w] = false;
break;
}
}
and the arcball class
// Ariel and V3ga's arcball class with a couple tiny mods by Robert Hodgin and smaller mods by cubesareneat
class Arcball {
float center_x, center_y, radius;
Vec3 v_down, v_drag;
Quat q_now, q_down, q_drag;
Vec3[] axisSet;
int axis;
float mxv, myv;
float x, y;
float degreeW_count = 0;
float degreeS_count = 0;
float rotatebywithincludedfloaterror =0;
Arcball(float center_x, float center_y, float radius){
this.center_x = center_x;
this.center_y = center_y;
this.radius = radius;
v_down = new Vec3();
v_drag = new Vec3();
q_now = new Quat();
q_down = new Quat();
q_drag = new Quat();
axisSet = new Vec3[] {new Vec3(1.0f, 0.0f, 0.0f), new Vec3(0.0f, 1.0f, 0.0f), new Vec3(0.0f, 0.0f, 1.0f)};
axis = -1; // no constraints...
}
void rollforward(float radians2turn) {
rotatebywithincludedfloaterror = rotatebywithincludedfloaterror + (-1 * (((sin(radians2turn) * radius))/2));
if(degreeW_count >= 360) {
arcball.rollback(rotatebywithincludedfloaterror);
degreeW_count = 0;
rotatebywithincludedfloaterror = 0;
}
rollortilt(0, -1 * (((sin(radians2turn) * radius))/2));
degreeW_count = degreeW_count + 1; // need to edit this later to work with rotations other then 1 degree
}
void rollback(float radians2turn) {
rollortilt(0, ((sin(radians2turn) * radius))/2);
}
void rollortilt(float xtra, float ytra){
q_down.set(q_now);
v_down = XY_to_sphere(center_x, center_y);
q_down.set(q_now);
q_drag.reset();
v_drag = XY_to_sphere(center_x + xtra, center_y + ytra);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
/*
void mousePressed(){
v_down = XY_to_sphere(mouseX, mouseY);
q_down.set(q_now);
q_drag.reset();
}
void mouseDragged(){
v_drag = XY_to_sphere(mouseX, mouseY);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
*/
void run(){
q_now = Quat.mul(q_drag, q_down);
applyQuat2Matrix(q_now);
x += mxv;
y += myv;
mxv -= mxv * .01;
myv -= myv * .01;
}
Vec3 XY_to_sphere(float x, float y){
Vec3 v = new Vec3();
v.x = (x - center_x) / radius;
v.y = (y - center_y) / radius;
float mag = v.x * v.x + v.y * v.y;
if (mag > 1.0f){
v.normalize();
} else {
v.z = sqrt(1.0f - mag);
}
return (axis == -1) ? v : constrain_vector(v, axisSet[axis]);
}
Vec3 constrain_vector(Vec3 vector, Vec3 axis){
Vec3 res = new Vec3();
res.sub(vector, Vec3.mul(axis, Vec3.dot(axis, vector)));
res.normalize();
return res;
}
void applyQuat2Matrix(Quat q){
// instead of transforming q into a matrix and applying it...
float[] aa = q.getValue();
rotate(aa[0], aa[1], aa[2], aa[3]);
}
}
static class Vec3{
float x, y, z;
Vec3(){
}
Vec3(float x, float y, float z){
this.x = x;
this.y = y;
this.z = z;
}
void normalize(){
float length = length();
x /= length;
y /= length;
z /= length;
}
float length(){
return (float) Math.sqrt(x * x + y * y + z * z);
}
static Vec3 cross(Vec3 v1, Vec3 v2){
Vec3 res = new Vec3();
res.x = v1.y * v2.z - v1.z * v2.y;
res.y = v1.z * v2.x - v1.x * v2.z;
res.z = v1.x * v2.y - v1.y * v2.x;
return res;
}
static float dot(Vec3 v1, Vec3 v2){
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
static Vec3 mul(Vec3 v, float d){
Vec3 res = new Vec3();
res.x = v.x * d;
res.y = v.y * d;
res.z = v.z * d;
return res;
}
void sub(Vec3 v1, Vec3 v2){
x = v1.x - v2.x;
y = v1.y - v2.y;
z = v1.z - v2.z;
}
}
static class Quat{
float w, x, y, z;
Quat(){
reset();
}
Quat(float w, float x, float y, float z){
this.w = w;
this.x = x;
this.y = y;
this.z = z;
}
void reset(){
w = 1.0f;
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
void set(float w, Vec3 v){
this.w = w;
x = v.x;
y = v.y;
z = v.z;
}
void set(Quat q){
w = q.w;
x = q.x;
y = q.y;
z = q.z;
}
static Quat mul(Quat q1, Quat q2){
Quat res = new Quat();
res.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z;
res.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y;
res.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z;
res.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x;
return res;
}
float[] getValue(){
// transforming this quat into an angle and an axis vector...
float[] res = new float[4];
float sa = (float) Math.sqrt(1.0f - w * w);
if (sa < EPSILON){
sa = 1.0f;
}
res[0] = (float) Math.acos(w) * 2.0f;
res[1] = x / sa;
res[2] = y / sa;
res[3] = z / sa;
return res;
}
}
keep track of the floating error margin to return same number of degrees arcball.rollforward()
void rollforward(float radians2turn) {
rotatebywithincludedfloaterror = rotatebywithincludedfloaterror + (-1 * (((sin(radians2turn) * radius))/2));
if(degreeW_count >= 360) {
arcball.rollback(rotatebywithincludedfloaterror);
degreeW_count = 0;
rotatebywithincludedfloaterror = 0;
}
rollortilt(0, -1 * (((sin(radians2turn) * radius))/2));
degreeW_count = degreeW_count + 1; // need to edit this later to work with rotations other then 1 degree
}

using my idea in the question to reset every 2*PI
if(keys[w]) {
arcball.rollforward(PI/180);
degreeW_count = degreeW_count + 1;
}
if(degreeW_count == 360) {
arcball = new Arcball(width/2, height/2, 100); // setset to original arcball at 0 degrees
degreeW_count = 0;
}
in arcball
void rollforward(float degrees2turn) {
rollortilt(0, -1 * (((sin(degrees2turn) * radius))/2)); // one degree forward 180/PI
}
this totally circumvents the any rounding error that would accumulate with any data type using irrational numbers and periodic functions!

Related

circle collision causes circle to go supersonic

can anyone tell me why the circles i draw are getting stuck on each other or going super fast and flying off the window. all i want is for the circles to bounce off each other, but instead they get stuck 1/3 of the time, accelerate drastically another 1/3 of the time and only bounce the other 1/3. sorry if its messy, i've been trying a lot of different ideas and it kind of got out of hand. code uses maths that i got from gist.github/christopher4lis/f9ccb589ee8ecf751481f05a8e59b1dc. its horribly done but its the best i can do for now.
Please let me know if there is any other code that you need to help fix this.
public void bounce() {
collision = false;
for (int i = 0; i < size; i++) {
for (int a = 0; a < size; a++) {
float xVelDiff = dots[i][2] - dots[a][2];
float yVelDiff = dots[i][3] - dots[a][3];
float xDist = dots[i][0] - dots[a][0];
float yDist = dots[i][1] - dots[a][1];
if (xVelDiff * xDist + yVelDiff * yDist <= 0) {
angle = (float) -Math.atan2(dots[a][0] - dots[i][0], dots[a][1] - dots[i][1]);
float m1 = dots[i][7];
BigDecimal bd = new BigDecimal(m1).setScale(2, RoundingMode.HALF_UP);
m1 = bd.floatValue();
float m2 = dots[a][7];
float[] u1 = rotate(dots[i][2], dots[i][3], (float) angle);
float[] u2 = rotate(dots[a][2], dots[a][3], (float) angle);
float[] v1 = new float[2];
v1[0] = u1[0] * (m1 - m2) / (m1 + m2) + u2[0] * 2 * m2 / (m1 + m2);
v1[1] = u1[1];
float[] v2 = new float[2];
v2[0] = u2[0] * (m1 - m2) / (m1 + m2) + u1[0] * 2 * m2 / (m1 + m2);
v2[1] = u2[1];
float[] vFinal1 = rotate(v1[0], v1[1], (float) -angle);
float[] vFinal2 = rotate(v2[0], v2[1], (float) -angle);
if (a != i && !(dots[a][0] == 0 && dots[a][1] == 0)) {
boolean thisCollision = (dots[a][0] - dots[i][0]) * (dots[a][0] - dots[i][0]) + (dots[a][1] - dots[i][1]) * (dots[a][1] - dots[i][1]) <= (dots[a][4] + dots[i][4]) * (dots[a][4] + dots[i][4]);
// System.out.println("collision: "+collision+" i="+i+" a="+a);
if (thisCollision) {
System.out.println(vFinal2[0] + " " + vFinal2[1]);
collision = true;
dots[i][2] = vFinal1[0];
dots[i][3] = vFinal1[1];
dots[a][2] = vFinal2[0];
dots[a][3] = vFinal2[1];
return;
}
}
}
}
}
}
public float[] rotate(float velocityX, float velocityY, float angle) {
float x1 = (float) (velocityX * Math.cos(angle) - velocityY * Math.sin(angle));
float y1 = (float) (velocityX * Math.cos(angle) - velocityY * Math.sin(angle));
float vel[] = new float[2];
vel[0] = x1;
vel[1] = y1;
return vel;
}

Java draw 3d points to 2d system using 3d projection

I have tried to follow the Wikipedia page on 3d projection to draw my own 3d line using java and AWT with Swing.
The output file that is created as a result of the rendering doesn't contain anything.
What have I done wrong?
Why isn't this producing an image file that has points on it?
Are my formulas off?
I was following this Wikipedia page as my reference: https://en.wikipedia.org/wiki/3D_projection#Perspective_projection
I have a set of points which are generated using a loop.
I then calculate the position of the 3d coordinate to 2d without using matrices, like one of the suggested method in the link.
Have I miss-interpreted something or not implemented something?
Any help and feedback are welcome.
I known nothing is drawn to the window. That isn't a priority at the moment. The priority it's getting the algorithms to work.
public class Window {
JFrame f = new JFrame();
public Window() {
JPanel p = new JPanel();
render();
f.add(p);
f.setSize(500, 500);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
Window w = new Window();
}
void render() {
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(Color.red);
for (int i = 0; i < 10; i++) {
Vector3 point = new Vector3(1 + i, 2, 1 + i);
int x = calculateBX(point, new Vector3(0, 0, 0));
int y = calculateBY(point, new Vector3(1, 1, 1));
g.drawLine(x, y, x, y);
}
try {
File outputfile = new File("saved.png");
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
}
double calculatDX(Vector3 v, Vector3 camera) {
double cx = Math.cos(0);
double cy = Math.cos(0);
double cz = Math.cos(0);
double sx = Math.sin(0);
double sy = Math.sin(0);
double sz = Math.sin(0);
int x = v.getX() - camera.getX();
int y = v.getY() - camera.getY();
int z = v.getZ() - camera.getZ();
double dx = cy * ((sz) * (y) + (cz) * (x)) - (sy) * (z);
return dx;
}
double calculatDY(Vector3 v, Vector3 camera) {
double cx = Math.cos(0);
double cy = Math.cos(0);
double cz = Math.cos(0);
double sx = Math.sin(0);
double sy = Math.sin(0);
double sz = Math.sin(0);
int x = v.getX() - camera.getX();
int y = v.getY() - camera.getY();
int z = v.getZ() - camera.getZ();
double dy = sx * ((cy) * (z) + (sy) * ((sz) * (y) + (cz) * (x))) + (cx) * (cz * (y) - sz * x);
return dy;
}
double calculatDZ(Vector3 v, Vector3 camera) {
double cx = Math.cos(0);
double cy = Math.cos(0);
double cz = Math.cos(0);
double sx = Math.sin(0);
double sy = Math.sin(0);
double sz = Math.sin(0);
int x = v.getX() - camera.getX();
int y = v.getY() - camera.getY();
int z = v.getZ() - camera.getZ();
double dz = cx * ((cy) * (z) + (sy) * ((sz) * (y) + (cz) * (x))) - (sx) * (cz * (y) - sz * x);
return dz;
}
int calculateBX(Vector3 v, Vector3 camera) {
int ez = camera.getZ();
int ex = camera.getX();
double dz = calculatDZ(v, camera);
double dx = calculatDX(v, camera);
return (int) ((ez / dz) * dx) - ex;
}
int calculateBY(Vector3 v, Vector3 camera) {
int ez = camera.getZ();
int ey = camera.getY();
double dz = calculatDZ(v, camera);
double dy = calculatDY(v, camera);
return (int) ((ez / dz) * dy) - ey;
}
}
Vector Class
public class Vector3 {
private int x, y, z;
public Vector3(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
}

elastic collision of balls

i am having a bug which i can't figure out.I tried many times,the collision detection and calculating new velocities seems fine ,but some balls seem to stuck with each other i don't why.Can you please help me out.
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.JFrame;
public class ElasticCollision extends Canvas implements Runnable {
private static final int WIDTH = 300;
private static final int HEIGHT = WIDTH / 16 * 9;
private static final int SCALE = 3;
private static final String TITLE = "Elastic collision";
private boolean running = false;
private JFrame frame;
private Thread thread;
private Random random = new Random();
private Color color;
private int a, b, c;
private Ball[] ball;
private int x = 0, y = 0;
private int radius = 0;
private int speedX = 0, speedY = 0;
private int noOfBalls = 25;
private double newVelX1 = 0, newVelY1 = 0;
private double newVelX2 = 0, newVelY2 = 0;
private double angle1 = 0, angle2 = 0, angle3 = 0;
private int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
public ElasticCollision() {
Dimension size = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
setPreferredSize(size);
frame = new JFrame();
ball = new Ball[noOfBalls];
}
public void start() {
for (int i = 0; i < noOfBalls; i++) {
x = random.nextInt(getWidth());
y = random.nextInt(getHeight());
radius = 25 + random.nextInt(25);
speedX = 1 + random.nextInt(2);
speedY = 1 + random.nextInt(2);
ball[i] = new Ball(x, y, radius, speedX, speedY);
}
running = true;
thread = new Thread(this);
thread.start();
}
public void stop() {
running = false;
}
public void run() {
long lastTime = System.nanoTime();
double unprocessed = 0;
double nsPerTick = 1000000000.0 / 60;
int frames = 0;
int ticks = 0;
long lastTimer = System.currentTimeMillis();
while (running) {
long now = System.nanoTime();
unprocessed += (now - lastTime) / nsPerTick;
lastTime = now;
while (unprocessed >= 1) {
ticks++;
update();
unprocessed -= 1;
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < noOfBalls; i++) {
for (int j = i + 1; j < noOfBalls; j++) {
if (ball[i].inCollision != true
|| ball[j].inCollision != true)
checkCollision(ball[i], ball[j]);
}
}
frames++;
render();
if (System.currentTimeMillis() - lastTimer > 1000) {
lastTimer += 1000;
frame.setTitle(TITLE + " | " + ticks + " ticks, " + frames
+ " fps");
frames = 0;
ticks = 0;
}
}
stop();
}
public void update() {
for (int i = 0; i < noOfBalls; i++) {
ball[i].x += ball[i].speedX;
ball[i].y += ball[i].speedY;
if (ball[i].x >= getWidth() - ball[i].radius && ball[i].speedX > 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].x <= ball[i].radius && ball[i].speedX < 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].y >= getHeight() - ball[i].radius && ball[i].speedY > 0)
ball[i].speedY = -ball[i].speedY;
if (ball[i].y <= ball[i].radius && ball[i].speedY < 0)
ball[i].speedY = -ball[i].speedY;
}
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.yellow);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < noOfBalls; i++)
ball[i].paint(g);
g.dispose();
bs.show();
}
public void checkCollision(Ball ball1, Ball ball2) {
double distance;
if (ball1.x + ball1.radius + ball2.radius > ball2.x
&& ball1.x < ball1.x + ball1.radius + ball2.radius
&& ball1.y + ball1.radius + ball2.radius > ball2.y
&& ball1.y < ball2.y + ball1.radius + ball2.radius) {
distance = Math.sqrt(((ball1.x - ball2.x) * (ball1.x - ball2.x))
+ ((ball1.y - ball2.y) * (ball1.y - ball2.y)));
if ((int) distance < ball1.radius + ball2.radius) {
ball1.collision = true;
ball2.collision = true;
ball1.inCollision = true;
ball2.inCollision = true;
ball1.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball1.collisionY = ((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball2.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
ball2.collisionY =
((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
/*
* x1 = (ball1.x - getWidth()) / 2; y1 = (ball2.y - getHeight())
* / 2; angle1 = Math.toDegrees(Math.atan2(y1, x1));
*
* x2 = (ball1.x - getWidth()) / 2; y2 = (ball2.y - getHeight())
* / 2; angle2 = Math.toDegrees(Math.atan2(y2, x2));
*/
double colision_angle = Math.toDegrees(Math.atan2(
(ball2.y - ball1.y), (ball2.x - ball1.x)));
double speed1 = Math.sqrt(ball1.speedX * ball1.speedX
+ ball1.speedY * ball1.speedY);
double speed2 = Math.sqrt(ball2.speedX * ball2.speedX
+ ball2.speedY * ball2.speedY);
double direction1 = Math.atan2(ball1.speedY, ball1.speedX);
double direction2 = Math.atan2(ball2.speedY, ball2.speedX);
double vx_1 = speed1 * Math.cos(direction1 - colision_angle);
double vy_1 = speed1 * Math.sin(direction1 - colision_angle);
double vx_2 = speed2 * Math.cos(direction2 - colision_angle);
double vy_2 = speed2 * Math.sin(direction2 - colision_angle);
double final_vx_1 = ((ball1.radius - ball2.radius) * vx_1 + (ball2.radius + ball2.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vx_2 = ((ball1.radius + ball1.radius) * vx_1 + (ball2.radius - ball1.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vy_1 = vy_1;
double final_vy_2 = vy_2;
newVelX1 = (int) (Math.cos(colision_angle) * final_vx_1 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_1);
newVelY1 = (int) (Math.sin(colision_angle) * final_vx_1 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_1);
newVelX2 = (int) (Math.cos(colision_angle) * final_vx_2 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_2);
newVelY2 = (int) (Math.sin(colision_angle) * final_vx_2 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_2);
ball1.speedX = (int) newVelX1;
ball1.speedY = (int) newVelY1;
ball2.speedX = (int) newVelX2;
ball2.speedY = (int) newVelY2;
ball1.x = ball1.x + (int) newVelX1;
ball1.y = ball1.y + (int) newVelY1;
ball2.x = ball2.x + (int) newVelX2;
ball2.y = ball2.y + (int) newVelY2;
}
}
}
public static void main(String[] args) {
ElasticCollision balls = new ElasticCollision();
balls.frame.setResizable(false);
balls.frame.add(balls);
balls.frame.pack();
balls.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
balls.frame.setVisible(true);
balls.start();
}
}
class Ball {
protected int x = 0, y = 0;
protected int radius;
protected int speedX = 0, speedY = 0;
protected boolean collision = false;
protected int collisionX = 0, collisionY = 0;
protected boolean inCollision = false;
public Ball(int x, int y, int radius, int speedX, int speedY) {
this.x = x + radius;
this.y = y + radius;
this.radius = radius;
this.speedX = speedX;
this.speedY = speedY;
}
public void paint(Graphics g) {
if (!collision) {
g.setColor(Color.red);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
} else {
g.setColor(Color.green);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
g.setColor(Color.blue);
g.fillOval(collisionX - radius, collisionY - radius, 20, 20);
g.setColor(Color.black);
g.drawOval(collisionX - radius, collisionY - radius, 20, 20);
collision = false;
inCollision = false;
}
g.setColor(Color.black);
g.drawOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
My guess is that your logic doesn't let the balls move apart once the collision is detected. Only change the direction of movement once and not every instant the balls are close to one another.

Point inside Oriented Bounding Box?

I have an OBB2D class based on SAT.
This is my point in OBB method:
public boolean pointInside(float x, float y)
{
float newy = (float) (Math.sin(angle) * (y - center.y) + Math.cos(angle) *
(x - center.x));
float newx = (float) (Math.cos(angle) * (x - center.x) - Math.sin(angle) *
(y - center.y));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
public boolean pointInside(Vector2D v)
{
return pointInside(v.x,v.y);
}
Here is the rest of the class; the parts that pertain:
public class OBB2D
{
private Vector2D projVec = new Vector2D();
private static Vector2D projAVec = new Vector2D();
private static Vector2D projBVec = new Vector2D();
private static Vector2D tempNormal = new Vector2D();
private Vector2D deltaVec = new Vector2D();
private ArrayList<Vector2D> collisionPoints = new ArrayList<Vector2D>();
// Corners of the box, where 0 is the lower left.
private Vector2D corner[] = new Vector2D[4];
private Vector2D center = new Vector2D();
private Vector2D extents = new Vector2D();
private RectF boundingRect = new RectF();
private float angle;
//Two edges of the box extended away from corner[0].
private Vector2D axis[] = new Vector2D[2];
private double origin[] = new double[2];
public OBB2D(float centerx, float centery, float w, float h, float angle)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(centerx,centery,w,h,angle);
}
public OBB2D(float left, float top, float width, float height)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(left + (width / 2), top + (height / 2),width,height,0.0f);
}
public void set(float centerx,float centery,float w, float h,float angle)
{
float vxx = (float)Math.cos(angle);
float vxy = (float)Math.sin(angle);
float vyx = (float)-Math.sin(angle);
float vyy = (float)Math.cos(angle);
vxx *= w / 2;
vxy *= (w / 2);
vyx *= (h / 2);
vyy *= (h / 2);
corner[0].x = centerx - vxx - vyx;
corner[0].y = centery - vxy - vyy;
corner[1].x = centerx + vxx - vyx;
corner[1].y = centery + vxy - vyy;
corner[2].x = centerx + vxx + vyx;
corner[2].y = centery + vxy + vyy;
corner[3].x = centerx - vxx + vyx;
corner[3].y = centery - vxy + vyy;
this.center.x = centerx;
this.center.y = centery;
this.angle = angle;
computeAxes();
extents.x = w / 2;
extents.y = h / 2;
computeBoundingRect();
}
//Updates the axes after the corners move. Assumes the
//corners actually form a rectangle.
private void computeAxes()
{
axis[0].x = corner[1].x - corner[0].x;
axis[0].y = corner[1].y - corner[0].y;
axis[1].x = corner[3].x - corner[0].x;
axis[1].y = corner[3].y - corner[0].y;
// Make the length of each axis 1/edge length so we know any
// dot product must be less than 1 to fall within the edge.
for (int a = 0; a < axis.length; ++a)
{
float l = axis[a].length();
float ll = l * l;
axis[a].x = axis[a].x / ll;
axis[a].y = axis[a].y / ll;
origin[a] = corner[0].dot(axis[a]);
}
}
public void computeBoundingRect()
{
boundingRect.left = JMath.min(JMath.min(corner[0].x, corner[3].x), JMath.min(corner[1].x, corner[2].x));
boundingRect.top = JMath.min(JMath.min(corner[0].y, corner[1].y),JMath.min(corner[2].y, corner[3].y));
boundingRect.right = JMath.max(JMath.max(corner[1].x, corner[2].x), JMath.max(corner[0].x, corner[3].x));
boundingRect.bottom = JMath.max(JMath.max(corner[2].y, corner[3].y),JMath.max(corner[0].y, corner[1].y));
}
public void set(RectF rect)
{
set(rect.centerX(),rect.centerY(),rect.width(),rect.height(),0.0f);
}
// Returns true if other overlaps one dimension of this.
private boolean overlaps1Way(OBB2D other)
{
for (int a = 0; a < axis.length; ++a) {
double t = other.corner[0].dot(axis[a]);
// Find the extent of box 2 on axis a
double tMin = t;
double tMax = t;
for (int c = 1; c < corner.length; ++c) {
t = other.corner[c].dot(axis[a]);
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
}
// We have to subtract off the origin
// See if [tMin, tMax] intersects [0, 1]
if ((tMin > 1 + origin[a]) || (tMax < origin[a])) {
// There was no intersection along this dimension;
// the boxes cannot possibly overlap.
return false;
}
}
// There was no dimension along which there is no intersection.
// Therefore the boxes overlap.
return true;
}
public void moveTo(float centerx, float centery)
{
float cx,cy;
cx = center.x;
cy = center.y;
deltaVec.x = centerx - cx;
deltaVec.y = centery - cy;
for (int c = 0; c < 4; ++c)
{
corner[c].x += deltaVec.x;
corner[c].y += deltaVec.y;
}
boundingRect.left += deltaVec.x;
boundingRect.top += deltaVec.y;
boundingRect.right += deltaVec.x;
boundingRect.bottom += deltaVec.y;
this.center.x = centerx;
this.center.y = centery;
computeAxes();
}
// Returns true if the intersection of the boxes is non-empty.
public boolean overlaps(OBB2D other)
{
if(right() < other.left())
{
return false;
}
if(bottom() < other.top())
{
return false;
}
if(left() > other.right())
{
return false;
}
if(top() > other.bottom())
{
return false;
}
if(other.getAngle() == 0.0f && getAngle() == 0.0f)
{
return true;
}
return overlaps1Way(other) && other.overlaps1Way(this);
}
public Vector2D getCenter()
{
return center;
}
public float getWidth()
{
return extents.x * 2;
}
public float getHeight()
{
return extents.y * 2;
}
public void setAngle(float angle)
{
set(center.x,center.y,getWidth(),getHeight(),angle);
}
public float getAngle()
{
return angle;
}
public void setSize(float w,float h)
{
set(center.x,center.y,w,h,angle);
}
public float left()
{
return boundingRect.left;
}
public float right()
{
return boundingRect.right;
}
public float bottom()
{
return boundingRect.bottom;
}
public float top()
{
return boundingRect.top;
}
public RectF getBoundingRect()
{
return boundingRect;
}
public boolean overlaps(float left, float top, float right, float bottom)
{
if(right() < left)
{
return false;
}
if(bottom() < top)
{
return false;
}
if(left() > right)
{
return false;
}
if(top() > bottom)
{
return false;
}
return true;
}
public static float distance(float ax, float ay,float bx, float by)
{
if (ax < bx)
return bx - ay;
else
return ax - by;
}
public Vector2D project(float ax, float ay)
{
projVec.x = Float.MAX_VALUE;
projVec.y = Float.MIN_VALUE;
for (int i = 0; i < corner.length; ++i)
{
float dot = Vector2D.dot(corner[i].x,corner[i].y,ax,ay);
projVec.x = JMath.min(dot, projVec.x);
projVec.y = JMath.max(dot, projVec.y);
}
return projVec;
}
public Vector2D getCorner(int c)
{
return corner[c];
}
public int getNumCorners()
{
return corner.length;
}
public boolean pointInside(float x, float y)
{
float newy = (float) (Math.sin(angle) * (y - center.y) + Math.cos(angle) *
(x - center.x));
float newx = (float) (Math.cos(angle) * (x - center.x) - Math.sin(angle) *
(y - center.y));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
public boolean pointInside(Vector2D v)
{
return pointInside(v.x,v.y);
}
public ArrayList<Vector2D> getCollsionPoints(OBB2D b)
{
collisionPoints.clear();
for(int i = 0; i < corner.length; ++i)
{
if(b.pointInside(corner[i]))
{
collisionPoints.add(corner[i]);
}
}
for(int i = 0; i < b.corner.length; ++i)
{
if(pointInside(b.corner[i]))
{
collisionPoints.add(b.corner[i]);
}
}
return collisionPoints;
}
};
What could be wrong? When I getCollisionPoints for 2 OBBs I know are penetrating, it returns no points.
Thanks
I also tried:
public boolean pointInside(float x, float y)
{
float xx = (x - center.x);
float yy = (y - center.y);
float newx = (float) (xx * Math.cos(angle) - yy * Math.sin(angle));
float newy = (float) (xx * Math.sin(angle) + yy * Math.cos(angle));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
With no luck.
I didn't read all of your class, but I'm assuming angle is the angle by which you would need to rotate your rectangle clockwise in order to make it axis-aligned.
I believe that both sin(angle) * (y-center.y) and cos(angle) * (x-center.x) are equal to the distance between your center point and the point you're testing. So newy will always equal twice that distance, and newx will always equal 0.
This is how I prefer to rotate a point about another point: get the angle and distance between the two points, then apply the rotation to the angle, then calculate the new position from the angle and distance. In pseudocode:
//takes a point and rotates it `theta` angles
//counterclockwise around the given center point
function rotateAboutPoint(x,y, centerX, centerY, theta){
radius = sqrt((centerX-x)**2 + (centerY-y)**2) //`**` is the exponentiation operator
currentAngle = atan2(y-centerY, x-centerX) //prefer `atan2` over ordinary `atan` if you can get it
newAngle = currentAngle + theta
newX = centerX + radius*cos(newAngle)
newY = centerY + radius*sin(newAngle)
return (newX, newY)
}
function pointInside(x,y){
//point must be rotated clockwise, so we provide a negative angle
newX, newY = rotateAboutPoint(x,y,center.x, center.y, -angle)
return (
newY > center.y - (getHeight() / 2) &&
newY < center.y + (getHeight() / 2) &&
newX > center.x - (getHeight() / 2) &&
newX < center.x + (getHeight() / 2) &&
)
}

Application not working as expected

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

Categories

Resources