C++ How to scale a shape and create an if function to not print if too big after scale? - c++

given a shapes orignal centroid + vertices .. i.e. if its a triangle, i know all three vertices coords. How could i then create a scaling function with a scaling factor as a parameter as below.. however my current code is with error and the result are huge shapes, much more than what im scaling by (only want scale factor of 2).
void Shape::scale(double factor)
{
int x, y, xx, xy;
int disx, disy;
for (itr = vertices.begin(); itr != vertices.end(); ++itr) {
//translate obj to origin (0,0)
x = itr->getX() - centroid.getX();
y = itr->getY() - centroid.getY();
//finds distance between centroid and vertex
disx = x + itr->getX();
disy = y + itr->getY();
xx = disx * factor;
xy = disy * factor;
//translate obj back
xx = xx + centroid.getX();
xy = xy + centroid.getY();
//set new coord
itr->setX(xx);
itr->setY(xy);
}
}
I know of using iterations to run through the vertices, my main point of confusion is how can i do the maths between the factor to scale my shapes size?
this is how i declare and itialise a vertex
// could i possible do (scale*x,scale*y)? or would that be problematic..
vertices.push_back(Vertex(x, y));
Also.. the grid is i.e. 100x100. if a scaled shape was to be too big to fit into that grid, i want an exit from the scale function so that the shape wont be enlarged, how can this be done effectively? so far i have a for look but that just loops on vertices, so it will only stop those that would be outside the grid, instead of cancelling the entire shape which would be ideal
if my question is too broad, please ask and i shall edit further to standard

First thing you need to do is find the center of mass of your set of points. That is the arithmetic mean of the coordinates of your points. Then, for each point calculate the line between the center of mass and that point. Now the only thing left is to put the point on that line, but in factor * current_distance away, where current_distance is the distance from the mass center to the given point before rescaling.
void Shape::scale(double factor)
{
Vertex mass_center = Vertex(0., 0.);
for(int i = 0; i < vertices.size(); i++)
{
mass_center.x += vertices[i].x;
mass_center.y += vertices[i].y;
}
mass_center.x /= vertices.size();
mass_center.y /= vertices.size();
for(int i = 0; i < vertices.size(); i++)
{
//this is a vector that leads from mass center to current vertex
Vertex vec = Vertex(vertices[i].x - mass_center.x, vertices[i].y - mass_center.y);
vertices[i].x = mass_center.x + factor * vec.x;
vertices[i].y = mass_center.y + factor * vec.y;
}
}

