How do I make this recursive function faster? (Quadtree) - c++

I'm learning C++ and am doing something I'm comfortable with in java to start out. Particle simulation and flocking using a quadtree to cheaply find particles in a region. Everything is working but when I use the quadtree to get the particles from a region it's really slow (about 1s for 5000 calls).
I tried replacing the vector with an array and measuring the execution time of various parts of the function.
Am I making any rooky mistakes like unnecessarily copying objects etc.? I'm using 5000 particles, I can't imagine 1fps is the fastest it can go.
Full code for minimal reproducable example as per request:
main.cpp
#include <string>
#include <iostream>
#include <random>
#include <chrono>
#include <thread>
#include <cmath>
#include "Particle.h"
#include "Quadtree.h"
// Clock
using namespace std::chrono;
using namespace std::this_thread;
// Global constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int desiredFPS = 30;
const int frameTimeMS = int(1000 / (double)desiredFPS);
const int numberOfParticles = 5000;
// Random number generation
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_real_distribution<> dist(0, 1);
Particle particles[numberOfParticles];
Quadtree quadtree = Quadtree(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
int main(int argc, char* args[])
{
for (int i = 0; i < numberOfParticles; i++)
{
particles[i] = Particle(dist(rng) * SCREEN_WIDTH, dist(rng) * SCREEN_HEIGHT);
}
// Clock for making all frames equally long and achieving the desired framerate when possible
auto lapStartTime = system_clock::now();
// Main loop
for (int i = 0; i < 1; i++)
{
// Insert the particles into the quadtree
quadtree = Quadtree(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
for (int i = 0; i < numberOfParticles; i++)
{
quadtree.insert(&particles[i]);
}
double neighbourhoodRadius = 40;
for (int i = 0; i < numberOfParticles; i++)
{
// THIS IS THE PART THAT IS SLOW
std::vector<Particle*> neighbours = quadtree.getCircle(
particles[i].x,
particles[i].y,
neighbourhoodRadius
);
}
// Update clocks
auto nextFrameTime = lapStartTime + milliseconds(frameTimeMS);
sleep_until(nextFrameTime);
lapStartTime = nextFrameTime;
}
return 0;
}
Quadtree.h
#pragma once
#include <vector>
#include "Particle.h"
#include "Rect.h"
class Quadtree
{
public:
const static int capacity = 10; // Capacity of any section
Quadtree(double px, double py, double width, double height);
Quadtree(Rect r);
bool insert(Particle* p); // Add a particle to the tree
std::vector<Particle*> getCircle(double px, double py, double r);
int numberOfItems(); // Total amount in the quadtree
private:
std::vector<Particle*> particles; // Particles stored by this section
std::vector<Quadtree> sections; // Branches (only if split)
Rect area; // Region occupied by the quadtree
bool isSplit() { return sections.size() > 0; }
void split(); // Split the quadtree into 4 branches
};
Quadtree.cpp
#include <iostream>
#include "Quadtree.h"
Quadtree::Quadtree(double px, double py, double width, double height)
{
area = Rect(px, py, width, height);
sections = {};
particles = {};
}
Quadtree::Quadtree(Rect r)
{
area = r;
sections = {};
particles = {};
}
bool Quadtree::insert(Particle* p)
{
if (area.intersectPoint(p->x, p->y))
{
if (!isSplit() && particles.size() < capacity)
{
particles.push_back(p);
}
else
{
if (!isSplit()) // Capacity is reached and tree is not split yet
{
split();
}
// That this is a reference is very important!
// Otherwise a copy of the tree will be modified
for (Quadtree& s : sections)
{
if (s.insert(p))
{
return true;
}
}
}
return true;
}
else
{
return false;
}
}
std::vector<Particle*> Quadtree::getCircle(double px, double py, double r)
{
std::vector<Particle*> selection = {};
if (!isSplit())
{
// Add all particles from this section that lie within the circle
for (Particle* p : particles)
{
double a = px - p->x;
double b = py - p->y;
if (a * a + b * b <= r * r)
{
selection.push_back(p);
}
}
}
else
{
// The section is split so add all the particles from the
// branches together
for (Quadtree& s : sections)
{
// Check if the branch and the circle even have any intersection
if (s.area.intersectRect(Rect(px - r, py - r, 2 * r, 2 * r)))
{
// Get the particles from the branch and add them to selection
std::vector<Particle*> branchSelection = s.getCircle(px, py, r);
selection.insert(selection.end(), branchSelection.begin(), branchSelection.end());
}
}
}
return selection;
}
void Quadtree::split()
{
sections.push_back(Quadtree(area.getSection(2, 2, 0, 0)));
sections.push_back(Quadtree(area.getSection(2, 2, 0, 1)));
sections.push_back(Quadtree(area.getSection(2, 2, 1, 0)));
sections.push_back(Quadtree(area.getSection(2, 2, 1, 1)));
std::vector<Particle*> oldParticles{ particles };
particles.clear();
for (Particle* p : oldParticles)
{
bool success = insert(p);
}
}
int Quadtree::numberOfItems()
{
if (!isSplit())
{
return particles.size();
}
else
{
int result = 0;
for (Quadtree& q : sections)
{
result += q.numberOfItems();
}
return result;
}
}
Particle.h
#pragma once
class Particle {
public:
double x;
double y;
Particle(double px, double py) : x(px), y(py) {}
Particle() = default;
};
Rect.h
#pragma once
class Rect
{
public:
double x;
double y;
double w;
double h;
Rect(double px, double py, double width, double height);
Rect() : x(0), y(0), w(0), h(0) {}
bool intersectPoint(double px, double py);
bool intersectRect(Rect r);
Rect getSection(int rows, int cols, int ix, int iy);
};
Rect.cpp
#include "Rect.h"
Rect::Rect(double px, double py, double width, double height)
{
x = px;
y = py;
w = width;
h = height;
}
bool Rect::intersectPoint(double px, double py)
{
return px >= x && px < x + w && py >= y && py < y + h;
}
bool Rect::intersectRect(Rect r)
{
return x + w >= r.x && y + h >= r.y && x <= r.x + r.w && y <= r.y + r.w;
}
Rect Rect::getSection(int cols, int rows, int ix, int iy)
{
return Rect(x + ix * w / cols, y + iy * h / rows, w / cols, h / rows);
}

So... In the original code creating the quadtree takes about 0.001s (relatively insignificant), and the neighbor search takes about 0.06s - here is our culprit (as mentioned by the OP).
Passing the std::vector<Particle*> neighbours as a reference to the getCircle function, gets rid of the insert call at the end of the function as well as new vector allocations (hi to everyone saying "oh, it will be optimized away automatically"). The time is reduced to 0.011s.
The nieghbours vector can be taken out of the main loop, and cleared after use, so that it only resizes on the first frame.
I do not see any more immediately obvious targets (without doing a complete rewrite). Maybe I will add something later.
I decided to approach this more systematically: I added an #if switch for every change I made and actually recorded some statistics, instead of eyeballing it. (Evey change is added incrementally, times include tree construction).
original
by reference
out of loop
min time:
0.0638s
0.0127s
0.0094s
avg time:
0.0664s
0.0136s
0.0104s
max time:
0.0713s
0.0157s
0.0137s
All measurements were done on my machine, with optimized build, using QueryPerfoemanceCounter.
I did end up rewriting the whole thing...
Got rid of vectors.
The Quadtree::particles is now Particle* particles[capacity] with a count.
sections is a pointer; isSplit just checks if sections is 0.
Since the total (or maximum) number of particles is known, the number of particles that can be returned by getCircle can't be more than that. So I allocate that much outside of the main loop to store neighbours. Adding another result involves just bumping a pointer (without even a check in release). And resetting it after use is done by setting the count to 0 (see arena or bump allocator).
The maximum number of quadtree nodes can be inferred from the number of particles. So, similarly, splitting just bumps the pointer by 4.
Trying to precompute the Rect in getCircle, or put px, py, r (and/or that rect as well) in a struct (passed as value or reference) does not yield any improvement (or is detremental). (was suggested by Goswin von Brederlow).
Then I flipped the recursion (was suggested by Ted Lyngmo). The temporary stack is, again, preallocated. And then I did the same thing for insert.
rewrite
non-recursive
insert as well
min_time:
0.0077
0.0069
0.0068
avg_time:
0.0089
0.0073
0.0070
max_time:
0.0084
0.0078
0.0074
So in the end the most impactful thing was the very first - not inserting and not creating unnecessary vectors every call, but instead passing the same one by reference.
One last thing - might want to store the quadtree particles separately, since most of the time getCircle is traversing nodes, where particles are not stored.
Otherwise, I do not see how to improve this any more. At this point it would require someone actually smart or crazy...

Related

how do I handle an Array for a 2D Vector in an Object of another class?

I need a little help with an appointment of mine.
My professor gave us this class (and a Color class that has RGB colors as float variables inside) now I have to implement the functions shown in the header.
#include "color.h"
#include <assert.h>
Color::Color()
{
R = 255;
G = 255;
B = 255;
}
Color::Color( float r, float g, float b)
{
R = r;
G = g;
B = b;
}
Color Color::operator*(const Color& c) const
{
return Color(R * c.R, G * c.G, B * c.B );
}
Color Color::operator*(const float Factor) const
{
return Color(R * Factor, G * Factor, B * Factor);
}
Color Color::operator+(const Color& c) const
{
return Color(R + c.R, G + c.G, B + c.B);
}
Color& Color::operator+=(const Color& c)
{
R += c.R;
G += c.G;
B += c.B;
return *this;
}
Header RGBImage
The Konstruktor should create a 2DImage memory to save width*height Pixel. (Dunno what the best solution here would be? Array of type Color or a Vector?)
My first guess was this:
RGBImage class (i just got empty methodes)
#include "rgbimage.h"
#include "color.h"
#include "assert.h"
using namespace std;
RGBImage::RGBImage( unsigned int Width, unsigned int Height)
{
m_Image = new Color[Width * Height]; // probably wrong?
m_Width = Width;
m_Height = Height;
}
RGBImage::~RGBImage()
{
}
void RGBImage::setPixelColor( unsigned int x, unsigned int y, const Color& c)
{
if (x < width() && y < height())
{
// get offset of pixel in 2D array.
const unsigned offset = (y * width()) + x;
m_Image[offset] = c;
}
}
const Color& RGBImage::getPixelColor( unsigned int x, unsigned int y) const
{
if (x < width() && y < height())
{
// get offset of pixel in 2D array.
const unsigned offset = (y * width()) + x;
return m_Image[offset];
}
}
unsigned int RGBImage::width() const
{
return this->m_Width;
}
unsigned int RGBImage::height() const
{
return this->m_Height;
}
unsigned char RGBImage::convertColorChannel( float v)
{
if (v < 0) {
v = 0;
}
else if (v > 1) {
v = 1;
}
int convertedColorChannel = v * 255;
return convertedColorChannel;
}
bool RGBImage::saveToDisk( const char* Filename)
{
// TODO: add your code
return false; // dummy (remove)
}
afterward, I realized Color arrays are no variable of the Class RGBImage per definition of his Header so how can I save the Pixel in an RGBImage, or is it a viable option to continue this approach. If so how can I set the Color in a setter? tryed it with this.bildspeicher[x] didnt work...
I'm fairly new to Programming, and this is my first question on this platform, so sorry if I stated my problem poorly.
RGB data is usually stored in a one-dimensional array, as is the case for RGBImage. The pixels are packed line-by-line, starting either from the bottom left, or the top-left of the image. The orientation should not affect the functions accessing individual rows of pixels, but will affect how the calling application handles the pixel data.
For accessing individual pixels, use this formula:
// ...
inline void setPixel(unsigned x, unsigned y, const Color& clr)
{
if (x < width() && y < height()) // ALWAYS crop!
{
// get offset of pixel in 2D array.
const unsigned offset = (y * width()) + x;
m_image[offset] = clr;
}
}
I've put the formula in its own line of code, but this is usually done as a one-liner.
The same formula can be used for reading pixels. Note that this formula assumes the lines of pixels have no alignment. Some bitmap fprmats do require 2 of 4 byte alignment of each line within the 2d array, in which case you'd multiply y by alignedWidth() instead of width()).

How do I implement infinite voxel chunks without slowing down the performance in c++?

So I'm currently working in a chunk with a size of 16x256x16 and create a integer grid for block types. But my problem is how do I implement infinite voxel chunks???
I use SFML 1.6 by the way.
Here is my code:
Header (chunk.hpp):
#ifndef CHUNK_HPP
#define CHUNK_HPP
#pragma once
#include <SFML/Graphics.hpp>
#include "player.hpp"
#include "types.hpp"
#include "frustumcull.hpp"
#include "noise_generator.hpp"
const int horiz_chunksize = 16;
const int vert_chunksize = 256;
class Chunk {
public:
Chunk();
~Chunk();
int get(int x, int y, int z); // get block type in position
void set(int x, int y, int z, int type); // set block type in position
void render(Player &p, FrustumCull &cull, int renderDistance = 20); // render chunk
int getTerrainHeight(int x, int y); // returns noise height on the given position
private:
Perlin_Noise m_noise;
void update(int x, int y, int z, int type); // update block faces
};
#endif // CHUNK_HPP
Source (chunk.cpp):
I put the m_blockgrid in .cpp because if I put it in the header it will
only draw one cube and also does in Chunk::~Chunk() delete[] chnk::m_blockgrid necessary???
#include "chunk.hpp"
#include "block.hpp"
#include "maths.hpp"
#include <iostream>
namespace chnk {
int m_blockgrid[horiz_chunksize][vert_chunksize][horiz_chunksize]; // block grid
Block *m_block;
}
Chunk::Chunk() {
chnk::m_block = new Block(); // initialize block class
m_noise.setSeed(sf::Randomizer::Random(2736473, 8476864));
for(int x = 0; x < horiz_chunksize; x++) {
for(int z = 0; z < horiz_chunksize; z++) {
int heightmap = 16;
for(int y =-1; y < heightmap; y++) {
if(y > heightmap-2) set(x, y, z, BlockType::GRASS);
if(y < heightmap-1 && y > heightmap - 4) set(x, y, z, BlockType::DIRT);
if(y < heightmap-3 && y > 0) set(x, y, z, BlockType::STONE);
if(y == 0) set(x, y, z, BlockType::BEDROCK);
}
}
}
}
Chunk::~Chunk() {
delete[] chnk::m_blockgrid;
}
int Chunk::get(int x, int y, int z) {
// check boundary
if((x<0) || (x>=horiz_chunksize) ||
(y<0) || (y>=vert_chunksize) ||
(z<0) || (z>=horiz_chunksize)) return BlockType::AIR;
return chnk::m_blockgrid[x][y][z];
}
void Chunk::set(int x, int y, int z, int type) {
chnk::m_blockgrid[x][y][z] = type;
m_update = true;
}
void Chunk::render(Player &p, FrustumCull &cull, int renderDistance) {
int px = p.m_position.x / chnk::m_block->m_size;
int py = (p.m_position.y + p.m_bottom) / chnk::m_block->m_size;
int pz = p.m_position.z / chnk::m_block->m_size;
float radius = sqrt(Maths::sqr(chnk::m_block->m_size) * 5);
glEnable(GL_CULL_FACE); // hide back face
glEnable(GL_DEPTH_TEST); // depth testing
// render object(s)
for(int x = 0; x < horiz_chunksize; x++) {
for(int z = 0; z < horiz_chunksize; z++) {
for(int y = 0; y < vert_chunksize; y++) {
int type = get(x, y, z);
if(!cull.sphereInFrustum(sf::Vector3f(chnk::m_block->m_size * x + chnk::m_block->m_size / 2, chnk::m_block->m_size * y + chnk::m_block->m_size / 2, chnk::m_block->m_size * z + chnk::m_block->m_size / 2), radius)) continue;
update(x, y, z, type); // update for block texture & etc.
}
}
}
}
void Chunk::update(int x, int y, int z, int type) {
// only show face in outside not inside
// I use get(x, y, z) to get block position at given grid
if(BlockType::getSolidBlocks(type)) {
if(BlockType::getSolidBlocks(get(x, y+1, z)) == 0 && get(x, y+1, z) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Top); // Top Face
}
if(BlockType::getSolidBlocks(get(x, y-1, z)) == 0 && get(x, y-1, z) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Bottom); // Bottom Face
}
if(BlockType::getSolidBlocks(get(x, y, z-1)) == 0 && get(x, y, z-1) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Front); // Front Face
}
if(BlockType::getSolidBlocks(get(x, y, z+1)) == 0 && get(x, y, z+1) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Back); // Back Face
}
if(BlockType::getSolidBlocks(get(x-1, y, z)) == 0 && get(x-1, y, z) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Left); // Left Face
}
if(BlockType::getSolidBlocks(get(x+1, y, z)) == 0 && get(x+1, y, z) != type) {
chnk::m_block->setupBlock(x, y, z, Block::Right); // Right Face
}
}
}
int Chunk::getTerrainHeight(int x, int y) {
return ( m_noise.getHeight(x, y) + 64 ); // total height of the given coordinates
}
Some people uses unordered_map to store loaded/unloaded chunk but I dont know how to use it and if it does work??
Anyone would like to help me?? :)
I will make the following suppositions:
Access to a given chunk is the priority, they are accessed several times a frame so access needs to be O(k)
Insertion and deletion of chunks needs to be done as fast as possible, because they will be generated on the fly, and dropped on the fly. Not as critical as the access.
Most of the time, the amount of chunks in memory is about constant.
Now, let see what are our possibilities:
std::container
Insertion
Access
Erase
Find
PersistentIterators
vector/string
Back: O(1) or O(n)Other: O(n)
O(1)
Back: O(1)Other: O(n)
Sorted: O(log n)Other: O(n)
No
deque
Back/Front: O(1)Other: O(n)
O(1)
Back/Front: O(1)Other: O(n)
Sorted: O(log n)Other: O(n)
Pointers only
list/forward_list
Back/Front: O(1)With iterator: O(1)Index: O(n)
Back/Front: O(1)With iterator: O(1)Index: O(n)
Back/Front: O(1)With iterator: O(1)Index: O(n)
O(n)
Yes
set/map
O(log n)
-
O(log n)
O(log n)
Yes
unordered_set/unordered_map
O(1) or O(n)
O(1) or O(n)
O(1) or O(n)
O(1) or O(n)
Pointers only
priority_queue
O(log n)
O(1)
O(log n)
-
-
std::unordered_map is the most adequate structure for this because it allows constant access, and (most of the time) constant insertion/deletion:
struct ChunkCoordinate
{
int32_t x,
int32_t y
};
class ChunkCoordinateHash{
public:
size_t operator()(const ChunkCoordinate &val) const
{
static_assert(sizeof(size_t)==8);
return (static_cast<size_t>(val.x)<<32ull) + (static_cast<size_t>(val.y)&0xffffffff);
}
};
std::unordered_map<ChunkCoordinate, unique_ptr<Chunk>, ChunkCoordinateHash> m_chunks;
Note: Insertion and Erase on unordered_map is usually O(k)(constant), unless space is missing and all the map needs to be relocated. as you have usually a constant number of chunks loaded, you can unordered_map::reserve a sufficient amount of chunks so that it is never relocated.
Note2: I use pointers to Chunk to make any relocation faster.
Here is some example of usage:
https://onlinegdb.com/FiGzEzHgD

