I am trying to implement the rasterization method in cc+. I am trying to implement an interpolation function that handles the interpolation between the x,y and z vertices. That way I can save the inverse of z in a depth buffer.
At this point I get only the vertices drawn on the rendered image. Can someone see what is wrong with my code? I have posted the full code so you can see the whole program.
Many thanks in advance.
EDIT
I saw that I had made an error in vertexshader by writing pixel.zinv = 1 / vPrime.z instead of p.zinv = 1/ vPrime.z. Now nothing renders, just a black screen.
EDIT 2
My check to see if a pixel should be painted was wrong.
if (depthBuffer[row[i].x][row[i].y] < row[i].zinv)
is correct. Now I get little pieces of color.
#include <iostream>
#include <glm/glm.hpp>
#include <SDL.h>
#include "SDLauxiliary.h"
#include "TestModel.h"
using namespace std;
using glm::vec2;
using glm::vec3;
using glm::ivec2;
using glm::mat3;
using glm::max;
// ----------------------------------------------------------------------------
// GLOBAL VARIABLES
int cc = 0;
const int SCREEN_WIDTH = 500;
const int SCREEN_HEIGHT = 500;
SDL_Surface* screen;
int t;
vector<Triangle> triangles;
vec3 cameraPos(0, 0, -3.001);
float f = 500;
double yaw = 0;
vec3 c1(cos(yaw), 0, -sin(yaw));
vec3 c2(0, 1, 0);
vec3 c3(sin(yaw), 0, cos(yaw));
glm::mat3 R(c1, c2, c3);
float translation = 0.1; // use this to set translation increment
const float PI = 3.1415927;
vec3 currentColor;
float depthBuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
// ----------------------------------------------------------------------------
// STUCTURES
struct Pixel
{
int x;
int y;
float zinv;
}pixel;
// ----------------------------------------------------------------------------
// FUNCTIONS
void Update();
void Draw();
void VertexShader(const vec3& v, Pixel& p);
void Interpolate(ivec2 a, ivec2 b, vector<ivec2>& result);
void DrawLineSDL(SDL_Surface* surface, ivec2 a, ivec2 b, vec3 color);
void DrawPolygonEdges(const vector<vec3>& vertices);
void ComputePolygonRows(const vector<Pixel>& vertexPixels, vector<Pixel>& leftPixels, vector<Pixel>& rightPixels);
void DrawPolygonRows(const vector<Pixel>& leftPixels, const vector<Pixel>& rightPixels);
void DrawPolygon(const vector<vec3>& vertices);
void Interpolate2(Pixel a, Pixel b, vector<Pixel>& result);
int main(int argc, char* argv[])
{
LoadTestModel(triangles);
screen = InitializeSDL(SCREEN_WIDTH, SCREEN_HEIGHT);
t = SDL_GetTicks(); // Set start value for timer.
while (NoQuitMessageSDL())
{
Draw();
}
//Draw();
//cin.get();
SDL_SaveBMP(screen, "screenshot.bmp");
return 0;
}
void Draw()
{
SDL_FillRect(screen, 0, 0);
if (SDL_MUSTLOCK(screen))
SDL_LockSurface(screen);
for (int y = 0; y<SCREEN_HEIGHT; ++y)
for (int x = 0; x<SCREEN_WIDTH; ++x)
depthBuffer[y][x] = 0;
for (int i = 0; i<triangles.size(); ++i)
{
currentColor = triangles[i].color;
vector<vec3> vertices(3);
int aa = 24;
vertices[0] = triangles[i].v0;
vertices[1] = triangles[i].v1;
vertices[2] = triangles[i].v2;
DrawPolygon(vertices);
}
if (SDL_MUSTLOCK(screen))
SDL_UnlockSurface(screen);
SDL_UpdateRect(screen, 0, 0, 0, 0);
}
void VertexShader(const vec3& v, Pixel& p)
{
vec3 vPrime = (v - cameraPos)*R;
p.zinv = 1 / vPrime.z;
p.x = f * vPrime.x / vPrime.z + SCREEN_WIDTH / 2;
p.y = f * vPrime.y / vPrime.z + SCREEN_HEIGHT / 2;
//cout << p.x << " this is it " << p.y << endl;
depthBuffer[p.x][p.y] = pixel.zinv;
}
void ComputePolygonRows(const vector<Pixel>& vertexPixels,
vector<Pixel>& leftPixels, vector<Pixel>& rightPixels)
{
// Find y-min,max for the 3 vertices
vec3 vp(vertexPixels[0].y, vertexPixels[1].y, vertexPixels[2].y);
Pixel start; Pixel end; Pixel middle;
int yMin = 1000;
int yMax = -1000;
int w=0; int s=0;
for (int k = 0; k < vertexPixels.size(); ++k)
{
if (vp[k] <= yMin)
{
yMin = vp[k];
end = vertexPixels[k];
w = k;
}
}
for (int k = 0; k < vertexPixels.size(); ++k)
{
if (vp[k] >= yMax)
{
yMax = vp[k];
start = vertexPixels[k];
s = k;
}
}
for (int k = 0; k < vertexPixels.size(); ++k)
{
if (vertexPixels[k].y != start.y
&& vertexPixels[k].y != end.y)
{
middle = vertexPixels[k];
}
if (w!= k && s!= k)
{
middle = vertexPixels[k];
}
}
int ROWS = yMax - yMin + 1;
leftPixels.resize(ROWS);
rightPixels.resize(ROWS);
for (int i = 0; i<ROWS; ++i)
{
leftPixels[i].x = +numeric_limits<int>::max();
rightPixels[i].x = -numeric_limits<int>::max();
}
int pixels1 = glm::abs(start.y - end.y) + 1;
vector<Pixel> line1(pixels1);
Interpolate2(end, start, line1);
int pixels2 = glm::abs(end.y - middle.y) + 1;
vector<Pixel> line2(pixels2);
Interpolate2(end, middle, line2);
int pixels3 = glm::abs(middle.y - start.y) + 1;
vector<Pixel> line3(pixels3);
Interpolate2(middle, start, line3);
vector<Pixel> side1(ROWS);
for (int i = 0; i < line2.size(); ++i)
{
side1[i] = line2[i];
}
for (int i = 0; i < line3.size(); ++i)
{
side1[line2.size()+i-1] = line3[i];
}
for (int i = 0; i < ROWS; ++i)
{
if (line1[i].x < leftPixels[i].x)
{
leftPixels[i] = line1[i];
}
if (line1[i].x > rightPixels[i].x)
{
rightPixels[i] = line1[i];
}
if (side1[i].x < leftPixels[i].x)
{
leftPixels[i] = side1[i];
}
if (side1[i].x > rightPixels[i].x)
{
rightPixels[i] = side1[i];
}
}
}
void DrawPolygonRows(const vector<Pixel>& leftPixels, const vector<Pixel>& rightPixels)
{
//cout << cc++ << endl;
for (int k = 0; k < leftPixels.size(); ++k)
{
int pixels = glm::abs(leftPixels[k].x - rightPixels[k].x) + 1;
vector<Pixel> row(pixels);
Interpolate2(leftPixels[k], rightPixels[k], row);
for (int i = 0; i < pixels; ++i)
{
if (depthBuffer[row[i].x][row[i].y] < row[i].zinv)
{
PutPixelSDL(screen, row[i].x, row[i].y, currentColor);
depthBuffer[row[i].x][row[i].y] = row[i].zinv;
}
}
}
}
void DrawPolygon(const vector<vec3>& vertices)
{
int V = vertices.size();
vector<Pixel> vertexPixels(V);
for (int i = 0; i<V; ++i)
VertexShader(vertices[i], vertexPixels[i]);
vector<Pixel> leftPixels;
vector<Pixel> rightPixels;
ComputePolygonRows(vertexPixels, leftPixels, rightPixels);
DrawPolygonRows(leftPixels, rightPixels);
}
void Interpolate2(Pixel a, Pixel b, vector<Pixel>& result)
{
int N = result.size();
float stepx = (b.x - a.x) / float(glm::max(N - 1, 1));
float stepy = (b.y - a.y) / float(glm::max(N - 1, 1));
float stepz = (b.zinv - a.zinv) / float(glm::max(N - 1, 1));
float currentx = a.x;
float currenty = a.y;
float currentz = a.zinv;
for (int i = 0; i<N; ++i)
{
result[i].x = currentx;
result[i].y = currenty;
result[i].zinv = currentz;
currentx = a.x;
currenty = a.y;
currentz = a.zinv;
currentx += stepx;
currenty += stepy;
currentz += stepz;
}
}
The last loop in the last function seems incorrect to me. You define currentx outside the loop. Then, define a local variable inside the loop with the same name and use it later in the loop. I'd suggest not using the same name for variable inside the loop and outside it to make it more readable. Also, using global variables make the code difficult to read too, since I prefer to look at a function as a separate entity for analysis.
Related
I'm trying to create my own CFD in C++. I have watched some videos on youtube about the Lattice Boltzmann method, but I cant get my simulations to look like the simulations performed in the videos with lattice Boltzmann implemented in Python.
I use SDL2 to create an image on my screen. I am not trying to create anything fast. Just something that will make pretty simulations on the CPU.
Here is my class for each cell:
//cell class
class cell {
public:
double Fi[nL] = {0,0,0,0,0,0,0,0,0};
double density = 0;
double momentumX = 0;
double momentumY = 0;
double velocityX = 0;
double velocityY = 0;
double Fieq[nL] = {0,0,0,0,0,0,0,0,0};
//obstacle
bool obstacle = false;
void densityOperator() {
for (int i = 0; i < nL; i++) {
density += Fi[i];
}
}
void momentumOperator() {
for (int i = 0; i < nL; i++) {
momentumX += Fi[i] * cX[i];
momentumY += Fi[i] * cY[i];
}
}
void velocityOperator() {
for (int i = 0; i < nL; i++) {
if (density == 0) {
density += 0.001;
}
velocityX += momentumX / density; // prolly very slow
velocityY += momentumY / density;
//velocityX += cX[i];
//velocityY += cY[i];
}
}
void FieqOperator() {
for (int i = 0; i < nL; i++) {
Fieq[i] = weights[i] * density *
(
1 +
(cX[i] * velocityX + cY[i] * velocityY) / Cs +
pow((cX[i] * velocityX + cY[i] * velocityY), 2) / (2 * pow(Cs, 4)) -
(velocityX * velocityX + velocityY * velocityY) / (2 * pow(Cs, 2))
);
}
}
void FiOperator() {
for (int i = 0; i < nL; i++) {
Fi[i] = Fi[i] - (timestep / tau) * (Fi[i] - Fieq[i]);
}
}
void addRightVelocity() {
Fi[0] = 1.f;
Fi[1] = 1.f;
Fi[2] = 1.f;
Fi[3] = 6.f;
Fi[4] = 1.f;
Fi[5] = 1.f;
Fi[6] = 1.f;
Fi[7] = 1.f;
Fi[8] = 1.f;
}
};
Please note that im am using a vector for my cells instead of a 2d array. I am using a index function to go from x,y to 1d cordinate.
int index(int x, int y) {
return x * nY + y;
}
Variables:
//box
const int nX = 400;
const int nY = 100;
//viscosity
float tau = 0.5; // 0.53
//time delta time per iteration
float timestep = 1;
//distance between cells
float dist = 1000;
//Speed of sound
float Cs = 1 / sqrt(3) * (dist / timestep);
//viscociti
float v = pow(Cs, 2) * (tau - timestep / 2); // tau will need to be much smaller
//time steps
int nT = 3000;
//lattice speeds and weights
const int nL = 9;
//Ci vector direction, discrete velocity
int cX[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
int cY[9] = { 0, 1, 1, 0, -1, -1, -1, 0 , 1 };
//weights, based on navier stokes
float weights[9] = { 4 / 9, 1 / 9, 1 / 36, 1 / 9, 1 / 36, 1 / 9, 1 / 36, 1 / 4, 1 / 36 };
//opposite populations
int cO[9] = { 0, 5, 6, 7, 8, 1, 2, 3, 4 };
My main function:
int main() {
//init vector cells
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cell cellUnit;
cells.push_back(cellUnit);
TempCells.push_back(cellUnit);
}
}
//SDL
//SDL
//-------------------------------------------------------------
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Init(SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(nX* 3, nY * 3, 0, &window, &renderer);
SDL_RenderSetScale(renderer, 3, 3);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
//-------------------------------------------------------------//
//Circle Object Gen
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
//cicle position
int circleX = 5;
int circleY = 50;
//circle radius
float radius = 10;
//distance bewtween cell and circle pos
float distance = sqrt(pow(circleX - x, 2) + pow(circleY - y, 2));
if (distance < radius) {
cells[index(x,y)].obstacle = true;
}
else {
cells[index(x, y)].obstacle = false;
}
}
}
//add velocity
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cells[index(x, y)].addRightVelocity();
//random velocity
for (int i = 0; i < nL; i++) {
cells[index(x,y)].Fi[i] += (rand() % 200) / 100;
}
}
}
for (int t = 0; t < nT; t++) {
//SDL
//--------------------------------------------------------------
//clear renderer
if (t % 20 == 0) {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
}
//--------------------------------------------------------------
//streaming:
//because we will loop over the same populations we do not want to switch the same population twice
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
if (x == 0) {
cells[index(x, y)].Fi[3] += 0.4;
}
//for populations
for (int i = 0; i < nL; i++) {
//boundary
//checs if cell is object or air
if (cells[index(x, y)].obstacle == false) {
//air
//targetet cell
int cellX = x + cX[i];
int cellY = y + cY[i];
//out of bounds check + rearange to other side
if (cellX < 0) {
//left to right
cellX = nX;
}
if (cellX >= nX) {
//right to left
cellX = 0;
continue;
}
if (cellY < 0) {
//top to buttom
cellY = nY;
}
if (cellY >= nY) {
//bottom to top
cellY = 0;
}
//if neighborinig cell is object --> collision with object
if (cells[index(cellX, cellY)].obstacle == true) {
//Boundary handling https://youtu.be/jfk4feD7rFQ?t=2821
TempCells[index(x,y)].Fi[cO[i]] = cells[index(x, y)].Fi[i];
}
//if not then stream to neighbor air cell with oposite population
TempCells[index(cellX, cellY)].Fi[cO[i]] = cells[index(x, y)].Fi[i];
}
else {
//wall
//SDL GRAPICHS
if (t % 20 == 0) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
}
}
}
}
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
for (int i = 0; i < nL; i++) {
cells[index(x, y)].Fi[i] = TempCells[index(x, y)].Fi[cO[i]];
}
}
}
//collision:
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
//density:
cells[index(x, y)].densityOperator();
//momentum:
cells[index(x, y)].momentumOperator();
//velocity:
cells[index(x, y)].velocityOperator();
//Fieq + new new Fi:
for (int i = 0; i < nL; i++) {
cells[index(x, y)].FieqOperator();
}
//SDL Graphics
if (t % 20 == 0) {
if (cells[index(x, y)].obstacle == false) {
SDL_SetRenderDrawColor(renderer, cells[index(x, y)].density, cells[index(x, y)].density , 255 , 255);
SDL_RenderDrawPoint(renderer, x, y);
}
}
}
}
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cells[index(x, y)].FiOperator();
}
}
//SDL Graphics
if (t % 20 == 0 ) {
SDL_RenderPresent(renderer);
}
}
return 0;
}
I do realize my code might be a bit messy and not easy to understand at first. And it is definitely not optimal.
If anyone has any experience in programming their own LBM in c++ i would like to hear your input.
It seams like my simulations is working but i do not get those bueatiful animations like in, https://youtu.be/ZUXmO4hu-20?t=3394
Thanks for any help.
Edit:
I have edited my script to reset, density, velocity X Y and Momentum X Y
Simulation visualised by density, pink is higher, loops if density exceeds color range of 255
Simulation visualised by density
Simulation visualised by density
How do I make this blob. I'm kind of confused on how to make the mesh rise up like that when I click the mouse button
Here is my mesh code. Its not too important to read through it. I just want to know how I get the blob by rising the the mesh:
{
QuadMesh qm; // The new quad mesh to be returned
qm.numVertices = 0;
qm.vertices = NULL;
qm.numQuads = 0;
qm.quads = NULL;
qm.numFacesDrawn = 0;
qm.maxMeshSize = maxMeshSize < minMeshSize ? minMeshSize : maxMeshSize;
CreateMemoryQM(&qm);
// Set up default material used for the mesh
qm.mat_ambient[0] = 0.0;
qm.mat_ambient[1] = 0.0;
qm.mat_ambient[2] = 0.0;
qm.mat_ambient[3] = 1.0;
qm.mat_specular[0] = 0.0;
qm.mat_specular[1] = 0.0;
qm.mat_specular[2] = 0.0;
qm.mat_specular[3] = 1.0;
qm.mat_diffuse[0] = 0.75;
qm.mat_diffuse[1] = 0.5;
qm.mat_diffuse[2] = 0.0;
qm.mat_diffuse[3] = 1.0;
qm.mat_shininess[0] = 0.0;
return qm;
}
void SetMaterialQM(QuadMesh* qm, Vector3D ambient, Vector3D diffuse, Vector3D specular, double shininess)
{
qm->mat_ambient[0] = ambient.x;
qm->mat_ambient[1] = ambient.y;
qm->mat_ambient[2] = ambient.z;
qm->mat_ambient[3] = 1.0;
qm->mat_specular[0] = specular.x;
qm->mat_specular[1] = specular.y;
qm->mat_specular[2] = specular.z;
qm->mat_specular[3] = 1.0;
qm->mat_diffuse[0] = diffuse.x;
qm->mat_diffuse[1] = diffuse.y;
qm->mat_diffuse[2] = diffuse.z;
qm->mat_diffuse[3] = 1.0;
qm->mat_shininess[0] = (float)shininess;
}
// Allocate dynamic arrays.
bool CreateMemoryQM(QuadMesh* qm)
{
const int maxVertices = (qm->maxMeshSize + 1) * (qm->maxMeshSize + 1);
qm->vertices = (MeshVertex *)malloc(sizeof(MeshVertex) * maxVertices);
if (qm->vertices == NULL)
{
return false;
}
const int maxQuads = qm->maxMeshSize * qm->maxMeshSize;
qm->quads = (MeshQuad *)malloc(sizeof(MeshQuad) * maxQuads);
if (qm->quads == NULL)
{
return false;
}
return true;
}
// Fills the array of vertices and the array of quads.
bool InitMeshQM(QuadMesh* qm, int meshSize, Vector3D origin, double meshLength, double meshWidth, Vector3D dir1, Vector3D dir2)
{
Vector3D o;
double sf1, sf2;
Vector3D v1,v2;
v1.x = dir1.x;
v1.y = dir1.y;
v1.z = dir1.z;
sf1 = meshLength/meshSize;
ScalarMul(&v1, (float)sf1, &v1);
v2.x = dir2.x;
v2.y = dir2.y;
v2.z = dir2.z;
sf2 = meshWidth/meshSize;
ScalarMul(&v2, (float)sf2, &v2);
Vector3D meshpt;
// Build Vertices
qm->numVertices=(meshSize+1)*(meshSize+1);
int currentVertex = 0;
// Starts at front left corner of mesh
Set(&o, origin.x,origin.y,origin.z);
for(int i=0; i< meshSize+1; i++)
{
for(int j=0; j< meshSize+1; j++)
{
// compute vertex position along mesh row (along x direction)
meshpt.x = o.x + j * v1.x;
meshpt.y = o.y + j * v1.y;
meshpt.z = o.z + j * v1.z;
Set(&qm->vertices[currentVertex].position, meshpt.x,meshpt.y,meshpt.z);
currentVertex++;
}
// go to next row in mesh (negative z direction)
Add(&o, &v2, &o);
}
// Build Quad Polygons
qm->numQuads=(meshSize)*(meshSize);
int currentQuad=0;
for (int j=0; j < meshSize; j++)
{
for (int k=0; k < meshSize; k++)
{
// Counterclockwise order
qm->quads[currentQuad].vertices[0]=&qm->vertices[j* (meshSize+1)+k];
qm->quads[currentQuad].vertices[1]=&qm->vertices[j* (meshSize+1)+k+1];
qm->quads[currentQuad].vertices[2]=&qm->vertices[(j+1)*(meshSize+1)+k+1];
qm->quads[currentQuad].vertices[3]=&qm->vertices[(j+1)*(meshSize+1)+k];
currentQuad++;
}
}
ComputeNormalsQM(qm);
return true;
}
// Draw the mesh by drawing all quads.
void DrawMeshQM(QuadMesh* qm, int meshSize)
{
int currentQuad=0;
glMaterialfv(GL_FRONT, GL_AMBIENT, qm->mat_ambient);
glMaterialfv(GL_FRONT, GL_SPECULAR, qm->mat_specular);
glMaterialfv(GL_FRONT, GL_DIFFUSE, qm->mat_diffuse);
glMaterialfv(GL_FRONT, GL_SHININESS, qm->mat_shininess);
for(int j=0; j < meshSize; j++)
{
for(int k=0; k < meshSize; k++)
{
glBegin(GL_QUADS);
glNormal3f(qm->quads[currentQuad].vertices[0]->normal.x,
qm->quads[currentQuad].vertices[0]->normal.y,
qm->quads[currentQuad].vertices[0]->normal.z);
glVertex3f(qm->quads[currentQuad].vertices[0]->position.x,
qm->quads[currentQuad].vertices[0]->position.y,
qm->quads[currentQuad].vertices[0]->position.z);
glNormal3f(qm->quads[currentQuad].vertices[1]->normal.x,
qm->quads[currentQuad].vertices[1]->normal.y,
qm->quads[currentQuad].vertices[1]->normal.z);
glVertex3f(qm->quads[currentQuad].vertices[1]->position.x,
qm->quads[currentQuad].vertices[1]->position.y,
qm->quads[currentQuad].vertices[1]->position.z);
glNormal3f(qm->quads[currentQuad].vertices[2]->normal.x,
qm->quads[currentQuad].vertices[2]->normal.y,
qm->quads[currentQuad].vertices[2]->normal.z);
glVertex3f(qm->quads[currentQuad].vertices[2]->position.x,
qm->quads[currentQuad].vertices[2]->position.y,
qm->quads[currentQuad].vertices[2]->position.z);
glNormal3f(qm->quads[currentQuad].vertices[3]->normal.x,
qm->quads[currentQuad].vertices[3]->normal.y,
qm->quads[currentQuad].vertices[3]->normal.z);
glVertex3f(qm->quads[currentQuad].vertices[3]->position.x,
qm->quads[currentQuad].vertices[3]->position.y,
qm->quads[currentQuad].vertices[3]->position.z);
glEnd();
currentQuad++;
}
}
}
// Deallocate dynamic arrays.
void FreeMemoryQM(QuadMesh* qm)
{
if (qm->vertices != NULL)
free(qm->vertices);
qm->vertices=NULL;
qm->numVertices=0;
if (qm->quads != NULL)
free(qm->quads);
qm->quads=NULL;
qm->numQuads=0;
}
// Use cross-products to compute the normal vector at each vertex
void ComputeNormalsQM(QuadMesh* qm)
{
int currentQuad=0;
for(int j=0; j < qm->maxMeshSize; j++)
{
for(int k=0; k < qm->maxMeshSize; k++)
{
Vector3D n0, n1, n2, n3;
Vector3D e0, e1, e2, e3;
for (int i=0; i < 4; i++)
{
LoadZero(&qm->quads[currentQuad].vertices[i]->normal);
}
Subtract(&qm->quads[currentQuad].vertices[1]->position, &qm->quads[currentQuad].vertices[0]->position, &e0);
Subtract(&qm->quads[currentQuad].vertices[2]->position, &qm->quads[currentQuad].vertices[1]->position, &e1);
Subtract(&qm->quads[currentQuad].vertices[3]->position, &qm->quads[currentQuad].vertices[2]->position, &e2);
Subtract(&qm->quads[currentQuad].vertices[0]->position, &qm->quads[currentQuad].vertices[3]->position, &e3);
Normalize(&e0);
Normalize(&e1);
Normalize(&e2);
Normalize(&e3);
Vector3D w; // Working vector;
Negate(&e3, &w);
CrossProduct(&e0, &w, &n0);
Normalize(&n0);
Add(&qm->quads[currentQuad].vertices[0]->normal, &n0, &qm->quads[currentQuad].vertices[0]->normal);
Negate(&e0, &w);
CrossProduct(&e1, &w, &n1);
Normalize(&n1);
Add(&qm->quads[currentQuad].vertices[1]->normal, &n1, &qm->quads[currentQuad].vertices[1]->normal);
Negate(&e1, &w);
CrossProduct(&e2, &w, &n2);
Normalize(&n2);
Add(&qm->quads[currentQuad].vertices[2]->normal, &n2, &qm->quads[currentQuad].vertices[2]->normal);
Negate(&e2, &w);
CrossProduct(&e3, &w, &n3);
Normalize(&n3);
Add(&qm->quads[currentQuad].vertices[3]->normal, &n3, &qm->quads[currentQuad].vertices[3]->normal);
for (int i = 0; i < 4; i++)
{
Normalize(&qm->quads[currentQuad].vertices[i]->normal);
}
currentQuad++;
}
}
}
It will not if you try it because its linked to a main.c file but its basically generating a paper thin mesh with a green shade.
I have to write a function, which detects intersection and returns true or false.
I have Shape.cpp file, and rectangle.cpp, circle.cpp files inherits of it. I tried to calculate it, but i failed. There is no error, but when my program starts, it crashes. MY Question is why it crashes? is my way wrongs? Here is circle.cpp file.
bool Circ::intersects(Shape* pshape)
{
Rect *p1 = dynamic_cast<Rect*>(pshape);
Circ *p2 = dynamic_cast<Circ*>(pshape);
if(p1)
{
float circleDistance_x = abs(p2->getPos().x - p1->getPos().x);
float circleDistance_y = abs(p2->getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + p2->getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + p2->getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= p2->getRad()^2);
}
return false;
}
This is not the code all i want to write. But when it fails, i stopped to write.
and my Shapes.h file
#ifndef _SHAPES_H
#define _SHAPES_H
struct Point2d
{
float x, y;
};
struct Point3d
{
float r, g, b;
};
class Shape
{
protected:
bool m_bMarked;
Point3d m_col;
Point2d m_veldir;
Point2d m_pos;
float m_vel;
public:
Shape(Point2d& pos, Point2d& veldir, float vel, Point3d& col)
:m_pos(pos),m_veldir(veldir),m_vel(vel),m_col(col)
{
m_bMarked = false;
}
virtual ~Shape() {}
virtual void draw() = 0;
virtual bool intersects(Shape*) = 0;
inline void move() { m_pos.x += m_veldir.x*m_vel; m_pos.y += m_veldir.y*m_vel; }
inline void invert_xdir() { m_veldir.x *= -1; }
inline void invert_ydir() { m_veldir.y *= -1; }
inline void MarkShape() { m_bMarked = true; }
inline void UnMarkShape() { m_bMarked = false; }
inline bool isMarked() { return m_bMarked; }
inline void increase_vel() { m_vel += 0.01f; }
inline void decrease_vel() { m_vel -= 0.01f; }
};
#endif
And finally my ShapesMain.cpp file
#include <time.h>
#include <GL/glut.h>
#include <cmath>
#include "Rectangle.h"
#include "Circle.h"
// YOU CAN CHANGE THE NUMBER OF SHAPES
#define SHAPE_COUNT 20
// YOU CAN MODIFY WINDOW SIZE BY CHANGING THESE
// YOU MAY ALSO VIEW WINDOW IN FULL SCREEN
#define WINDOWX 500
#define WINDOWY 500
// UNCOMMENT THE LINE BELOW TO STOP MOVING SHAPES
//#define NO_MOTION
// CHANGE THESE DIMENSIONS HOWEVER YOU LIKE
#define MAX_SHAPE_DIM 70
#define MIN_SHAPE_DIM 10
float g_windowWidth = WINDOWX;
float g_windowHeight = WINDOWY;
Shape* g_shapeList[SHAPE_COUNT];
int g_numShapes = 0;
bool g_bShowIntersection = true;
//------------------------------------
void Initialize()
{
srand ( time(NULL) );
// delete previous shapes, if there is any
if (g_numShapes > 0)
{
for (int i = 0; i < g_numShapes; i++)
delete g_shapeList[i];
}
// create a new shape repository
do {
g_numShapes = rand() % SHAPE_COUNT; // number of shapes are randomly determined
} while (g_numShapes < 5); // we dont want to have less than 5 shapes
int rect_count = g_numShapes * (rand() % 10 / 10.0f);
int circle_count = g_numShapes - rect_count;
int half_wind_x = 3* g_windowWidth / 4;
int half_wind_y = 3* g_windowHeight / 4;
int max_dim = MAX_SHAPE_DIM; // max dim. of any shape
int min_dim = MIN_SHAPE_DIM; // min dim. of any shape
int quad_wind = g_windowWidth / 4;
for (int i= 0; i<g_numShapes; i++)
{
float x, y;
float v1, v2;
// set positions
do {
x = rand() % half_wind_x;
} while (x <= quad_wind);
do {
y = rand() % half_wind_y;
} while (y <= quad_wind);
Point2d pos = { x,y };
// set velocity directions
do{
v1 = rand() % 10 / 10.0f;
v2 = rand() % 10 / 10.0f;
} while (v1 == 0 || v2 == 0);
v1 *= (rand() % 2) ? -1 : 1;
v2 *= (rand() % 2) ? -1 : 1;
float vnorm = sqrt(v1*v1 + v2*v2);
Point2d veldir = { v1 / vnorm, v2 / vnorm };
// set velocity
float vel;
do {
vel = rand() % 2 / 10.0f;
} while (vel == 0);
#ifdef NO_MOTION
vel = 0.0f;
#endif
//set color
float R = rand()%100/100.0f;
float G = rand()%100/100.0f;
float B = rand()%100/100.0f;
Point3d color = { R,G,B };
// construct objects
if (i < rect_count)
{
float wx;
float wy;
do {
wx = rand() % quad_wind;
} while (wx < min_dim || wx>max_dim);
do {
wy = rand() % quad_wind;
} while (wy < min_dim || wy>max_dim);
Point2d size = { wx, wy };
Rect* pRect = new Rect(pos, size, veldir, vel, color);
g_shapeList[i] = pRect;
}
else
{
float rad;
do {
rad = rand() % quad_wind;
} while (rad < min_dim || rad>max_dim);
Circ* pCirc = new Circ(pos, rad, veldir, vel, color);
g_shapeList[i] = pCirc;
}
}
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}
//-------------------------------------
// This function handles the intersections of shapes.
// if the user is not interested in marking intersections
// s/he can set bMarkIntersections to false..in this case
// no intersection test is performed
void MarkObjects(bool bMarkIntersections)
{
if (bMarkIntersections == false)
{
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->UnMarkShape();
}
else
{
// reset the states of all shapes as unmarked
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->UnMarkShape();
for (int i = 0; i < g_numShapes; i++)
{
for (int j = i+1; j < g_numShapes; j++)
{
if (g_shapeList[i]->intersects(g_shapeList[j]))
{
g_shapeList[i]->MarkShape();
g_shapeList[j]->MarkShape();
}
}
}
}
}
//------------------------------------
void UpdateData()
{
// create viewport bounding rectangles to keep the shapes within the viewport
Point2d Winpos = { -1.0,0.0 };
Point2d Winsize = { 1.0 , g_windowHeight };
Point2d Winveldir = { 0,0 }; // dummy veldir
float Winvel = 0.0f; //not moving
Point3d Wincol = { 0,0,0 }; // dummy color
Rect WindowRectLeft(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = 0.0; Winpos.y = -1.0;
Winsize.x = g_windowWidth; Winsize.y = 1.0;
Rect WindowRectBottom(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = g_windowWidth; Winpos.y = 0.0;
Winsize.x = 1; Winsize.y = g_windowHeight;
Rect WindowRectRight(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = 0.0; Winpos.y = g_windowHeight;
Winsize.x = g_windowWidth; Winsize.y = 1.0f;
Rect WindowRectUp(Winpos, Winsize, Winveldir, Winvel, Wincol);
for (int i = 0; i < g_numShapes; i++)
{
// move the shape
g_shapeList[i]->move();
// if it bounces to the window walls, invert its veldir
if (g_shapeList[i]->intersects(&WindowRectLeft) ||
g_shapeList[i]->intersects(&WindowRectRight))
g_shapeList[i]->invert_xdir();
if (g_shapeList[i]->intersects(&WindowRectBottom) ||
g_shapeList[i]->intersects(&WindowRectUp))
g_shapeList[i]->invert_ydir();
}
}
//------------------------------------
void ChangeSize(GLsizei w, GLsizei h)
{
if(h == 0)
h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
g_windowHeight = h;
g_windowWidth = w;
glOrtho(0, g_windowWidth, 0, g_windowHeight , 1.0f, -1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
//------------------------------------
void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 'q') // PRESS 'q' to terminate the application
exit(0);
if(key=='r') // PRESS 'r' ket to reset the shapes
Initialize();
if (key == 's') // toggle between showing the intersections or not
g_bShowIntersection = g_bShowIntersection ? false: true;
}
//------------------------------------
void processSpecialKeys(int key, int x, int y)
{
switch(key) {
case GLUT_KEY_LEFT :
break;
case GLUT_KEY_RIGHT :
break;
case GLUT_KEY_UP:
// PRESSING UP ARROW KEY INCREASES THE SHAPE VELOCITIES
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->increase_vel();
break;
case GLUT_KEY_DOWN:
// PRESSING DOWN ARROW KEY DECREASES THE SHAPE VELOCITIES
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->decrease_vel();
break;
}
}
//-------------------------------------
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
UpdateData();
MarkObjects(g_bShowIntersection);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
for (int i= 0; i<g_numShapes; i++)
g_shapeList[i]->draw();
glutSwapBuffers();
}
//------------------------------------
int main(int argc, char* argv[])
{
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
glutInitWindowPosition(100,100);
glutInitWindowSize(WINDOWX, WINDOWY);
glutCreateWindow("COM102B - PA4");
// Register callback handler for window re-paint
glutDisplayFunc(display);
glutReshapeFunc(ChangeSize);
glutIdleFunc(display);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(processSpecialKeys);
Initialize();
glutMainLoop(); // Enter infinitely event-processing loop
return 0;
}
Your problem is in these lines:
Rect *p1 = dynamic_cast<Rect*>(pshape);
Circ *p2 = dynamic_cast<Circ*>(pshape);
Unless you had inherited Rect from Circ or vice versa this is what makes your program crash, you can't cast your pShape to Circ if it is a Rect, so when you pass a Rect object to your function it will correctly cast to Rect* but it will fail with Circ* returning nullptr, so then when you try to access methods from p2 it will crash becouse you are accessing to invalid memory (0x00000000) :
if(p1)
{
float circleDistance_x = abs(p2->getPos().x - p1->getPos().x);
float circleDistance_y = abs(p2->getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + p2->getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + p2->getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= p2->getRad()^2);
}
So, you could simply just cast the first p1 pointer since the method is from circle, it's obvious that it was called from a Circ Object so there's no need for p2 pointer.
Rect *p1 = dynamic_cast<Rect*>(pshape);
if(p1)
{
float circleDistance_x = abs(getPos().x - p1->getPos().x);
float circleDistance_y = abs(getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= getRad()^2);
}
Also on the line:
return (cornerDistance_sq <= getRad()^2)
i think you are trying to get the radius square but this wont do it, what it is actually doing is
(cornerDistance_sq <= getRad()) ^ 2
becouse <= has greater precedence to ^, plus ^ is not a square operator it is a bitwise operator. So what you actually want is :
return cornerDistance_sq <= getRad() * getRad();
I add a Sprite as background.
Now I wish my Sprite can blur gradually become blurred.
I think I may modify the Texture2D to do the job, but it seems that Texture2D can not be modified.
So, what should I do?
You can use shader for that. You can get simple blur shader from cocos test project, like this:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec2 resolution;
uniform float blurRadius;
uniform float sampleNum;
vec4 blur(vec2);
void main(void)
{
vec4 col = blur(v_texCoord); //* v_fragmentColor.rgb;
gl_FragColor = vec4(col) * v_fragmentColor;
}
vec4 blur(vec2 p)
{
if (blurRadius > 0.0 && sampleNum > 1.0)
{
vec4 col = vec4(0);
vec2 unit = 1.0 / resolution.xy;
float r = blurRadius;
float sampleStep = r / sampleNum;
float count = 0.0;
for(float x = -r; x < r; x += sampleStep)
{
for(float y = -r; y < r; y += sampleStep)
{
float weight = (r - abs(x)) * (r - abs(y));
col += texture2D(CC_Texture0, p + vec2(x * unit.x, y * unit.y)) * weight;
count += weight;
}
}
return col / count;
}
return texture2D(CC_Texture0, p);
}
If you don't know how to add custom shader to your sprite - here is an example!
You extend Sprite class:
class MySpriteBlur : public Sprite {
public:
~MySpriteBlur();
bool initWithTexture(Texture2D* texture, const Rect& rect);
void initGLProgram();
static MySpriteBlur *create(const char *pszFileName);
void setBlurRadius(float radius);
void setBlurSampleNum(float num);
protected:
float _blurRadius;
float _blurSampleNum;
};
And then implement it:
MySpriteBlur::~MySpriteBlur() {
}
MySpriteBlur* MySpriteBlur::create(const char *pszFileName) {
MySpriteBlur* pRet = new (std::nothrow) MySpriteBlur();
if (pRet && pRet->initWithFile(pszFileName)) {
pRet->autorelease();
} else {
CC_SAFE_DELETE(pRet);
}
return pRet;
}
bool MySpriteBlur::initWithTexture(Texture2D* texture, const Rect& rect) {
_blurRadius = 0;
if (Sprite::initWithTexture(texture, rect)) {
#if CC_ENABLE_CACHE_TEXTURE_DATA
auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event) {
initGLProgram();
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
#endif
initGLProgram();
return true;
}
return false;
}
void MySpriteBlur::initGLProgram() {
std::string fragSource = FileUtils::getInstance()->getStringFromFile(
FileUtils::getInstance()->fullPathForFilename("shaders/example_blur.fsh"));
auto program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource.data());
auto glProgramState = GLProgramState::getOrCreateWithGLProgram(program);
setGLProgramState(glProgramState);
auto size = getTexture()->getContentSizeInPixels();
getGLProgramState()->setUniformVec2("resolution", size);
getGLProgramState()->setUniformFloat("blurRadius", _blurRadius);
getGLProgramState()->setUniformFloat("sampleNum", 7.0f);
}
void MySpriteBlur::setBlurRadius(float radius) {
_blurRadius = radius;
getGLProgramState()->setUniformFloat("blurRadius", _blurRadius);
}
void MySpriteBlur::setBlurSampleNum(float num) {
_blurSampleNum = num;
getGLProgramState()->setUniformFloat("sampleNum", _blurSampleNum);
}
Hope that will help!
You have three options:
1) make a blurred background in photoshop (quick and simple, but extra size),
2) use a shader (not that simple and blur is a heavy operation),
3) redraw (on the fly) your background making it a new texture.
Here's my post how to draw on texture:
http://discuss.cocos2d-x.org/t/is-it-possible-to-erase-some-pixels-from-a-sprite/34460/5?u=piotrros
Knowing this here's a function from my project, which blurs one image (a data array) to another one:
void Sample::blur(unsigned char* inputData, unsigned char* outputData, float r) {
int R2 = pow(r + 2, 2);
for(int i = 0; i < canvasHeight; i++){
for(int j = 0; j < canvasWidth; j++) {
int val1 = 0;
int val2 = 0;
int val3 = 0;
int val4 = 0;
int index2 = (j + (canvasHeight - i - 1) * canvasWidth) * 4;
for(int iy = i - r; iy < i + r + 1; iy++){
for(int ix = j - r; ix < j + r + 1; ix++) {
int x = CLAMP(ix, 0, canvasWidth - 1);
int y = CLAMP(iy, 0, canvasHeight - 1);
int index = (x + (canvasHeight - y - 1) * canvasWidth) * 4;
val1 += inputData[index];
val2 += inputData[index + 1];
val3 += inputData[index + 2];
val4 += inputData[index + 3];
}
}
outputData[index2] = val1 / R2;
outputData[index2 + 1] = val2 / R2;
outputData[index2 + 2] = val3 / R2;
outputData[index2 + 3] = val4 / R2;
}
}
}
Just remember that blur is heavy and long operation so if you have a big image it may take a while.
I have the assignment to finish a mandelbrot program in C++. I'm not that good in C++, I prefer Java or C# but this has to be done in C++. I got some sample code which I have to finish. I'm trying to put the drawing code in the main (between the works comments) into a method (draw_Mandelbrot). The code in the main method works and gives me a nice mandelbrot image but when I use the draw_Mandelbrot method (and comment the draw code in main) I get a grey rectangle image as output. How can I make the draw_Mandelbrot method work? The code above the draw_Mandelbrot method is all sample code and not created by myself.
// mandelbrot.cpp
// compile with: g++ -std=c++11 mandelbrot.cpp -o mandelbrot
// view output with: eog mandelbrot.ppm
#include <fstream>
#include <complex> // if you make use of complex number facilities in C++
#include <iostream>
#include <cstdlib>
#include <complex>
using namespace std;
template <class T> struct RGB { T r, g, b; };
template <class T>
class Matrix {
public:
Matrix(const size_t rows, const size_t cols) : _rows(rows), _cols(cols) {
_matrix = new T*[rows];
for (size_t i = 0; i < rows; ++i) {
_matrix[i] = new T[cols];
}
}
Matrix(const Matrix &m) : _rows(m._rows), _cols(m._cols) {
_matrix = new T*[m._rows];
for (size_t i = 0; i < m._rows; ++i) {
_matrix[i] = new T[m._cols];
for (size_t j = 0; j < m._cols; ++j) {
_matrix[i][j] = m._matrix[i][j];
}
}
}
~Matrix() {
for (size_t i = 0; i < _rows; ++i) {
delete [] _matrix[i];
}
delete [] _matrix;
}
T *operator[] (const size_t nIndex)
{
return _matrix[nIndex];
}
size_t width() const { return _cols; }
size_t height() const { return _rows; }
protected:
size_t _rows, _cols;
T **_matrix;
};
// Portable PixMap image
class PPMImage : public Matrix<RGB<unsigned char> >
{
public:
PPMImage(const size_t height, const size_t width) : Matrix(height, width) { }
void save(const std::string &filename)
{
std::ofstream out(filename, std::ios_base::binary);
out <<"P6" << std::endl << _cols << " " << _rows << std::endl << 255 << std::endl;
for (size_t y=0; y<_rows; y++)
for (size_t x=0; x<_cols; x++)
out << _matrix[y][x].r << _matrix[y][x].g << _matrix[y][x].b;
}
};
void draw_Mandelbrot(PPMImage image, const unsigned width, const unsigned height, double cxmin, double cxmax, double cymin, double cymax,unsigned int max_iterations)
{
for (std::size_t ix = 0; ix < width; ++ix)
for (std::size_t iy = 0; iy < height; ++iy)
{
std::complex<double> c(cxmin + ix / (width - 1.0)*(cxmax - cxmin), cymin + iy / (height - 1.0)*(cymax - cymin));
std::complex<double> z = 0;
unsigned int iterations;
for (iterations = 0; iterations < max_iterations && std::abs(z) < 2.0; ++iterations)
z = z*z + c;
image[iy][ix].r = image[iy][ix].g = image[iy][ix].b = iterations;
}
}
int main()
{
const unsigned width = 1600;
const unsigned height = 1600;
PPMImage image(height, width);
//image[y][x].r = image[y][x].g = image[y][x].b = 255; // white pixel
//image[y][x].r = image[y][x].g = image[y][x][b] = 0; // black pixel
//image[y][x].r = image[y][x].g = image[y][x].b = 0; // black pixel
//// red pixel
//image[y][x].r = 255;
//image[y][x].g = 0;
//image[y][x].b = 0;
draw_Mandelbrot(image, width, height, -2.0, 0.5, -1.0, 1.0, 10);
//works
//double cymin = -1.0;
//double cymax = 1.0;
//double cxmin = -2.0;
//double cxmax = 0.5;
//unsigned int max_iterations = 100;
//for (std::size_t ix = 0; ix < width; ++ix)
// for (std::size_t iy = 0; iy < height; ++iy)
// {
// std::complex<double> c(cxmin + ix / (width - 1.0)*(cxmax - cxmin), cymin + iy / (height - 1.0)*(cymax - cymin));
// std::complex<double> z = 0;
// unsigned int iterations;
// for (iterations = 0; iterations < max_iterations && std::abs(z) < 2.0; ++iterations)
// z = z*z + c;
// image[iy][ix].r = image[iy][ix].g = image[iy][ix].b = iterations;
// }
//works
image.save("mandelbrot.ppm");
return 0;
}
Output image when using the code in the main method
You're passing the image by value, so the function works on a separate image to the one in main, which is left in its initial state.
Either pass by reference:
void draw_Mandelbrot(PPMImage & image, ...)
or return a value:
PPMImage draw_Mandelbrot(...) {
PPMImage image(height, width);
// your code here
return image;
}
// in main
PPMImage image = draw_Mandelbrot(...);