If you already know the centroid of a shape and the vertexes are the distance from that point then scaling in rectangular coordinates is just multiplying the x and y components of each vertex by the appropriate scaling factor (with a negative value flipping the shape around the axis.
void Shape::scale(double x_factor, double y_factor){
for(auto i=0; i < verticies.size();++i){
verticies[i].x *= x_scale;
verticies[i].y *= y_scale;
}
}
You could then just overload this function with one that takes a single parameter and calls this function with the same value for x and y.
void Shape::scale(double factor){
Shape::scale(factor, factor);
}
If you're vertex values are not centered at the origin then you will also have to multiply those values by your scaling factor.

Related

Rotate vector to new base vector

In raycaster I am developing I am trying to implement hemisphere random sampling, with option to rotate hemisphere to direction and then take random point.
First version worked fine because sampling was uniform, and change of direction was just swapping to other hemisphere, which was simple.
Vec3f UniformSampleSphere() {
const Vec2f& u = GetVec2f(); // get two random numbers
float z = 1 - 2 * u.x;
float r = std::sqrt(std::max((float)0, (float)1 - z * z));
float phi = 2 * PI_F * u.y;
return Vec3f(r * std::cos(phi), r * std::sin(phi), z);
}
Vec3f GetRandomOnHemiSphere(Vec3f direction) {
auto toReturn = GetRandomOnSphere();
if (Dot(toReturn - direction, toReturn) < 0)
toReturn = -toReturn;
return toReturn;
}
But with cosine weighted hemisphere sampling I am in trouble to rotate properly and find random direction in correctly rotated hemisphere.
On picture's left we can see what is working now, and on right is after applying magic rotation that is that big deal I want.
So final function will be something like this:
Vec3f GetRandomOnHemiSphere(Vec3f direction) {
auto toReturn = CosineSampleHemisphere();
/*
Some magic here that rotates to correct direction of hemisphere
*/
return toReturn;
}
I used code from Socine weighted hemisphere sampling.

Optimizing a Ray Tracer

I'm tasked with optimizing the following ray tracer:
void Scene::RayTrace()
{
for (int v = 0; v < fb->h; v++) // all vertical pixels in framebuffer
{
calculateFPS(); // calculates the current fps and prints it
for (int u = 0; u < fb->w; u++) // all horizontal pixels in framebuffer
{
fb->Set(u, v, 0xFFAAAAAA); // background color
fb->SetZ(u, v, FLT_MAX); // sets the Z values to all be maximum at beginning
V3 ray = (ppc->c + ppc->a*((float)u + .5f) + ppc->b*((float)v + .5f)).UnitVector(); // gets the camera ray
for (int tmi = 0; tmi < tmeshesN; tmi++) // iterates over all triangle meshes
{
if (!tmeshes[tmi]->enabled) // doesn't render a tmesh if it's not set to be enabled
continue;
for (int tri = 0; tri < tmeshes[tmi]->trisN; tri++) // iterates over all triangles in the mesh
{
V3 Vs[3]; // triangle vertices
Vs[0] = tmeshes[tmi]->verts[tmeshes[tmi]->tris[3 * tri + 0]];
Vs[1] = tmeshes[tmi]->verts[tmeshes[tmi]->tris[3 * tri + 1]];
Vs[2] = tmeshes[tmi]->verts[tmeshes[tmi]->tris[3 * tri + 2]];
V3 bgt = ppc->C.IntersectRayWithTriangleWithThisOrigin(ray, Vs); // I don't entirely understand what this does
if (bgt[2] < 0.0f || bgt[0] < 0.0f || bgt[1] < 0.0f || bgt[0] + bgt[1] > 1.0f)
continue;
if (fb->zb[(fb->h - 1 - v)*fb->w + u] < bgt[2])
continue;
fb->SetZ(u, v, bgt[2]);
float alpha = 1.0f - bgt[0] - bgt[1];
float beta = bgt[0];
float gamma = bgt[1];
V3 Cs[3]; // triangle vertex colors
Cs[0] = tmeshes[tmi]->cols[tmeshes[tmi]->tris[3 * tri + 0]];
Cs[1] = tmeshes[tmi]->cols[tmeshes[tmi]->tris[3 * tri + 1]];
Cs[2] = tmeshes[tmi]->cols[tmeshes[tmi]->tris[3 * tri + 2]];
V3 color = Cs[0] * alpha + Cs[1] * beta + Cs[2] * gamma;
fb->Set(u, v, color.GetColor()); // sets this pixel accordingly
}
}
}
fb->redraw();
Fl::check();
}
}
Two things:
I don't entirely understand what ppc->C.IntersectRayWithTriangleWithThisOrigin(ray, Vs); does. Can anyone explain this, in terms of ray-tracing, to me? Here is the function inside my "Planar Pinhole Camera" class (this function was given to me):
V3 V3::IntersectRayWithTriangleWithThisOrigin(V3 r, V3 Vs[3])
{
M33 m; // 3X3 matrix class
m.SetColumn(0, Vs[1] - Vs[0]);
m.SetColumn(1, Vs[2] - Vs[0]);
m.SetColumn(2, r*-1.0f);
V3 ret; // Vector3 class
V3 &C = *this;
ret = m.Inverse() * (C - Vs[0]);
return ret;
}
The basic steps of this are apparent, I just don't see what it's actually doing.
How would I go about optimizing this ray-tracer from here? I've found something online about "kd trees," but I'm unsure how complex they are. Does anyone have some good resources on simple solutions for optimizing this? I've had some difficulty deciphering what's out there.
Thanks!
Probably the largest optimisation by far would be to use some sort of bounding volume hierarchy. Right now the code intersects all rays with all triangles of all objects. With a BVH, we instead ask: "given this ray, which triangles intersect?" This means that for each ray, you generally only need to test for intersection with a handful of primitives and triangles, rather than every single triangle in the scene.
IntersectRayWithTriangleWithThisOrigin
from the look of it
it creates inverse transform matrix from the triangle edges (triangle basis vectors are X,Y)
do not get the Z axis I would expect the ray direction there and not position of pixel (ray origin)
but can be misinterpreting something
anyway the inverse matrix computation is the biggest problem
you are computing it for each triangle per pixel that is a lot
faster would be having computed inverse transform matrix of each triangle before raytracing (once)
where X,Y are the basis and Z is perpendicular to booth of them facing always the same direction to camera
and then just transform your ray into it and check for limits of intersection
that is just matrix*vector and few ifs instead of inverse matrix computation
another way would be to algebraically solve ray vs. plane intersection
that should lead to much simpler equation then matrix inversion
after that is that just a mater of basis vector bound checking

3d projection to 2d screen coordinates

Current Code:
//calculating View Project matrix
abfw::Matrix44 view_proj_matrix_ = camera_.GetMatrix(PROJ) * camera_.GetMatrix(VIEW);
//3D position
abfw::Vector3 ball_position_ = balls_[look_at_index_].GetPosition();
abfw::Vector3 sprite_position;
//transform world to screen ratio in rang -1 to 1
ball_position_ = ball_position_.Transform(view_proj_matrix_);
//move to range 0 - 2
ball_position_.x += 1;
//scale to range 0 -1
ball_position_.x /= 2;
//scale to screen size
ball_position_.x *= platform_.width();
// same as above
ball_position_.y -= 1;
ball_position_.y /= -2;
ball_position_.y *= platform_.height();
//set sprite position
sprite_position = ball_position_;
// z is discounted
sprite_position.z = 0;
I'm getting x and y values that are way out of range of the screen but I'm not seeing why. I have broken the algorithm down as much as possible to try and see where I'm going wrong, but I can't.
Edit: when the camera is at (0,0,7) switching between balls on the same y value give the same sprite position.
GetMatrix() uses this code:
view_matrix_.LookAt(camera_eye_, camera_lookat_, camera_up_);
projection_matrix_ = platform_.PerspectiveProjectionFov(camera_fov_, (float)platform_.width() / (float)platform_.height(), near_plane_, far_plane_);
which is what I'm also using to draw with so I'm assuming it to be right.
using row vectors I believe.
Transform Code:
Vector3 result = Vector3(0.0f, 0.0f, 0.0f);
result.x = x*_mat.m[0][0]+y*_mat.m[1][0]+z*_mat.m[2][0]+_mat.m[3][0];
result.y = x*_mat.m[0][1]+y*_mat.m[1][1]+z*_mat.m[2][1]+_mat.m[3][1];
result.z = x*_mat.m[0][2]+y*_mat.m[1][2]+z*_mat.m[2][2]+_mat.m[3][2];
I have no idea what the namespace abfw stands for, but if it handles vectors and matrices in the same way as DirectX, you have to swap projection and view transformations:
abfw::Matrix44 view_proj_matrix_ = camera_.GetMatrix(VIEW) * camera_.GetMatrix(PROJ);

Optimizing a simple 2D Tile engine (+potential bugfix)

Preface
Yes, there is plenty to cover here... but I'll do my best to keep this as well-organized, informative and straight-to-the-point as I possibly can!
Using the HGE library in C++, I have created a simple tile engine.
And thus far, I have implemented the following designs:
A CTile class, representing a single tile within a CTileLayer, containing row/column information as well as an HGE::hgeQuad (which stores vertex, color and texture information, see here for details).
A CTileLayer class, representing a two-dimensional 'plane' of tiles (which are stored as a one-dimensional array of CTile objects), containing the # of rows/columns, X/Y world-coordinate information, tile pixel width/height information, and the layer's overall width/height in pixels.
A CTileLayer is responsible for rendering any tiles which are either fully or partially visible within the boundaries of a virtual camera 'viewport', and to avoid doing so for any tiles which are outside of this visible range. Upon creation, it pre-calculates all information to be stored within each CTile object, so the core of engine has more room to breathe and can focus strictly on the render loop. Of course, it also handles proper deallocation of each contained tile.
Issues
The problem I am now facing essentially boils down to the following architectural/optimization issues:
In my render loop, even though I am not rendering any tiles which are outside of visible range, I am still looping through all of the tiles, which seems to have a major performance impact for larger tilemaps (i.e., any thing above 100x100 rows/columns # 64x64 tile dimensions still drops the framerate down by 50% or more).
Eventually, I intend to create a fancy tilemap editor to coincide with this engine.
However, since I am storing all two-dimensional information inside one or more 1D arrays, I don't have any idea how possible it would be to implement some sort of rectangular-select & copy/paste feature, without some MAJOR performance hit -- involving looping through every tile twice per frame. And yet if I used 2D arrays, there would be a slightly less but more universal FPS drop!
Bug
As stated before... In my render code for a CTileLayer object, I have optimized which tiles are to be drawn based upon whether or not they are within viewing range. This works great, and for larger maps I noticed only a 3-8 FPS drop (compared to a 100+ FPS drop without this optimization).
But I think I'm calculating this range incorrectly, because after scrolling halfway through the map you can start to see a gap (on the topmost & leftmost sides) where tiles aren't being rendered, as if the clipping range is increasing faster than the camera can move (even though they both move at the same speed).
This gap gradually increases in size the further along into the X & Y axis you go, eventually eating up nearly half of the top & left sides of the screen on a large map.
My render code for this is shown below...
Code
//
// [Allocate]
// For pre-calculating tile information
// - Rows/Columns = Map Dimensions (in tiles)
// - Width/Height = Tile Dimensions (in pixels)
//
void CTileLayer::Allocate(UINT numColumns, UINT numRows, float tileWidth, float tileHeight)
{
m_nColumns = numColumns;
m_nRows = numRows;
float x, y;
UINT column = 0, row = 0;
const ULONG nTiles = m_nColumns * m_nRows;
hgeQuad quad;
m_tileWidth = tileWidth;
m_tileHeight = tileHeight;
m_layerWidth = m_tileWidth * m_nColumns;
m_layerHeight = m_tileHeight * m_nRows;
if(m_tiles != NULL) Free();
m_tiles = new CTile[nTiles];
for(ULONG l = 0; l < nTiles; l++)
{
m_tiles[l] = CTile();
m_tiles[l].column = column;
m_tiles[l].row = row;
x = (float(column) * m_tileWidth) + m_offsetX;
y = (float(row) * m_tileHeight) + m_offsetY;
quad.blend = BLEND_ALPHAADD | BLEND_COLORMUL | BLEND_ZWRITE;
quad.tex = HTEXTURE(nullptr); //Replaced for the sake of brevity (in the engine's code, I used a globally allocated texture array and did some random tile generation here)
for(UINT i = 0; i < 4; i++)
{
quad.v[i].z = 0.5f;
quad.v[i].col = 0xFF7F7F7F;
}
quad.v[0].x = x;
quad.v[0].y = y;
quad.v[0].tx = 0;
quad.v[0].ty = 0;
quad.v[1].x = x + m_tileWidth;
quad.v[1].y = y;
quad.v[1].tx = 1.0;
quad.v[1].ty = 0;
quad.v[2].x = x + m_tileWidth;
quad.v[2].y = y + m_tileHeight;
quad.v[2].tx = 1.0;
quad.v[2].ty = 1.0;
quad.v[3].x = x;
quad.v[3].y = y + m_tileHeight;
quad.v[3].tx = 0;
quad.v[3].ty = 1.0;
memcpy(&m_tiles[l].quad, &quad, sizeof(hgeQuad));
if(++column > m_nColumns - 1) {
column = 0;
row++;
}
}
}
//
// [Render]
// For drawing the entire tile layer
// - X/Y = world position
// - Top/Left = screen 'clipping' position
// - Width/Height = screen 'clipping' dimensions
//
bool CTileLayer::Render(HGE* hge, float cameraX, float cameraY, float cameraTop, float cameraLeft, float cameraWidth, float cameraHeight)
{
// Calculate the current number of tiles
const ULONG nTiles = m_nColumns * m_nRows;
// Calculate min & max X/Y world pixel coordinates
const float scalarX = cameraX / m_layerWidth; // This is how far (from 0 to 1, in world coordinates) along the X-axis we are within the layer
const float scalarY = cameraY / m_layerHeight; // This is how far (from 0 to 1, in world coordinates) along the Y-axis we are within the layer
const float minX = cameraTop + (scalarX * float(m_nColumns) - m_tileWidth); // Leftmost pixel coordinate within the world
const float minY = cameraLeft + (scalarY * float(m_nRows) - m_tileHeight); // Topmost pixel coordinate within the world
const float maxX = minX + cameraWidth + m_tileWidth; // Rightmost pixel coordinate within the world
const float maxY = minY + cameraHeight + m_tileHeight; // Bottommost pixel coordinate within the world
// Loop through all tiles in the map
for(ULONG l = 0; l < nTiles; l++)
{
CTile tile = m_tiles[l];
// Calculate this tile's X/Y world pixel coordinates
float tileX = (float(tile.column) * m_tileWidth) - cameraX;
float tileY = (float(tile.row) * m_tileHeight) - cameraY;
// Check if this tile is within the boundaries of the current camera view
if(tileX > minX && tileY > minY && tileX < maxX && tileY < maxY) {
// It is, so draw it!
hge->Gfx_RenderQuad(&tile.quad, -cameraX, -cameraY);
}
}
return false;
}
//
// [Free]
// Gee, I wonder what this does? lol...
//
void CTileLayer::Free()
{
delete [] m_tiles;
m_tiles = NULL;
}
Questions
What can be done to fix those architectural/optimization issues, without greatly impacting any other rendering optimizations?
Why is that bug occurring? How can it be fixed?
Thank you for your time!
Optimising the iterating of the map is fairly straight forward.
Given a visible rect in world coordinates (left, top, right, bottom) it's fairly trivial to work out the tile positions, simply by dividing by the tile size.
Once you have those tile coordinates (tl, tt, tr, tb) you can very easily calculate the first visible tile in your 1D array. (The way you calculate any tile index from a 2D coordinate is (y*width)+x - remember to make sure the input coordinate is valid first though.) You then just have a double for loop to iterate the visible tiles:
int visiblewidth = tr - tl + 1;
int visibleheight = tb - tt + 1;
for( int rowidx = ( tt * layerwidth ) + tl; visibleheight--; rowidx += layerwidth )
{
for( int tileidx = rowidx, cx = visiblewidth; cx--; tileidx++ )
{
// render m_Tiles[ tileidx ]...
}
}
You can use a similar system for selecting a block of tiles. Just store the selection coordinates and calculate the actual tiles in exactly the same way.
As for your bug, why do you have x, y, left, right, width, height for the camera? Just store camera position (x,y) and calculate the visible rect from the dimensions of your screen/viewport along with any zoom factor you have defined.
This is a pseudo codish example, geometry variables are in 2d vectors. Both the camera object and the tilemap has a center-position and a extent (half size). The math is just the same even if you decide to stick with pure numbers. Even if you don't use center coordinates and extent, perhaps you'll get an idea on the math. All of this code is in the render function, and is rather simplified. Also, this example assume you already got a 2D array -like object that holds the tiles.
So, first a full example, and I'll explain each part further down.
// x and y are counters, sx is a placeholder for x start value as x will
// be in the inner loop and need to be reset each iteration.
// mx and my will be the values x and y will count towards too.
x=0,
y=0,
sx=0,
mx=total_number_of_tiles_on_x_axis,
my=total_number_of_tiles_on_y_axis
// calculate the lowest and highest worldspace values of the cam
min = cam.center - cam.extent
max = cam.center + cam.extent
// subtract with tilemap corners and divide by tilesize to get
// the anount of tiles that is outside of the cameras scoop
floor = Math.floor( min - ( tilemap.center - tilemap.extent ) / tilesize)
ceil = Math.ceil( max - ( tilemap.center + tilemap.extent ) / tilesize)
if(floor.x > 0)
sx+=floor.x
if(floor.y > 0)
y+=floor.y
if(ceil.x < 0)
mx+=ceil.x
if(ceil.y < 0)
my+=ceil.y
for(; y<my; y++)
// x need to be reset each y iteration, start value are stored in sx
for(x=sx; x<mx; x++)
// render tile x in tilelayer y
Explained bit by bit. First thing in the render function, we will use a few variables.
// x and y are counters, sx is a placeholder for x start value as x will
// be in the inner loop and need to be reset each iteration.
// mx and my will be the values x and y will count towards too.
x=0,
y=0,
sx=0,
mx=total_number_of_tiles_on_x_axis,
my=total_number_of_tiles_on_y_axis
To prevent rendering all tiles, you need to provide either a camera-like object or information on where the visible area starts and stops (in worldspace if the scene is movable)
In this example I'm providing a camera object to the render function which has a center and an extent stored as 2d vectors.
// calculate the lowest and highest worldspace values of the cam
min = cam.center - cam.extent
max = cam.center + cam.extent
// subtract with tilemap corners and divide by tilesize to get
// the anount of tiles that is outside of the cameras scoop
floor = Math.floor( min - ( tilemap.center - tilemap.extent ) / tilesize)
ceil = Math.ceil( max - ( tilemap.center + tilemap.extent ) / tilesize)
// floor & ceil is 2D vectors
Now, if floor is higher than 0 or ceil is lower than 0 on any axis, it means that there just as many tiles outside of the camera scoop.
// check if there is any tiles outside to the left or above of camera
if(floor.x > 0)
sx+=floor.x// set start number of sx to amount of tiles outside of camera
if(floor.y > 0)
y+=floor.y // set startnumber of y to amount of tiles outside of camera
// test if there is any tiles outisde to the right or below the camera
if(ceil.x < 0)
mx+=ceil.x // then add the negative value to mx (max x)
if(ceil.y < 0)
my+=ceil.y // then add the negative value to my (max y)
A normal render of the tilemap would go from 0 to number of tiles that axis, this using a loop within a loop to account for both axis. But thanks to the above code x and y will always stick to the space within the border of the camera.
// will loop through only the visible tiles
for(; y<my; y++)
// x need to be reset each y iteration, start value are stored in sx
for(x=sx; x<mx; x++)
// render tile x in tilelayer y
Hope this helps!

Rotating coordinates around an axis

I'm representing a shape as a set of coordinates in 3D, I'm trying to rotate the whole object around an axis (In this case the Z axis, but I'd like to rotate around all three once I get it working).
I've written some code to do this using a rotation matrix:
//Coord is a 3D vector of floats
//pos is a coordinate
//angles is a 3d vector, each component is the angle of rotation around the component axis
//in radians
Coord<float> Polymers::rotateByMatrix(Coord<float> pos, const Coord<float> &angles)
{
float xrot = angles[0];
float yrot = angles[1];
float zrot = angles[2];
//z axis rotation
pos[0] = (cosf(zrot) * pos[0] - (sinf(zrot) * pos[1]));
pos[1] = (sinf(zrot) * pos[0] + cosf(zrot) * pos[1]);
return pos;
}
The image below shows the object I'm trying to rotate (looking down the Z axis) before the rotation is attempted, each small sphere indicates one of the coordinates I'm trying to rotate
alt text http://www.cs.nott.ac.uk/~jqs/notsquashed.png
The rotation is performed for the object by the following code:
//loop over each coordinate in the object
for (int k=start; k<finish; ++k)
{
Coord<float> pos = mp[k-start];
//move object away from origin to test rotation around origin
pos += Coord<float>(5.0,5.0,5.0);
pos = rotateByMatrix(pos, rots);
//wrap particle position
//these bits of code just wrap the coordinates around if the are
//outside of the volume, and write the results to the positions
//array and so shouldn't affect the rotation.
for (int l=0; l<3; ++l)
{
//wrap to ensure torroidal space
if (pos[l] < origin[l]) pos[l] += dims[l];
if (pos[l] >= (origin[l] + dims[l])) pos[l] -= dims[l];
parts->m_hPos[k * 4 + l] = pos[l];
}
}
The problem is that when I perform the rotation in this way, with the angles parameter set to (0.0,0.0,1.0) it works (sort of), but the object gets deformed, like so:
alt text http://www.cs.nott.ac.uk/~jqs/squashed.png
which is not what I want. Can anyone tell me what I'm doing wrong and how I can rotate the entire object around the axis without deforming it?
Thanks
nodlams
Where you do your rotation in rotateByMatrix, you compute the new pos[0], but then feed that into the next line for computing the new pos[1]. So the pos[0] you're using to compute the new pos[1] is not the input, but the output. Store the result in a temp var and return that.
Coord<float> tmp;
tmp[0] = (cosf(zrot) * pos[0] - (sinf(zrot) * pos[1]));
tmp[1] = (sinf(zrot) * pos[0] + cosf(zrot) * pos[1]);
return tmp;
Also, pass the pos into the function as a const reference.
const Coord<float> &pos
Plus you should compute the sin and cos values once, store them in temporaries and reuse them.