Pathfinding algorithm isn't finding shortest route

I am attempting an online coding challenge wherein I am to implement a pathfinding algorithm that finds the shortest path between two points on a 2D grid. The code that is submitted is tested against a number of test cases that I, unfortunately, am unable to see, but it will however tell me if my answer for shortest distance is correct or not. My implementation of the A* algorithm returns a correct answer on 2/3 test cases and I cannot seem to figure out what scenario might create an incorrect answer on the third?
I have tried several of my own test cases and have gotten correct answers for all of those and at this point am feeling a little bit lost. There must be something small in my code that I am not seeing that is causing this third case to fail.
More details
The grid is w by h and contains only 1's (passable) and 0's (impassable) with every edge having a cost of 1 and the pathway cannot move diagonally
It all starts with the FindPath function which is to return the length of the shortest path, or -1 if no path is available
pOutBuffer is used to contain the path taken from beginning to end (excluding the starting point). If multiple paths are available then any will be accepted. So it isnt looking for one path in particular
I know the issue is not the result of time or memory inefficiency. I has to be either the distance returned is incorrect, or the values in pOutBuffer are incorrect.
Any help would be greatly appreciated as I am just about out of ideas as to what could possibly be wrong here. Thank you.
#include <set>
#include <vector>
#include <tuple>
#include <queue>
#include <unordered_map>
inline int PositionToIndex(const int x, const int y, const int w, const int h)
{
return x >= 0 && y >= 0 && x < w && y < h? x + y * w : -1;
}
inline std::pair<int, int> IndexToPosition(const int i, const int w)
{
return std::make_pair<int, int>(i % w, i / w);
}
inline int Heuristic(const int xa, const int ya, const int xb, const int yb)
{
return std::abs(xa - xb) + std::abs(ya - yb);
}
class Map
{
public:
const unsigned char* mapData;
int width, height;
const std::vector<std::pair<int, int>> directions = { {1,0}, {0,1}, {-1,0}, {0,-1} };
Map(const unsigned char* pMap, const int nMapWidth, const int nMapHeight)
{
mapData = pMap;
width = nMapWidth;
height = nMapHeight;
}
inline bool IsWithinBounds(const int x, const int y)
{
return x >= 0 && y >= 0 && x < width && y < height;
}
inline bool IsPassable(const int i)
{
return mapData[i] == char(1);
}
std::vector<int> GetNeighbours(const int i)
{
std::vector<int> ret;
int x, y, neighbourIndex;
std::tie(x, y) = IndexToPosition(i, width);
for (auto pair : directions)
{
neighbourIndex = PositionToIndex(x + pair.first, y + pair.second, width, height);
if (neighbourIndex >= 0 && IsWithinBounds(x + pair.first, y + pair.second) && IsPassable(neighbourIndex))
ret.push_back(neighbourIndex);
}
return ret;
}
};
int FindPath(const int nStartX, const int nStartY,
const int nTargetX, const int nTargetY,
const unsigned char* pMap, const int nMapWidth, const int nMapHeight,
int* pOutBuffer, const int nOutBufferSize)
{
int ret = -1;
// create the map
Map map(pMap, nMapWidth, nMapHeight);
// get start and end indecies
int targetIndex = PositionToIndex(nTargetX, nTargetY, nMapWidth, nMapHeight);
int startIndex = PositionToIndex(nStartX, nStartY, nMapWidth, nMapHeight);
// if start and end are same exit
if (targetIndex == startIndex) return 0;
std::unordered_map<int, int> pathway = { {startIndex, startIndex} };
std::unordered_map<int, int> distances = { {startIndex, 0} };
// queue for indecies to process
typedef std::pair<int, int> WeightedLocation;
std::priority_queue<WeightedLocation, std::vector<WeightedLocation>, std::greater<WeightedLocation>> queue;
queue.emplace(0, startIndex);
while (!queue.empty())
{
int currentWeight, currentIndex;
std::tie(currentWeight, currentIndex) = queue.top();
queue.pop();
if (currentIndex == targetIndex)
break;
int newDistance = distances[currentIndex] + 1;
for (int n : map.GetNeighbours(currentIndex))
{
if (distances.find(n) == distances.end() || newDistance < distances[n])
{
distances[n] = newDistance;
int weight = newDistance + Heuristic(n % nMapWidth, n / nMapWidth, nTargetX, nTargetY);
queue.emplace(weight, n);
pathway[n] = currentIndex;
}
}
}
if (pathway.find(targetIndex) != pathway.end())
{
int current = targetIndex;
while (current != startIndex)
{
int outIndex = distances[current] - 1;
pOutBuffer[distances[current] - 1] = current;
current = pathway[current];
}
ret = distances[targetIndex];
}
return ret;
}

