I've been writing a raycaster in C++ and to render stuff I use GDI/GDI+. I know that using WGDI to render graphics is not the best idea in the world and I should probably use OpenGL, SFML and etc. but this raycaster does not involve any super-high-level real-time graphics, so in this case WGDI does the job. Besides I probably will be showing this in my school and installing OpenGL there would be a huge pain.
Okay, so the actual problem I wanted to talk about is that whenever I change the map grid from 8x8 to e.g. 8x16, the way that some walls are rendered is pretty bizzarre:
If someone can explain why such issue occurrs I would be very happy to discover what's wrong with my code.
main.cpp
/*
* Pseudo-code of the void renderer():
* Horizontal gridline check:
* Set horizontal distance to a pretty high value, horizontal coordinates to camera coordinates
* Calculate negative inverse of tangent
* Set DOF variable to 0
* If ray angle is bigger than PI calculate ray Y-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray X-coordinate and offset coordinates for the ray moovement over the gridline
* If ray angle is smaller than PI do the same as if ray angle < PI but add whatever the size of the map is to ray Y-coordinate
* If ray angle is straight up or down set ray coordinates to camera coordinates and DOF to map size
* Loop only if DOF is smaller than map size:
* Calculate actual gridline coordinates
* If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
* Else update ray coordinates with the earlier calculated offsets
*
* Vertical gridline check:
* Set vertical distance to a pretty high value, vertical coordinates to camera coordinates
* Calculate inverse of tangent
* Set DOF variable to 0
* If ray angle is bigger than PI / 2 and smaller than 3 * PI / 2 calculate ray X-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray Y-coordinate and offset coordinates for the ray moovement over the gridline
* If ray angle is smaller than PI / 2 or bigger than 3 * PI / 2 do the same as if ray angle > PI / 2 && < 3 * PI / 2 but add whatever the size of the map is to ray X-coordinate
* If ray angle is straight left or right set ray coordinates to camera coordinates and DOF to map size
* Loop only if DOF is smaller than map size:
* Calculate actual gridline coordinates
* If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
* Else update ray coordinates with the earlier calculated offsets
*
* If the vertical distance is smaller than the horizontal one update ray coordinates to the horizontal ones and set final distance to the horizontal one
* Else update ray coordinates to the vertical ones and set final distance to the vertical one
* Fix fisheye effect
* Add one radian to the ray angle
* Calculate line height by multiplying constant integer 400 by the map size and dividing that by the final distance
* Calculate line offset (to make it more centered) by subtracting half of the line height from constant integer 400
* Draw 8-pixels wide column at [ray index * 8, camera Z-offset + line offset] and [ray index * 8, camera Z-offset + line offset + line height] (the color doesn't matter i think)
*/
#include "../../LIB/wsgl.hpp"
#include "res/maths.hpp"
#include <memory>
using namespace std;
const int window_x = 640, window_y = 640;
float camera_x = 256, camera_y = 256, camera_z = 75;
float camera_a = 0.001;
int camera_fov = 80;
int map_x;
int map_y;
int map_s;
shared_ptr<int[]> map_w;
void controls()
{
if(wsgl::is_key_down(wsgl::key::w))
{
int mx = (camera_x + 30 * cos(camera_a)) / map_s;
int my = (camera_y + 30 * sin(camera_a)) / map_s;
int mp = my * map_x + mx;
if(mp >= 0 && mp < map_s && !map_w[mp])
{camera_x += 15 * cos(camera_a); camera_y += 15 * sin(camera_a);}
}
if(wsgl::is_key_down(wsgl::key::s))
{
int mx = (camera_x - 30 * cos(camera_a)) / map_s;
int my = (camera_y - 30 * sin(camera_a)) / map_s;
int mp = my * map_x + mx;
if(mp >= 0 && mp < map_s && !map_w[mp])
{camera_x -= 5 * cos(camera_a); camera_y -= 5 * sin(camera_a);}
}
if(wsgl::is_key_down(wsgl::key::a_left))
{camera_a = reset_ang(camera_a - 5 * RAD);}
if(wsgl::is_key_down(wsgl::key::a_right))
{camera_a = reset_ang(camera_a + 5 * RAD);}
if(wsgl::is_key_down(wsgl::key::a_up))
{camera_z += 15;}
if(wsgl::is_key_down(wsgl::key::a_down))
{camera_z -= 15;}
}
void renderer()
{
int map_x_pos, map_y_pos, map_cell, dof;
float ray_x, ray_y, ray_a = reset_ang(camera_a - deg_to_rad(camera_fov / 2));
float x_offset, y_offset, tangent, distance_h, distance_v, h_x, h_y, v_x, v_y;
float final_distance, line_height, line_offset;
wsgl::clear_window();
for(int i = 0; i < camera_fov; i++)
{
distance_h = 1000000, h_x = camera_x, h_y = camera_y;
tangent = -1 / tan(ray_a);
dof = 0;
if(ray_a > PI)
{ray_y = (((int)camera_y / map_s) * map_s) - 0.0001; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = -map_s; x_offset = -y_offset * tangent;}
if(ray_a < PI)
{ray_y = (((int)camera_y / map_s) * map_s) + map_s; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = map_s; x_offset = -y_offset * tangent;}
if(ray_a == 0 || ray_a == PI)
{ray_x = camera_x; ray_y = camera_y; dof = map_s;}
for(dof; dof < map_s; dof++)
{
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
{dof = map_s; h_x = ray_x; h_y = ray_y; distance_h = distance(camera_x, camera_y, h_x, h_y);}
else
{ray_x += x_offset; ray_y += y_offset;}
}
distance_v = 1000000, v_x = camera_x, v_y = camera_y;
tangent = -tan(ray_a);
dof = 0;
if(ray_a > PI2 && ray_a < PI3)
{ray_x = (((int)camera_x / map_s) * map_s) - 0.0001; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = -map_s; y_offset = -x_offset * tangent;}
if(ray_a < PI2 || ray_a > PI3)
{ray_x = (((int)camera_x / map_s) * map_s) + map_s; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = map_s; y_offset = -x_offset * tangent;}
if(ray_a == PI2 || ray_a == PI3)
{ray_x = camera_x; ray_y = camera_y; dof = map_s;}
for(dof; dof < map_s; dof++)
{
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
{dof = map_s; v_x = ray_x; v_y = ray_y; distance_v = distance(camera_x, camera_y, v_x, v_y);}
else
{ray_x += x_offset; ray_y += y_offset;}
}
if(distance_v < distance_h)
{ray_x = v_x; ray_y = v_y; final_distance = distance_v;}
else
{ray_x = h_x; ray_y = h_y; final_distance = distance_h;}
final_distance *= cos(reset_ang(camera_a - ray_a));
ray_a = reset_ang(ray_a + RAD);
line_height = (map_s * 400) / final_distance;
line_offset = 200 - line_height / 2;
wsgl::draw_line({i * 8, camera_z + line_offset}, {i * 8, camera_z + line_offset + line_height}, {0, 255 / (final_distance / 250 + 1), 0}, 8);
if(i == camera_fov / 2)
{wsgl::draw_text({0, 0}, {255, 255, 255}, L"Final distance: " + to_wstring(final_distance) + L" Line height: " + to_wstring(line_height) + L" X: " + to_wstring(camera_x) + L" Y: " + to_wstring(camera_y));}
}
wsgl::render_frame();
}
void load_map(wsgl::wide_str wstr, int cell_size = 1)
{
shared_ptr<wsgl::bmp> map = shared_ptr<wsgl::bmp>(wsgl::bmp::FromFile(wstr.c_str(), true));
map_x = map->GetWidth();
map_y = map->GetHeight();
map_s = map_x * map_y;
map_w = shared_ptr<int[]>(new int[map_s]);
wsgl::color color;
for(int y = 0; y < map_y; y += cell_size)
{
for(int x = 0; x < map_x; x += cell_size)
{
map->GetPixel(x, y, &color);
if(color.GetR() == 255 && color.GetG() == 255 && color.GetB() == 255)
{*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 0;}
else
{*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 1;}
}
}
}
int main()
{
wsgl::session sess = wsgl::startup(L"raycaster", {window_x, window_y});
load_map(L"res/map.png");
while(true)
{controls(); renderer();}
}
maths.hpp
#include <cmath>
const float PI = 3.14159265359;
const float PI2 = PI / 2;
const float PI3 = 3 * PI2;
const float RAD = PI / 180;
float deg_to_rad(float deg)
{return deg * RAD;}
float distance(float ax, float ay, float bx, float by)
{
float dx = bx - ax;
float dy = by - ay;
return sqrt(dx * dx + dy * dy);
}
float reset_ang(float ang)
{
if(ang < 0)
{ang += 2 * PI;}
if(ang > 2 * PI)
{ang -= 2 * PI;}
return ang;
}
If someone asks whats wsgl.hpp thats just my wrapper library over some WGDI routines and etc.
I think the problem lies here:
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
You need to change the order of operations:
map_x_pos = (int)(ray_x / map_s);
map_y_pos = (int)(ray_y / map_s);
map_cell = map_y_pos * map_x + map_x_pos;
With your current implementation, you first truncate ray_x and ray_y, then divide by map_s (which should probably be a floating point value, but is an integer in your current implementation), then truncate again to integer values. Your current implementation needlessly sacrifices precision and will be unpredictable for small map_s values.
Additionally, map_s seems incorrect. You set map_s to represent the total area of your map, but in the above code, you use it like it was the side length of the map.
To be correct, you would need something like
#include <cmath>
map_x_pos = (int)(ray_x / sqrtf(map_s));
map_y_pos = (int)(ray_y / sqrtf(map_s));
map_cell = map_y_pos * map_x + map_x_pos;
double shootx = vx + dx / t0;
double shooty = vy + dy / t0;
double radians = atan2((double)-shooty, shootx);
deg_to_aim = (int)((radians * 360) / (2 * 3.141592653589793238462));
myprintf("(A) radians = %f deg to aim = %d\n", radians, deg_to_aim);
radians 3.14 = 180 should be 0
radians 0 = 0 should be 180
radians -1.584827 = -90 should be 90
radians -1579912 = 90 should be -90
how do I make the values show up properly for all sides.
At the moment if I spin around the dot it will show a out wards motion like behind actual point when it should have the point always pointing at the dot.
Also it goes from 179 to -179 never hitting the 180.
Full code looks like this
/* Relative player position */
float const dx = (MyShip.XCoordinate + 18) - (Enemy.XCoordinate + 18);
float const dy = (MyShip.YCoordinate + 18) - (Enemy.YCoordinate + 18);
/* Relative player velocity */
float const vx = MyShip.XSpeed - Enemy.XSpeed;
float const vy = MyShip.YSpeed - Enemy.YSpeed;
float const a = vx * vx + vy * vy - bulletSpeed * bulletSpeed;
float const b = 2.f * (vx * dx + vy * dy);
float const c = dx * dx + dy * dy;
float const discriminant = b * b - 4.f * a * c;
int deg_to_aim = 0;
if (discriminant >= 0) {
float t0 = (float)(-b + sqrt(discriminant)) / (2 * a);
float t1 = (float)(-b - sqrt(discriminant)) / (2 * a);
if (t0 < 0.f || (t1 < t0 && t1 >= 0.f))
t0 = t1;
if (t0 >= 0.f)
{
// Aim at
double shootx = vx + dx / t0;
double shooty = vy + dy / t0;
double radians = atan2((double)-shooty, shootx);
deg_to_aim = (int)((radians * 360) / (2 * 3.141592653589793238462));
myprintf("(A) radians = %f deg to aim = %d\n", radians, deg_to_aim);
}
}
else {
myprintf("Error found!!!!!!! no solution\n");
}
Fixed it just Flip the MyShip Enemy values around.
Instead of MyShip - Enemy
you do Enemy - MyShip
Lets say I'm moving my mouse in a circle with the end point of a line following the mouse position. When holding down Left-Ctrl the end point of this line should stick to a 45 degrees depending on the angle the end-point has to the start-point of the line.
Note that I am viewing the line from ABOVE (y+) looking straight down at origo, that's why I'm using X and Z to define the position.
This is how far I've gotten. So I got the closest 45 degree angle, now I only need to calculate the correct end-point for the line.
p1 = Start point of line
p2 = End point of line (mouse position)
if (l_vIsCtrlPressed) {
var length = Algo.Length(p1, p2);
var angle = Algo.Angle(p1, p2);
angle = Algo.ConvertToDegree(angle);
angle = Math.round(angle / 45) * 45;
}
I tried using the following calculation but with no success.
p2.x = p1.x - length * Math.cos(angle);
p2.z = p1.z - length * Math.sin(angle);
Angles(degree)
0 LEFT
-90 DOWN
90 UP
180 && -180 RIGHT
I forgot to convert the angle back to radians -.-"
The following code fixes my issue. Line is now drawn with an angle with degree 0, 45, 90, 135, 180.. etc
if (l_vIsCtrlPressed) {
var length = Algo.Length(p1, p2);
var angle = Algo.Angle(p1, p2);
angle = Algo.ConvertToDegrees(angle);
angle = Math.round(angle / 45) * 45;
angle = Algo.ConvertToRadians(angle); // FORGOT TO CONVERT BACK TO RADIANS
p2.x = p1.x - length * Math.cos(angle);
p2.z = p1.z - length * Math.sin(angle);
}
I'm working on an Android game and I need to bounce 2 circles of each other (like 2 pool balls bouncing off each other). The collision is an elastic collision, and I need to calculate only 1 circles (called a Particle in my code) new velocity after the collision (the other circle, called a Barrier in my code will remain stationary and will not move because of a collision).
I am using a formula that I found on Wikipedia (http://en.wikipedia.org/wiki/Elastic_collision), but my end result for the new velocity of the particle after the collision is exactly the same as the velocity before the collision?
This is def wrong but I cant see where I am going wrong. Can anyone spot where I am going wrong?
I have just used a Java program to simulate my velocities and locations for the 2 circles as I dont wanna try it in my main Android game at the moment for fear of "breaking something"
Here is what I have so far (like I mentioned, this is just a simulation in NetBeans for the moment and I will use a menthod in my Android game to keep things a bit tidier):
double randomNextDouble = (new Random()).nextDouble();
System.out.println("Random.nextDouble: " + randomNextDouble);
double mathPI = Math.PI * 2;
System.out.println("Math PI: " + mathPI);
// get a random direction for the Particle to move towards
double direction = (new Random()).nextDouble() * Math.PI * 2;
System.out.println("Direction: " + direction);
// Then set the Particle's velocity - Increase to make Particles move faster
int velocity = 10;
System.out.println("Velocity: " + velocity);
// Then calculate the xv and the yv
// Velocity value on the x and y axis
double xv = (velocity * Math.cos(direction));
double yv = (velocity * Math.sin(direction));
System.out.println("\nXV: " + xv + "\nYV: " + yv);
// Genareting a random number for the Particle and Barrier's positions on screen
double Xmin = 0;
double Xmax = 300;
double Ymin = 0;
double Ymax = 300;
double randomNumber1 = Xmin + (int)(Math.random() * ((Xmax - Xmin) + 1));
double randomNumber2 = Ymin + (int)(Math.random() * ((Ymax - Ymin) + 1));
double randomNumber3 = Xmin + (int)(Math.random() * ((Xmax - Xmin) + 1));
double randomNumber4 = Ymin + (int)(Math.random() * ((Ymax - Ymin) + 1));
// Setting the Particle and Barrier's radius
double particleRadius = 8;
double barrierRadius = 16;
// Setting up the Particle and Barrier's mass
double particleMass = 100;
double barrierMass = 200;
// Assigning a random number to the Particle to simulate its position on screen
double particleX = randomNumber1;
double particleY = randomNumber2;
System.out.println("\nParticle X: " + particleX + " Particle Y: " + particleY);
// Assigning a random number to the Barrier to simulate its position on screen
double barrierX = randomNumber3;
double barrierY = randomNumber4;
System.out.println("Barrier X: " + barrierX + " Barrier Y: " + barrierY);
double distanceXToBarrier = barrierX - particleX;
System.out.println("\nBarrier X - Particle X: " + distanceXToBarrier);
double distanceYToBarrier = barrierY - particleY;
System.out.println("Barrier Y - Particle Y: " + distanceYToBarrier);
// Get the distance between the Particle and the Barrier
// Used for collision detection
double distance = Math.sqrt((distanceXToBarrier * distanceXToBarrier) + (distanceYToBarrier * distanceYToBarrier));
System.out.println("\nDistance: " + distance);
// Check to see if the Particle and Barrier has collided
if (distance <= particleRadius + barrierRadius)
{
System.out.println("Distance is less than 2 Radii");
}
else
System.out.println("Distance is NOT less than 2 Radii");
// Velx = (v1.u) * u + (v1 - (v1.u) * u)
// Vely = (v1.u) * u + (v1 - (v1.u) * u)
// Where v1 = xv and yv respectively
// Break it into 2 equations
// (v1.u) * u AND
// (v1 - (v1.u) * u)
//
// u = normalised Vector
// To normalize you just devide the x, y, z coords by the length of the vector.
// This then gives you the Unit Vector.
//
//Normalize the vector
double particleXNormalized = particleX * (1.0 / distance);
double particleYNormalized = particleY * (1.0 / distance);
System.out.println("\nParticle X Normalised: " + particleXNormalized +
"\nParticle Y Normalised: " + particleYNormalized);
// Calculating the first part of the eqaution
// (v1.u)
double v1DotUForX = xv * particleXNormalized;
double v1DotUForY = yv * particleYNormalized;
System.out.println("\nv1.u for X: " + v1DotUForX +
"\nv1.u for Y: " + v1DotUForY);
// The first part of the equation
// (v1.u) * u
double part1X = v1DotUForX * particleXNormalized;
double part1Y = v1DotUForY * particleYNormalized;
System.out.println("\nPart 1 for X: " + part1X +
"\nPart 1 for Y: " + part1Y);
// The second part of the equation
// (v1 - (v1.u) * u)
double part2X = (xv - (v1DotUForX) * particleXNormalized);
double part2Y = (yv - (v1DotUForY) * particleYNormalized);
System.out.println("\nPart 2 for X: " + part2X +
"\nPart 2 for Y: " + part2Y);
// Solving for:
// (((mass 1 - mass2) / (mass1 + mass2) * (v1.u) * u + ((2mass2) / (mass1 + mass2) * ((v1.u) * u))) +
// (v1 - (v1.u) * u))
double newXV = ((((particleMass - barrierMass) / (particleMass + barrierMass)) * part1X) + (((2 * barrierMass) / (particleMass + barrierMass)) * part1X) + part2X);
double newYV = ((((particleMass - barrierMass) / (particleMass + barrierMass)) * part1Y) + (((2 * barrierMass) / (particleMass + barrierMass)) * part1Y) + part2Y);
System.out.println("\nNew XV: " + newXV + "\nNew YV: " + newYV);
Looking at your algorithm, you appear to have made errors in the implementation. Why are you normalizing the coordinates of the particle? Shouldn't you be doing that to the velocity? In the usual equations, u is velocity, not position.
And why do you give the particle a random velocity (xv, yv) that has nothing to do with the two random coordinates you set up for the particle and barrier? (Surely the velocity should be some multiple of (barrier - particle) vector?)
I want to latitude/longitude into X,Y coordinates in flash, So i tried these methods by googling.
Please can someone tell me some other way to calculate. because its not showing the expected places
public function getPoint(lat, lon) {
// First method
/*stationX = (180+lon) * (mapwidth / 360);
stationY = (90-lat) * (mapheight / 180);*/
// Second method
lat = (lat * -1) + 90;
lon+=180;
stationX = p.TnMap.x+Math.round(lon * (mapwidth / 360))+ 50;
stationY = p.TnMap.y+Math.round(lat * (mapheight / 180));
// Thirdmethod
/*stationX = radius_of_world * Math.cos(lon) * Math.cos(lat);
stationY = radius_of_world * Math.sin(lon) * Math.cos(lat);
stationZ = radius_of_world * Math.sin(lat);
stationX = stationX * 150 / (150 + stationZ);
stationY = stationY * 150 / (150 + stationZ);*/
}