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 ?
Related
I'm writing a program which gets the distances from five ultrasonic sensors and uses a trilateration algorithm on every combination of distance values. It first outputs all the coordinates it recieves from the trilateration algorithm, then it filters out values which are too small or too large. However, it keeps outputting values from previous trilateration cycles as the array has not been reset.
#include "NewPing.h"
#include<stdio.h>
#include<stdlib.h>
#define TP0 11
#define EP0 13
#define TP1 3
#define EP1 4
#define TP2 6
#define EP2 2
#define TP3 5
#define EP3 7
#define TP4 10
#define EP4 8
#define MAX_DISTANCE 150
//sensor coordinates
int xcords [5] = {3, 45, 87, 87, 3};
int ycords [5] = {0, 16, 0, 58, 58};
int r = 3;
int n = 5;
float x = 0;
float y = 0;
int MAX_X = 85;
int MAX_Y = 62;
int sensor;
int count = 0;
int resultCount = 0;
bool reading = false;
void setup() {
Serial.begin (9600);
}
void
loop() {
while(count < 1){
count += 1;
int distances [3];
for (int r = 0; r<5; r++){
distances[r] = getValue(r);
if (distances[r] >= MAX_DISTANCE || distances[r] <= 2) {
getValue(r);
}
//Serial.print("Distance =");
//Serial.print(distances[r]);
//Serial.println(" cm ");
}
printCombination(distances, xcords, ycords, n, r);
//trilateration(distances[0], distances[1], distances[2]);
delay(5000);
}
}
int combinationUtil(int distances[], int data[], int dataCords[], int start, int end, int index, int r, float xresults[], float yresults[]);
// Needed for qsort. See http://w...content-available-to-author-only...s.com/reference/cstdlib/qsort/
int compare (const void * a, const void * b)
{ return ( *(int*)a - *(int*)b ); }
// The main function that prints all combinations of size r
// in arr[] of size n. This function mainly uses combinationUtil()
int printCombination(int distances[], int xcords[], int ycords[], int n, int r)
{
// A temporary array to store all combination one by one
int data[r];
int xdataCords[r];
int ydataCords[r];
float xresults[10];// the arrays causing the issue
float yresults[10];
// Sort array to handle duplicates
qsort (distances, n, sizeof(int), compare);
// Print all combination using temprary array 'data[]'
combinationUtil(distances, data, xdataCords, ydataCords, 0, n-1, 0, r, xresults, yresults);
sorting(xresults,yresults);
resultCount = 0;
Serial.print("\n");
}
/* distances[] ---> Input Array
data[] ---> Temporary array to store current combination
start & end ---> Staring and Ending indexes in distances[]
index ---> Current index in data[]
r ---> Size of a combination to be printed */
float* combinationUtil(int distances[], int data[], int xdataCords[], int ydataCords[], int start, int end, int index, int r, float xresults[], float yresults[])
{
// Current combination is ready to be printed, print it
if (index == r)
{
trilateration(data[0], data[1], data[2], xdataCords[0], xdataCords[1], xdataCords[2], ydataCords[0], ydataCords[1], ydataCords[2], xresults, yresults);
resultCount++;
}
// replace index with all possible elements. The condition
// "end-i+1 >= r-index" makes sure that including one element
// at index will make a combination with remaining elements
// at remaining positions
for (int i=start; i<=end && end-i+1 >= r-index; i++)
{
data[index] = distances[i];
xdataCords[index] = xcords[i];
ydataCords[index] = ycords[i];
combinationUtil(distances, data, xdataCords, ydataCords, i+1, end, index+1, r, xresults, yresults);
// Remove duplicates
while (distances[i] == distances[i+1])
i++;
}
}
float* trilateration(int d1,int d2, int d3, int x1, int x2, int x3, int y1, int y2, int y3, float xresults[], float yresults[]){
float va = ((sq(d2) - sq(d3)) - (sq(x2) - sq(x3)) - (sq(y2) - sq(y3))) / (2) ;
float vb = ((sq(d2) - sq(d1)) - (sq(x2) - sq(x1)) - (sq(y2) - sq(y1))) / (2) ;
y = ((vb*(x3-x2)) - (va*(x1-x2))) / (((y1-y2)*(x3-x2)) - ((y3-y2)*(x1-x2)));
x = (va - (y*(y3-y2))) / (x3-x2);
//Serial.print(d1);
//Serial.print(d2);
//Serial.print(d3);
Serial.print("The coordinates are: ");
Serial.print(abs(x));
xresults[resultCount] = abs(x);
Serial.print(", ");
Serial.println(abs(y));
yresults[resultCount] = abs(y);
//sorting(xresults, yresults);
return xresults, yresults;
}
void sorting(float xresults[], float yresults[]){
float xfinal[10];
float yfinal[10];
qsort (xresults, 10, sizeof(int), compare);
qsort (yresults, 10, sizeof(int), compare);
for (int i = 0; i<10; i++){
if (xresults[i] > 3 && xresults[i] < MAX_X){
xfinal[i] = xresults[i];
Serial.print("x=");
Serial.print(xresults[i]);
Serial.print("\n");
}
if (yresults[i] > 10 && yresults[i] < MAX_Y){
yfinal[i] = yresults[i];
Serial.print("y=");
Serial.print(yresults[i]);
Serial.print("\n");
}
}
}
float getValue(int sensor){
NewPing sonar0(TP0, EP0, MAX_DISTANCE);
NewPing sonar1(TP1, EP1, MAX_DISTANCE);
NewPing sonar2(TP2, EP2, MAX_DISTANCE);
NewPing sonar3(TP3, EP3, MAX_DISTANCE);
NewPing sonar4(TP4, EP4, MAX_DISTANCE);
switch(sensor){
case 0:
return sonar0.ping_cm();
case 1:
return sonar1.ping_cm();
case 2:
return sonar2.ping_cm();
case 3:
return sonar3.ping_cm();
case 4:
return sonar4.ping_cm();
}
}
the arrays "xresults and yresults" is the array causing the problem. You can see that I have redefined it every time printCombination is called so it should be cleared right?
This is my output:
The coordinates are: 40.33, 34.88
The coordinates are: 58.62, 8.00
The coordinates are: 42.09, 8.00
The coordinates are: nan, 7.74
The coordinates are: 59.14, 24.17
The coordinates are: 47.81, 13.41
The coordinates are: 47.81, 7.74
x=40.25
x=58.56
y=24.16
x=47.81
y=13.41
x=47.81
x=32.99
New distance call
The coordinates are: 40.65, 35.72
The coordinates are: 58.62, 8.00
The coordinates are: 42.09, 8.00
The coordinates are: nan, 7.28
The coordinates are: 60.07, 25.03
The coordinates are: 47.81, 13.41
The coordinates are: 47.81, 7.28
x=40.50
x=58.56
y=35.56
x=47.81
y=13.41
x=47.81
x=32.99
New distance call
The coordinates are: 24.98, 15.69
The coordinates are: 50.48, 8.00
The coordinates are: 112.35, 8.00
The coordinates are: nan, 25.91
The coordinates are: 14.66, 84.64
The coordinates are: 91.00, 12.26
The coordinates are: 91.00, 25.91
x=14.63
x=50.31
y=15.70
y=12.26
y=25.91
x=32.99
As you can see there are values which are not in the list of coordinates appearing in the print out after the coordinates.
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...
The Mandelbrot set currently displays the image in one whole set by calling the function from the main.
// This shows the whole set.
compute_mandelbrot(-2.0, 1.0, 1.125, -1.125);
My plan is to split the image up into 16 horizontal slices and then display it to improve the speed as can then parallel program this in.
I'm unsure how to create these slices, can someone explain, redirect me or show some example code
image details:
// The size of the image to generate.
const int WIDTH = 100;
const int HEIGHT = 100;
// The number of times to iterate before we assume that a point isn't in the
// Mandelbrot set.
const int MAX_ITERATIONS = 500;
For the purpose of testing, ill send the full code, there are no errors - it is not coded efficiently evidently as the whole process takes over 30 seconds to output, which is way too long for a Mandelbrot set, hence the urgency of the slicing and parallel programming.
If anyone has any other pointers then they would be greatly appreciated
e.g. where to implement parallel programming
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::complex;
using std::cout;
using std::endl;
using std::ofstream;
// Define the alias "the_clock" for the clock type we're going to use.
typedef std::chrono::steady_clock the_clock;
// The size of the image to generate.
const int WIDTH = 100;
const int HEIGHT = 100;
// The number of times to iterate before we assume that a point isn't in the
// Mandelbrot set.
const int MAX_ITERATIONS = 500;
// The image data.
// Each pixel is represented as 0xRRGGBB.
uint32_t image[HEIGHT][WIDTH];
// Write the image to a TGA file with the given name.
// Format specification: http://www.gamers.org/dEngine/quake3/TGA.txt
void write_tga(const char *filename)
{
ofstream outfile(filename, ofstream::binary);
uint8_t header[18] = {
0, // no image ID
0, // no colour map
2, // uncompressed 24-bit image
0, 0, 0, 0, 0, // empty colour map specification
0, 0, // X origin
0, 0, // Y origin
WIDTH & 0xFF, (WIDTH >> 8) & 0xFF, // width
HEIGHT & 0xFF, (HEIGHT >> 8) & 0xFF, // height
24, // bits per pixel
0, // image descriptor
};
outfile.write((const char *)header, 18);
for (int y = 0; y < HEIGHT; ++y)
{
for (int x = 0; x < WIDTH; ++x)
{
uint8_t pixel[3] = {
image[y][x] & 0xFF, // blue channel
(image[y][x] >> 8) & 0xFF, // green channel
(image[y][x] >> 16) & 0xFF, // red channel
};
outfile.write((const char *)pixel, 3);
}
}
outfile.close();
if (!outfile)
{
// An error has occurred at some point since we opened the file.
cout << "Error writing to " << filename << endl;
exit(1);
}
}
// Render the Mandelbrot set into the image array.
// The parameters specify the region on the complex plane to plot.
void compute_mandelbrot(double left, double right, double top, double bottom)
{
for (int y = 0; y < HEIGHT; ++y)
{
for (int x = 0; x < WIDTH; ++x)
{
// Work out the point in the complex plane that
// corresponds to this pixel in the output image.
complex<double> c(left + (x * (right - left) / WIDTH),
top + (y * (bottom - top) / HEIGHT));
// Start off z at (0, 0).
complex<double> z(0.0, 0.0);
// Iterate z = z^2 + c until z moves more than 2 units
// away from (0, 0), or we've iterated too many times.
int iterations = 0;
while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
{
z = (z * z) + c;
++iterations;
}
/*if (iterations == MAX_ITERATIONS)
{
// z didn't escape from the circle.
// This point is in the Mandelbrot set.
image[y][x] = 0x58DC77; // green
}*/
if (iterations <= 10)
{
// z didn't escape from the circle.
// This point is in the Mandelbrot set.
image[y][x] = 0xA9C3F6; // light blue
}
else if (iterations <=100)
{
// This point is in the Mandelbrot set.
image[y][x] = 0x36924B; // darkest green
}
else if (iterations <= 200)
{
// This point is in the Mandelbrot set.
image[y][x] = 0x5FB072; // lighter green
}
else if (iterations <= 300)
{
// z didn't escape from the circle.
// This point is in the Mandelbrot set.
image[y][x] = 0x7CD891; // mint green
}
else if (iterations <= 450)
{
// z didn't escape from the circle.
// This point is in the Mandelbrot set.
image[y][x] = 0x57F97D; // green
}
else
{
// z escaped within less than MAX_ITERATIONS
// iterations. This point isn't in the set.
image[y][x] = 0x58DC77; // light green
}
}
}
}
int main(int argc, char *argv[])
{
cout << "Processing" << endl;
// Start timing
the_clock::time_point start = the_clock::now();
// This shows the whole set.
compute_mandelbrot(-2.0, 1.0, 1.125, -1.125);
// This zooms in on an interesting bit of detail.
//compute_mandelbrot(-0.751085, -0.734975, 0.118378, 0.134488);
// Stop timing
the_clock::time_point end = the_clock::now();
// Compute the difference between the two times in milliseconds
auto time_taken = duration_cast<milliseconds>(end - start).count();
cout << "Computing the Mandelbrot set took " << time_taken << " ms." << endl;
write_tga("output.tga");
return 0;
}
Lets say you want to use N parallel threads for the rendering, then each thread will handle HEIGHT / N lines.
For simplicities sake I pick an N that divides your HEIGHT evenly, like 5. That means each thread will handle 20 lines each (with your HEIGHT being equal to 100).
You could implement it something like this:
constexpr int THREADS = 5; // Our "N", divides HEIGHT evenly
void compute_mandelbrot_piece(double left, double right, double top, double bottom, unsigned y_from, unsigned y_to)
{
for (unsigned y = y_from; y < y_to; ++y)
{
for (unsigned x = 0; y < WIDTH; ++x)
{
// Existing code to calculate value for y,x
// ...
}
}
}
void compute_mandelbrot(double left, double right, double top, double bottom)
{
std::vector<std::thread> render_threads;
render_threads.reserve(THREADS); // Allocate memory for all threads, keep the size zero
// Create threads, each handling part of the image
for (unsigned y = 0; y < HEIGHT; y += HEIGHT / THREADS)
{
render_threads.emplace_back(&compute_mandelbrot_piece, left, right, top, bottom, y, y + HEIGHT / THREADS);
}
// Wait for the threads to finish, and join them
for (auto& thread : render_threads)
{
thread.join();
}
// Now all threads are done, and the image should be fully rendered and ready to save
}
I am trying to make optimal algorithm to draw rectangle onto 1D array. I wrote this function:
/** Draws a rectangle in 1D array
* Arguments:
* pixmap - 1D array of Color
* color - rectangle color
* w - rectangle width
* h - rectanhle height
* x - x position, negative coordinates are outside draw area
* y - y position, negative coordinates are outside draw area
* pixmapWidth - width of the image (height can be deducted from width if needed but is practically unnecessary) */
void rectangle(std::vector<int>& pixmap, const int& color, const int w, const int h, int x, const int y, const int pixmapWidth)
{
if(x>=pixmapWidth)
return;
if(x+w<0)
return;
if(y+h<0)
return;
// Width of one consistent line of color of the rectangle
// if the rectangle is partially out of pixmap area,
// thw width is smaller than rectangle width
const int renderWidth = std::min(w, pixmapWidth-x);
// offset in the arrray where the rendering starts
// 0 would be for [0,0] coordinate
int tg_offset = y*pixmapWidth+x;
// maximum offset to ever render, which is the array size
const int tg_end = pixmap.size();
int lines = 0;
for(; tg_offset<tg_end && lines<h; tg_offset+=pixmapWidth) {
for(int cx=0; cx<renderWidth; ++cx) {
// This check keeps failing and my program crashes
if(tg_offset+cx >= pixmap.size())
throw "Oh no, what a bad thing to happen!";
pixmap[tg_offset+cx] = color;
}
lines++;
}
}
Note that I know there's a lot of picture drawing libraries, but I'm trying to learn by doing this. But now I'm stuck and I need help.
The problem is that in the inner loop, condition if(tg_offset+cx >= pixmap.size()) keeps failing meaning I am trying to render outside the array. I have no idea why this keeps happening.
Example problematic code:
const int pixmap_width = 20;
const int pixmap_height = 20;
std::vector<int> pixmap(pixmap_width*pixmap_height);
// tries to render outside the array
rectangle(pixmap, 0, 10, 10, -1, 18, pixmap_width);
Here is a testcase including ASCII output of the pixmap: http://ideone.com/SoJPFF
I don't know how could I improve the question any more...
Making no changes produces a quadrilateral. Is this not the desired functionality?
for(; tg_offset<tg_end && lines<h; tg_offset+=pixmapWidth) {
cout <<"" << endl;
for(int cx=0; cx<renderWidth; ++cx) {
cout << " " << pixmap[tg_offset+cx];
// This check keeps failing and my program crashes
if(tg_offset+cx >= pixmap.size())
throw "Oh no, what a bad thing to happen!";
pixmap[tg_offset+cx] = color;
}
lines++;
}
}
int main()
{
std::vector<int> pixmap(16);
pixmap = { 1,1,1,1,1,0,0,1,1,0,0,1,1,1,1,1 };
int color = 0;
int w = 4;
int h = 4;
int x = 0;
int y = 0;
int pixmapWidth = 4;
cout << "Hello World" << endl;
rectangle(pixmap, color, w, h, x, y, pixmapWidth);
return 0;
}
produces:
Hello World
1 1 1 1
1 0 0 1
1 0 0 1
1 1 1 1
I think a large part of the problem with your function is it being a lot more complex than it needs to be. Here's a much simpler version of your function, done by simply looping over x and y.
void rectangle(std::vector<int>& pixmap, const int& color, const int width, const int height,
int left, const int top, const int pixmapWidth)
{
for (int x = std::max(left, 0); x < left + width && x < pixmapWidth; x++)
for (int y = std::max(top, 0); y < top + height && y*pixmapWidth + x < pixmap.size(); y++)
pixmap[y*pixmapWidth + x] = color;
}
I'm not sure exactly what the output you want when x or y are negative. In your actual algorithm things goes wrong if x is negative due the fact that tg_offset goes back, so the tg_offset + cx can fail.
To solve this you can limit the second for to avoid this, like this:
for(int cx=0; cx<std::min(renderWidth, tg_end - tg_offset); ++cx)
but I think that limiting x and y to be only positive is more correct:
if ( x < 0 ) x = 0;
if ( y < 0 ) y = 0;
So I'm writing a program that solves a maze in the fastest time possible but you're only given a small area around where you currently are to see the surrounding area. I had planned to make it so that it always steps in the order of right > forward > left > backward. To make it go faster I intended to add a function that checks a path to see if it results in a dead end. Whether or not you can go in a certain direction is represented by the last 4 digits. I tried using a breadth first search algorithm to find dead ends. The function works by checking to see if it steps outside of the given area and if it does it returns false, otherwise it returns true. I'm not entirely sure where it's going wrong but it definitely does not work. The function is passed an array that represents the visible area, the height and width of the area, the x,y coordinates and the direction it is originally facing represented as a unsigned char. this is & with the last 4 bits to check if it's possible to move in that direction. Any help would be appreciated.
//WILL RETURN TRUE IF THERE IS A DEAD END, FALSE OTHERWISE
bool ActualPlayer::deadEnd(const AdvancedMapTile* area, const uint width, const uint height, const uint x, const uint y, unsigned char dir)
{
//dynamic allocation
bool *marked = nullptr;
marked = new bool[height * width];
for(uint i = 0; i < (height * width) ; i++)
marked[i] = false;
queue<cord> q;
bool deadEnd = true;
cord temp;
marked[x + y*width] = true;
temp.x = x;
temp.y = y;
q.push(temp);
//perform bfs
while(!q.empty())
{
AdvancedMapTile loc = area[x + y*width];
temp = q.front();
q.pop();
for(unsigned char i = 1; i < 9; i = i*2)//goes for 1, 2, 4, 8
{
uint tempx = temp.x;
uint tempy = temp.y;
changeXY(tempx, tempy, i, height, width);
if(!marked[tempx + tempy*width] && (loc.exits & i) != 0)
{
q.push(temp);
marked[tempx + tempy*width] = true;
if(tempx < 0 || tempx == width || tempy < 0 || tempy == height)
return false;
}
}
}
delete [] marked;
return deadEnd;
}