i keep getting list iterator not dereferencable, what am i doing wrong?

I'm having trouble with with this project of mine. i want it to draw out an octagon, the code i used worked perfectly fine for other shapes like rhombus and triangle.
the octagon.cpp file http://pastebin.com/iVfdkKEB
the header file http://pastebin.com/a50UQi5F
the main part which runs it all http://pastebin.com/quepi6az
#include "shape.h"
class Octagon : public Shape
{
int radius;
void plotVertices();
public:
Octagon(Vertex point, int radius = 10);
int area();
int perimeter();
};
#include "octagon.h"
Octagon::Octagon(Vertex point, int radius) : Shape(point)
{
// constructs a Octagon of radius around a point in 2D space
if ((radius>centroid.getX() / 2) || (radius>centroid.getX() / 2))
{
cout << "Object must fit on screen." << endl;
system("pause");
exit(0);
this->radius = radius;
plotVertices();
}
// place your code here and add comments that describe your understanding of what is happening
}
void Octagon::plotVertices()
{
int x, y, _x, _y; // declare and intiliase variables for x and y co-ordinates
double radians;
x = centroid.getX(); // places first point A at the centroid
y = centroid.getY() + radius;
vertices.push_back(Vertex(x, y));
x = vertices.back().getX() - centroid.getX();
y = vertices.back().getY() - centroid.getY();
for (int i = 45; i < 360; i += 45) // for loop to draw the shape itself by creating the points.
// i = size of interior angle.
{
radians = i * PI / 180;
_x = round(x * cos(radians) - y * sin(radians));
_y = round(y* cos(radians) + x * sin(radians));
_x = _x + centroid.getX();
_y = _y + centroid.getY();
vertices.push_back(Vertex(_x, _y));
}
}
#pragma once
#include "vertex.h"
#include "shape.h"
#include "octagon.h"
#include "console.h"
#include <iostream>
#include <list>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
list<Shape*> shapes;
int x = 20, y = 60;
shapes.push_back(new Octagon(Vertex(20, 60), 8));
list<Shape*>::iterator itr = shapes.begin();
while(itr!=shapes.end())
{
(*itr)->drawShape();
system("pause");
(*itr)->outputStatistics();
// output shape statistics
(*itr)->scale(2.0);
(*itr)->drawShape();
// scale shape (double it)
// draw shape
(*itr)->rotate(20);
(*itr)->drawShape();
// rotate shape by 20 degrees
// draw shape
itr++;
}
Console::gotoXY(0,0);
system("pause");
return 0;
}
and the draw shape function
void Shape::drawShape()
{
// plots each vertex and draws a line between them using Bresenham's algorithm
// you can adjust your console font and dimensions if you want to increase the resolution of your shapes
list<Vertex>::iterator current = vertices.begin();
list<Vertex>::iterator previous = vertices.begin();
while (current != vertices.end())
{
Console::gotoXY((*current).getX(), (*current).getY());
cout << "*";
if (current != vertices.begin())
drawLine((*current).getX(), (*current).getY(), (*previous).getX(), (*previous).getY());
previous = current;
current++;
}
drawLine(vertices.back().getX(), vertices.back().getY(), vertices.front().getX(), vertices.front().getY());
}
I'm far from sure, but I think that the problem can be in the last line of void Shape::drawShape()
drawLine(vertices.back().getX(), vertices.back().getY(), vertices.front().getX(), vertices.front().getY());
where isn't a check to see if vertices is empty. In that case, vertices.back() and vertices.front() are undefined (both equal to vertices.end()?) and dereferencing they, to call getX() and getY(), can cause your problem.
It's possible an empty vertices? I don't know because I don't see all code but I see that there is an exit(0) in the if body of the Octagon constructor.
It's plotVertices() that populate vertices? In this case, the exit(0) say us that vertices is empty and that the last drawline() in Shape::drawshape can cause the trouble.
The solution (a solution) is obvious and simple: check if vertices is empty
if ( false == vertices.empty() )
drawLine(vertices.back().getX(), vertices.back().getY(), vertices.front().getX(), vertices.front().getY());

How to add/subtract to value rather than just be that value

Using the Openframeworks library in C++, I have the radius of a glow (max_distance) that is determined by the stretch of the mouse dragging across the screen (mouseDragX). It works fine.
But rather than every time I resize it (by dragging the mouse), I want it not to start at 0 and follow the mouse drag directly.
max_distance = mouseDragX/2;
But rather, if I have already dragged the mouse to the right to say 200 on a previous drag, that the next time I drag the mouse, and go into the opposite direction (negative numbers) that the value of max_distance decreases by that amount, instead of just being that amount.
I thought it would be
max_distance += mouseDragX/2;
but that seems to kill it altogether
Can you help me?
#include "testApp.h"
//--------------------------------------------------------------
void testApp::setup(){
ofSetWindowShape(700,700);
max_distance = 700; // ofDist didn't work(?) // ofDist(0,0,700,700);
ofEnableSmoothing();
ofEnableAlphaBlending();
}
//--------------------------------------------------------------
void testApp::update(){
max_distance = mouseDragX/2;
if (max_distance < 0) max_distance = 0;
}
//--------------------------------------------------------------
void testApp::draw(){
string str = "mouseDragX: ";
str += ofToString(mouseDragX)+" ";
ofSetWindowTitle(str);
int i,j;
int height = ofGetHeight();
int width = ofGetWidth();
for(i = 0; i <= height; i += 20) {
for(j = 0; j <= width; j += 20) {
float dist_color = getDistance(mouseX, mouseY, i, j); // for definition of getDistance, look below!
dist_color = dist_color/max_distance * 100;
// to get the colors into the range between 0 and 255, multiply the values by 5.
ofSetColor(dist_color*5,dist_color*5,dist_color*5, 123);
ofEllipse(i, j, 20, 20);
}
}
}
//--------------------------------------------------------------
void testApp::keyPressed (int key){
}
//--------------------------------------------------------------
void testApp::keyReleased (int key){
}
//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
// shift values down
for (int i = 0; i < 1; /*<<- length of array*/ i++) {
pmouseX[i] = pmouseX[i+1];
pmouseY[i] = pmouseY[i+1];
}
// make pmouseX/Y[0] be the previous mouse position. [1] = current
pmouseX[1] = mouseX;
pmouseY[1] = mouseY;
}
//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
mouseDragX = (mouseX - pmouseX[0]);
}
//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
// mouseDragX = mouseDragY = 0; // The drag starts here
}
//--------------------------------------------------------------
void testApp::mouseReleased(){
}
float testApp::getDistance(int startX, int startY, int endX, int endY){
return sqrt((endX-startX)*(endX-startX) + (endY-startY)*(endY-startY));
}
Thank you so much.
If I understand correctly, you want todo something like this.
// Every time the mouse *stops* moving, (say on mouse-up
// message) save previous max_distance
int base = max_distance;
// when mouse moves
max_distance = base + mouseDragX/2;
If max_distance and mouseDragX are int values, the division by 2 results in an integer division that can induce losses.
This is especially true if mouseDragX value's is 1 at some time. This will result in 1 / 2 (integer division) and returns 0.
Example:
Lets consider that mouseDragX takes 3 different values (3 cycles):
3, 1, -4
One would expect that max_distance will be increased by (3 / 2) + (1 / 2) - (4 / 2) = 0.
But due to integer truncation, this will infact result to 1 + 0 - 2 = -1.
What if you use floats instead of int, and just round max_distance to an int when you really need it's value ?